diff --git a/cogs/admins.py b/cogs/admins.py index 45db7cc..78f2610 100644 --- a/cogs/admins.py +++ b/cogs/admins.py @@ -604,7 +604,7 @@ class Admins(commands.Cog): @commands.is_owner() async def test_evolution(self, ctx, team_abbrev: str): this_team = await get_team_by_abbrev(team_abbrev) - await evolve_pokemon(this_team, ctx.channel) + await evolve_pokemon(this_team, ctx.channel, responders=ctx.author) async def setup(bot): diff --git a/cogs/gameplay.py b/cogs/gameplay.py index 0f5ec8c..187f50c 100644 --- a/cogs/gameplay.py +++ b/cogs/gameplay.py @@ -10,7 +10,7 @@ from discord.ext import commands, tasks import pygsheets import sqlalchemy -from sqlmodel import or_ +from sqlmodel import func, or_ from api_calls import db_get from command_logic.logic_gameplay import advance_runners, bunts, chaos, complete_game, doubles, flyballs, frame_checks, get_full_roster_from_sheets, get_lineups_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_conflicts, popouts, read_lineup, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play @@ -313,7 +313,7 @@ class Gameplay(commands.Cog): done = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, human_team, int(roster.value)) logger.info(f'done: {done}') if done: - sp_view = starting_pitcher_dropdown_view(session, this_game, human_team) + sp_view = starting_pitcher_dropdown_view(session, this_game, human_team, this_game.league_name, [interaction.user]) await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view) await final_message.edit( @@ -502,7 +502,7 @@ class Gameplay(commands.Cog): # Get pitchers from rosterlinks done = await get_full_roster_from_sheets(session, interaction, self.sheets, this_game, human_team, 1) if done: - sp_view = starting_pitcher_dropdown_view(session, this_game, human_team, game_type=f'gauntlet-{this_event["id"]}') + sp_view = starting_pitcher_dropdown_view(session, this_game, human_team, game_type=this_game.league_name, responders=[interaction.user]) sp_message = await interaction.channel.send(content=f'### {human_team.lname} Starting Pitcher', view=sp_view) await final_message.edit( @@ -576,6 +576,15 @@ class Gameplay(commands.Cog): await interaction.edit_original_response(content='Bruh. Only GMs of the active teams can pull lineups.') return + all_lineups = get_game_lineups(session, this_game, this_team) + if len(all_lineups) > 1: + play_count = session.exec(select(func.count(Play.id)).where(Play.game == this_game, Play.complete == True)).one() + if play_count > 0: + await interaction.edit_original_response( + content=f'Since {play_count} play{"s" if play_count != 1 else ""} ha{"ve" if play_count != 1 else "s"} been logged, you will have to run `/substitution batter` to replace any of your batters.' + ) + return + logger.info(f'lineup: {lineup} / value: {lineup.value} / name: {lineup.name}') try: this_play = await read_lineup( @@ -615,6 +624,13 @@ class Gameplay(commands.Cog): try: check_sp = get_one_lineup(session, this_game, this_team, position='P') + play_count = session.exec(select(func.count(Play.id)).where(Play.game == this_game, Play.complete == True, Play.pitcher == check_sp)).one() + if play_count > 0: + await interaction.edit_original_response( + content=f'Since {play_count} play{"s" if play_count != 1 else ""} ha{"ve" if play_count != 1 else "s"} been logged, you will have to run `/substitution pitcher` to replace {check_sp.player.name}.' + ) + return + except sqlalchemy.exc.NoResultFound as e: # if 'NoResultFound' not in str(e): # logger.error(f'Error checking for existing sp: {e}') @@ -638,22 +654,8 @@ class Gameplay(commands.Cog): session.delete(check_sp) session.commit() - sp_view = starting_pitcher_dropdown_view(session, this_game, this_team, game_type=this_game.league_name) + sp_view = starting_pitcher_dropdown_view(session, this_game, this_team, game_type=this_game.league_name, responders=[interaction.user]) await interaction.edit_original_response(content=f'### {this_team.lname} Starting Pitcher', view=sp_view) - - await sp_view.wait() - if not sp_view.values: - await interaction.edit_original_response(content=f'Run `/set starting-pitcher` command again to select your SP', view=None) - - for x in range(15): - try: - this_play = this_game.initialize_play(session) - await self.post_play(session, interaction, this_play) - return - - except LineupsMissingException as e: - logger.info(f'Waiting for SP to be set in game {this_game.id}') - await asyncio.sleep(2) @app_commands.command(name='gamestate', description='Post the current game state') async def gamestate_command(self, interaction: discord.Interaction, include_lineups: bool = False): @@ -719,7 +721,7 @@ class Gameplay(commands.Cog): this_order = int(batting_order) logger.info(f'sub batter - this_play: {this_play}') - bat_view = sub_batter_dropdown_view(session, this_game, owner_team, this_order) + 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) group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game') diff --git a/cogs/gameplay_legacy.py b/cogs/gameplay_legacy.py index 90c043f..d4d80db 100644 --- a/cogs/gameplay_legacy.py +++ b/cogs/gameplay_legacy.py @@ -2401,7 +2401,8 @@ class Gameplay(commands.Cog): is_win=winning_team['gmid'] == gauntlet_team['gmid'], this_team=gauntlet_team, bot=self.bot, - channel=interaction.channel + channel=interaction.channel, + responders=[interaction.user] ) this_run = await db_get('gauntletruns', object_id=int(this_game.game_type.split('-')[3])) diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index 56aea09..c14ddd0 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -13,7 +13,7 @@ from typing import Literal from api_calls import db_delete, db_get, db_post from dice import DTwentyRoll, d_twenty_roll, frame_plate_check, sa_fielding_roll from exceptions import * -from helpers import DEFENSE_LITERAL, SBA_COLOR, get_channel +from helpers import COLORS, DEFENSE_LITERAL, SBA_COLOR, get_channel from in_game.game_helpers import PUBLIC_FIELDS_CATEGORY_NAME, legal_check from in_game.gameplay_models import BattingCard, Game, Lineup, PositionRating, RosterLink, Team, Play from in_game.gameplay_queries import get_available_batters, get_batter_card, get_batting_statline, get_pitching_statline, 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 @@ -61,13 +61,20 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo gt_string = ' - Exhibition' logger.info(f'get_scorebug_embed - this_game: {this_game} / gt_string: {gt_string}') + curr_play = this_game.current_play_or_none(session) + + if curr_play.pitcher.is_fatigued: + embed_color = COLORS['red'] + elif curr_play.is_new_inning: + embed_color = COLORS['yellow'] + else: + embed_color = COLORS['sba'] + embed = discord.Embed( title=f'{this_game.away_team.sname} @ {this_game.home_team.sname}{gt_string}', - color=int(SBA_COLOR, 16) + color=embed_color ) - curr_play = this_game.current_play_or_none(session) - if curr_play is None: try: curr_play = this_game.initialize_play(session) @@ -178,6 +185,8 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo battingcard = curr_play.batter.card.batterscouting.battingcard pit_string = f'{pitchingcard.hand.upper()}HP | {curr_play.pitcher.player.name_card_link('pitching')}' + if curr_play.pitcher.is_fatigued: + pit_string += f'\n***F A T I G U E D***' if len(baserunner_string) > 0: logger.info(f'Adding pitcher hold to scorebug') pitchingcard = curr_play.pitcher.card.pitcherscouting.pitchingcard @@ -261,7 +270,7 @@ async def get_scorebug_embed(session: Session, this_game: Game, full_length: boo return embed -def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team, game_type: str = None): +def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team: Team, game_type: str = None, responders: list[discord.User] = None): pitchers = get_available_pitchers(session, this_game, human_team, sort='starter-desc') logger.info(f'sorted pitchers: {pitchers}') sp_selection = SelectStartingPitcher( @@ -270,12 +279,13 @@ def starting_pitcher_dropdown_view(session: Session, this_game: Game, human_team session=session, league_name=this_game.game_type if game_type is None else game_type, 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 starting pitcher' + placeholder='Select your starting pitcher', + responders=responders ) return DropdownView(dropdown_objects=[sp_selection]) -def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team, batting_order: int): +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) logger.info(f'batters: {batters}') bat_selection = SelectBatterSub( @@ -284,7 +294,8 @@ def sub_batter_dropdown_view(session: Session, this_game: Game, human_team: Team session=session, batting_order=batting_order, options=[SelectOption(label=f'{x.batterscouting.battingcard.hand.upper()} | {x.player.name_with_desc}', value=x.id) for x in batters], - placeholder='Select your Sub' + placeholder='Select your Sub', + responders=responders ) return DropdownView(dropdown_objects=[bat_selection]) @@ -464,13 +475,15 @@ def complete_play(session:Session, this_play: Play): away_score = this_play.away_score + runs_scored home_score = this_play.home_score + logger.info(f'Check for go-ahead run') if runs_scored > 0 and this_play.away_score <= this_play.home_score and away_score > home_score: this_play.is_go_ahead = True else: away_score = this_play.away_score home_score = this_play.home_score + runs_scored - + + logger.info(f'Check for go-ahead run') if runs_scored > 0 and this_play.home_score <= this_play.away_score and home_score > away_score: this_play.is_go_ahead = True @@ -485,6 +498,21 @@ def complete_play(session:Session, this_play: Play): new_batter = get_one_lineup(session, this_play.game, new_batter_team, batting_order=nbo) logger.info(f'new_batter: {new_batter}') + new_pitcher = get_one_lineup(session, this_play.game, new_pitcher_team, position='P') + logger.info(f'Check for {new_pitcher.player.name} POW') + outs = session.exec(select(func.sum(Play.outs)).where( + Play.game == this_play.game, Play.pitcher == new_pitcher, Play.complete == True + )).one() + pow_outs = new_pitcher.card.pitcherscouting.pitchingcard.starter_rating * 3 + if outs >= pow_outs and not new_pitcher.is_fatigued: + new_pitcher.is_fatigued = True + session.add(new_pitcher) + + if outs >= (pow_outs - 3): + in_pow = True + else: + in_pow = False + new_play = Play( game=this_play.game, play_num=this_play.play_num + 1, @@ -497,7 +525,7 @@ def complete_play(session:Session, this_play: Play): home_score=home_score, batter=new_batter, batter_pos=new_batter.position, - pitcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='P'), + pitcher=new_pitcher, catcher=get_one_lineup(session, this_play.game, new_pitcher_team, position='C'), is_new_inning=switch_sides, is_tied=away_score == home_score, @@ -505,6 +533,7 @@ def complete_play(session:Session, this_play: Play): on_second=on_second, on_third=on_third, managerai=this_play.managerai, + in_pow=in_pow, re24=get_re24(this_play, runs_scored, new_obc=obc, new_starting_outs=nso) ) @@ -2711,7 +2740,8 @@ async def show_defense_cards(session: Session, interaction: discord.Interaction, this_play=this_play, base_embed=player_embed, session=session, - sorted_lineups=sorted_lineups + sorted_lineups=sorted_lineups, + responders=[interaction.user] ) dropdown_view = DropdownView(dropdown_objects=[player_dropdown], timeout=60) @@ -3171,6 +3201,9 @@ async def manual_end_game(session: Session, interaction: discord.Interaction, th async def groundballs(session: Session, interaction: discord.Interaction, this_play: Play, groundball_letter: Literal['a', 'b', 'c']): + if this_play.starting_outs == 2: + return await gb_result(session, interaction, this_play, 1) + if this_play.on_base_code == 2 and groundball_letter in ['a', 'b']: logger.info(f'Groundball {groundball_letter} with runner on second') to_right_side = await ask_confirm( diff --git a/gauntlets.py b/gauntlets.py index c58c89e..f36149f 100644 --- a/gauntlets.py +++ b/gauntlets.py @@ -1786,7 +1786,7 @@ async def end_run(this_run, this_event, this_team, force_end: bool = False): return l_message -async def evolve_pokemon(this_team, channel): +async def evolve_pokemon(this_team, channel, responders): c_query = await db_get( 'cards', params=[('team_id', this_team['id']), ('order_by', 'new'), ('limit', 26)] @@ -1799,7 +1799,7 @@ async def evolve_pokemon(this_team, channel): SelectOption(label=f'{x["player"]["rarity"]["name"]} | {x["player"]["p_name"]}', value=x['id']) for x in evolvable_mons ] view = DropdownView( - dropdown_objects=[SelectPokemonEvolution(options=evo_target_options, this_team=this_team)] + dropdown_objects=[SelectPokemonEvolution(options=evo_target_options, this_team=this_team, responders=responders)] ) await channel.send( content='What? One of your pokemon is ready to evolve!\n\n-# The selected pokemon will be removed from your team and replaced with its evolution', @@ -1809,7 +1809,7 @@ async def evolve_pokemon(this_team, channel): await channel.send('All of your Pokemon are fully evolved!') -async def post_result(run_id: int, is_win: bool, this_team, bot, channel): +async def post_result(run_id: int, is_win: bool, this_team, bot, channel, responders: list[discord.User] = None): this_run = await db_get('gauntletruns', object_id=run_id) this_event = await db_get('events', object_id=this_run['gauntlet']['id']) t_query = await db_get('teams', params=[('abbrev', f'{this_team["abbrev"].replace("Gauntlet-","")}')]) @@ -1936,7 +1936,7 @@ async def post_result(run_id: int, is_win: bool, this_team, bot, channel): logging.info(f'Post-game evolution check: Gauntlet ID {this_run["id"]} / Wins: {this_run["wins"]} / Losses: {this_run["losses"]}') if this_event['id'] == 7 and this_run['wins'] < 10 and this_run['losses'] < 2: logging.info(f'trying to evolve now') - await evolve_pokemon(this_team, channel) + await evolve_pokemon(this_team, channel, responders) diff --git a/helpers.py b/helpers.py index 42c1074..6d63afc 100644 --- a/helpers.py +++ b/helpers.py @@ -249,6 +249,30 @@ SELECT_CARDSET_OPTIONS = [ ACTIVE_EVENT_LITERAL = Literal['1998 Season'] DEFENSE_LITERAL = Literal['Pitcher', 'Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', 'Center Field', 'Right Field'] ACTIVE_EVENT_LITERAL = Literal['1998 Season', 'Brilliant Stars'] +COLORS = { + 'sba': int('a6ce39', 16), + 'yellow': int('FFEA00', 16), + 'red': int('C70039', 16) +} +INSULTS = [ + 'Ugh, who even are you?', + 'Ugh, who even are you? Go away.', + 'Ugh, who even are you? Leave me alone.', + 'I will call the fucking cops!', + 'I will call the fucking cops! Go away.', + 'I will call the fucking cops! Leave me alone', + 'Please don\'t talk to me', + 'Don\'t talk to me.', + 'Eww, don\'t talk to me.', + 'Get away from me.', + 'Get away from me, creep.', + 'Get away from me, loser.', + 'Get away from me, pedobear.', + 'Why are you even here?', + 'Why are you even here? Get lost.', + 'Why are you even here? Scram.', + 'Why are you even here? No one knows who you are.', +] class Question: @@ -3343,3 +3367,7 @@ def user_has_role(user: discord.User | discord.Member, role_name: str) -> bool: return True return False + + +def random_insult() -> str: + return random_from_list(INSULTS) diff --git a/utilities/dropdown.py b/utilities/dropdown.py index 929d17a..0c89a84 100644 --- a/utilities/dropdown.py +++ b/utilities/dropdown.py @@ -10,7 +10,7 @@ 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 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 @@ -66,14 +66,22 @@ class DropdownView(discord.ui.View): class SelectViewDefense(discord.ui.Select): - def __init__(self, options: list, this_play: Play, base_embed: discord.Embed, session: Session, sorted_lineups: list[Lineup]): + 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]) @@ -87,27 +95,35 @@ class SelectViewDefense(discord.ui.Select): this_play=self.play, base_embed=self.embed, session=self.session, - sorted_lineups=self.sorted_lineups + sorted_lineups=self.sorted_lineups, + responders=self.responders ) new_view = DropdownView( dropdown_objects=[player_dropdown], timeout=60 ) - await interaction.response.edit_message(content=None, embed=self.embed, view=new_view) + 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] = ...) -> 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') 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): - await interaction.response.defer() + 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 @@ -154,14 +170,20 @@ class SelectStartingPitcher(discord.ui.Select): log_exception(e, 'Couldn\'t clean up after selecting sp') - class SelectSubPosition(discord.ui.Select): - def __init__(self, session: Session, this_lineup: Lineup, custom_id = ..., placeholder = None, options: List[SelectOption] = ...): + 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) @@ -185,16 +207,23 @@ class SelectSubPosition(discord.ui.Select): 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] = ...): + 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]}') @@ -258,7 +287,7 @@ class SelectBatterSub(discord.ui.Select): 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] + 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) @@ -298,13 +327,20 @@ class SelectBatterSub(discord.ui.Select): class SelectPokemonEvolution(discord.ui.Select): - def __init__(self, *, placeholder = 'Evolve the selected Pokemon', min_values = 1, max_values = 1, options = List[SelectOption], this_team): + 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): logging.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) 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() try: