From 0e3b98eb658df35fdf5ee2443aaeb66d0c7a29ac Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sun, 9 Nov 2025 06:12:46 -0600 Subject: [PATCH] S10 Updates + PR Bugfix --- cogs/economy.py | 8 +- cogs/economy_new/help_system.py | 8 +- command_logic/logic_gameplay.py | 63 +++++++++++++- constants.py | 54 +++++++----- gauntlets.py | 18 ++++ help_text.py | 8 +- manual_pack_distribution.py | 148 ++++++++++++++++++++++++++++++++ utilities/dropdown.py | 28 ++++-- 8 files changed, 293 insertions(+), 42 deletions(-) create mode 100644 manual_pack_distribution.py diff --git a/cogs/economy.py b/cogs/economy.py index 1ec9e45..919135b 100644 --- a/cogs/economy.py +++ b/cogs/economy.py @@ -421,22 +421,22 @@ class Economy(commands.Cog): embed.description = 'Cardset Requirements' embed.add_field( name='Ranked Legal', - value='2024 Season + Promos, 2018 Season + Promos', + value='2005, 2025 Seasons + Promos', inline=False ) embed.add_field( name='Minor League', - value='Humans: Unlimited\nAI: 2024 Season / 2018 Season as backup', + value='Humans: Unlimited\nAI: 2005 Season / 2025 Season as backup', inline=False ) embed.add_field( name='Major League', - value='Humans: Ranked Legal\nAI: 2024, 2018, 2016, 2008 Seasons / 2023 & 2022 as backup', + value='Humans: Ranked Legal\nAI: 2005, 2025, 2018, 2012 Seasons + Promos', inline=False ) embed.add_field( name='Flashback', - value='2016, 2013, 2012, 2008 Seasons', + value='2018, 2019, 2021, 2022 Seasons', inline=False ) embed.add_field( diff --git a/cogs/economy_new/help_system.py b/cogs/economy_new/help_system.py index d028396..33669b8 100644 --- a/cogs/economy_new/help_system.py +++ b/cogs/economy_new/help_system.py @@ -211,22 +211,22 @@ class HelpSystem(commands.Cog): embed.description = 'Cardset Requirements' embed.add_field( name='Ranked Legal', - value='2024 Season + Promos, 2018 Season + Promos', + value='2005 Season + Promos, 2025 Season + Promos', inline=False ) embed.add_field( name='Minor League', - value='Humans: Unlimited\nAI: 2024 Season / 2018 Season as backup', + value='Humans: Unlimited\nAI: 2005 Season / 2025 Season as backup', inline=False ) embed.add_field( name='Major League', - value='Humans: Ranked Legal\nAI: 2024, 2018, 2016, 2008 Seasons / 2023 & 2022 as backup', + value='Humans: Ranked Legal\nAI: 2005, 2025, 2018, 2012 Seasons + Promos', inline=False ) embed.add_field( name='Flashback', - value='2016, 2013, 2012, 2008 Seasons', + value='2018, 2019, 2021, 2022 Seasons', inline=False ) embed.add_field( diff --git a/command_logic/logic_gameplay.py b/command_logic/logic_gameplay.py index d254ee0..f1c1f1f 100644 --- a/command_logic/logic_gameplay.py +++ b/command_logic/logic_gameplay.py @@ -891,9 +891,10 @@ async def get_full_roster_from_sheets(session: Session, interaction: discord.Int try: card_ids = [row[0].value for row in raw_cells] logger.info(f'card_ids: {card_ids}') - except ValueError as e: + except (ValueError, TypeError) as e: logger.error(f'Could not pull roster for {this_team.abbrev}: {e}') - log_exception(GoogleSheetsException, f'Uh oh. Looks like your roster might not be saved. I am reading blanks when I try to get the card IDs') + logger.error(f'raw_cells type: {type(raw_cells)} / raw_cells: {raw_cells}') + raise GoogleSheetsException(f'Uh oh. I had trouble reading your roster from Google Sheets (range {l_range}). Please make sure your roster is saved properly and all cells contain valid card IDs.') for x in card_ids: this_card = await get_card_or_none(session, card_id=x) @@ -978,6 +979,64 @@ def log_run_scored(session: Session, runner: Lineup, this_play: Play, is_earned: return True +def create_pinch_runner_entry_play(session: Session, game: Game, current_play: Play, pinch_runner_lineup: Lineup) -> Play: + """ + Creates a "pinch runner entry" Play record when a pinch runner substitutes for a player on base. + This Play has PA=0, AB=0 so it doesn't affect counting stats, but provides a record to mark + when the pinch runner scores. + + Commits the new Play. + + Args: + session: Database session + game: The current game + current_play: The current active Play (not yet complete) + pinch_runner_lineup: The Lineup record for the pinch runner + + Returns: + The newly created entry Play + """ + logger.info(f'Creating pinch runner entry Play for {pinch_runner_lineup.player.name_with_desc} in Game {game.id}') + + # Get the next play number + max_play_num = session.exec(select(func.max(Play.play_num)).where(Play.game == game)).one() + next_play_num = max_play_num + 1 if max_play_num else 1 + + # Create the entry Play + entry_play = Play( + game=game, + play_num=next_play_num, + batter=pinch_runner_lineup, + pitcher=current_play.pitcher, + catcher=current_play.catcher, + inning_half=current_play.inning_half, + inning_num=current_play.inning_num, + batting_order=pinch_runner_lineup.batting_order, + starting_outs=current_play.starting_outs, + away_score=current_play.away_score, + home_score=current_play.home_score, + on_base_code=current_play.on_base_code, + batter_pos=pinch_runner_lineup.position, + # This is NOT a plate appearance + pa=0, + ab=0, + run=0, # Will be set to 1 if they score + # All other stats default to 0 + complete=True, # This "event" is complete + is_tied=current_play.is_tied, + is_go_ahead=False, + is_new_inning=False, + managerai_id=current_play.managerai_id + ) + + session.add(entry_play) + session.commit() + session.refresh(entry_play) + + logger.info(f'Created entry Play #{entry_play.play_num} for pinch runner {pinch_runner_lineup.player.name_with_desc}') + return entry_play + + def advance_runners(session: Session, this_play: Play, num_bases: int, only_forced: bool = False, earned_bases: int = None) -> Play: """ No commits diff --git a/constants.py b/constants.py index 3be9951..209cbd0 100644 --- a/constants.py +++ b/constants.py @@ -8,43 +8,42 @@ import discord from typing import Literal # Season Configuration -SBA_SEASON = 11 -PD_SEASON = 9 -ranked_cardsets = [20, 21, 22, 17, 18, 19] -LIVE_CARDSET_ID = 24 -LIVE_PROMO_CARDSET_ID = 25 +SBA_SEASON = 12 +PD_SEASON = 10 +ranked_cardsets = [24, 25, 26, 27, 28, 29] +LIVE_CARDSET_ID = 27 +LIVE_PROMO_CARDSET_ID = 28 MAX_CARDSET_ID = 30 # Cardset Configuration CARDSETS = { - 'Ranked': { + 'ranked': { 'primary': ranked_cardsets, 'human': ranked_cardsets }, - 'Minor League': { - 'primary': [20, 8], # 1998, Mario - 'secondary': [6], # 2013 - 'human': [x for x in range(1, MAX_CARDSET_ID)] + 'minor-league': { + 'primary': [27, 8], # 2005, Mario + 'secondary': [24], # 2025 + 'human': [x for x in range(1, 30)] }, - 'Major League': { - 'primary': [20, 21, 17, 18, 12, 6, 7, 8], # 1998, 1998 Promos, 2024, 24 Promos, 2008, 2013, 2012, Mario + 'major-league': { + 'primary': [27, 28, 24, 25, 13, 14, 6, 8], # 2005 + Promos, 2025 + Promos, 2018 + Promos, 2012, Mario 'secondary': [5, 3], # 2019, 2022 'human': ranked_cardsets }, - 'Hall of Fame': { - 'primary': [x for x in range(1, MAX_CARDSET_ID)], - 'secondary': [], + 'hall-of-fame': { + 'primary': [x for x in range(1, 30)], 'human': ranked_cardsets }, - 'Flashback': { - 'primary': [5, 1, 3, 9, 8], # 2019, 2021, 2022, 2023, Mario - 'secondary': [13, 5], # 2018, 2019 - 'human': [5, 1, 3, 9, 8] # 2019, 2021, 2022, 2023 + 'flashback': { + 'primary': [13, 5, 1, 3, 8], # 2018, 2019, 2021, 2022, Mario + 'secondary': [24], # 2025 + 'human': [13, 5, 1, 3, 8] # 2018, 2019, 2021, 2022 }, 'gauntlet-3': { 'primary': [13], # 2018 'secondary': [5, 11, 9], # 2019, 2016, 2023 - 'human': [x for x in range(1, MAX_CARDSET_ID)] + 'human': [x for x in range(1, 30)] }, 'gauntlet-4': { 'primary': [3, 6, 16], # 2022, 2013, Backyard Baseball @@ -54,17 +53,26 @@ CARDSETS = { 'gauntlet-5': { 'primary': [17, 8], # 2024, Mario 'secondary': [13], # 2018 - 'human': [x for x in range(1, MAX_CARDSET_ID)] + 'human': [x for x in range(1, 30)] }, 'gauntlet-6': { 'primary': [20, 8], # 1998, Mario 'secondary': [12], # 2008 - 'human': [x for x in range(1, MAX_CARDSET_ID)] + 'human': [x for x in range(1, 30)] }, 'gauntlet-7': { 'primary': [5, 23], # 2019, Brilliant Stars 'secondary': [1], # 2021 - 'human': [x for x in range(1, MAX_CARDSET_ID)] + 'human': [x for x in range(1, 30)] + }, + 'gauntlet-8': { + 'primary': [24], # 2025 + 'secondary': [17], + 'human': [24, 25, 22, 23] + }, + 'gauntlet-9': { + 'primary': [27], # 2005 + 'secondary': [24] # 2025 } } diff --git a/gauntlets.py b/gauntlets.py index 4a13ebf..7286d84 100644 --- a/gauntlets.py +++ b/gauntlets.py @@ -267,6 +267,24 @@ async def get_opponent(session: Session, this_team, this_event, this_run) -> Tea else: raise KeyError(f'Hmm...I do not know who you should be playing right now.') t_id = teams[random.randint(0, len(teams)-1)] + elif this_event['id'] == 9: + gp_to_tid = { + 0: 7, + 1: 4, + 2: 19, + 3: 22, + 4: 30, + 5: 3, + 6: 2, + 7: 1, + 8: 26, + 9: 20, + 10: 6 + } + try: + t_id = gp_to_tid[gp] + except KeyError: + raise KeyError(f'Hmm...I do not know who you should be playing with {gp} games played.') else: return None diff --git a/help_text.py b/help_text.py index 9956ad4..73c481d 100644 --- a/help_text.py +++ b/help_text.py @@ -45,7 +45,7 @@ HELP_NEWGAME = ( HELP_PLAYGAME = ( - f'- Run `!help Dice` to see all roll commands available; `!ab` is the one you will roll for every at bat\n' + f'- Run `/gamestate` for the current scorebug and click the Roll AB button\n' f'- Log results with `/log `; all results on the card should be represented, some have ' f'nested commands (e.g. `/log flyball b`)\n' f'- When you mess up a result, run `/log undo-play` and it will roll back one play\n' @@ -100,13 +100,13 @@ HELP_TS_MENU = ( HELP_REWARDS_PREMIUM = ( '- Win a 9-inning game\n' - '- Purchasable for 200₼\n' + '- Purchasable for 300₼\n' '- Purchasable for $3 on the ko-fi shop' ) HELP_REWARDS_STANDARD = ( '- Win a 3-inning game\n' - '- Purchasable for 100₼\n' + '- Purchasable for 200₼\n' '- Every fifth check-in (`/comeonmanineedthis`)\n' '- Purchasable for $2 on the ko-fi shop' ) @@ -146,5 +146,5 @@ HELP_START_PLAY = ( ) HELP_START_ASK = ( - f'Feel free to ask any questions down in ' + f'Feel free to ask any questions down in #paper-dynasty-chat' ) diff --git a/manual_pack_distribution.py b/manual_pack_distribution.py new file mode 100644 index 0000000..d37f2d8 --- /dev/null +++ b/manual_pack_distribution.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +""" +Manual script to distribute packs to all human-controlled teams +""" +import argparse +import asyncio +import logging +import os +import sys +from api_calls import db_get, db_post, DB_URL + +# Set up logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger('manual_pack_dist') + + +def check_environment(): + """Check that required environment variables are set""" + api_token = os.getenv('API_TOKEN') + database_env = os.getenv('DATABASE', 'dev') + + if not api_token: + logger.error("ERROR: API_TOKEN environment variable is not set!") + logger.error("Please set it with: export API_TOKEN='your-token-here'") + sys.exit(1) + + logger.info(f"Using database environment: {database_env}") + logger.info(f"API URL: {DB_URL}") + logger.info(f"API Token: {'*' * (len(api_token) - 4)}{api_token[-4:] if len(api_token) > 4 else '****'}") + logger.info("") + + +async def distribute_packs(num_packs: int = 5, exclude_team_abbrev: list[str] = None): + """Distribute packs to all human-controlled teams + + Args: + num_packs: Number of packs to give to each team (default: 5) + exclude_team_abbrev: List of team abbreviations to exclude (default: None) + """ + if exclude_team_abbrev is None: + exclude_team_abbrev = [] + + # Convert to uppercase for case-insensitive matching + exclude_team_abbrev = [abbrev.upper() for abbrev in exclude_team_abbrev] + + # Step 1: Get current season + logger.info("Fetching current season...") + current = await db_get('current') + if current is None: + logger.error("Failed to fetch current season from API. Check your API_TOKEN and DATABASE environment variables.") + logger.error(f"API URL: {DB_URL}") + logger.error("Make sure the API endpoint is accessible and your token is valid.") + raise ValueError("Could not connect to the database API") + + logger.info(f"Current season: {current['season']}") + + # Step 2: Get all teams for current season + logger.info(f"Fetching all teams for season {current['season']}...") + all_teams = await db_get('teams', params=[('season', current['season'])]) + if all_teams is None: + logger.error("Failed to fetch teams from API") + raise ValueError("Could not fetch teams from the database API") + + logger.info(f"Found {all_teams['count']} total teams") + + # Step 3: Filter for human-controlled teams only + qualifying_teams = [] + for team in all_teams['teams']: + if not team['is_ai'] and 'gauntlet' not in team['abbrev'].lower(): + # Check if team is in exclusion list + if team['abbrev'].upper() in exclude_team_abbrev: + logger.info(f" - {team['abbrev']}: {team['sname']} (EXCLUDED)") + continue + qualifying_teams.append(team) + logger.info(f" - {team['abbrev']}: {team['sname']}") + + logger.info(f"\nFound {len(qualifying_teams)} human-controlled teams") + if exclude_team_abbrev: + logger.info(f"Excluded teams: {', '.join(exclude_team_abbrev)}") + + # Step 4: Distribute packs + pack_type_id = 1 # Standard packs + + logger.info(f"\nDistributing {num_packs} standard packs to each team...") + + for team in qualifying_teams: + logger.info(f"Giving {num_packs} packs to {team['abbrev']} ({team['sname']})...") + + # Create pack payload + pack_payload = { + 'packs': [{ + 'team_id': team['id'], + 'pack_type_id': pack_type_id, + 'pack_cardset_id': None + } for _ in range(num_packs)] + } + + try: + result = await db_post('packs', payload=pack_payload) + logger.info(f" ✓ Successfully gave packs to {team['abbrev']}") + except Exception as e: + logger.error(f" ✗ Failed to give packs to {team['abbrev']}: {e}") + + logger.info(f"\n🎉 All done! Distributed {num_packs} packs to {len(qualifying_teams)} teams") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Distribute packs to all human-controlled teams', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Give 5 packs to all teams (default) + python manual_pack_distribution.py + + # Give 10 packs to all teams + python manual_pack_distribution.py --num-packs 10 + + # Give 5 packs, excluding certain teams + python manual_pack_distribution.py --exclude-team-abbrev NYY BOS + + # Give 3 packs, excluding one team + python manual_pack_distribution.py --num-packs 3 --exclude-team-abbrev LAD + +Environment Variables: + API_TOKEN - Required: API authentication token + DATABASE - Optional: 'dev' (default) or 'prod' + """ + ) + parser.add_argument( + '--num-packs', + type=int, + default=5, + help='Number of packs to give to each team (default: 5)' + ) + parser.add_argument( + '--exclude-team-abbrev', + nargs='*', + default=[], + help='Team abbreviations to exclude (space-separated, e.g., NYY BOS LAD)' + ) + + args = parser.parse_args() + + # Check environment before running + check_environment() + + asyncio.run(distribute_packs(num_packs=args.num_packs, exclude_team_abbrev=args.exclude_team_abbrev)) diff --git a/utilities/dropdown.py b/utilities/dropdown.py index 49e9f2c..3b7f134 100644 --- a/utilities/dropdown.py +++ b/utilities/dropdown.py @@ -456,7 +456,8 @@ class SelectBatterSub(discord.ui.Select): try: last_lineup.active = False self.session.add(last_lineup) - logger.info(f'Set {last_lineup.card.player.name_with_desc} as inactive') + self.session.flush() # Flush to ensure the change is applied + logger.info(f'Set lineup ID {last_lineup.id} as inactive') except Exception as e: log_exception(e) @@ -487,25 +488,42 @@ class SelectBatterSub(discord.ui.Select): self.session.add(human_bat_lineup) # self.session.commit() - logger.info(f'Inserted {human_bat_lineup.card.player.name_with_desc} in the {self.batting_order} spot') + logger.info(f'Inserted player {human_batter_card.player_id} (card {human_batter_card.id}) in the {self.batting_order} spot') + is_pinch_runner = False if this_play.batter == last_lineup: logger.info(f'Setting new sub to current play batter') this_play.batter = human_bat_lineup this_play.batter_pos = position elif this_play.on_first == last_lineup: - logger.info(f'Setting new sub to run at first') + logger.info(f'Setting new sub to run at first - this is a pinch runner') this_play.on_first = human_bat_lineup + is_pinch_runner = True elif this_play.on_second == last_lineup: - logger.info(f'Setting new sub to run at second') + logger.info(f'Setting new sub to run at second - this is a pinch runner') this_play.on_second = human_bat_lineup + is_pinch_runner = True elif this_play.on_third == last_lineup: - logger.info(f'Setting new sub to run at third') + logger.info(f'Setting new sub to run at third - this is a pinch runner') this_play.on_third = human_bat_lineup + is_pinch_runner = True logger.info(f'Adding play to session: {this_play}') self.session.add(this_play) self.session.commit() + # If this is a pinch runner, create an entry Play record for them + if is_pinch_runner: + # Import inside function to avoid circular import + from command_logic.logic_gameplay import create_pinch_runner_entry_play + + logger.info(f'Creating pinch runner entry Play for {human_bat_lineup.player.name_with_desc}') + create_pinch_runner_entry_play( + session=self.session, + game=self.game, + current_play=this_play, + pinch_runner_lineup=human_bat_lineup + ) + # if not same_position: # pos_dict_list = { # 'Pinch Hitter': 'PH',