S10 Updates + PR Bugfix

This commit is contained in:
Cal Corum 2025-11-09 06:12:46 -06:00
parent 80e94498fa
commit 0e3b98eb65
8 changed files with 293 additions and 42 deletions

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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 <play-data>`; 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'
)

148
manual_pack_distribution.py Normal file
View File

@ -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))

View File

@ -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',