From 0c638ae6a5bc3a920d31b5f53fabfab27186114c Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Sun, 19 Feb 2023 21:34:38 -0600 Subject: [PATCH] Initial commit --- ai_manager.py | 469 ++++++ cogs/admins.py | 1334 ++++++++++++---- cogs/economy.py | 3367 +++++++++++++++++++++++++++++----------- cogs/gameplay.py | 3488 ++++++++++++++++++++++++++++++++++++++++++ cogs/owner.py | 108 +- cogs/players.py | 1592 ++++++++++++++++--- db_calls.py | 161 ++ db_calls_gameplay.py | 2128 ++++++++++++++++++++++++++ dice.py | 917 +++++++++++ gameplay_helpers.py | 157 ++ help_text.py | 146 ++ helpers.py | 1898 +++++++++++++++++++++++ paperdynasty.py | 64 +- requirements.txt | 6 + 14 files changed, 14456 insertions(+), 1379 deletions(-) create mode 100644 ai_manager.py create mode 100644 cogs/gameplay.py create mode 100644 db_calls.py create mode 100644 db_calls_gameplay.py create mode 100644 dice.py create mode 100644 gameplay_helpers.py create mode 100644 help_text.py create mode 100644 helpers.py create mode 100644 requirements.txt diff --git a/ai_manager.py b/ai_manager.py new file mode 100644 index 0000000..9d9a9dd --- /dev/null +++ b/ai_manager.py @@ -0,0 +1,469 @@ +import copy +import logging +import random + +import pydantic +from db_calls_gameplay import StratPlay, get_team_lineups +from db_calls import db_get, db_post +import db_calls_card_creation as scouting +from peewee import * +from typing import Optional, Literal + +db = SqliteDatabase( + 'storage/ai-database.db', + pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1 * 64000, + 'synchronous': 0 + } +) + + +class BaseModel(Model): + class Meta: + database = db + + +class Lineup(BaseModel): + team_id = IntegerField() + game_level = CharField() # 'minor', 'major', 'hof' + vs_hand = CharField() # 'left', 'right' + bat_one_card = IntegerField() + bat_one_pos = CharField() + bat_two_card = IntegerField() + bat_two_pos = CharField() + bat_three_card = IntegerField() + bat_three_pos = CharField() + bat_four_card = IntegerField() + bat_four_pos = CharField() + bat_five_card = IntegerField() + bat_five_pos = CharField() + bat_six_card = IntegerField() + bat_six_pos = CharField() + bat_seven_card = IntegerField() + bat_seven_pos = CharField() + bat_eight_card = IntegerField() + bat_eight_pos = CharField() + bat_nine_card = IntegerField() + bat_nine_pos = CharField() + + +class Rotation(BaseModel): + team_id = IntegerField() + game_level = CharField() + sp_one_card = IntegerField() + sp_two_card = IntegerField() + sp_three_card = IntegerField() + sp_four_card = IntegerField() + sp_five_card = IntegerField() + + +class Bullpen(BaseModel): + team_id = IntegerField() + game_level = CharField() # 'minor', 'major', 'hof' + vs_hand = CharField() # 'left', 'right' + + +# def grade_player() +def batter_grading(vs_hand, rg_data): + raw_bat = (rg_data[f'contact-{vs_hand}'] + rg_data[f'power-{vs_hand}'] + + min(rg_data[f'contact-{vs_hand}'], rg_data[f'power-{vs_hand}'])) / 3 + other_metrics = [ + rg_data['vision'], rg_data['speed'], rg_data['stealing'], rg_data['reaction'], rg_data['arm'], + rg_data['fielding'] + ] + blended_rating = sum(sorted(other_metrics, reverse=True)[:4], raw_bat * 4) / 8 + return { + 'overall': rg_data['overall'], + 'raw-bat': raw_bat, + 'blended': blended_rating + } + + +def get_or_create_card(player: dict, team: dict) -> int: + # get player card; create one if none found + z = 0 + card_id = None + while z < 2 and card_id is None: + z += 1 + c_query = db_get('cards', + params=[('team_id', team['id']), ('player_id', player['player_id'])]) + if c_query['count'] > 0: + card_id = c_query['cards'][0]['id'] + else: + db_post( + 'cards', + payload={'cards': [ + {'player_id': player['player_id'], 'team_id': team['id'], 'pack_id': 1} + ]} + ) + if card_id is None: + logging.error(f'Could not create card for {player["p_name"]} on the {team["lname"]}') + + return card_id + + +# First attempt - pulls ratings guide info +def build_lineup_graded(team_object: dict, vs_hand: str, league_name: str, num_innings: int, batter_rg): + in_lineup = [] # player ids for quick checking + + players = { + 'c': [], + '1b': [], + '2b': [], + '3b': [], + 'ss': [], + 'lf': [], + 'cf': [], + 'rf': [], + 'dh': [] + } + + # Get all eligible players + try: + all_players = db_get( + endpoint='players', + params=[('mlbclub', team_object['lname']), ('pos_exclude', 'RP'), ('inc_dex', False)], + timeout=10 + )['players'] + except ConnectionError as e: + raise ConnectionError(f'Error pulling players for the {team_object["lname"]}. Cal help plz.') + + # Grade players and add to position lists + for x in all_players: + if x['pos_1'] not in ['SP', 'RP']: + this_guy = copy.deepcopy(x) + rg_data = next(x for x in batter_rg if x['player_id'] == this_guy['player_id']) + grading = batter_grading(vs_hand, rg_data) + logging.info(f'player: {this_guy} / grading: {grading}') + + this_guy['overall'] = grading['overall'] + this_guy['raw-bat'] = grading['raw-bat'] + this_guy['blended'] = grading['blended'] + + players['dh'].append(this_guy) + if 'C' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['c'].append(this_guy) + if '1B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['1b'].append(this_guy) + if '2B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['2b'].append(this_guy) + if '3B' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['3b'].append(this_guy) + if 'SS' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['ss'].append(this_guy) + if 'LF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['lf'].append(this_guy) + if 'CF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['cf'].append(this_guy) + if 'RF' in [this_guy['pos_1'], this_guy['pos_2'], this_guy['pos_3'], this_guy['pos_4'], this_guy['pos_5'], + this_guy['pos_6'], this_guy['pos_7'], this_guy['pos_8']]: + players['rf'].append(this_guy) + + # Select players for lineup + while len(players) > 0: + # Sort players dict by position with fewest eligible players + this_pass_data = sorted(players.items(), key=lambda item: len(item[1])) + logging.info(f'this_pass_data: {this_pass_data}') + # Pull one tuple ('', []) + this_pos_data = this_pass_data[0] + logging.info(f'this_pos_data: {this_pos_data}') + # Sort players at this position by blended rating (raw-bat for DH) + if this_pos_data[0] == 'dh': + this_pos = sorted(this_pos_data[1], key=lambda item: item['raw-bat'], reverse=True) + else: + this_pos = sorted(this_pos_data[1], key=lambda item: item['blended'], reverse=True) + logging.info(f'this_pos: {this_pos}') + + # Add top player to the lineup + in_lineup.append({'position': copy.deepcopy(this_pos_data[0].upper()), 'player': copy.deepcopy(this_pos[0])}) + logging.info(f'adding player: {this_pos[0]}') + logging.info(f'deleting position: {this_pos_data[0]}') + # Remove this position from consideration + del players[this_pos_data[0]] + + for key in players: + for x in players[key]: + # Remove duplicate players (even across cardsets) once in lineup + if x['strat_code'] == this_pos[0]['strat_code']: + players[key].remove(x) + + # Set batting order as list of lists: [ ['', ], ... ] + batting_order = [] + + return batting_order + + +def build_lineup(team_object: dict, game_id: int, league_name: str, vs_hand: str = 'r') -> list: + players = { + 'C': None, + '1B': None, + '2B': None, + '3B': None, + 'SS': None, + 'LF': None, + 'CF': None, + 'RF': None, + 'DH': None + } + p_names = [] + + # Pull players sorted by current cost + try: + all_players = db_get( + endpoint='players', + params=[ + ('mlbclub', team_object['lname']), ('pos_include', 'C'), ('pos_include', '1B'), ('pos_include', '2B'), + ('pos_include', '3B'), ('pos_include', 'SS'), ('pos_include', 'LF'), ('pos_include', 'CF'), + ('pos_include', 'RF'), ('pos_include', 'DH'), ('cardset_id_exclude', 2), ('cardset_id_exclude', 4), + ('inc_dex', False), ('sort_by', 'cost-desc') + ], + timeout=10 + )['players'] + except ConnectionError as e: + raise ConnectionError(f'Error pulling batters for the {team_object["lname"]}. Cal help plz.') + + logging.info(f'build_lineup - eligible batter count: {len(all_players)}') + + # Choose starting nine & position + # In order of cost: + # Try to add to pos_1; if unavailable, try remaining pos; if unavailable, add to DH + # If all pos unavailable; note player's pos_1 and check if incumbent can move; if so, check for new incumbent + # and recurse + # If not possible, pass player and continue + for guy in all_players: + placed = False + if guy['p_name'] not in p_names: + for pos in [ + guy['pos_1'], guy['pos_2'], guy['pos_3'], guy['pos_4'], guy['pos_5'], guy['pos_6'], guy['pos_7'], + guy['pos_8'] + ]: + if pos is None or pos in ['SP', 'RP', 'CP']: + break + elif players[pos] is None: + players[pos] = guy + p_names.append(guy["p_name"]) + placed = True + break + + if not placed: + if players['DH'] is None: + players['DH'] = guy + p_names.append(guy["p_name"]) + else: + logging.info(f'build_lineup - could not place {guy["p_name"]} in {team_object["sname"]} lineup') + else: + logging.info(f'build_lineup - {guy["p_name"]} already in lineup') + + if None in players.values(): + bad_pos = {x for x in players if players[x] is None} + raise ValueError(f'Could not find a {bad_pos} for the {team_object["sname"]}') + + logging.info(f'build_lineup - {players}') + + # Sort players into lineup + # Pseudo-random? Patterns? Something to mix up lineups + # 421356789 or [123 (rand)][456 (rand)][789 (rand)] (optional: chance to flip 3/4 and 6/7) + lineups = [] + + # [ (, ), (, ), etc. ] + sorted_players = sorted(players.items(), key=lambda x: x[1]['cost'], reverse=True) + + grp_1 = sorted_players[:3] + grp_2 = sorted_players[3:6] + grp_3 = sorted_players[6:] + random.shuffle(grp_1) + random.shuffle(grp_2) + random.shuffle(grp_3) + + i = 1 + for x in [grp_1, grp_2, grp_3]: + for y in x: + # get player card; create one if none found + z = 0 + card_id = None + while z < 2 and card_id is None: + z += 1 + c_query = db_get('cards', params=[('team_id', team_object['id']), ('player_id', y[1]['player_id'])]) + if c_query['count'] > 0: + card_id = c_query['cards'][0]['id'] + else: + db_post( + 'cards', + payload={'cards': [ + {'player_id': y[1]['player_id'], 'team_id': team_object['id'], 'pack_id': 1} + ]} + ) + if card_id is None: + logging.error(f'Could not create card for {y[1]["p_name"]} on the {team_object["lname"]}') + + lineups.append({ + 'game_id': game_id, + 'team_id': team_object['id'], + 'player_id': y[1]['player_id'], + 'card_id': card_id, + 'position': y[0], + 'batting_order': i, + 'after_play': 0 + }) + i += 1 + + logging.info(f'build_lineup - final lineup: {lineups}') + + return lineups + + +def get_starting_pitcher(team_object: dict, game_id: int, is_home: bool, league_name: str = None) -> dict: + # Pull starters sorted by current cost + try: + pitchers = db_get( + endpoint='players', + params=[ + ('mlbclub', team_object['lname']), ('pos_include', 'SP'), ('pos_exclude', 'RP'), + ('cardset_id_exclude', 2), ('cardset_id_exclude', 4), ('inc_dex', False), ('sort_by', 'cost-desc'), + ('limit', 5) + ], + timeout=10 + ) + except ConnectionError as e: + logging.error(f'Could not get pitchers for {team_object["lname"]}: {e}') + raise ConnectionError(f'Error pulling starting pitchers for the {team_object["lname"]}. Cal help plz.') + + logging.info(f'build_lineup - eligible starting pitcher count: {len(pitchers)}') + if pitchers['count'] == 0: + raise DatabaseError(f'Could not find any SP for {team_object["abbrev"]}. Seems like a Cal issue.') + + d_100 = random.randint(1, 100) + starter = None + if is_home: + if d_100 <= 30: + starter = pitchers['players'][0] + elif d_100 <= 55 and pitchers['count'] > 1: + starter = pitchers['players'][1] + elif d_100 <= 75 and pitchers['count'] > 2: + starter = pitchers['players'][2] + elif d_100 <= 90 and pitchers['count'] > 3: + starter = pitchers['players'][3] + elif pitchers['count'] == 5: + starter = pitchers['players'][4] + else: + starter = pitchers['players'][0] + else: + if d_100 <= 50: + starter = pitchers['players'][0] + elif d_100 <= 75 and pitchers['count'] > 1: + starter = pitchers['players'][1] + elif d_100 <= 85 and pitchers['count'] > 2: + starter = pitchers['players'][2] + elif d_100 <= 95 and pitchers['count'] > 3: + starter = pitchers['players'][3] + elif pitchers['count'] == 5: + starter = pitchers['players'][4] + else: + starter = pitchers['players'][0] + + # get player card; create one if none found + z = 0 + card_id = None + while z < 2 and card_id is None: + z += 1 + c_query = db_get('cards', params=[('team_id', team_object['id']), ('player_id', starter['player_id'])]) + if c_query['count'] > 0: + card_id = c_query['cards'][0]['id'] + else: + db_post( + 'cards', + payload={'cards': [ + {'player_id': starter['player_id'], 'team_id': team_object['id'], 'pack_id': 1} + ]} + ) + if card_id is None: + logging.error(f'Could not create card for {starter["p_name"]} on the {team_object["lname"]}') + + return { + 'game_id': game_id, + 'team_id': team_object['id'], + 'player_id': starter['player_id'], + 'card_id': card_id, + 'position': 'P', + 'batting_order': 10, + 'after_play': 0 + } + + +def get_relief_pitcher(this_play: StratPlay, ai_team: dict, used_pitchers: list, league_name: str = None) -> dict: + used_codes = [db_get('cards', object_id=x.card_id)["player"]["strat_code"] for x in used_pitchers] + logging.info(f'get_rp - used_pitchers: {used_codes}') + + reliever = None + attempts = 0 + while reliever is None: + attempts += 1 + if attempts > 3: + raise ValueError(f'Could not find a reliever for the {ai_team["sname"]}. Cal plz.') + + # Pull relievers sorted by current cost + params = [ + ('mlbclub', ai_team['lname']), ('pos_include', 'RP'), ('cardset_id_exclude', 2), ('cardset_id_exclude', 4), + ('inc_dex', False), ('sort_by', 'cost-desc') + ] + + alt_flag = False + if attempts == 1: + # Try to get long man + if this_play.inning_num < 6: + logging.info(f'get_rp - game {this_play.game.id} try for long man') + params.append(('pos_include', 'SP')) + alt_flag = True + + # Try to get closer + elif this_play.inning_num > 8: + if (this_play.inning_half == 'top' and this_play.home_score >= this_play.away_score) or \ + (this_play.inning_half == 'bot' and this_play.away_score >= this_play.home_score): + logging.info(f'get_rp - game {this_play.game.id} try for closer') + params.append(('pos_include', 'CP')) + alt_flag = True + else: + params.append(('pos_exclude', 'CP')) + + # Try to exclude long men + elif attempts == 1: + logging.info(f'get_rp - game {this_play.game.id} try to exclude long men') + params.append(('pos_exclude', 'SP')) + + try: + pitchers = db_get( + endpoint='players', + params=params, + timeout=10 + ) + except ConnectionError as e: + logging.error(f'Could not get pitchers for {ai_team["lname"]}: {e}') + raise ConnectionError(f'Error pulling starting pitchers for the {ai_team["lname"]}. Cal help plz.') + + if pitchers['count'] > 0: + if alt_flag or this_play.inning_num > 9 or attempts > 2: + start = 0 + else: + start = 9 - this_play.inning_num + + for count, guy in enumerate(pitchers['players']): + if count >= start and guy['strat_code'] not in used_codes: + card_id = get_or_create_card(guy, ai_team) + + return { + 'game_id': this_play.game.id, + 'team_id': ai_team['id'], + 'player_id': guy['player_id'], + 'card_id': card_id, + 'position': 'P', + 'batting_order': 10, + 'after_play': this_play.play_num - 1 + } diff --git a/cogs/admins.py b/cogs/admins.py index f5b45b0..6c3c5bb 100644 --- a/cogs/admins.py +++ b/cogs/admins.py @@ -1,297 +1,1089 @@ -import os -import csv +import copy +import datetime +import logging +import re + import discord -from db_engine import * -from discord.ext import commands +import pygsheets +import requests + +from helpers import * +from db_calls import * +from discord import Member +from discord.ext import commands, tasks +from discord import app_commands +from difflib import get_close_matches + +# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' +# logging.basicConfig( +# filename=f'logs/{date}.log', +# format='%(asctime)s - %(levelname)s - %(message)s', +# level=logging.WARNING +# ) class Admins(commands.Cog): def __init__(self, bot): self.bot = bot - self.helpers = self.bot.get_cog('Helpers') + self.weekly_reset_done = False - db.connect(reuse_if_open=True) - db.create_tables([Current, Rarity, Player, Team, Card, Roster, Pack, Special, Result]) - db.close() + # async def cog_load(self): + # await self.bot.change_presence(activity=discord.Game(name='strat | .help')) - async def cog_load(self): - await self.bot.change_presence(activity=discord.Game(name='strat: .help')) + async def cog_command_error(self, ctx, error): + await ctx.send(f'{error}') - @commands.command(name='refresh') + # @tasks.loop(minutes=10) + # async def weekly_loop(self): + # now = datetime.datetime.now() + # if now.hour == 6 and now.weekday() == 0 and not self.weekly_reset_done: + # weekly_packs = await self.increment_week() + # self.weekly_reset_done = True + # + # sba = self.bot.get_guild(613880856032968834) + # news_ticker = discord.utils.get(sba.text_channels, name='pd-news-ticker') + # await news_ticker.send('The new week is here! Run `.comeonmanineedthis` for your weekly pack!\n\n' + # 'Cal will hand out packs for the final standings when he wakes his lazy ass up. ') + # + # cal_private = discord.utils.get(sba.text_channels, name='hello-manticorum67') + # await cal_private.send(f'Weekly packs:\n\n{weekly_packs}') + # + # if now.weekday() != 0 and self.weekly_reset_done: + # self.weekly_reset_done = False + # + # db.close() + + # @staticmethod + # async def increment_week(): + # current = Current.get() + # current.week += 1 + # current.save() + # + # weekly_string = '' + # all_teams = Team.select() + # for x in all_teams: + # weekly_string += f'{x.sname}: {x.weeklypacks} packs\n' + # x.weeklyclaim = False + # x.dailyclaim = False + # x.weeklypacks = 0 + # x.save() + # + # return weekly_string + + group_give = app_commands.Group(name='give', description='Mod: Distribute packs or tokens') + + @group_give.command(name='packs') + async def give_packs_subcommand( + self, interaction: discord.Interaction, num_packs: int, pack_type: Literal['Standard', 'Premium', 'MVP'], + team_abbrevs: str): + if not owner_only(interaction): + await interaction.response.send_message(random_no_gif()) + return + + current = db_get('current') + await interaction.response.defer() + p_query = db_get('packtypes', params=[('name', pack_type)]) + + response = '' + for x in team_abbrevs.split(' '): + team = db_get('teams', params=[('abbrev', x), ('season', current['season'])])['teams'][0] + + if team: + total_packs = give_packs(team, num_packs, pack_type=p_query['packtypes'][0]) + response += f'Just gave {num_packs} {pack_type} pack{"s" if num_packs > 1 else ""} to the ' \ + f'{team["sname"]}. They now have {total_packs["count"]} ' \ + f'pack{"s" if total_packs["count"] > 1 else ""}.\n' + + elif x.upper() == 'LEAGUE': + all_teams = db_get('teams', params=[('season', current['season'])]) + for y in all_teams['teams']: + logging.warning(f'Giving {num_packs} pack(s) to team: {y["abbrev"]}') + give_packs(team, num_packs) + response = f'Just gave all {all_teams["count"]} teams {num_packs} ' \ + f'standard pack{"s" if num_packs > 1 else ""}!' + + else: + await interaction.edit_original_response(content=f'Hmm...I\'m not sure who **{x.upper()}** is.') + return + + logging.info(f'give info: {response}') + await interaction.edit_original_response(content=f'{response if len(response) > 0 else "All done!"}') + + @commands.hybrid_command(name='post-guide', help='Mod: Post the ratings guide to team sheet') @commands.is_owner() - async def import_players(self, ctx, file='import.csv'): - rarities = {'MVP': 10, 'All-Star': 7, 'Starter': 5, 'Reserve': 3, 'Replacement': 0} - war_vals = { - 'SP': {'MVP': 7.44, 'All-Star': 5.76, 'Starter': 3.06, 'Reserve': 1.30}, - 'RP': {'MVP': 2.56, 'All-Star': 1.93, 'Starter': 1.03, 'Reserve': 0.37}, - 'Pos': {'MVP': 7.76, 'All-Star': 5.50, 'Starter': 3.01, 'Reserve': 1.06}, + async def post_guide_command(self, ctx, gm: Member): + team = get_team_by_owner(gm.id) + db_patch('teams', object_id=team['id'], params=[('has_guide', True)]) + + post_ratings_guide(team, self.bot) + + await ctx.send(random_conf_gif()) + + @commands.hybrid_command(name='sync-sheets', help='Mod: Sync AI team sheets') + @commands.is_owner() + async def sync_sheets_command(self, ctx): + t_query = db_get('teams', params=[('is_ai', True)]) + + response = await ctx.send(f'Alright, I\'m getting started...') + + sheets = get_sheets(self.bot) + for count, team in enumerate(t_query['teams']): + this_sheet = sheets.open_by_key(team['gsheet']) + team_data = this_sheet.worksheet_by_title('Team Data') + team_data.update_values( + crange='B1:B2', + values=[[f'{team["id"]}'], [f'\'{team_hash(team)}']] + ) + await response.edit(content=f'Just finished the {team["sname"]} ({count + 1}/{len(t_query["teams"])})...') + + await response.edit(content=f'All done!') + + @commands.hybrid_command(name='update-rarity', help='Mod: Pull current rarities and update players') + @commands.is_owner() + async def update_rarity_command(self, ctx, sheet_url: str): + await ctx.send(f'Oh boy, here I go sheetsing again!') + + # Template: + # https://docs.google.com/spreadsheets/d/14FpNo2JOmAKc7rdeiEGNUOud_gbLSrlcoic-OXlhsQc/edit#gid=1845561581 + sheets = get_sheets(self.bot) + this_sheet = sheets.open_by_url(sheet_url) + logging.info(f'this_sheet: {this_sheet}') + up_sheet = this_sheet.worksheet_by_title('Rarity Updates') + logging.info(f'up_sheet: {up_sheet}') + all_players = up_sheet.range('A2:E') + # logging.info(f'all_players: {all_players}') + rarities = { + 'MVP': 1, + 'All-Star': 2, + 'Starter': 3, + 'Reserve': 4, + 'Replacement': 5, + 'Hall of Fame': 99 } - for x in rarities.keys(): - check_rar = Rarity.get_or_none(Rarity.name == x) - if not check_rar: - new_rar = Rarity(value=rarities[x], name=x) - new_rar.save() - curr = Current.get_or_none() - if not curr: - new_curr = Current(season=1, week=1, packlimit=8) - new_curr.save() + def new_cost(player, new_rarity, old_rarity): + old_cost = player['cost'] + old_rarity = old_rarity + new_rarity = new_rarity + logging.info(f'old_rarity: {old_rarity} / new_rarity: {new_rarity}') - tba_players = [] - if os.path.exists(file): - with open(file, newline='') as cardfile: - async with ctx.typing(): - spamreader = csv.reader(cardfile) - for row in spamreader: - name = row[0] - cardset = row[2] + if old_rarity == 1: + if new_rarity == 2: + return max(old_cost - 540, 100) + elif new_rarity == 3: + return max(old_cost - 720, 50) + elif new_rarity == 4: + return max(old_cost - 780, 15) + elif new_rarity == 5: + return max(old_cost - 800, 5) + elif old_rarity == 2: + if new_rarity == 1: + return old_cost + 540 + elif new_rarity == 3: + return max(old_cost - 180, 50) + elif new_rarity == 4: + return max(old_cost - 240, 15) + elif new_rarity == 5: + return max(old_cost - 260, 5) + elif old_rarity == 3: + if new_rarity == 1: + return old_cost + 720 + elif new_rarity == 2: + return old_cost + 180 + elif new_rarity == 4: + return max(old_cost - 60, 15) + elif new_rarity == 5: + return max(old_cost - 80, 5) + elif old_rarity == 4: + if new_rarity == 1: + return old_cost + 780 + elif new_rarity == 2: + return old_cost + 240 + elif new_rarity == 3: + return old_cost + 60 + elif new_rarity == 5: + return max(old_cost - 20, 5) + elif old_rarity == 5: + if new_rarity == 1: + return old_cost + 800 + elif new_rarity == 2: + return old_cost + 260 + elif new_rarity == 3: + return old_cost + 80 + elif new_rarity == 4: + return old_cost + 20 - this_guy = Player.get_or_none(Player.name == name, Player.cardset == cardset) - flag = False - if not this_guy: - mlbclub = row[1] - cardset = row[2] - wara = float(row[3]) - primary = row[4] - url = row[5] - if row[6] != '': - url2 = row[6] - else: - url2 = None - if row[7] != '': - pos1 = row[7] - else: - pos1 = None - if row[8] != '': - pos2 = row[8] - else: - pos2 = None - if row[9] != '': - pos3 = row[9] - else: - pos3 = None - if row[10] != '': - pos4 = row[10] - else: - pos4 = None - if row[11] != '': - pos5 = row[11] - else: - pos5 = None - if row[12] != '': - pos6 = row[12] - else: - pos6 = None - if row[13] != '': - pos7 = row[13] - else: - pos7 = None - if row[14] != '': - pos8 = row[14] - else: - pos8 = None - rarity = Rarity.get(Rarity.name == 'Replacement') + raise KeyError(f'Could not find a cost update for {player["p_name"]} from {player["rarity"]["name"]} to ' + f'{new_rarity}') - if primary == 'RP': - if wara >= war_vals['RP']['MVP']: - rarity = Rarity.get(Rarity.name == 'MVP') - elif wara >= war_vals['RP']['All-Star']: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif wara >= war_vals['RP']['Starter']: - rarity = Rarity.get(Rarity.name == 'Starter') - elif wara > war_vals['RP']['Reserve']: - rarity = Rarity.get(Rarity.name == 'Reserve') - elif primary == 'SP': - if wara >= war_vals['SP']['MVP']: - rarity = Rarity.get(Rarity.name == 'MVP') - elif wara >= war_vals['SP']['All-Star']: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif wara >= war_vals['SP']['Starter']: - rarity = Rarity.get(Rarity.name == 'Starter') - elif wara > war_vals['SP']['Reserve']: - rarity = Rarity.get(Rarity.name == 'Reserve') - else: - if wara >= war_vals['Pos']['MVP']: - rarity = Rarity.get(Rarity.name == 'MVP') - elif wara >= war_vals['Pos']['All-Star']: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif wara >= war_vals['Pos']['Starter']: - rarity = Rarity.get(Rarity.name == 'Starter') - elif wara > war_vals['Pos']['Reserve']: - rarity = Rarity.get(Rarity.name == 'Reserve') + player_id = 804 + errors = [] + new_players = [] + done = 0 + for line in all_players: + # logging.info(f'line: {line}') + if line[4].value == 'CHANGE': + try: + logging.info(f'Updating {line[1].value}') + this_player = db_get('players', object_id=line[0].value) - tba_players.append({ - 'name': name, - 'mlbclub': mlbclub, - 'cardset': cardset, - 'rarity': rarity, - 'wara': wara, - 'primary': primary, - 'url': url, - 'pos1': pos1, - 'pos2': pos2, - 'pos3': pos3, - 'pos4': pos4, - 'pos5': pos5, - 'pos6': pos6, - 'pos7': pos7, - 'pos8': pos8, - }) - else: - print(f'Matched: {this_guy.name}') - if this_guy.mlbclub != row[1]: - print(f'mlbclub: {this_guy.mlbclub} to {row[1]}') - this_guy.mlbclub = row[1] - flag = True - if this_guy.cardset != row[2]: - print(f'cardset: {this_guy.cardset} to {row[2]}') - this_guy.cardset = row[2] - flag = True - # if this_guy.wara != row[3]: - # print(f'wara: {this_guy.wara} to {row[3]}') - # this_guy.wara = float(row[3]) - # flag = True - if this_guy.primary != row[4]: - print(f'primary: {this_guy.primary} to {row[4]}') - this_guy.primary = row[4] - flag = True - if this_guy.url != row[5]: - print(f'url: {this_guy.url} to {row[5]}') - this_guy.url = row[5] - flag = True - if this_guy.pos1 != row[7]: - print(f'pos1: {this_guy.pos1} to {row[7]}') - this_guy.pos1 = row[7] - flag = True - rarity = Rarity.get(Rarity.name == 'Replacement') + logging.info(f'this_player: {this_player["p_name"]}') + new_player = copy.deepcopy(this_player) + new_player['player_id'] = player_id + done + logging.info(f'new_player: {new_player["player_id"]}') + new_player['image'] = new_player['image'].replace("-launch", "-launch/update01") + new_player['rarity_id'] = rarities[line[3].value] + # new_player['cardset_id'] = 3 + new_player['cost'] = new_cost(this_player, rarities[line[3].value], rarities[line[2].value]) + logging.info(f'new_player cost: {new_player["cost"]}') - if this_guy.primary == 'RP': - if this_guy.wara >= war_vals['RP']['MVP']: - rarity = Rarity.get(Rarity.name == 'MVP') - elif this_guy.wara >= war_vals['RP']['All-Star']: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif this_guy.wara >= war_vals['RP']['Starter']: - rarity = Rarity.get(Rarity.name == 'Starter') - elif this_guy.wara > war_vals['RP']['Reserve']: - rarity = Rarity.get(Rarity.name == 'Reserve') - elif this_guy.primary == 'SP': - if this_guy.wara >= war_vals['SP']['MVP']: - rarity = Rarity.get(Rarity.name == 'MVP') - elif this_guy.wara >= war_vals['SP']['All-Star']: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif this_guy.wara >= war_vals['SP']['Starter']: - rarity = Rarity.get(Rarity.name == 'Starter') - elif this_guy.wara > war_vals['SP']['Reserve']: - rarity = Rarity.get(Rarity.name == 'Reserve') - else: - if this_guy.wara >= war_vals['Pos']['MVP']: - rarity = Rarity.get(Rarity.name == 'MVP') - elif this_guy.wara >= war_vals['Pos']['All-Star']: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif this_guy.wara >= war_vals['Pos']['Starter']: - rarity = Rarity.get(Rarity.name == 'Starter') - elif this_guy.wara > war_vals['Pos']['Reserve']: - rarity = Rarity.get(Rarity.name == 'Reserve') + this_player = db_patch('players', object_id=this_player['player_id'], params=[ + ('cost', new_player['cost']) + ]) + logging.info(f'patched_player: {this_player["p_name"]} / cardset: {this_player["cardset"]["id"]}') + # new_players.append(new_player) - if this_guy.rarity != rarity: - this_guy.rarity = rarity - flag = True + # new_player = db_patch('players', object_id=this_player['player_id'], params=[ + # ('image', this_player['image'].replace("-launch", "-launch/update01")), + # ('rarity_id', rarities[line[3].value]), ('cost', new_cost(this_player, rarities[line[3].value])) + # ]) + done += 1 + except Exception as e: + e_message = f'{line[1].value}: {e}' + logging.error(e_message) + errors.append(e_message) - if flag: - print(f'Updating {this_guy.name}') - this_guy.save() - flag = False + db_post('players', payload={'players': new_players}) + await ctx.send(f'Updated {done} players!') + if len(errors) > 0: + e_string = "\n- ".join(errors) + await ctx.send(f'I encountered the following errors:\n\n{e_string}') - print(f'We have {len(tba_players)} players to update.') - with db.atomic(): - try: - for batch in chunked(tba_players, 20): - Player.insert_many(batch).execute() - except Exception as e: - print(f'**ERROR** (import_players): {e}') - await ctx.send(f'Oof, I ran into an issue importing those players. ' - f'This might be ugly:\n\n{e}') - finally: - num_players = Player.select().count() - await ctx.send(f'Alright, I have {num_players} players in the database now.') - else: - await ctx.send('Yikes, I could not find the import.csv file.') + # @commands.command(name='refresh') + # @commands.is_owner() + # async def import_players(self, ctx): + # rarities = {'MVP': 10, 'All-Star': 7, 'Starter': 5, 'Reserve': 3, 'Replacement': 0} + # war_vals = { + # 'SP': {'MVP': 7.5, 'All-Star': 5.5, 'Starter': 3.5, 'Reserve': 1.5}, + # 'RP': {'MVP': 7.5, 'All-Star': 5.5, 'Starter': 3.5, 'Reserve': 1.5}, + # 'Pos': {'MVP': 7.5, 'All-Star': 5.5, 'Starter': 3.5, 'Reserve': 1.5}, + # 'Park': {'MVP': 4, 'All-Star': 3, 'Starter': 2, 'Reserve': 1} + # } + # for x in rarities.keys(): + # check_rar = Rarity.get_or_none(Rarity.name == x) + # if not check_rar: + # new_rar = Rarity(value=rarities[x], name=x) + # new_rar.save() + # + # curr = Current.get_or_none() + # if not curr: + # new_curr = Current(season=2, week=1, packlimit=6) + # new_curr.save() + # + # await ctx.send('Let me go check the data sheet...') + # + # # Get data from Sheets + # async with ctx.typing(): + # sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json') + # try: + # data_sheet = sheets.open_by_key('1amZzO8Fm4RN8QD5j-WVdp5XqSw54fU7CT8mummzN3Z4').\ + # worksheet_by_title('Player Import') + # raw_data = data_sheet.get_values('A2', 'P10000') + # except Exception as e: + # logging.error(f'{e}') + # await ctx.send(f'Yikes. I need a grown-up to read this. I don\'t know what it means:\n\n{e}') + # return + # else: + # await ctx.send(f'Noice - got it! Give me a minute to go through this data...') + # + # tba_players = [] + # async with ctx.typing(): + # for row in raw_data: + # if row[0] != '': + # name = row[0] + # cardset = row[2] + # + # this_guy = Player.get_or_none((Player.name == name) & (Player.cardset == cardset)) + # flag = False + # if not this_guy: + # mlbclub = row[1] + # cardset = row[2] + # wara = float(row[3]) + # primary = row[4] + # url = row[5] + # franchise = row[15] + # if row[6] != '': + # url2 = row[6] + # else: + # url2 = None + # if row[7] != '': + # pos1 = row[7] + # else: + # pos1 = None + # if row[8] != '': + # pos2 = row[8] + # else: + # pos2 = None + # if row[9] != '': + # pos3 = row[9] + # else: + # pos3 = None + # if row[10] != '': + # pos4 = row[10] + # else: + # pos4 = None + # if row[11] != '': + # pos5 = row[11] + # else: + # pos5 = None + # if row[12] != '': + # pos6 = row[12] + # else: + # pos6 = None + # if row[13] != '': + # pos7 = row[13] + # else: + # pos7 = None + # if row[14] != '': + # pos8 = row[14] + # else: + # pos8 = None + # + # rarity = Rarity.get(Rarity.name == 'Replacement') + # + # if primary == 'RP': + # if wara >= war_vals['RP']['MVP']: + # rarity = Rarity.get(Rarity.name == 'MVP') + # elif wara >= war_vals['RP']['All-Star']: + # rarity = Rarity.get(Rarity.name == 'All-Star') + # elif wara >= war_vals['RP']['Starter']: + # rarity = Rarity.get(Rarity.name == 'Starter') + # elif wara > war_vals['RP']['Reserve']: + # rarity = Rarity.get(Rarity.name == 'Reserve') + # elif primary == 'SP': + # if wara >= war_vals['SP']['MVP']: + # rarity = Rarity.get(Rarity.name == 'MVP') + # elif wara >= war_vals['SP']['All-Star']: + # rarity = Rarity.get(Rarity.name == 'All-Star') + # elif wara >= war_vals['SP']['Starter']: + # rarity = Rarity.get(Rarity.name == 'Starter') + # elif wara > war_vals['SP']['Reserve']: + # rarity = Rarity.get(Rarity.name == 'Reserve') + # elif primary == 'Park': + # if wara >= war_vals['Park']['MVP']: + # rarity = Rarity.get(Rarity.name == 'MVP') + # elif wara >= war_vals['Park']['All-Star']: + # rarity = Rarity.get(Rarity.name == 'All-Star') + # elif wara >= war_vals['Park']['Starter']: + # rarity = Rarity.get(Rarity.name == 'Starter') + # elif wara > war_vals['Park']['Reserve']: + # rarity = Rarity.get(Rarity.name == 'Reserve') + # else: + # if wara >= war_vals['Pos']['MVP']: + # rarity = Rarity.get(Rarity.name == 'MVP') + # elif wara >= war_vals['Pos']['All-Star']: + # rarity = Rarity.get(Rarity.name == 'All-Star') + # elif wara >= war_vals['Pos']['Starter']: + # rarity = Rarity.get(Rarity.name == 'Starter') + # elif wara > war_vals['Pos']['Reserve']: + # rarity = Rarity.get(Rarity.name == 'Reserve') + # + # tba_players.append({ + # 'name': name, + # 'mlbclub': mlbclub, + # 'cardset': cardset, + # 'rarity': rarity, + # 'wara': wara, + # 'primary': primary, + # 'url': url, + # 'url2': url2, + # 'pos1': pos1, + # 'pos2': pos2, + # 'pos3': pos3, + # 'pos4': pos4, + # 'pos5': pos5, + # 'pos6': pos6, + # 'pos7': pos7, + # 'pos8': pos8, + # 'franchise': franchise + # }) + # else: + # flag = False + # if row[8] != '': + # flag = True + # this_guy.pos2 = row[8] + # if row[9] != '': + # flag = True + # this_guy.pos3 = row[9] + # if row[10] != '': + # flag = True + # this_guy.pos4 = row[10] + # if row[11] != '': + # flag = True + # this_guy.pos5 = row[11] + # if row[12] != '': + # flag = True + # this_guy.pos6 = row[12] + # if row[13] != '': + # flag = True + # this_guy.pos7 = row[13] + # if row[14] != '': + # flag = True + # this_guy.pos8 = row[14] + # + # if flag: + # this_guy.save() + # flag = False + # + # # if this_guy.mlbclub != row[1]: + # # upd_players.append(f'{this_guy.name} mlbclub: {this_guy.mlbclub} to {row[1]}\n') + # # this_guy.mlbclub = row[1] + # # flag = True + # # if this_guy.wara != float(row[3]): + # # upd_players.append(f'{this_guy.name} wara: {this_guy.wara} to {row[3]}\n') + # # this_guy.wara = float(row[3]) + # # flag = True + # # if this_guy.primary != row[4]: + # # upd_players.append(f'{this_guy.name} primary: {this_guy.primary} to {row[4]}\n') + # # this_guy.primary = row[4] + # # flag = True + # # if this_guy.url != row[5]: + # # upd_players.append(f'{this_guy.name} url updated\n') + # # this_guy.url = row[5] + # # flag = True + # # temp_url2 = None if row[6] == '' else row[6] + # # if this_guy.url2 != temp_url2: + # # upd_players.append(f'{this_guy.name} url2 updated\n') + # # this_guy.url2 = temp_url2 + # # flag = True + # # if this_guy.pos1 != row[7]: + # # upd_players.append(f'{this_guy.name} pos1: {this_guy.pos1} to {row[7]}\n') + # # this_guy.pos1 = row[7] + # # flag = True + # # rarity = Rarity.get(Rarity.name == 'Replacement') + # # + # # if this_guy.primary == 'RP': + # # if this_guy.wara >= war_vals['RP']['MVP']: + # # rarity = Rarity.get(Rarity.name == 'MVP') + # # elif this_guy.wara >= war_vals['RP']['All-Star']: + # # rarity = Rarity.get(Rarity.name == 'All-Star') + # # elif this_guy.wara >= war_vals['RP']['Starter']: + # # rarity = Rarity.get(Rarity.name == 'Starter') + # # elif this_guy.wara > war_vals['RP']['Reserve']: + # # rarity = Rarity.get(Rarity.name == 'Reserve') + # # elif this_guy.primary == 'SP': + # # if this_guy.wara >= war_vals['SP']['MVP']: + # # rarity = Rarity.get(Rarity.name == 'MVP') + # # elif this_guy.wara >= war_vals['SP']['All-Star']: + # # rarity = Rarity.get(Rarity.name == 'All-Star') + # # elif this_guy.wara >= war_vals['SP']['Starter']: + # # rarity = Rarity.get(Rarity.name == 'Starter') + # # elif this_guy.wara > war_vals['SP']['Reserve']: + # # rarity = Rarity.get(Rarity.name == 'Reserve') + # # elif this_guy.primary == 'Park': + # # if this_guy.wara >= war_vals['Park']['MVP']: + # # rarity = Rarity.get(Rarity.name == 'MVP') + # # elif this_guy.wara >= war_vals['Park']['All-Star']: + # # rarity = Rarity.get(Rarity.name == 'All-Star') + # # elif this_guy.wara >= war_vals['Park']['Starter']: + # # rarity = Rarity.get(Rarity.name == 'Starter') + # # elif this_guy.wara > war_vals['Park']['Reserve']: + # # rarity = Rarity.get(Rarity.name == 'Reserve') + # # else: + # # if this_guy.wara >= war_vals['Pos']['MVP']: + # # rarity = Rarity.get(Rarity.name == 'MVP') + # # elif this_guy.wara >= war_vals['Pos']['All-Star']: + # # rarity = Rarity.get(Rarity.name == 'All-Star') + # # elif this_guy.wara >= war_vals['Pos']['Starter']: + # # rarity = Rarity.get(Rarity.name == 'Starter') + # # elif this_guy.wara > war_vals['Pos']['Reserve']: + # # rarity = Rarity.get(Rarity.name == 'Reserve') + # # + # # if this_guy.rarity != rarity: + # # upd_players.append(f'{this_guy.name} rarity: {this_guy.rarity.name} to {rarity.name}\n') + # # this_guy.rarity = rarity + # # flag = True + # # + # # if flag: + # # this_guy.save() + # # flag = False + # + # with db.atomic(): + # try: + # for batch in chunked(tba_players, 20): + # Player.insert_many(batch).execute() + # except Exception as e: + # logging.error(f'(import_players): {e}') + # await ctx.send(f'Oof, I ran into an issue importing those players. ' + # f'This might be ugly:\n\n{e}') + # db.close() + # return + # + # num_players = Player.select().count() + # await ctx.send(f'Alright, I have {num_players} players in the database.\n' + # f'I added {len(tba_players)} new players.\n') + # + # db.close() + # + # @commands.command(name='rebalance') + # @commands.is_owner() + # async def rebalance_command(self, ctx, pos, url_key, allow_new: bool = False): + # await ctx.send('Let me go check the data sheet...') + # if pos.upper() in ['SP', 'RP', 'POS']: + # sheet_title = f'{pos.upper()} Updates' + # else: + # await helpers.react_and_reply(ctx, '❌', 'I can only take SP, RP, or POS for position.') + # return + # + # # Get data from Sheets + # async with ctx.typing(): + # sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json') + # data_sheet = sheets.open_by_key(url_key).worksheet_by_title(sheet_title) + # raw_data = data_sheet.get_values('A3', 'P10000') + # await ctx.send('Alrighty, pulled the data...') + # + # # Parse data + # update_players = [] + # new_players = [] + # error_players = [] + # rarities = { + # 'MVP': Rarity.get(Rarity.name == 'MVP'), + # 'All-Star': Rarity.get(Rarity.name == 'All-Star'), + # 'Starter': Rarity.get(Rarity.name == 'Starter'), + # 'Reserve': Rarity.get(Rarity.name == 'Reserve'), + # 'Replacement': Rarity.get(Rarity.name == 'Replacement') + # } + # + # await ctx.send(f'I will look for updates now...') + # async with ctx.typing(): + # for row in raw_data: + # if row[0] != '': + # cardset = row[0][:4] + # player_name = row[0][5:] + # rarity_name = row[1] + # this_player = Player.get_or_none(Player.cardset == cardset, Player.name == player_name) + # if not this_player: + # if allow_new: + # # Create new player object and save + # pass + # else: + # error_players.append(row[0]) + # elif this_player.rarity != rarities[rarity_name]: + # this_player.rarity = rarities[rarity_name] + # update_players.append(this_player) + # else: + # break + # + # logging.info(f'Update players: {update_players}\n\nNew players: {new_players}\n\n' + # f'Error players: {error_players}') + # update_message = f'Here\'s what I\'ve got for ya:\n' \ + # f'Updates: {len(update_players)}\n' \ + # f'Errors: {len(error_players)}' + # await ctx.send(update_message) + # + # if len(error_players) == 0: + # async with ctx.typing(): + # with db.atomic(): + # for x in update_players: + # x.save() + # + # db.close() + # await ctx.send('All done!') + # + # @commands.command(name='tempteam', help='Admin command to add team') + # @commands.is_owner() + # async def add_team(self, ctx, abbrev: str, sname: str, lname: str, gmid: int, gmname: str): + # new_team = Team(abbrev=abbrev, + # sname=sname, + # lname=lname, + # gmid=gmid, + # gmname=gmname, + # season=1) + # + # if new_team.save() == 1: + # await ctx.send(f'Hey {ctx.guild.fetch_member(new_team.gmid).mention}, ' + # f'you are now the GM of the {new_team.lname}!') + # else: + # await ctx.send(f'Nope. They suck and don\'t get a team. It has nothing to do with this stack of ' + # f'errors I got when I tried to create their team.') + # + # db.close() - @commands.command(name='tempteam', help='Admin command to add team') - @commands.is_owner() - async def add_team(self, ctx, abbrev: str, sname: str, lname: str, gmid: int, gmname: str): - new_team = Team(abbrev=abbrev, - sname=sname, - lname=lname, - gmid=gmid, - gmname=gmname, - season=1) + # @commands.command(name='setprimary', help='Admin command to set pos') + # @commands.is_owner() + # async def set_primary_command(self, ctx, pos, cardset, *, player_name): + # if pos.upper() not in ['C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH', 'SP', 'RP']: + # await ctx.send(f'I cannot set a primary postion to **{pos.upper()}**.') + # return + # + # yp_query = Player.select(Player.name).where(Player.cardset == cardset) + # yearly_players = [] + # for x in yp_query: + # yearly_players.append(x.name.lower()) + # + # try: + # great_match = get_close_matches(player_name.lower(), yearly_players, cutoff=0.75)[0] + # this_guy = Player.get((fn.Lower(Player.name) == great_match.lower()), Player.cardset == cardset) + # except Exception as e: + # await ctx.send(f'I could not find {player_name.title()}. Is that the right year?') + # logging.error(f'**ERROR** (display_player): {e}') + # db.close() + # return + # + # this_guy.primary = pos.upper() + # this_guy.pos1 = pos.upper() + # this_guy.save() + # + # await ctx.send(helpers.random_conf_gif()) + # + # db.close() + # + # @commands.command(name='setcardurl', help='Admin command to set card') + # @commands.is_owner() + # async def set_cardurl_command(self, ctx, card_url, cardset, *, player_name): + # yp_query = Player.select(Player.name).where(Player.cardset == cardset) + # yearly_players = [] + # for x in yp_query: + # yearly_players.append(x.name.lower()) + # + # try: + # great_match = get_close_matches(player_name.lower(), yearly_players, cutoff=0.75)[0] + # this_guy = Player.get((fn.Lower(Player.name) == great_match.lower()), Player.cardset == cardset) + # except Exception as e: + # await ctx.send(f'I could not find {player_name.title()}. Is that the right year?') + # logging.error(f'**ERROR** (display_player): {e}') + # db.close() + # return + # + # embed = discord.Embed(title=f'{this_guy.name} New Card') + # embed.set_image(url=card_url) + # await ctx.send(content=None, embed=embed) + # + # prompt = 'Is this the URL you would like to use?' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, 'yesno', 45) + # resp = await this_q.ask([ctx.author]) + # + # if not resp: + # await ctx.send('No worries, let me know if you want to try again later.') + # db.close() + # return + # else: + # this_guy.url = card_url + # this_guy.save() + # + # await ctx.send(helpers.random_conf_gif()) + # + # db.close() + # + # @commands.command(name='setrarity', help='Admin command to set rarity') + # @commands.is_owner() + # async def set_rarity_command(self, ctx, rarity, cardset, *, player_name): + # this_rarity = Rarity.get_or_none(fn.Lower(Rarity.name) == rarity.lower()) + # if not this_rarity: + # await ctx.send(f'I could not find **{rarity}** - is that a rarity name?') + # return + # + # yp_query = Player.select(Player.name).where(Player.cardset == cardset) + # yearly_players = [] + # for x in yp_query: + # yearly_players.append(x.name.lower()) + # + # try: + # great_match = get_close_matches(player_name.lower(), yearly_players, cutoff=0.75)[0] + # this_guy = Player.get((fn.Lower(Player.name) == great_match.lower()), Player.cardset == cardset) + # except Exception as e: + # await ctx.send(f'I could not find {player_name.title()}. Is that the right year?') + # logging.error(f'**ERROR** (display_player): {e}') + # db.close() + # return + # + # prompt = f'**{this_guy.cardset} {this_guy.name}** is currently rated **{this_guy.rarity.name}**; would '\ + # f'you like to set the rarity to **{this_rarity.name}**?' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, qtype='yesno', timeout=20) + # resp = await this_q.ask([ctx.author]) + # + # if not resp: + # await ctx.send(f'No worries! I will leave {this_guy.name} alone.') + # return + # else: + # this_guy.rarity = this_rarity + # this_guy.save() + # await ctx.send(helpers.random_conf_gif()) - if new_team.save() == 1: - await ctx.send(f'Hey {discord.utils.get(ctx.guild.members, id=new_team.gmid).mention}, ' - f'you are now the GM of the {new_team.lname}!') - else: - await ctx.send(f'Nope. They suck and don\'t get a team. It has nothing to do with this stack of ' - f'errors I got when I tried to create their team.') + # @commands.command(name='newspecial') + # @commands.is_owner() + # async def new_special_command(self, ctx): + # name_message = 'What name should this special have?' + # sdesc_message = 'What should the short description be?' + # ldesc_message = 'What should the long description be?' + # url_message = 'What URL should display with this special?' + # thumbnail_message = 'What thumbnail image would you like to use?' + # special_name = '' + # special_sdesc = '' + # special_ldesc = '' + # special_url = '' + # special_thumbnail = '' + # + # def check(mes): + # return mes.author == ctx.author and mes.channel == ctx.channel + # + # # Get special name + # await ctx.send(name_message) + # try: + # resp = await self.bot.wait_for('message', check=check, timeout=15.0) + # special_name = resp.content + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # # Get special short description + # await ctx.send(sdesc_message) + # try: + # resp = await self.bot.wait_for('message', check=check, timeout=30.0) + # special_sdesc = resp.content + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # # Get special long description + # await ctx.send(ldesc_message) + # try: + # resp = await self.bot.wait_for('message', check=check, timeout=60.0) + # special_ldesc = resp.content + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # # Get special URL + # await ctx.send(url_message) + # try: + # resp = await self.bot.wait_for('message', check=check, timeout=30.0) + # special_url = resp.content + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # # Get special URL + # await ctx.send(thumbnail_message) + # try: + # resp = await self.bot.wait_for('message', check=check, timeout=30.0) + # special_thumbnail = resp.content + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # embed = discord.Embed(title=special_name, color=0x008000, description=special_sdesc) + # embed.add_field(name='Description', value=special_ldesc) + # if special_thumbnail.lower() != 'none': + # embed.set_thumbnail(url=special_thumbnail) + # if special_url.lower() != 'none': + # embed.set_image(url=special_url) + # + # await ctx.send(content='Is this what you want?', embed=embed) + # + # def confirmation(mes): + # return mes.author == ctx.author and mes.channel == ctx.channel and mes.content.lower() == 'yes' + # + # try: + # resp = await self.bot.wait_for('message', check=confirmation, timeout=20.0) + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # new_special = Special(name=special_name, short_desc=special_sdesc, url=special_url, + # long_desc=special_ldesc, thumbnail=special_thumbnail, active=False) + # + # if new_special.save() == 1: + # await ctx.send(f'Alrighty - it is in! Its ID is {new_special.get_id()}.') + # else: + # await ctx.send('Yikes. That didn\'t go through. Sorry about that.') + # + # db.close() + # + # @commands.command(name='flipspecial') + # @commands.is_owner() + # async def flip_special_command(self, ctx, special_id): + # this_special = Special.get_by_id(special_id) + # if not this_special: + # await ctx.send('I cannot find a special with that ID.') + # return + # + # await ctx.send(content='Is this the special you\'d like to toggle?', + # embed=helpers.get_special_embed(this_special)) + # + # def confirmation(mes): + # return mes.author == ctx.author and mes.channel == ctx.channel and mes.content.lower() == 'yes' + # + # try: + # resp = await self.bot.wait_for('message', check=confirmation, timeout=10.0) + # except TimeoutError: + # await ctx.send('We can do this again later.') + # return + # + # if this_special.active: + # this_special.active = False + # else: + # this_special.active = True + # + # if this_special.save() == 1: + # await ctx.send('Done!') + # else: + # await ctx.send('Huh...well I tried, but it didn\'t work.') + # + # db.close() + # + # @commands.command(name='newweek', help='Advance week in bot\'s db') + # @commands.is_owner() + # async def advanceweek(self, ctx): + # current = Current.get() + # logging.info(f'Updating week from {current.week} to {current.week + 1}...') + # update_string = await self.increment_week() + # + # await ctx.send(f'Updated week from {current.week - 1} to {current.week}.') + # await ctx.send(update_string) + # + # db.close() + # + # @commands.command(name='setteamweekly', help='Admin command to set team\'s weekly packs') + # @commands.is_owner() + # async def set_weekly_command(self, ctx, team_abbrev, number): + # team = Team.get_or_none(Team.abbrev == team_abbrev.upper()) + # if not team: + # await ctx.send(f'I couldn\'t find **{team_abbrev}**. Is that the team\'s abbreviation?') + # return + # + # team.weeklypacks = number + # if team.save() == 1: + # await ctx.send(f'Just set {team.abbrev}\'s weekly packs to {number}') + # else: + # await ctx.send(f'Oof. I tried. I failed. Sue me.') + # + # db.close() - @commands.command(name='rates', help='Check current pull rates') - @commands.has_any_role('Paper Dynasty Players') - async def all_card_pulls(self, ctx): - await self.bot.change_presence(activity=discord.Game(name='strat | .help')) - total_count = Card.select().count() - mvp_count = (Card - .select() - .join(Player) - .join(Rarity) - .where(Card.player.rarity.value == 10)).count() - als_count = (Card - .select() - .join(Player) - .join(Rarity) - .where(Card.player.rarity.value == 7)).count() - sta_count = (Card - .select() - .join(Player) - .join(Rarity) - .where(Card.player.rarity.value == 5)).count() - res_count = (Card - .select() - .join(Player) - .join(Rarity) - .where(Card.player.rarity.value == 3)).count() - rep_count = (Card - .select() - .join(Player) - .join(Rarity) - .where(Card.player.rarity.value == 0)).count() + # @commands.group(name='donation', help='Award a player for their donation') + # @commands.is_owner() + # async def donation_command(self, ctx): + # if ctx.invoked_subcommand is None: + # await ctx.send('The format for this command is: `donation <@donator>`') + # + # @donation_command.command(name='standard') + # @commands.is_owner() + # async def std_donation_command(self, ctx, gm: Member, *num_packs: int): + # donator_team = Team.get_by_owner(gm.id) + # if not donator_team: + # await ctx.send(f'I can\'t find {gm.display_name}\'s team! HALP') + # return + # + # if num_packs: + # count = num_packs[0] + # else: + # count = 1 + # + # economy = self.bot.get_cog('Economy') + # logging.info(f'Granting {donator_team.sname} {count} standard pack{"s" if count > 1 else ""} for ' + # f'their donation.') + # economy.give_pack(donator_team, count) + # await ctx.send(helpers.random_conf_gif()) + # + # db.close() + # + # @donation_command.command(name='premium') + # @commands.is_owner() + # async def prem_donation_command(self, ctx, gm: Member, *num_packs: int): + # donator_team = Team.get_by_owner(gm.id) + # if not donator_team: + # await ctx.send(f'I can\'t find {gm.display_name}\'s team! HALP') + # return + # + # if num_packs: + # count = num_packs[0] + # else: + # count = 1 + # + # economy = self.bot.get_cog('Economy') + # logging.info(f'Granting {donator_team.sname} {count} premium pack{"s" if count > 1 else ""} for ' + # f'their donation.') + # economy.give_pack(donator_team, num=count, p_type='premium') + # await ctx.send(helpers.random_conf_gif()) + # + # all_teams = Team.select_season() + # for x in all_teams: + # if x != donator_team: + # economy.give_pack(x, count) + # + # await helpers.send_to_news( + # ctx, + # f'Everybody just got {count} pack{"s" if count > 1 else ""} thanks to {gm.mention}\'s donation. ' + # f'Go rip {"them" if count > 1 else "it"} with .open!', + # None + # ) + # + # db.close() - print(total_count) - embed = discord.Embed(title='Current Pull Rates', color=0x800080) - embed.add_field(name='Total Pulls', value=f'{total_count}') - embed.add_field(name='MVPs', value=f'{mvp_count} ({(mvp_count / total_count)*100:.2f}%)\n' - f'Target: 0.33%', inline=False) - embed.add_field(name='All-Stars', value=f'{als_count} ({(als_count / total_count)*100:.2f}%)\n' - f'Target: 2.50%', inline=False) - embed.add_field(name='Starters', value=f'{sta_count} ({(sta_count / total_count)*100:.2f}%)\n' - f'Target: 18.83%', inline=False) - embed.add_field(name='Reserves', value=f'{res_count} ({(res_count / total_count)*100:.2f}%)\n' - f'Target: 45.00%', inline=False) - embed.add_field(name='Replacements', value=f'{rep_count} ({(rep_count / total_count)*100:.2f}%)\n' - f'Target: 33.33%', inline=False) - await ctx.send(content=None, embed=embed) - - @commands.command(name='query', help='Run queries against db') - @commands.is_owner() - async def dynamic_query(self, ctx, *, query): - print(f'Query: {query}') - result = eval(query) - print(f'Result: {result}') - - embed = discord.Embed(title='Custom Query') - embed.add_field(name='Result', value=result) - - await ctx.send(content=None, embed=embed) + # @commands.command(name='setdaily', help='Set daily flag') + # @commands.is_owner() + # async def set_daily_command(self, ctx, gm: Member): + # this_team = Team.get_by_owner(gm.id) + # if not this_team: + # await ctx.send(f'I can\'t find {gm.display_name}\'s team! HALP') + # return + # + # this_team.dailyclaim = 1 + # this_team.save() + # + # await ctx.message.add_reaction('✅') + # db.close() + # + # @commands.command(name='settheme', help='Set pack theme') + # @commands.is_owner() + # async def set_theme_command(self, ctx, theme): + # cardsets = [1941, 1962, 1986, 1996, 2001, 2007, 2014, 2018, 2019, 2020] + # # franchises = [] + # + # if int(theme) in cardsets: + # current = Current.get() + # this_theme = PackTheme.get_or_none(PackTheme.cardset == theme) + # if not this_theme: + # this_theme = PackTheme(name=f'{theme} Cardset', cardset=theme) + # this_theme.save() + # + # current.active_theme = this_theme + # current.save() + # + # await ctx.send(f'Got it! Just set the current PackTheme to {theme.title()}') + # else: + # await ctx.send(f'Hmm...**{theme}** is not an available theme.') + # + # @commands.command(name='cleartheme', help='Clear pack theme') + # @commands.is_owner() + # async def clear_theme_command(self, ctx): + # current = Current.get() + # if not current.active_theme: + # await ctx.send('There is no theme currently set.') + # else: + # this_theme = PackTheme.get_by_id(current.active_theme) + # current.active_theme = None + # current.save() + # await ctx.send(f'Just deactivated the **{this_theme.name}** theme.') + # + # @commands.command(name='newaward', help='Grant award') + # @commands.is_owner() + # async def new_award_command(self, ctx, *, award_name): + # # f'{"In-Season" if in_or_off.lower() == "in" else "Off-Season"}' + # current = Current.get() + # award = { + # 'name': award_name, + # 'season': current.season, + # 'timing': "In-Season", + # 'player': None, + # 'team': None, + # 'image': None, + # 'invalid': False + # } + # + # async def add_recipient(search_string): + # this_team = Team.get_or_none(Team.abbrev == search_string.upper(), Team.season == award['season']) + # if this_team: + # award['team'] = this_team + # else: + # data = re.split(' ', search_string) + # cardset = data[0] + # name = data[1] + # + # yp_query = Player.select(Player.name).where(Player.cardset == cardset) + # yearly_players = [] + # for x in yp_query: + # yearly_players.append(x.name.lower()) + # + # try: + # close_matches = get_close_matches(name.lower(), yearly_players, cutoff=0.75) + # great_match = close_matches[0] + # this_guy = Player.get((fn.Lower(Player.name) == great_match), Player.cardset == cardset) + # award['player'] = this_guy + # except Exception as e: + # return None + # + # def get_embed(): + # this_embed = discord.Embed(title=award['name']) + # this_embed.description = f'{award["timing"]} - S{award["season"]}' + # if award['player']: + # this_embed.add_field(name='Player', value=f'{award["player"].name}') + # if award['team']: + # this_embed.add_field(name='Team', value=f'{award["team"].lname}') + # if award['image']: + # try: + # this_embed.set_image(url=award['image']) + # except: + # award['invalid'] = True + # this_embed.add_field(name='Image', value='Invalid URL') + # return this_embed + # + # # Get team/player + # while True: + # prompt = 'Please enter the team abbreviation, player name, or manager name of the recipient.' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, 'text', timeout=45) + # resp = await this_q.ask([ctx.author]) + # + # if resp is None: + # await ctx.send('You think on it and hit me up when you\'re ready.') + # return + # else: + # await add_recipient(resp) + # + # await ctx.send('Here is the current award', embed=get_embed()) + # + # prompt = 'Would you like to (re)enter a recipient?' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, 'yesno', timeout=45) + # resp = await this_q.ask([ctx.author]) + # + # if resp is None: + # await ctx.send('We can hold off on this for now. Let me know when you\'re ready to start again.') + # return + # elif not resp: + # break + # + # # Get image URL + # while True: + # prompt = 'Would you like to (re)enter an image URL for this award?' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, 'yesno', timeout=45, embed=get_embed()) + # resp = await this_q.ask([ctx.author]) + # + # if not resp: + # break + # else: + # prompt = 'Please enter the image URL.' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, 'url', timeout=45) + # resp = await this_q.ask([ctx.author]) + # + # if resp is None: + # await ctx.send('Okey doke, nevermind. I get it. It\'s fine. We only did all this work for nothing. ' + # 'Let me know when you want to start again.') + # return + # elif not resp: + # await ctx.send(f'**{resp}** is not a valid URL.') + # else: + # award['image'] = resp + # + # prompt = 'Would you like to submit this award?' + # this_q = helpers.Question(self.bot, ctx.channel, prompt, 'yesno', timeout=45, embed=get_embed()) + # resp = await this_q.ask([ctx.author]) + # + # if not resp: + # await ctx.send('Really? After all the ti- nevermind. Fine. Kill it. It\'s over. Bye.') + # return + # else: + # this_award = Award( + # name=award['name'], + # season=award['season'], + # timing=award['timing'], + # player=award['player'], + # team=award['team'], + # image=award['image'] + # ) + # this_award.save() + # db.close() + # await ctx.send(helpers.random_conf_gif()) -def setup(bot): - bot.add_cog(Admins(bot)) +async def setup(bot): + await bot.add_cog(Admins(bot)) diff --git a/cogs/economy.py b/cogs/economy.py index 941aca3..941cbb4 100644 --- a/cogs/economy.py +++ b/cogs/economy.py @@ -1,284 +1,1914 @@ -import math +import helpers +from helpers import * +import logging +import os +import random +import re + import discord import asyncio +from discord.ext import commands, tasks +from discord.ext.commands import Greedy +from discord import app_commands, Member +from typing import Optional, Literal +from discord.app_commands import Choice + import datetime import pygsheets -from db_engine import * -from discord.ext import commands +from db_calls import db_get, db_post, db_patch, db_delete, get_team_by_abbrev, post_to_dex +from help_text import * + +# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' +# logging.basicConfig( +# filename=f'logs/{date}.log', +# format='%(asctime)s - %(levelname)s - %(message)s', +# level=logging.WARNING +# ) + + +# async def legal_channel(ctx): +# bad_channels = ['paper-dynasty-chat', 'pd-news-ticker'] +# if ctx.message.channel.name in bad_channels: +# raise discord.ext.commands.CheckFailure(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') +# else: +# return True class Economy(commands.Cog): def __init__(self, bot): self.bot = bot - self.cheat_time = {} - self.logging = True - self.helpers = self.bot.get_cog('Helpers') + self.daily_message_sent = True + self.weekly_update = False + bot.tree.on_error = self.on_app_command_error - @commands.command(name='give', help='Admin command to grant card packs') - @commands.is_owner() - async def give_command(self, ctx, team_abbrev: str, num=1): - team = Team.get_or_none(Team.abbrev == team_abbrev) - if team: - team_packs = self.give_pack(team, num) - await ctx.send(f'Just gave {num} pack{"s" if num > 1 else ""} to {team.sname}. ' - f'They now have {team_packs} pack{"s" if team_packs > 1 else ""}.') - elif team_abbrev == 'LEAGUE': - await ctx.send(f'Ohhh, snap! Errybody getting a little somethin somethin.') - for x in Team.select(): - print(f'Team: {x}') - self.give_pack(x, num) + # self.pd_ticker.start() + self.notif_check.start() - @give_command.error - async def give_command_error(self, ctx, error): - if isinstance(error, commands.CheckFailure): - await ctx.message.add_reaction('🖕') + # def cog_unload(self): + # self.pd_ticker.cancel() - @commands.command(name='open', help='Open up one of your card packs!') - @commands.has_any_role('Paper Dynasty Players') - async def open_command(self, ctx, *pack_id): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') - return + async def cog_command_error(self, ctx, error): + await ctx.send(f'{error}\n\nRun .help to see the command requirements') - if pack_id: - this_pack = Pack.get_or_none(Pack.id == pack_id) - else: - team = Team.get_by_owner(ctx.author.id) - if not team: - await ctx.send(f'You...do I know you? I don\'t think I do. Go on and git.') - return - try: - this_pack = Pack.get((Pack.team == team) & (Pack.card1.is_null())) - except Exception as e: - print(f'Could not find unoped pack for {team.gmname} - {e}') - await ctx.send(f'It doesn\'t look like you have any unopened packs. ' - f'If you do, let an adult know...maybe I lost it.') + async def on_app_command_error(self, interaction: discord.Interaction, error: discord.app_commands.AppCommandError): + await interaction.channel.send(f'{error}') + + # async def slash_error(self, ctx, error): + # await ctx.send(f'{error}') + + # @tasks.loop(minutes=10) + # async def pd_ticker(self): + # now = datetime.datetime.now() + # logging.info(f'Datetime: {now} / weekday: {now.weekday()}') + # guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + # if not guild: + # return + # + # # Daily Specials Message + # if now.hour == 11 and not self.daily_message_sent: + # try: + # await helpers.send_to_channel(self.bot, 'pd-bot-hole', 'Here are the current specials:') + # await self.display_specials('pd-bot-hole') + # self.daily_message_sent = True + # except: + # await helpers.send_to_channel(self.bot, 'commissioners-office', + # 'Just tried and failed to send specials to news.') + # elif now.hour == 12 and self.daily_message_sent: + # self.daily_message_sent = False + # + # # Weekly Standings Message + # # if now.hour == 20 and not self.weekly_update: + # if now.weekday() == 0 and now.hour == 0 and not self.weekly_update: + # current = Current.get() + # + # # Send standings to Cal + # standings_embeds = self.bot.get_cog('Players').get_standings_embeds( + # current, 'week', f'Week {current.week} Standings' + # ) + # await helpers.send_to_channel( + # self.bot, 'pd-news-ticker', + # content=f'Here are the final standings for week {current.week}! Cal will hand out packs in the morning.' + # ) + # for x in standings_embeds: + # await helpers.send_to_channel(self.bot, 'commissioners-office', content=None, embed=x) + # await helpers.send_to_channel(self.bot, 'pd-news-ticker', content=None, embed=x) + # + # # Increment Week + # current.week += 1 + # current.save() + # + # all_teams = Team.select() + # for x in all_teams: + # x.weeklyclaim = False + # x.dailyclaim = False + # x.weeklypacks = 0 + # x.save() + # + # await helpers.send_to_channel( + # self.bot, + # 'commissioners-office', + # f'Flipped the week to {current.week} and updated {all_teams.count()} teams for their weekly.' + # ) + # self.weekly_update = True + # elif now.weekday() != 0 and self.weekly_update: + # self.weekly_update = False + # + # db.close() + + @tasks.loop(minutes=10) + async def notif_check(self): + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Cannot access guild; pausing for 15 seconds') + await asyncio.sleep(15) + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Still cannot access guild; trying again in 10 minutes') return - if not this_pack: - await ctx.send(f'Hmm...I couldn\'t find pack #{pack_id}.') + # Check for notifications + all_notifs = db_get('notifs', params=[('ack', False)]) + if not all_notifs: + logging.debug(f'No notifications') return - if not await self.expand_pack(ctx, this_pack.team, this_pack): - await ctx.send(f'Oh, no. This is bad. This is really, really...well not great. Can you find me an adult?') - return + topics = { + 'Price Change': { + 'channel_name': 'pd-market-watch', + 'desc': 'Modified by buying and selling', + 'notifs': [] + }, + 'Rare Pull': { + 'channel_name': 'pd-network-news', + 'desc': 'MVP and All-Star cards pulled from packs', + 'notifs': [] + } + } + for line in all_notifs['notifs']: + if line['title'] in topics: + topics[line['title']]['notifs'].append(line) - overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), - ctx.author: discord.PermissionOverwrite(read_messages=True, send_messages=True), - discord.utils.get(ctx.guild.roles, name='Paper Dynasty Players'): - discord.PermissionOverwrite(read_messages=True, send_messages=False), - ctx.guild.me: discord.PermissionOverwrite(read_messages=True)} - op_ch = await ctx.guild.create_text_channel( - f'{this_pack.team.abbrev.lower()}-pack{this_pack.id}-opening', - overwrites=overwrites, - category=discord.utils.get(ctx.guild.categories, name='Paper Dynasty Openings') + logging.debug(f'topics:\n{topics}') + for topic in topics: + embed = get_team_embed(title=f'{topic}{"s" if len(topics[topic]["notifs"]) > 1 else ""}') + embed.description = topics[topic]['desc'] + p_list = {} + if topics[topic]['notifs']: + for x in topics[topic]['notifs']: + if x['field_name'] not in p_list: + p_list[x['field_name']] = { + 'field_name': x['field_name'], + 'message': f'{x["message"]}' + } + else: + p_list[x['field_name']]['message'] += f'\n{x["message"]}' + db_patch('notifs', object_id=x['id'], params=[('ack', True)]) + logging.debug(f'p_list: {p_list}') + + for player in p_list: + embed.add_field(name=p_list[player]['field_name'], value=p_list[player]['message'], inline=False) + await send_to_channel(self.bot, topics[topic]['channel_name'], embed=embed) + + @commands.hybrid_group(name='help-pd', help='FAQ for Paper Dynasty and the bot', aliases=['helppd']) + @commands.check(legal_channel) + async def pd_help_command(self, ctx: commands.Context): + if ctx.invoked_subcommand is None: + embed = get_team_embed(f'Paper Dynasty Help') + embed.description = 'Frequently Asked Questions' + embed.add_field( + name='What the Heck is Paper Dynasty', + value=f'Well, whipper snapper, have a seat and I\'ll tell you. We\'re running a diamond dynasty / ' + f'perfect team style game with electronic card and dice baseball!\n\nGet a starter pack, play ' + f'games at your leisure either solo or against another player, and collect cards from the ' + f'custom 2021 player set.', + inline=False + ) + embed.add_field( + name='How Do I Get Started', + value=f'Run the `.in` command - that\'s a period followed by the word "in". That\'ll get you the ' + f'Paper Dynasty Players role so you can run all of the other PD commands!\n\nOnce you get your ' + f'role, run `/newteam` and follow the prompts to get your starter team.', + inline=False + ) + embed.add_field( + name='How Do I Play', + value='A step-by-step of how to play was written by Riles [starting here](https://discord.com/channels' + '/613880856032968834/633456305830625303/985968300272001054). ' + 'In addition, you can find the Rules Reference [right here](https://docs.google.com/document/d/' + '1yGZcHy9zN2MUi4hnce12dAzlFpIApbn7zR24vCkPl1o).\n\nThere are three key differences from league ' + 'play:\n1) Injuries: there are no injuries in Paper Dynasty!\n2) sWAR: there is no sWAR "salary ' + 'cap" for your team like in league play. Some events will have roster construction rules to ' + 'follow, though!\n3) The Universal DH is in effect; teams may forfeit the DH at their ' + 'discretion.', + inline=False + ) + await ctx.send( + content=None, + embed=embed + ) + + @pd_help_command.command(name='start', help='FAQ for Paper Dynasty and the bot', aliases=['faq']) + @commands.check(legal_channel) + async def help_faq(self, ctx: commands.Context): + embed = get_team_embed(f'Paper Dynasty Help') + embed.description = 'Frequently Asked Questions' + embed.add_field( + name='What the Heck is Paper Dynasty', + value=HELP_START_WHAT, + inline=False + ) + embed.add_field( + name='How Do I Get Started', + value=HELP_START_HOW, + inline=False + ) + embed.add_field( + name='How Do I Play', + value=HELP_START_PLAY, + inline=False + ) + embed.add_field( + name='Other Questions?', + value=f'Feel free to ask any questions down in {get_channel(ctx, "paper-dynasty-chat")} or check out ' + f'the other `/help-pd` commands for the FAQs!' + ) + await ctx.send( + content=None, + embed=embed ) - cards = [this_pack.card1, this_pack.card2, this_pack.card3, - this_pack.card4, this_pack.card5, this_pack.card6] + @pd_help_command.command(name='links', help='Helpful links for Paper Dynasty') + @commands.check(legal_channel) + async def help_links(self, ctx: commands.Context): + current = db_get('current') + embed = get_team_embed(f'Paper Dynasty Help') + embed.description = 'Resources & Links' + embed.add_field( + name='Team Sheet Template', + value=f'{get_roster_sheet({"gsheet": current["gsheet_template"]}, allow_embed=True)}' + ) + embed.add_field( + name='Paper Dynasty Season 4 Guidelines', + value='https://docs.google.com/document/d/1b2_eUWaIhdGgD9DapvZrppu5famukqYKTC4wu2DyyFY/edit?usp=sharing', + inline=False + ) + embed.add_field( + name='Rules Reference', + value='https://docs.google.com/document/d/1yGZcHy9zN2MUi4hnce12dAzlFpIApbn7zR24vCkPl1o', + inline=False + ) + embed.add_field( + name='Changelog', + value='https://manticorum.notion.site/Paper-Dynasty-Season-4-Changelog-75b5e51ab4f246a9b6a3f69df9745d53', + inline=False + ) + await ctx.send(content=None, embed=embed) - await op_ch.send(f'{ctx.author.mention} - let\'s take a look at these cards...') - for x in cards: - await asyncio.sleep(5) - await self.present_player(ctx, op_ch, x) + @pd_help_command.command(name='rewards', help='How to Earn Rewards in Paper Dynasty') + @commands.check(legal_channel) + async def help_rewards(self, ctx: commands.Context): + embed = get_team_embed(f'Paper Dynasty Help') + embed.description = 'How to Earn Rewards' + embed.add_field( + name='Premium Pack', + value=HELP_REWARDS_PREMIUM, + inline=False + ) + embed.add_field( + name='Standard Pack', + value=HELP_REWARDS_STANDARD, + inline=False + ) + embed.add_field( + name='MantiBucks ₼', + value=HELP_REWARDS_MONEY, + inline=False + ) + embed.add_field( + name='Ko-fi Shop', + value=HELP_REWARDS_SHOP, + inline=False + ) + await ctx.send(content=None, embed=embed) - await self.helpers.pause_then_type(op_ch, f'I will clear out this channel in 5 minutes.') - if team.gsheet: - await self.helpers.pause_then_type(op_ch, 'Updating your google sheet now...') - async with ctx.typing(): - if await self.write_collection(op_ch, team, extra=0): - await self.helpers.pause_then_type(op_ch, 'All done!') - await asyncio.sleep(240) - await op_ch.send(f'1 minute left before this channel goes away.') - await asyncio.sleep(60) - await op_ch.delete() + @pd_help_command.command(name='team-sheet', help='How to Use Your Team Sheet') + @commands.check(legal_channel) + async def help_team_sheet(self, ctx: commands.Context): + embed = get_team_embed(f'Paper Dynasty Help') + embed.description = 'How to Use Your Team Sheet' + embed.add_field( + name='Your Dashboard', + value=HELP_TS_DASH, + inline=False + ) + embed.add_field( + name='Roster Management', + value=HELP_TS_ROSTER, + inline=False + ) + embed.add_field( + name='Marketplace', + value=HELP_TS_MARKET, + inline=False + ) + embed.add_field( + name='Paper Dynasty Menu', + value=HELP_TS_MENU, + inline=False + ) + embed.set_footer( + text='More details to come', + icon_url=IMAGES['logo'] + ) + await ctx.send(content=None, embed=embed) - @commands.command(name='comeonmanineedthis', help='You need help') - @commands.has_any_role('Paper Dynasty Players') - async def cheat_pack(self, ctx): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') + @pd_help_command.command(name='gameplay', help='How to Play Paper Dynasty') + @commands.check(legal_channel) + async def help_gameplay(self, ctx: commands.Context): + embed = get_team_embed(f'Paper Dynasty Help') + embed.description = 'How to Play Paper Dynasty' + embed.add_field( + name='Game Modes', + value=HELP_GAMEMODES, + inline=False + ) + embed.add_field( + name='Start a New Game', + value=HELP_NEWGAME, + inline=False, + ) + embed.add_field( + name='Playing the Game', + value=HELP_PLAYGAME, + inline=False + ) + embed.add_field( + name='Ending the Game', + value=f'{HELP_ENDGAME}\n' + f'- Go post highlights in {get_channel(ctx, "pd-news-ticker").mention}', + inline=False + ) + await ctx.send(content=None, embed=embed) + + @commands.hybrid_group(name='donation', help='Mod: Give packs for PD donations') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + async def donation(self, ctx: commands.Context): + if ctx.invoked_subcommand is None: + await ctx.send('To buy packs, visit https://ko-fi.com/manticorum/shop and include your discord username!') + + @donation.command(name='premium', help='Mod: Give premium packs', aliases=['p', 'prem']) + async def donation_premium(self, ctx: commands.Context, num_packs: int, gm: Member): + if ctx.author.id != self.bot.owner_id: + await ctx.send('Wait a second. You\'re not in charge here!') return - team = Team.get_by_owner(ctx.author.id) + team = get_team_by_owner(gm.id) + p_query = db_get('packtypes', params=[('name', 'Premium')]) + if p_query['count'] == 0: + await ctx.send('Oof. I couldn\'t find a Premium Pack') + return + + total_packs = give_packs(team, num_packs, pack_type=p_query['packtypes'][0]) + await ctx.send(f'The {team["lname"]} now have {total_packs["count"]} total packs!') + + @donation.command(name='standard', help='Mod: Give standard packs', aliases=['s', 'sta']) + async def donation_standard(self, ctx: commands.Context, num_packs: int, gm: Member): + if ctx.author.id != self.bot.owner_id: + await ctx.send('Wait a second. You\'re not in charge here!') + return + + team = get_team_by_owner(gm.id) + p_query = db_get('packtypes', params=[('name', 'Standard')]) + if p_query['count'] == 0: + await ctx.send('Oof. I couldn\'t find a Standard Pack') + return + + total_packs = give_packs(team, num_packs, pack_type=p_query['packtypes'][0]) + await ctx.send(f'The {team["lname"]} now have {total_packs["count"]} total packs!') + + @commands.hybrid_command(name='lastpack', help='Replay your last pack') + @commands.check(legal_channel) + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + async def last_pack_command(self, ctx: commands.Context): + team = get_team_by_owner(ctx.author.id) if not team: - await ctx.send(f'You...do I know you? I don\'t think I do. Go on and git.') + await ctx.send(f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!') return - if team.abbrev not in self.cheat_time.keys(): - self.cheat_time[team.abbrev] = datetime.datetime.now() - await ctx.send(f'Alright, you look like you could use a little somethin. Take a pack. ' - f'Come back in 20 minutes ;)') - self.give_pack(team, 1) + + p_query = db_get( + 'packs', + params=[('opened', True), ('team_id', team['id']), ('new_to_old', True), ('limit', 1)] + ) + if not p_query['count']: + await ctx.send(f'I do not see any packs for you, bub.') + return + + pack_name = p_query['packs'][0]['pack_type']['name'] + if pack_name == 'Standard': + pack_cover = IMAGES['pack-sta'] + elif pack_name == 'Premium': + pack_cover = IMAGES['pack-pre'] else: - delta = datetime.datetime.now() - self.cheat_time[team.abbrev] - if delta.total_seconds() < 1200: - minutes = 20 - math.floor((delta.seconds % 3600) / 60) - await ctx.message.add_reaction('❌') - await ctx.send(f'You\'ve got {minutes} minute{"s" if minutes > 1 else ""} before your next pull. ' - f'You\'ll be fine, though. Just take a deep breath and see ' - f'if anybody else can open one.') - else: - self.cheat_time[team.abbrev] = datetime.datetime.now() - await ctx.send(f'Alright, you look like you could use a little somethin. Take a pack. ' - f'Come back in 20 minutes ;)') - self.give_pack(team, 1) - print(f'now cheat time: {self.cheat_time}') + pack_cover = None - @commands.command(name='newteam', help='IN TESTING, get a sample starter team') - @commands.has_any_role('Paper Dynasty Players') - async def starter_team(self, ctx): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') + c_query = db_get( + 'cards', + params=[('pack_id', p_query['packs'][0]['id'])] + ) + if not c_query['count']: + await ctx.send(f'Hmm...I didn\'t see any cards in that pack.') return - logging = True + await display_cards(c_query['cards'], team, ctx.channel, ctx.author, self.bot, pack_cover=pack_cover) - owner_team = Team.get_by_owner(ctx.author.id) - # New team survey - if owner_team: - await ctx.send(f'Whoa there, bucko. I already have you down as GM of the {owner_team.sname}.') - return - else: - def abbrev_check(mes): - return mes.author == ctx.author and 3 >= len(mes.content) >= 2 and mes.content.isalpha() - - def name_check(mes): - return mes.author == ctx.author and len(mes.content) <= 40 - - overwrites = {ctx.guild.default_role: discord.PermissionOverwrite(read_messages=False), - ctx.author: discord.PermissionOverwrite(read_messages=True, send_messages=True), - ctx.guild.me: discord.PermissionOverwrite(read_messages=True)} - op_ch = await ctx.guild.create_text_channel( - f'hello-{ctx.author.name}', - overwrites=overwrites, - category=discord.utils.get(ctx.guild.categories, name='Paper Dynasty Openings') + @commands.hybrid_command(name='comeonmanineedthis', help='Daily check-in for cards, currency, and packs') + @commands.has_any_role(PD_PLAYERS) + @commands.check(legal_channel) + async def daily_checkin(self, ctx: commands.Context): + team = get_team_by_owner(ctx.author.id) + if not team: + await ctx.send( + f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!', + ephemeral=True ) - try: - await self.helpers.pause_then_type( - op_ch, - f'Oh, hi, {ctx.author.mention}! I am Paper Domo and I am going to get you all set up. ' - f'First, what is your name?') - new_gmname = await self.bot.wait_for('message', check=name_check, timeout=30.0) - new_gmname = new_gmname.content.replace('"', '') + return - await self.helpers.pause_then_type(op_ch, f'Alrighty, {new_gmname}, what would you like your team\'s ' - f'2 or 3 letter abbreviation to be?') - new_abbrev = await self.bot.wait_for('message', check=abbrev_check, timeout=30.0) - new_abbrev = new_abbrev.content.replace('"', '') + current = db_get('current') + now = datetime.datetime.now() + midnight = int_timestamp(datetime.datetime(now.year, now.month, now.day, 0, 0, 0)) + daily = db_get('rewards', params=[ + ('name', 'Daily Check-in'), ('team_id', team['id']), ('created_after', midnight) + ]) + logging.debug(f'midnight: {midnight} / now: {int_timestamp(now)}') + logging.debug(f'daily_return: {daily}') - await self.helpers.pause_then_type(op_ch, - f'Got it! What is the full name of {new_abbrev}? ' - f'Something like "Milwaukee Brewers" or "Baltimore Orioles".') - new_lname = await self.bot.wait_for('message', check=name_check, timeout=30.0) - new_lname = new_lname.content.replace('"', '') - if new_lname == new_gmname or new_lname == new_abbrev: - await self.helpers.pause_then_type(op_ch, - 'Let\'s try this again. Your team name can\'t match your name ' - 'or team abbreviation.') + if daily: + await ctx.send( + f'Looks like you already checked in today - come back at midnight Central!', + ephemeral=True + ) + return - await self.helpers.pause_then_type(op_ch, - f'Well that is a mouthful. What would {new_abbrev}\'s short name be? ' - f'Something like "Black Bears", "Angels", or "Crabbers".') - new_sname = await self.bot.wait_for('message', check=name_check, timeout=30.0) - new_sname = new_sname.content.replace('"', '') - if new_sname == new_gmname or new_sname == new_abbrev: - await self.helpers.pause_then_type( - op_ch, - 'Let\'s try this again. Your team name can\'t match your name or team abbreviation.') + db_post('rewards', payload={ + 'name': 'Daily Check-in', 'team_id': team['id'], 'season': current['season'], 'week': current['week'], + 'created': int_timestamp(now) + }) + check_ins = db_get('rewards', params=[ + ('name', 'Daily Check-in'), ('team_id', team['id']), ('season', db_get('current')['season']) + ]) + + check_count = check_ins['count'] % 5 + + # 2nd, 4th, and 5th check-ins + if check_count == 0 or check_count % 2 == 0: + # Every fifth check-in + if check_count == 0: + greeting = await ctx.send(f'Hey, you just earned a Standard pack of cards!') + pack_channel = get_channel(ctx, 'pack-openings') + + p_query = db_get('packtypes', params=[('name', 'Standard')]) + if not p_query: + await ctx.send(f'I was not able to pull this pack for you. Maybe ping {get_cal_user(ctx).mention}?') return - owner_team = Team(abbrev=new_abbrev, sname=new_sname, lname=new_lname, gmid=ctx.author.id, - gmname=new_gmname, weeklyclaim=False, dailyclaim=False, - weeklypacks=0, season=1) - await self.helpers.pause_then_type( - op_ch, - f'Noice. I\'ve got you down as the {new_sname}. Let\'s get you a starter team. ' - f'What MLB club would you like to use as your anchor?' - ) - choice_response = await self.bot.wait_for('message', check=name_check, timeout=30.0) - - await self.helpers.typing_pause(ctx, 1) - except TimeoutError: - await op_ch.send('Welp, I have to go. If you want to try setting up your team again, hit me up.') - await asyncio.sleep(60) - await op_ch.delete() - return - except Exception as e: - await op_ch.send('Welp, I have to go. If you want to try setting up your team again, hit me up.') - await asyncio.sleep(60) - await op_ch.delete() - return - - # Get team - team_flag = False - all_teams = { - 'Arizona Diamondbacks': ['ARI', 'Diamondbacks'], - 'Atlanta Braves': ['ATL', 'Braves'], - 'Baltimore Orioles': ['BAL', 'Orioles'], - 'Boston Red Sox': ['BOS', 'Red Sox'], - 'Chicago Cubs': ['CHC', 'Cubs'], - 'Chicago White Sox': ['CHW', 'White Sox'], - 'Cincinnati Reds': ['CIN', 'Reds'], - 'Cleveland Indians': ['CLE', 'Indians'], - 'Colorado Rockies': ['COL', 'Rockies'], - 'Detroit Tigers': ['DET', 'Tigers'], - 'Houston Astros': ['HOU', 'Astros'], - 'Kansas City Royals': ['KCR', 'Royals'], - 'Los Angeles Angels': ['LAA', 'CAL', 'Angels'], - 'Los Angeles Dodgers': ['LAD', 'Dodgers'], - 'Miami Marlins': ['MIA', 'Marlins'], - 'Milwaukee Brewers': ['MIL', 'MKE', 'Brewers'], - 'Minnesota Twins': ['MIN', 'Twins'], - 'New York Mets': ['NYM', 'Mets'], - 'New York Yankees': ['NYY', 'Yankees'], - 'Oakland Athletics': ['OAK', 'Athletics'], - 'Philadelphia Phillies': ['PHI', 'Phillies'], - 'Pittsburgh Pirates': ['PIT', 'Pirates'], - 'San Diego Padres': ['SDP', 'Padres'], - 'Seattle Mariners': ['SEA', 'Mariners'], - 'San Francisco Giants': ['SFG', 'Giants'], - 'St Louis Cardinals': ['STL', 'Cardinals'], - 'Tampa Bay Rays': ['TBR', 'Rays'], - 'Texas Rangers': ['TEX', 'Rangers'], - 'Toronto Blue Jays': ['TOR', 'Jays'], - 'Washington Nationals': ['WSN', 'Nationals'], - } - - if choice_response.content.title() in all_teams.keys(): - team_choice = choice_response.content - else: - for x in all_teams: - if choice_response.content.upper() in all_teams[x]: - team_choice = x - break - if choice_response.content.title() in all_teams[x]: - team_choice = x - break + # Every second and fourth check-in else: - await op_ch.send('Ope. I don\'t recognize that team. I try to recognize abbreviations (BAL), ' - 'short names (Orioles), and long names ("Baltimore Orioles"). If you have to enter ' - 'a space, put quotes around the team name ("Red Sox").') + greeting = await ctx.send(f'Hey, you just earned a player card!') + pack_channel = ctx.channel + + p_query = db_get('packtypes', params=[('name', 'Check-In Player')]) + if not p_query: + await ctx.send(f'I was not able to pull this card for you. Maybe ping {get_cal_user(ctx).mention}?') + return + + give_packs(team, 1, p_query['packtypes'][0]) + p_query = db_get( + 'packs', + params=[('opened', False), ('team_id', team['id']), ('new_to_old', True), ('limit', 1)] + ) + if not p_query['count']: + await ctx.send(f'I do not see any packs in here. {await get_emoji(ctx, "ConfusedPsyduck")}') return - print(team_choice) if logging else True + pack_ids = await roll_for_cards(p_query['packs'], extra_val=check_count) + if not pack_ids: + await greeting.edit( + content=f'I was not able to create these cards {await get_emoji(ctx, "slight_frown")}' + ) + return - team_allstars = Player.select().join(Rarity)\ - .where((Player.mlbclub == team_choice) & - (Player.rarity == Rarity.get(Rarity.name == 'All-Star')) & - (Player.primary != 'RP'))\ - .order_by(fn.Random()) - team_starters = Player.select().join(Rarity)\ - .where((Player.mlbclub == team_choice) & - (Player.rarity == Rarity.get(Rarity.name == 'Starter')) & - (Player.primary != 'RP'))\ - .order_by(fn.Random()) + all_cards = [] + for p_id in pack_ids: + new_cards = db_get('cards', params=[('pack_id', p_id)]) + all_cards.extend(new_cards['cards']) - print(f'AS count: {team_allstars.count()}\nSta count: {team_starters.count()}') if logging else True + if not all_cards: + await greeting.edit( + content=f'I was not able to pull these cards {await get_emoji(ctx, "slight_frown")}' + ) + return + + await display_cards(all_cards, team, pack_channel, ctx.author, self.bot) + await refresh_sheet(team, self.bot) + return + + # 1st, 3rd check-ins + else: + d_1000 = random.randint(1, 1000) + + m_reward = 0 + if d_1000 < 500: + m_reward = 10 + elif d_1000 < 900: + m_reward = 15 + elif d_1000 < 990: + m_reward = 20 + else: + m_reward = 25 + + team = db_post(f'teams/{team["id"]}/money/{m_reward}') + await ctx.send(f'You just earned {m_reward}₼! That brings your wallet to {team["wallet"]}₼!') + + @commands.hybrid_command(name='open', help='Rip a pack or 7') + @app_commands.describe(pack_type='Name of the pack you\'d like to open (e.g. Standard or Premium)') + @commands.has_any_role(PD_PLAYERS) + @commands.check(legal_channel) + async def open_slash( + self, ctx, pack_type: Literal['Standard', 'Premium', 'MVP'], quantity: Literal[1, 2, 3, 4, 5, 6, 7]): + team = get_team_by_owner(ctx.author.id) + if not team: + await ctx.send( + f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!', + ephemeral=True + ) + return + + p_query = db_get('packtypes', params=[('name', pack_type)]) + if not p_query['count']: + await ctx.send(f'I did not find any {pack_type} packs for you.') + return + this_pack_type = p_query['packtypes'][0] + + if this_pack_type['name'] in ['Premium', 'MVP']: + pack_cover = IMAGES['pack-pre'] + elif this_pack_type['name'] == 'Standard': + pack_cover = IMAGES['pack-sta'] + else: + pack_cover = None + + pack_query = db_get('packs', params=[ + ('team_id', team['id']), ('limit', quantity), ('opened', False), ('pack_type_id', this_pack_type['id']) + ]) + if not pack_query['count']: + content = f'Looks like you are clean out of {this_pack_type["name"]} packs, friendo.' + if pack_type in ['Standard', 'Premium']: + content += 'You can earn them by playing PD games or by donating to the league.' + await ctx.send(content) + return + all_packs = pack_query['packs'] + + greeting = await ctx.send(f'Alright, let me get that pack ready for you...') + + # Get the opening channel + pack_channel = get_channel(ctx, 'pack-openings') + if not pack_channel: + await ctx.send( + content=f'Yikes - I cannot find the pack-openings channel. {get_cal_user(ctx).mention} - halp?' + ) + return + + if pack_type in ['Standard', 'Premium']: + # Try to open the pack + # Get the cards from the opened pack + pack_ids = await roll_for_cards(all_packs) + if not pack_ids: + await greeting.edit( + content=f'I was not able to create these cards {await get_emoji(ctx, "slight_frown")}' + ) + return + + all_cards = [] + for p_id in pack_ids: + new_cards = db_get('cards', params=[('pack_id', p_id)]) + all_cards.extend(new_cards['cards']) + + if not all_cards: + await greeting.edit(f'I was not able to pull these cards {await get_emoji(ctx, "slight_frown")}') + return + + # Present cards to opening channel + await ctx.send(content=f'Let\'s head down to {pack_channel.mention}!') + async with pack_channel.typing(): + is_done = await display_cards(all_cards, team, pack_channel, ctx.author, self.bot, pack_cover=pack_cover) + + else: + # Get 4 MVP cards + rarity_id = 5 + if pack_type == 'HoF': + rarity_id = 8 + elif pack_type == 'All-Star': + rarity_id = 3 + + pl = db_get('players/random', params=[ + ('min_rarity', rarity_id), ('max_rarity', rarity_id), ('limit', 4) + ]) + if pl['count']: + players = pl['players'] + else: + raise ConnectionError(f'Could not create MVP pack') + + # Display them with pagination, prev/next/select + card_embeds = [ + await get_card_embeds( + {'player': x, 'team': {'lname': 'Paper Dynasty', 'season': PD_SEASON, 'logo': IMAGES['logo']}} + ) for x in players + ] + logging.info(f'card embeds: {card_embeds}') + page_num = 0 + + view = Pagination([ctx.author], timeout=30) + view.left_button.disabled = True + view.left_button.label = f'Prev: -/{len(card_embeds)}' + view.cancel_button.label = f'Take This Card' + view.cancel_button.style = discord.ButtonStyle.success + view.cancel_button.disabled = True + view.right_button.label = f'Next: 1/{len(card_embeds)}' + + # React to selection + await ctx.send(f'Let\'s head down to {pack_channel.mention}!') + msg = await pack_channel.send( + content=None, + embed=image_embed(pack_cover, title=f'{team["lname"]}', desc=f'MVP Pack - Choose 1 of 4 {pack_type}s!'), + view=view + ) + tmp_msg = await pack_channel.send(content=f'@here we\'ve got an MVP!') + + while True: + await view.wait() + + if view.value: + if view.value == 'cancel': + await msg.edit(view=None) + + try: + give_cards_to_team(team, players=[players[page_num - 1]], pack_id=all_packs[0]['id']) + except Exception as e: + logging.error(f'failed to create cards: {e}') + raise ConnectionError(f'Failed to distribute these cards.') + + db_patch('packs', object_id=all_packs[0]['id'], params=[ + ('open_time', int(datetime.datetime.timestamp(datetime.datetime.now()) * 1000)) + ]) + await tmp_msg.edit( + content=f'{players[page_num - 1]["p_name"]} has been added to the ' + f'**{team["sname"]}** binder!' + ) + await refresh_sheet(team, self.bot) + break + if view.value == 'left': + page_num -= 1 if page_num > 1 else len(card_embeds) + if view.value == 'right': + page_num += 1 if page_num < len(card_embeds) else 1 + else: + if page_num == len(card_embeds): + page_num = 1 + else: + page_num += 1 + + view.value = None + + view = Pagination([ctx.author], timeout=30) + view.left_button.label = f'Prev: {page_num - 1}/{len(card_embeds)}' + view.cancel_button.label = f'Take This Card' + view.cancel_button.style = discord.ButtonStyle.success + view.right_button.label = f'Next: {page_num + 1}/{len(card_embeds)}' + if page_num == 1: + view.left_button.label = f'Prev: -/{len(card_embeds)}' + view.left_button.disabled = True + elif page_num == len(card_embeds): + view.right_button.label = f'Next: -/{len(card_embeds)}' + view.right_button.disabled = True + + await msg.edit(content=None, embeds=card_embeds[page_num - 1], view=view) + + await refresh_sheet(team, self.bot) + + # @commands.command(name='newteam', aliases=['newseason'], help='New players get your team') + # @commands.has_any_role('Paper Dynasty Players') + # async def starter_team(self, ctx): + # owner_team = Team.get_by_owner(ctx.author.id) + # last_team = Team.get_by_owner(ctx.author.id, Current.get().season - 1) + # current = Current.get() + # get_cards = True + # + # # Check for existing team + # if owner_team: + # await ctx.send(f'Whoa there, bucko. I already have you down as GM of the {owner_team.sname}.') + # return + # + # # Get personal bot channel + # hello_channel = discord.utils.get(ctx.guild.text_channels, name=f'hello-{ctx.author.name}'.lower()) + # if hello_channel: + # op_ch = hello_channel + # else: + # op_ch = await helpers.create_channel_old( + # ctx, + # f'hello-{ctx.author.name}', + # 'Paper Dynasty Team', + # everyone_read=False, + # allowed_members=[ctx.author] + # ) + # + # async def timed_delete(): + # await op_ch.send('Welp, I have to go. If you want to try setting up your team again, hit me up.') + # # await asyncio.sleep(60) + # # await op_ch.delete() + # + # async def create_roster_sheet(team): + # await helpers.pause_then_type( + # op_ch, 'I am going to go create your roster sheet. This will take a few seconds.') + # async with op_ch.typing(): + # sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json') + # this_sheet = sheets.drive.copy_file( + # '1Z-g3M_rSnRdr-xjXP0YqxMhnOIGsaUK_2v5M3fUODH8', + # f'{team.lname} Roster Sheet', + # '163QSgtsduyFf67A0IWBFvAwYIpHMz_cD') + # + # return this_sheet + # + # await op_ch.send(f'Hey there, {ctx.author.mention}! I am Paper Domo and I am going to walk you through team ' + # f'creation. I have a series of questions for you about you and your team. Please do not ' + # f'include any special characters in your name or I get a headache and I will take it ' + # f'out on you.') + # await asyncio.sleep(5) + # reuse_team = None + # old_team = None + # logo_url = None + # team_role = None + # reset_packs = 0 + # rollover_standard = 0 + # rollover_premium = 0 + # reset_team = False + # + # # Returning GM + # if last_team: + # prompt = f'Hol up, I know you. You managed the {last_team.sname} last season! Do you want to ' \ + # f'keep this same branding?' + # reuse_q = Question(self.bot, op_ch, prompt, 'yesno', 30) + # reuse_team = await reuse_q.ask([ctx.author]) + # + # if reuse_team is None: + # await op_ch.send('You think on it. I\'ll probably still be here when you decide.') + # return + # + # old_team = Team( + # abbrev=last_team.abbrev, sname=last_team.sname, lname=last_team.lname, + # gmid=ctx.author.id, gmname=last_team.gmname, gsheet=last_team.gsheet, weeklyclaim=False, + # team_value=last_team.team_value, collection_value=last_team.collection_value, + # dailyclaim=False, weeklypacks=0, season=current.season - 1, + # logo=last_team.logo if last_team.logo else None + # ) + # + # # Get unopened packs + # tp_query = (Pack + # .select() + # .where((Pack.team == last_team) & (Pack.card1.is_null()))) + # prem_query = Pack.select().where( + # (Pack.team == last_team) & (Pack.card1.is_null()) & ( + # Pack.pack_type == PackType.get(PackType.name == 'Premium')) + # ) + # rollover_standard = tp_query.count() - prem_query.count() + # rollover_premium = prem_query.count() + # reset_packs += round((last_team.collection_value * 1.0) / 4.5) + # + # reset_prompt = f'Would you like to reset your collection? I will grant you 1 Standard pack for ' \ + # f'every 4.5 CV your team has. You will keep any unopened packs you already have. ' \ + # f'For the {last_team.sname}, that would be {reset_packs} ' \ + # f'pack{"s" if reset_packs > 1 else ""}.' + # reset_q = Question(self.bot, op_ch, reset_prompt, 'yesno', 15) + # reset_team = await reset_q.ask([ctx.author]) + # if reset_team is None: + # await ctx.send('This is a big moment for you. Why don\'t you think on it and get back to me once ' + # 'you\'re ready.') + # return + # + # # Get team branding + # if not reuse_team: + # # They have an old team, but want to rebrand or they did not have a team last year + # # Get GM name + # await op_ch.send('Alrighty, let\'s get you started with your new branding!') + # prompt = f'First, what is your GM name?' + # this_q = Question(self.bot, op_ch, prompt, 'text', 45) + # gm_name = await this_q.ask([ctx.author]) + # + # if not gm_name: + # await timed_delete() + # return + # else: + # gm_name = re.sub(r'\W +', '', gm_name) + # + # # Get team abbreviation + # while True: + # this_q.prompt = f'Alrighty, {gm_name}, what would you like your team\'s 2 or 3 letter abbreviation to be?' + # abbrev = await this_q.ask([ctx.author]) + # + # if not abbrev: + # await timed_delete() + # return + # elif len(abbrev) < 5: + # abbrev = re.sub(r'\W +', '', abbrev) + # if len(abbrev) > 0: + # if not Team.get_season(abbrev): + # abbrev = abbrev.upper() + # break + # else: + # await op_ch.send(f'Oof, {abbrev} is actually already taken.') + # else: + # await op_ch.send( + # 'Once I pulled out special characters, there was nothing left of your abbrev.') + # + # await op_ch.send('Please try again.') + # await asyncio.sleep(1) + # + # # Get team long name + # while True: + # this_q.prompt = f'Got it! What is the full name of {abbrev}? ' \ + # f'Something like Milwaukee Brewers or Baltimore Orioles. (Max of 40 characters)' + # team_lname = await this_q.ask([ctx.author]) + # + # if not team_lname: + # await timed_delete() + # return + # elif len(team_lname) <= 40: + # team_lname = re.sub(r'\W +', '', team_lname) + # if len(team_lname) > 0: + # break + # + # await op_ch.send('Let\'s try that again. Follow the rules this time.') + # await asyncio.sleep(1) + # + # # Get team short name + # while True: + # this_q.prompt = f'Well that is a mouthful. What would {abbrev}\'s short name be? ' \ + # f'Something like Black Bears, Angels, or Crabbers. (Max of 20 characters)' + # team_sname = await this_q.ask([ctx.author]) + # + # if not team_sname: + # await timed_delete() + # return + # elif len(team_sname) <= 20: + # team_sname = re.sub(r'\W +', '', team_sname) + # if len(team_sname) > 0: + # break + # + # await op_ch.send('Let\'s try that again. Follow the rules this time.') + # await asyncio.sleep(1) + # + # # Get team logo + # logo_prompt = f'Do you want to add a team logo? No worries if you don\'t have one now, ' \ + # f'this can be added later.' + # logo_q = Question(self.bot, op_ch, logo_prompt, 'yesno', 15) + # resp = await logo_q.ask([ctx.author]) + # + # if resp: + # while True: + # this_q.prompt = 'Please enter the URL to your logo. If it is an image in discord, right-click and ' \ + # 'click Copy Link and paste that here.' + # logo = await this_q.ask([ctx.author]) + # + # if logo is None: + # await op_ch.send('Let\'s come back to the logo another time.') + # break + # elif ' ' in logo: + # await op_ch.send('Yikes. I am just looking for the URL of your image. It can come from ' + # 'anywhere, but would look something like: `www.website.com/images/path.jpg`') + # else: + # logo_url = logo + # break + # + # if not reset_team: + # # Do not reset team + # owner_team = last_team + # owner_team.season = current.season + # + # if not reuse_team: + # old_team.save() + # owner_team.abbrev = abbrev + # owner_team.sname = team_sname + # owner_team.lname = team_lname + # if logo_url: + # owner_team.logo = logo_url + # team_roster_sheet = await create_roster_sheet(owner_team) + # owner_team.gsheet = team_roster_sheet['id'] + # + # owner_team.save() + # + # team_role = await helpers.get_or_create_role(ctx, owner_team.lname) + # await self.write_collection(op_ch, owner_team) + # await op_ch.send('Alright then you are all set, hoss!') + # get_cards = False + # else: + # if not reuse_team: + # owner_team = Team( + # abbrev=abbrev, sname=team_sname, lname=team_lname, gmid=ctx.author.id, gmname=gm_name + # ) + # if logo_url: + # owner_team.logo = logo_url + # else: + # owner_team = Team( + # abbrev=last_team.abbrev, sname=last_team.sname, lname=last_team.lname, gmid=ctx.author.id, + # gmname=last_team.gmname + # ) + # + # # New GM + # if not last_team: + # # Get GM name + # await op_ch.send('Alrighty, let\'s get you started with your new branding!') + # prompt = f'First, what is your GM name?' + # this_q = Question(self.bot, op_ch, prompt, 'text', 45) + # gm_name = await this_q.ask([ctx.author]) + # + # if not gm_name: + # await timed_delete() + # return + # else: + # gm_name = re.sub(r'\W +', '', gm_name) + # + # # Get team abbreviation + # while True: + # this_q.prompt = f'Alrighty, {gm_name}, what would you like your team\'s 2 or 3 letter abbreviation to be?' + # abbrev = await this_q.ask([ctx.author]) + # + # if not abbrev: + # await timed_delete() + # return + # elif len(abbrev) < 5: + # abbrev = re.sub(r'\W +', '', abbrev) + # if len(abbrev) > 0: + # if not Team.get_season(abbrev): + # abbrev = abbrev.upper() + # break + # else: + # await op_ch.send(f'Oof, {abbrev} is actually already taken.') + # else: + # await op_ch.send( + # 'Once I pulled out special characters, there was nothing left of your abbrev.') + # + # await op_ch.send('Please try again.') + # await asyncio.sleep(1) + # + # # Get team long name + # while True: + # this_q.prompt = f'Got it! What is the full name of {abbrev}? ' \ + # f'Something like Milwaukee Brewers or Baltimore Orioles. (Max of 40 characters)' + # team_lname = await this_q.ask([ctx.author]) + # + # if not team_lname: + # await timed_delete() + # return + # elif len(team_lname) <= 40: + # team_lname = re.sub(r'\W +', '', team_lname) + # if len(team_lname) > 0: + # break + # + # await op_ch.send('Let\'s try that again. Follow the rules this time.') + # await asyncio.sleep(1) + # + # # Get team short name + # while True: + # this_q.prompt = f'Well that is a mouthful. What would {abbrev}\'s short name be? ' \ + # f'Something like Black Bears, Angels, or Crabbers. (Max of 20 characters)' + # team_sname = await this_q.ask([ctx.author]) + # + # if not team_sname: + # await timed_delete() + # return + # elif len(team_sname) <= 20: + # team_sname = re.sub(r'\W +', '', team_sname) + # if len(team_sname) > 0: + # break + # + # await op_ch.send('Let\'s try that again. Follow the rules this time.') + # await asyncio.sleep(1) + # + # # Get team logo + # logo_prompt = f'Do you want to add a team logo? No worries if you don\'t have one now, ' \ + # f'this can be added later.' + # logo_q = Question(self.bot, op_ch, logo_prompt, 'yesno', 15) + # resp = await logo_q.ask([ctx.author]) + # + # if resp: + # while True: + # this_q.prompt = 'Please enter the URL to your logo. If it is an image in discord, right-click and ' \ + # 'click Copy Link and paste that here.' + # logo = this_q.ask([ctx.author]) + # + # if logo is None: + # await op_ch.send('Let\'s come back to the logo another time.') + # break + # elif ' ' in logo: + # await op_ch.send('Yikes. I am just looking for the URL of your image. It can come from ' + # 'anywhere, but would look something like: `www.website.com/images/path.jpg`') + # else: + # logo_url = logo + # break + # + # owner_team = Team( + # abbrev=abbrev, sname=team_sname, lname=team_lname, gmid=ctx.author.id, gmname=gm_name + # ) + # team_roster_sheet = await create_roster_sheet(owner_team) + # owner_team.gsheet = team_roster_sheet['id'] + # + # if get_cards: + # # Get MLB anchor team + # while True: + # prompt = f'Noice. I\'ve got you down as the {owner_team.sname}. Let\'s get you a starter team. ' \ + # f'What MLB club would you like to use as your anchor? Something like MKE, Orioles, or ' \ + # f'Texas Rangers.' + # this_q = Question(self.bot, op_ch, prompt, 'text', 45) + # team_string = await this_q.ask([ctx.author]) + # + # if not team_string: + # await timed_delete() + # return + # + # if team_string.title() in all_mlb_teams.keys(): + # team_choice = team_string.title() + # break + # else: + # match = False + # for x in all_mlb_teams: + # if team_string.upper() in all_mlb_teams[x] or team_string.title() in all_mlb_teams[x]: + # team_choice = x + # match = True + # break + # if match: + # break + # + # await op_ch.send(f'Ope. I don\'t recognize **{team_string}**. I try to recognize abbreviations (BAL), ' + # f'short names (Orioles), and long names ("Baltimore Orioles").') + # + # logging.warning(f'{ctx.author.name} has selected {team_choice}') + # + # team_allstars = Player.select().join(Rarity) \ + # .where((Player.franchise == team_choice) & + # (Player.rarity == Rarity.get(Rarity.name == 'All-Star')) & + # (Player.primary != 'RP')) \ + # .order_by(fn.Random()) + # team_starters = Player.select().join(Rarity) \ + # .where((Player.franchise == team_choice) & + # (Player.rarity == Rarity.get(Rarity.name == 'Starter')) & + # (Player.primary != 'RP')) \ + # .order_by(fn.Random()) + # + # logging.info(f'AS count: {team_allstars.count()}\nSta count: {team_starters.count()}') + # + # # Get anchor players + # roster_counts = { + # 'SP': 0, + # 'RP': 0, + # 'C': 0, + # '1B': 0, + # '2B': 0, + # '3B': 0, + # 'SS': 0, + # 'LF': 0, + # 'CF': 0, + # 'RF': 0, + # 'DH': 0, + # 'Reserve': 0, + # 'Replacement': 0, + # } + # roster_list = [] + # anchor_starters = '' + # if team_allstars.count() > 0: + # # Get anchor team All-Star + # anchor_allstar = None + # for x in team_allstars: + # anchor_allstar = x + # roster_list.append(x) + # roster_counts[x.primary] += 1 + # break + # logging.info(f'Anchor: {anchor_allstar}') + # + # # Get two starters + # starter_pool = Player.select().join(Rarity) \ + # .where(Player.rarity == Player.rarity == Rarity.get(Rarity.name == 'Starter')) \ + # .order_by(fn.Random()).limit(8) + # count = 0 + # for x in starter_pool: + # if roster_counts[x.primary] == 0: + # roster_list.append(x) + # roster_counts[x.primary] += 1 + # anchor_starters += f'{x}\n' + # logging.info(f'Anchor starter: {x}') + # count += 1 + # if count > 1: + # break + # elif team_starters.count() > 1: + # # Get two starters + # logging.info(f'anchor pool: {team_starters}') + # for x in team_starters: + # if roster_counts[x.primary] == 0: + # roster_list.append(x) + # roster_counts[x.primary] += 1 + # logging.info(f'Anchor starter: {x}') + # anchor_starters += f'{x}\n' + # if len(roster_list) >= 2: + # break + # + # # Get anchor All-Star + # anchor_allstar = Player.select().join(Rarity) \ + # .where((Player.rarity == Player.rarity == Rarity.get(Rarity.name == 'All-Star')) & + # (Player.primary != "RP") & + # (Player.primary != "CP")) \ + # .order_by(fn.Random()).limit(5) + # for x in anchor_allstar: + # if roster_counts[x.primary] == 0: + # anchor_allstar = x + # roster_list.append(x) + # roster_counts[x.primary] += 1 + # logging.info(f'Anchor All-Star: {x}') + # break + # else: + # await op_ch.send(f'It pains me to say this, but the {team_choice} don\'t have an All-Star nor do they ' + # f'have two starters to seed your team. Please select another team.') + # return + # logging.info(f'Post anchor roster comp: {roster_counts}') + # + # embed = discord.Embed(title=f'{team_choice} Anchors') + # embed.add_field(name='All-Star', value=f'{anchor_allstar}', inline=False) + # embed.add_field(name='Starters', + # value=anchor_starters, inline=False) + # embed.set_image(url=f'{anchor_allstar.url}') + # await op_ch.send(content=f'{ctx.author.mention}', embed=embed) + # + # # Get 5 SP + # random_sp = (Player + # .select() + # .join(Rarity) + # .where((Player.primary == 'SP') & + # ((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | + # (Player.rarity == Rarity.get(Rarity.name == 'Reserve')))) + # .order_by(fn.Random()) + # .limit(15)) + # count = 0 + # for x in random_sp: + # if x not in roster_list: + # roster_list.append(x) + # roster_counts[x.primary] += 1 + # roster_counts[x.rarity.name] += 1 + # count += 1 + # if count > 5: + # break + # + # # Get 5 RP + # random_rp = (Player + # .select() + # .join(Rarity) + # .where(((Player.primary == 'RP') | (Player.primary == 'CP')) & + # ((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | + # (Player.rarity == Rarity.get(Rarity.name == 'Reserve')))) + # .order_by(fn.Random()) + # .limit(15)) + # count = 0 + # for x in random_rp: + # if x not in roster_list: + # roster_list.append(x) + # roster_counts[x.primary] += 1 + # roster_counts[x.rarity.name] += 1 + # count += 1 + # if count > 5: + # break + # + # # Ensure all positions have two players + # for pos in roster_counts.keys(): + # logging.info(f'Starting {pos}') + # if pos == 'DH' or pos == 'Reserve' or pos == 'Replacement': + # pass + # else: + # logging.info(f'{pos} Count: {roster_counts[pos]}') + # while roster_counts[pos] < 2: + # random_draw = (Player + # .select() + # .where((Player.primary == pos) & + # ((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | + # (Player.rarity == Rarity.get(Rarity.name == 'Reserve')))) + # .order_by(fn.Random()) + # .limit(15)) + # logging.info(f'Replacement count: {roster_counts["Replacement"]}') + # if roster_counts['Replacement'] >= 15: + # for x in random_draw: + # if x.rarity.name == 'Reserve': + # roster_list.append(x) + # roster_counts[x.rarity.name] += 1 + # roster_counts[pos] += 1 + # break + # elif roster_counts['Reserve'] >= 12: + # for x in random_draw: + # if x.rarity.name == 'Replacement': + # roster_list.append(x) + # roster_counts[x.rarity.name] += 1 + # roster_counts[pos] += 1 + # break + # elif roster_counts['Reserve'] < 12 and roster_counts['Replacement'] < 15: + # roster_list.append(random_draw[0]) + # roster_counts[random_draw[0].rarity.name] += 1 + # roster_counts[pos] += 1 + # logging.info(f'Adding {pos}: {roster_list[-1]}') + # logging.info(f'{pos} Count: {roster_counts[pos]}') + # + # logging.info(f'roster_count: {roster_counts}') + # if roster_counts['Reserve'] + roster_counts['Replacement'] < 27: + # random_draw = (Player + # .select() + # .where((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | + # (Player.rarity == Rarity.get(Rarity.name == 'Reserve'))) + # .order_by(fn.Random()) + # .limit(15)) + # for x in random_draw: + # if x not in roster_list: + # roster_list.append(x) + # roster_counts[x.rarity.name] += 1 + # roster_counts[x.primary] += 1 + # if roster_counts['Reserve'] + roster_counts['Replacement'] >= 27: + # break + # + # logging.info('Building roster list') + # team_cards = [] + # for x in roster_list: + # team_cards.append({ + # 'player': x, + # 'team': owner_team, + # }) + # + # # Create team and starter cards + # logging.info('Create team roster sheet') + # team_roster_sheet = await create_roster_sheet(owner_team) + # + # logging.info('Final owner_team updates') + # owner_team.gsheet = team_roster_sheet['id'] + # owner_team.team_value = 17 + # owner_team.collection_value = 17 + # owner_team.weeklyclaim = False + # owner_team.dailyclaim = False + # owner_team.weeklypacks = 0 + # owner_team.season = current.season + # owner_team.save() + # logging.info('Save all of the cards') + # with db.atomic(): + # try: + # for batch in chunked(team_cards, 20): + # Card.insert_many(batch).execute() + # except Exception as e: + # error = f'**INSERT ERROR (cards):** {type(e).__name__} - {e}' + # logging.error(error) + # await op_ch.send(f'Jinkies, guys. I\'ve got an ugly error:\n\n{e}') + # return + # + # # Create default roster + # try: + # current_roster = helpers.get_sorted_collection(owner_team) + # roster_query = Roster.replace( + # team=owner_team, + # season=Current.get_by_id(1).season, + # card1=current_roster[0], + # card2=current_roster[1], + # card3=current_roster[2], + # card4=current_roster[3], + # card5=current_roster[4], + # card6=current_roster[5], + # card7=current_roster[6], + # card8=current_roster[7], + # card9=current_roster[8], + # card10=current_roster[9], + # card11=current_roster[10], + # card12=current_roster[11], + # card13=current_roster[12], + # card14=current_roster[13], + # card15=current_roster[14], + # card16=current_roster[15], + # card17=current_roster[16], + # card18=current_roster[17], + # card19=current_roster[18], + # card20=current_roster[19], + # card21=current_roster[20], + # card22=current_roster[21], + # card23=current_roster[22], + # card24=current_roster[23], + # card25=current_roster[24], + # card26=current_roster[25], + # ) + # roster_query.execute() + # except Exception as e: + # error = f'**INSERT ERROR (rosters):** {type(e).__name__} - {e}' + # logging.error(error) + # await op_ch.send(f'Jinkies, guys. I\'ve got an ugly error:\n\n{e}') + # return + # + # # Create team role + # team_role = await ctx.guild.create_role(name=f'{owner_team.lname}', mentionable=True) + # await ctx.author.add_roles(team_role, atomic=True) + # + # # Present starting roster + # sp_list = [] + # rp_list = [] + # if_list = [] + # of_list = [] + # + # for x in roster_list: + # if x.primary == 'SP': + # sp_list.append(x) + # elif x.primary == 'RP': + # rp_list.append(x) + # elif x.primary == 'C' or x.primary == '1B' or x.primary == '2B' or x.primary == '3B' or x.primary == 'SS': + # if_list.append(x) + # elif x.primary == 'LF' or x.primary == 'CF' or x.primary == 'RF' or x.primary == 'DH': + # of_list.append(x) + # sp_list.sort(key=lambda x: x.wara) + # rp_list.sort(key=lambda x: x.wara) + # if_list.sort(key=lambda x: x.wara) + # of_list.sort(key=lambda x: x.wara) + # + # # Give ballparks + # default_park = Player.get(Player.cardset == '2018', Player.name == 'Atlanta Ballpark') + # random_park = Player.select().where( + # (Player.rarity == Rarity.get(Rarity.name == 'Replacement')) & (Player.primary == 'Park') & + # (Player.name != 'Atlanta Ballpark') + # ).order_by(fn.Random())[0] + # + # park_card = Card( + # player=default_park, + # team=owner_team + # ) + # park_card.save() + # park_card1 = Card( + # player=random_park, + # team=owner_team + # ) + # park_card1.save() + # db.close() + # + # await op_ch.send('**First up, you start with two ballparks:**') + # for x in [park_card, park_card1]: + # await self.present_player(ctx, op_ch, x) + # async with op_ch.typing(): + # await asyncio.sleep(3) + # + # await helpers.pause_then_type(op_ch, '**Now for your roster! Here are your starting pitchers:**') + # for x in sp_list: + # await self.present_player(ctx, op_ch, Card.get(Card.player == x)) + # async with op_ch.typing(): + # await asyncio.sleep(8) + # + # await op_ch.send('**Next we\'ve got your bullpen:**') + # for x in rp_list: + # await self.present_player(ctx, op_ch, Card.get(Card.player == x)) + # async with op_ch.typing(): + # await asyncio.sleep(8) + # + # await op_ch.send('**Here are your infielders:**') + # for x in if_list: + # await self.present_player(ctx, op_ch, Card.get(Card.player == x)) + # async with op_ch.typing(): + # await asyncio.sleep(8) + # + # await op_ch.send('**Finally, here are your outfielders:**') + # for x in of_list: + # await self.present_player(ctx, op_ch, Card.get(Card.player == x)) + # async with op_ch.typing(): + # await asyncio.sleep(8) + # + # logging.info(f'roster_count: {roster_counts}') + # + # async with op_ch.typing(): + # await self.write_collection(op_ch, owner_team, 0) + # + # embed = helpers.get_active_roster(owner_team, f'{self.bot.get_user(owner_team.gmid).avatar_url}') + # await helpers.send_to_news(ctx, f'A new challenger approaches...', embed) + # + # if reset_team: + # self.give_pack(owner_team, rollover_standard, 'Standard') + # self.give_pack(owner_team, rollover_premium, 'Premium') + # self.give_pack(owner_team, reset_packs, 'Standard') + # await op_ch.send(f'Here is your roster sheet: {helpers.get_roster_sheet_legacy(owner_team)}') + # await helpers.pause_then_type(op_ch, f'{team_role.mention} \nThis is your personal bot channel. ' + # f'Good luck this season!') + # if owner_team.logo: + # thumb = owner_team.logo + # else: + # thumb = self.bot.get_user(owner_team.gmid).avatar_url + # await op_ch.send(content=None, embed=helpers.get_active_roster(owner_team, thumb)) + # + # helpers.collection_to_paperdex(owner_team) + # db.close() + + group_buy = app_commands.Group(name='buy', description='Make a purchase from the marketplace') + + @group_buy.command(name='card', description='Buy a player card from the marketplace') + @app_commands.checks.has_any_role(PD_PLAYERS) + @app_commands.describe( + player_name='Name of the player you want to purchase', + player_cardset='Optional: Name of the cardset the player is from' + ) + async def buy_card_slash( + self, interaction: discord.Interaction, player_name: str, player_cardset: Optional[str] = None): + if interaction.channel.name in ['paper-dynasty-chat', 'pd-news-ticker', 'pd-network-news']: + await interaction.response.send_message( + f'Please head to down to {get_channel(interaction, "pd-bot-hole")} to run this command.', + ephemeral=True + ) + return + + owner_team = get_team_by_owner(interaction.user.id) + if not owner_team: + await interaction.response.send_message( + f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!' + ) + + player_cog = self.bot.get_cog('Players') + proper_name = fuzzy_search(player_name, player_cog.player_list) + if not proper_name: + await interaction.response.send_message(f'No clue who that is.') + return + + all_params = [('name', proper_name)] + if player_cardset: + this_cardset = cardset_search(player_cardset, player_cog.cardset_list) + all_params.append(('cardset_id', this_cardset['id'])) + + p_query = db_get('players', params=all_params) + + if p_query['count'] == 0: + await interaction.response.send_message( + f'I didn\'t find any cards for {proper_name}' + ) + return + if p_query['count'] > 1: + await interaction.response.send_message( + f'I found {p_query["count"]} different cards for {proper_name}. Would you please run this again ' + f'with the cardset specified?' + ) + return + + this_player = p_query['players'][0] + logging.debug(f'this_player: {this_player}') + + c_query = db_get('cards', + params=[('player_id', this_player['player_id']), ('team_id', owner_team["id"])]) + num_copies = c_query['count'] if c_query else 0 + + if this_player['cost'] > owner_team['wallet']: + await interaction.response.send_message( + content=None, + embeds=await get_card_embeds(get_blank_team_card(this_player)) + ) + await interaction.channel.send( + content=f'You currently have {num_copies} cop{"ies" if num_copies != 1 else "y"} of this card.\n\n' + f'Your Wallet: {owner_team["wallet"]}₼\n' + f'Card Price: {this_player["cost"]}₼\n' + f'After Purchase: {await get_emoji(interaction.guild, "dead", False)}\n\n' + f'You will have to save up a little more.' + ) + return + + view = Confirm(responders=[interaction.user]) + await interaction.response.send_message( + content=None, + embeds=await get_card_embeds(get_blank_team_card(this_player)) + ) + question = await interaction.channel.send( + content=f'You currently have {num_copies} cop{"ies" if num_copies != 1 else "y"} of this card.\n\n' + f'Your Wallet: {owner_team["wallet"]}₼\n' + f'Card Price: {this_player["cost"]}₼\n' + f'After Purchase: {owner_team["wallet"] - this_player["cost"]}₼\n\n' + f'Would you like to make this purchase?', + view=view + ) + await view.wait() + + if not view.value: + await question.edit( + content='Saving that money. Smart.', + view=None + ) + return + + purchase = db_get( + f'teams/{owner_team["id"]}/buy/players', + params=[('ts', team_hash(owner_team)), ('ids', f'{this_player["player_id"]}')], + timeout=10 + ) + if not purchase: + await question.edit( + f'That didn\'t go through for some reason. If this happens again, go ping the shit out of Cal.', + view=None + ) + return + + await question.edit(content=f'It\'s all yours!', view=None) + await refresh_sheet(owner_team, self.bot) + + @group_buy.command(name='pack', description='Buy a pack or 7 from the marketplace') + @app_commands.checks.has_any_role(PD_PLAYERS) + @app_commands.describe( + num_packs='Number of packs to purchase', + pack_name='Name of the pack you want (e.g. \'Standard\', \'Premium\')' + ) + async def buy_pack_slash(self, interaction: discord.Interaction, num_packs: int = 1, + pack_name: Literal['Standard', 'Premium'] = None): + if interaction.channel.name in ['paper-dynasty-chat', 'pd-news-ticker', 'pd-network-news']: + await interaction.response.send_message( + f'Please head to down to {get_channel(interaction, "pd-bot-hole")} to run this command.', + ephemeral=True + ) + return + + owner_team = get_team_by_owner(interaction.user.id) + if not owner_team: + await interaction.response.send_message( + f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!' + ) + + if not pack_name: + p_query = db_get('packtypes', params=[('available', True)]) + if 'count' not in p_query: + await interaction.response.send_message( + f'Welp, I couldn\'t find any packs in my database. Should probably go ping ' + f'{get_cal_user(interaction).mention} about that.' + ) + return + + embed = get_team_embed('Packs for Purchase') + embed.description = 'Run `/buy pack `' + for x in p_query['packtypes']: + embed.add_field(name=f'{x["name"]} - {x["cost"]}₼', value=f'{x["description"]}') + + await interaction.response.send_message( + content=None, + embed=embed + ) + return + + p_query = db_get('packtypes', params=[('name', pack_name.lower().replace('pack', '')), ('available', True)]) + if 'count' not in p_query: + await interaction.response.send_message( + f'Hmm...I don\'t recognize {pack_name.title()} as a pack type. Check on that and get back to me.', + ephemeral=True + ) + return + pack_type = p_query['packtypes'][0] + + pack_cover = IMAGES['logo'] + if pack_type['name'] == 'Standard': + pack_cover = IMAGES['pack-sta'] + elif pack_type['name'] == 'Premium': + pack_cover = IMAGES['pack-pre'] + + total_cost = pack_type['cost'] * num_packs + pack_embed = image_embed( + pack_cover, + title=f'{owner_team["lname"]}', + desc=f'{num_packs if num_packs > 1 else ""}{"x " if num_packs > 1 else ""}' + f'{pack_type["name"]} Pack{"s" if num_packs != 1 else ""}', + ) + + if total_cost > owner_team['wallet']: + await interaction.response.send_message( + content=None, + embed=pack_embed + ) + await interaction.channel.send( + content=f'Your Wallet: {owner_team["wallet"]}₼\n' + f'Pack{"s" if num_packs > 1 else ""} Price: {total_cost}₼\n' + f'After Purchase: {await get_emoji(interaction.guild, "dead", False)}\n\n' + f'You will have to save up a little more.' + ) + return + + view = Confirm(responders=[interaction.user], timeout=30) + await interaction.response.send_message( + content=None, + embed=pack_embed + ) + question = await interaction.channel.send( + # content=f'You currently have {num_copies} cop{"ies" if num_copies != 1 else "y"} of this card.\n\n' + content=f'Your Wallet: {owner_team["wallet"]}₼\n' + f'Pack{"s" if num_packs > 1 else ""} Price: {total_cost}₼\n' + f'After Purchase: {owner_team["wallet"] - total_cost}₼\n\n' + f'Would you like to make this purchase?', + view=view + ) + await view.wait() + + if not view.value: + await question.edit( + content='Saving that money. Smart.', + view=None + ) + return + + purchase = db_get( + f'teams/{owner_team["id"]}/buy/pack/{pack_type["id"]}', + params=[('ts', team_hash(owner_team)), ('quantity', num_packs)] + ) + if not purchase: + await question.edit( + f'That didn\'t go through for some reason. If this happens again, go ping the shit out of Cal.', + view=None + ) + return + + await question.edit( + content=f'{"They are" if num_packs > 1 else "It is"} all yours! Go rip it with `/open`', + view=None + ) + + @app_commands.command(name='selldupes', description='Sell all of your duplicate cards') + @app_commands.checks.has_any_role(PD_PLAYERS) + @commands.check(legal_channel) + @app_commands.describe(immediately='Skip all prompts and sell dupes immediately; default False') + async def sell_dupes_command(self, interaction: discord.Interaction, immediately: bool = False): + team = get_team_by_owner(interaction.user.id) + if not team: + await interaction.response.send_message( + f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!', + ephemeral=True + ) + return + + await interaction.response.send_message( + f'Let me flip through your cards. This could take a while if you have a ton of cards...' + ) + + try: + c_query = db_get('cards', params=[('team_id', team['id']), ('dupes', True)], timeout=15) + except Exception as e: + await interaction.edit_original_response( + content=f'{e}\n\nSounds like a {get_cal_user(interaction).mention} problem tbh' + ) + return + + player_ids = [] + dupe_ids = '' + dupe_cards = [] + dupe_string = '' + + for card in c_query['cards']: + logging.debug(f'card: {card}') + if card['player']['player_id'] not in player_ids: + logging.debug(f'not a dupe') + player_ids.append(card['player']['player_id']) + else: + logging.info(f'{team["abbrev"]} duplicate card: {card["id"]}') + dupe_cards.append(card) + dupe_ids += f'{card["id"]},' + dupe_string += f'{card["player"]["rarity"]["name"]} {card["player"]["p_name"]} - ' \ + f'{card["player"]["cardset"]["name"]}\n' + + if len(dupe_cards) == 0: + await interaction.edit_original_response(content=f'You currently have 0 duplicate cards!') + return + + await interaction.edit_original_response( + content=f'You currently have {len(dupe_cards)} duplicate cards:\n\n{dupe_string}' + ) + + if not immediately: + view = Confirm(responders=[interaction.user]) + question = await interaction.channel.send('Would you like to sell all of them?', view=view) + await view.wait() + + if not view.value: + await question.edit( + content='We can leave them be for now.', + view=None + ) + return + await question.edit(content=f'The sale is going through...', view=None) + + # for card in dupe_cards: + sale = db_get( + f'teams/{team["id"]}/sell/cards', + params=[('ts', team_hash(team)), ('ids', dupe_ids)], + timeout=10 + ) + if not sale: + await interaction.channel.send( + f'That didn\'t go through for some reason. Go ping the shit out of {get_cal_user(interaction).mention}.' + ) + return + + await refresh_sheet(team, self.bot) + team = db_get('teams', object_id=team['id']) + await interaction.channel.send(f'Your Wallet: {team["wallet"]}₼') + + @app_commands.command(name='newteam', description='Get your fresh team for a new season') + @app_commands.checks.has_any_role(PD_PLAYERS) + @app_commands.describe( + gm_name='The fictional name of your team\'s GM', + team_abbrev='2, 3, or 4 character abbreviation (e.g. WV, ATL, MAD)', + team_full_name='City/location and name (e.g. Baltimore Orioles)', + team_short_name='Name of team (e.g. Yankees)', + mlb_anchor_team='2 or 3 character abbreviation of your anchor MLB team (e.g. NYM, MKE)', + team_logo_url='[Optional] URL ending in .png or .jpg for your team logo', + color='[Optional] Hex color code to highlight your team' + ) + async def new_team_slash( + self, interaction: discord.Interaction, gm_name: str, team_abbrev: str, team_full_name: str, + team_short_name: str, mlb_anchor_team: str, team_logo_url: str = None, color: str = None): + owner_team = get_team_by_owner(interaction.user.id) + current = db_get('current') + + # Check for existing team + if owner_team and not os.environ.get('TESTING'): + await interaction.response.send_message( + f'Whoa there, bucko. I already have you down as GM of the {owner_team["sname"]}.' + ) + return + + # Check for duplicate team data + dupes = db_get('teams', params=[('abbrev', team_abbrev)]) + if dupes['count']: + await interaction.response.send_message( + f'Yikes! {team_abbrev.upper()} is a popular abbreviation - it\'s already in use by the ' + f'{dupes["teams"][0]["sname"]}. No worries, though, you can run the `/newteam` command again to get ' + f'started!' + ) + return + + # Check for duplicate team data + dupes = db_get('teams', params=[('lname', team_full_name)]) + if dupes['count']: + await interaction.response.send_message( + f'Yikes! {team_full_name.title()} is a popular name - it\'s already in use by ' + f'{dupes["teams"][0]["abbrev"]}. No worries, though, you can run the `/newteam` command again to get ' + f'started!' + ) + return + + # Get personal bot channel + hello_channel = discord.utils.get( + interaction.guild.text_channels, + name=f'hello-{interaction.user.name.lower()}' + ) + if hello_channel: + op_ch = hello_channel + else: + op_ch = await helpers.create_channel( + interaction, + channel_name=f'hello-{interaction.user.name}', + category_name='Paper Dynasty Team', + everyone_read=False, + read_send_members=[interaction.user] + ) + + await share_channel(op_ch, interaction.guild.me) + await share_channel(op_ch, interaction.user) + try: + poke_role = get_role(interaction, 'Pokétwo') + await share_channel(op_ch, poke_role, read_only=True) + except Exception as e: + logging.error(f'unable to share sheet with Poketwo') + + await interaction.response.send_message( + f'Let\'s head down to your private channel: {op_ch.mention}', + ephemeral=True + ) + await op_ch.send(f'Hey there, {interaction.user.mention}! I am Paper Domo - welcome to season ' + f'{current["season"]} of Paper Dynasty! We\'ve got a lot of special updates in store for this ' + f'season including live cards, throwback cards, and special events.') + + # Confirm user is happy with branding + embed = get_team_embed( + f'Branding Check', + { + 'logo': team_logo_url if team_logo_url else None, + 'color': color if color else 'a6ce39', + 'season': 4 + } + ) + embed.add_field(name='GM Name', value=gm_name, inline=False) + embed.add_field(name='Full Team Name', value=team_full_name) + embed.add_field(name='Short Team Name', value=team_short_name) + embed.add_field(name='Team Abbrev', value=team_abbrev.upper()) + + view = Confirm(responders=[interaction.user]) + question = await op_ch.send('Are you happy with this branding? Don\'t worry - you can update it later!', + embed=embed, view=view) + await view.wait() + + if not view.value: + await question.edit( + content='~~Are you happy with this branding?~~\n\nI gotta go, but when you\'re ready to start again ' + 'run the `/newteam` command again and we can get rolling! Hint: you can copy and paste the ' + 'command from last time and make edits.', + view=None + ) + return + + await question.edit( + content='Looking good, champ in the making! Let\'s get you your starter team!', + view=None + ) + + team_choice = None + if mlb_anchor_team.title() in ALL_MLB_TEAMS.keys(): + team_choice = mlb_anchor_team.title() + else: + for x in ALL_MLB_TEAMS: + if mlb_anchor_team.upper() in ALL_MLB_TEAMS[x] or mlb_anchor_team.title() in ALL_MLB_TEAMS[x]: + team_choice = x + break + + team_string = mlb_anchor_team + logging.debug(f'team_string: {team_string} / team_choice: {team_choice}') + if not team_choice: + # Get MLB anchor team + while True: + prompt = f'I don\'t recognize **{team_string}**. I try to recognize abbreviations (BAL), ' \ + f'short names (Orioles), and long names ("Baltimore Orioles").\n\nWhat MLB club would you ' \ + f'like to use as your anchor team?' + this_q = Question(self.bot, op_ch, prompt, 'text', 120) + team_string = await this_q.ask([interaction.user]) + + if not team_string: + await op_ch.send( + f'Tell you hwat. You think on it and come back I gotta go, but when you\'re ready to start again ' + 'run the `/newteam` command again and we can get rolling! Hint: you can copy and paste the ' + 'command from last time and make edits.' + ) + return + + if team_string.title() in ALL_MLB_TEAMS.keys(): + team_choice = team_string.title() + break + else: + match = False + for x in ALL_MLB_TEAMS: + if team_string.upper() in ALL_MLB_TEAMS[x] or team_string.title() in ALL_MLB_TEAMS[x]: + team_choice = x + match = True + break + if not match: + await op_ch.send(f'Got it!') + + team = db_post('teams', payload={ + 'abbrev': team_abbrev.upper(), + 'sname': team_short_name, + 'lname': team_full_name, + 'gmid': interaction.user.id, + 'gmname': gm_name, + 'gsheet': 'None', + 'season': current['season'], + 'wallet': 100, + 'color': color if color else 'a6ce39', + 'logo': team_logo_url if team_logo_url else None + }) + + if not team: + await op_ch.send(f'Frick. {get_cal_user(interaction).mention}, can you help? I can\'t find this team.') + return + + t_role = await get_or_create_role(interaction, f'{team_abbrev} - {team_full_name}') + await interaction.user.add_roles(t_role) + + anchor_players = [] + anchor_all_stars = db_get( + 'players/random', + params=[ + ('min_rarity', 3), ('max_rarity', 3), ('franchise', team_choice), ('pos_exclude', 'RP'), ('limit', 1) + ] + ) + anchor_starters = db_get( + 'players/random', + params=[ + ('min_rarity', 2), ('max_rarity', 2), ('franchise', team_choice), ('pos_exclude', 'RP'), ('limit', 2) + ] + ) + if not anchor_all_stars: + await op_ch.send(f'I am so sorry, but the {team_choice} do not have an All-Star to ' + f'provide as your anchor player. Let\'s start this process over - will you please ' + f'run the `/newteam` command again with a new MLB club?\nHint: you can copy and paste the ' + 'command from last time and make edits.') + db_delete('teams', object_id=team['id']) + return + if not anchor_starters or anchor_starters['count'] <= 1: + await op_ch.send(f'I am so sorry, but the {team_choice} do not have two Starters to ' + f'provide as your anchor players. Let\'s start this process over - will you please ' + f'run the `/newteam` command again with a new MLB club?\nHint: you can copy and paste the ' + 'command from last time and make edits.') + db_delete('teams', object_id=team['id']) + return + + anchor_players.append(anchor_all_stars['players'][0]) + anchor_players.append(anchor_starters['players'][0]) + anchor_players.append(anchor_starters['players'][1]) + + this_pack = db_post('packs/one', + payload={'team_id': team['id'], 'pack_type_id': 2, + 'open_time': datetime.datetime.timestamp(datetime.datetime.now())*1000}) - # Get anchor players roster_counts = { 'SP': 0, 'RP': 0, @@ -291,678 +1921,653 @@ class Economy(commands.Cog): 'CF': 0, 'RF': 0, 'DH': 0, + 'All-Star': 0, + 'Starter': 0, 'Reserve': 0, 'Replacement': 0, } - roster_list = [] - anchor_starters = '' - if team_allstars.count() > 0: - # Get anchor team All-Star - anchor_allstar = None - for x in team_allstars: - anchor_allstar = x - roster_list.append(x) - roster_counts[x.primary] += 1 - break - print(f'Anchor: {anchor_allstar}') if logging else True - # Get two starters - starter_pool = Player.select().join(Rarity)\ - .where(Player.rarity == Player.rarity == Rarity.get(Rarity.name == 'Starter'))\ - .order_by(fn.Random()).limit(8) - count = 0 - for x in starter_pool: - if roster_counts[x.primary] == 0: - roster_list.append(x) - roster_counts[x.primary] += 1 - anchor_starters += f'{x}\n' - print(f'Anchor starter: {x}') if logging else True - count += 1 - if count > 1: - break - elif team_starters.count() > 1: - # Get two starters - print(f'anchor pool: {team_starters}') if logging else True - for x in team_starters: - if roster_counts[x.primary] == 0: - roster_list.append(x) - roster_counts[x.primary] += 1 - print(f'Anchor starter: {x}') if logging else True - anchor_starters += f'{x}\n' - if len(roster_list) >= 2: - break + def update_roster_counts(players: list): + for pl in players: + roster_counts[pl['rarity']['name']] += 1 + for x in get_all_pos(pl): + roster_counts[x] += 1 + logging.warning(f'Roster counts for {team["sname"]}: {roster_counts}') - # Get anchor All-Star - anchor_allstar = Player.select().join(Rarity)\ - .where((Player.rarity == Player.rarity == Rarity.get(Rarity.name == 'All-Star')) & - (Player.primary != "RP") & - (Player.primary != "CP"))\ - .order_by(fn.Random()).limit(5) - for x in anchor_allstar: - if roster_counts[x.primary] == 0: - anchor_allstar = x - roster_list.append(x) - roster_counts[x.primary] += 1 - print(f'Anchor All-Star: {x}') if logging else True - break - else: - await ctx.send(f'It pains me to say this, but the {team_choice} don\'t have an All-Star nor do they ' - f'have two starters to seed your team. Please select another team.') - return - print(f'Post anchor roster comp: {roster_counts}') if logging else True + # Add anchor position coverage + update_roster_counts(anchor_players) + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in anchor_players] + }, timeout=10) - embed = discord.Embed(title=f'{team_choice} Anchors') - embed.add_field(name='All-Star', value=f'{anchor_allstar}', inline=False) - embed.add_field(name='Starters', - value=anchor_starters, inline=False) - embed.set_image(url=f'{anchor_allstar.url}') - await op_ch.send(content=f'{ctx.author.mention}', embed=embed) + # Get 10 pitchers to seed team + five_sps = db_get('players/random', params=[('pos_include', 'SP'), ('max_rarity', 1), ('limit', 5)]) + five_rps = db_get('players/random', params=[('pos_include', 'RP'), ('max_rarity', 1), ('limit', 5)]) + team_sp = [x for x in five_sps['players']] + team_rp = [x for x in five_rps['players']] + update_roster_counts([*team_sp, *team_rp]) + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in [*team_sp, *team_rp]] + }, timeout=10) - # Get 5 SP - random_sp = (Player - .select() - .join(Rarity) - .where((Player.primary == 'SP') & - ((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | - (Player.rarity == Rarity.get(Rarity.name == 'Reserve')))) - .order_by(fn.Random()) - .limit(15)) - count = 0 - for x in random_sp: - if x not in roster_list: - roster_list.append(x) - roster_counts[x.primary] += 1 - roster_counts[x.rarity.name] += 1 - count += 1 - if count > 5: - break + # TODO: track reserve vs replacement and if rep < res, get rep, else get res + # Collect infielders + team_infielders = [] + for pos in ['C', '1B', '2B', '3B', 'SS']: + max_rar = 1 + if roster_counts['Replacement'] < roster_counts['Reserve']: + max_rar = 0 - # Get 5 RP - random_rp = (Player - .select() - .join(Rarity) - .where(((Player.primary == 'RP') | (Player.primary == 'CP')) & - ((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | - (Player.rarity == Rarity.get(Rarity.name == 'Reserve')))) - .order_by(fn.Random()) - .limit(15)) - count = 0 - for x in random_rp: - if x not in roster_list: - roster_list.append(x) - roster_counts[x.primary] += 1 - roster_counts[x.rarity.name] += 1 - count += 1 - if count > 5: - break + r_draw = db_get( + 'players/random', params=[('pos_include', pos), ('max_rarity', max_rar), ('limit', 2)], none_okay=False + ) + team_infielders.extend(r_draw['players']) - # Ensure all positions have two players - for pos in roster_counts.keys(): - print(f'Starting {pos}') if logging else True - if pos == 'DH' or pos == 'Reserve' or pos == 'Replacement': - pass - else: - print(f'{pos} Count: {roster_counts[pos]}') if logging else True - while roster_counts[pos] < 2: - random_draw = (Player - .select() - .where((Player.primary == pos) & - ((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | - (Player.rarity == Rarity.get(Rarity.name == 'Reserve')))) - .order_by(fn.Random()) - .limit(15)) - print(f'Replacement count: {roster_counts["Replacement"]}') if logging else True - if roster_counts['Replacement'] >= 15: - for x in random_draw: - if x.rarity.name == 'Reserve': - roster_list.append(x) - roster_counts[x.rarity.name] += 1 - roster_counts[pos] += 1 - break - elif roster_counts['Reserve'] >= 12: - for x in random_draw: - if x.rarity.name == 'Replacement': - roster_list.append(x) - roster_counts[x.rarity.name] += 1 - roster_counts[pos] += 1 - break - elif roster_counts['Reserve'] < 12 and roster_counts['Replacement'] < 15: - roster_list.append(random_draw[0]) - roster_counts[random_draw[0].rarity.name] += 1 - roster_counts[pos] += 1 - print(f'Adding {pos}: {roster_list[-1]}') if logging else True - print(f'{pos} Count: {roster_counts[pos]}') if logging else True + update_roster_counts(team_infielders) + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in team_infielders] + }, timeout=10) - print(f'roster_count: {roster_counts}') if logging else True - if roster_counts['Reserve'] + roster_counts['Replacement'] < 27: - random_draw = (Player - .select() - .where((Player.rarity == Rarity.get(Rarity.name == 'Replacement')) | - (Player.rarity == Rarity.get(Rarity.name == 'Reserve'))) - .order_by(fn.Random()) - .limit(15)) - for x in random_draw: - if x not in roster_list: - print(f'Trying to add: {x}') - roster_list.append(x) - roster_counts[x.rarity.name] += 1 - roster_counts[x.primary] += 1 - if roster_counts['Reserve'] + roster_counts['Replacement'] >= 27: - break + # Collect outfielders + team_outfielders = [] + for pos in ['LF', 'CF', 'RF']: + max_rar = 1 + if roster_counts['Replacement'] < roster_counts['Reserve']: + max_rar = 0 - team_cards = [] - for x in roster_list: - team_cards.append({ - 'player': x, - 'team': owner_team, - }) + r_draw = db_get( + 'players/random', params=[('pos_include', pos), ('max_rarity', max_rar), ('limit', 2)], none_okay=False + ) + team_outfielders.extend(r_draw['players']) + + update_roster_counts(team_outfielders) + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in team_outfielders] + }, timeout=10) - await self.helpers.pause_then_type( - op_ch, 'I am going to go create your roster sheet. This will take a few seconds.') - # Create team and starter cards async with op_ch.typing(): - sheets = pygsheets.authorize() - team_roster_sheet = sheets.drive.copy_file( - '1Z-g3M_rSnRdr-xjXP0YqxMhnOIGsaUK_2v5M3fUODH8', - f'{owner_team.lname} Roster Sheet', - '163QSgtsduyFf67A0IWBFvAwYIpHMz_cD') - - owner_team.gsheet = team_roster_sheet['id'] - owner_team.save() - with db.atomic(): - try: - for batch in chunked(team_cards, 20): - Card.insert_many(batch).execute() - except Exception as e: - error = f'**INSERT ERROR (cards):** {type(e).__name__} - {e}' - print(error) - await ctx.send(f'Jinkies, guys. I\'ve got an ugly error:\n\n{e}') - return - - # Create default roster - try: - print(f'team_cards[0]: {team_cards[0]}') - print(f'card 5: {team_cards[5]} / season: {Current.get_by_id(1).season} / team: {owner_team}') - current_roster = self.helpers.get_sorted_collection(owner_team) - roster_query = Roster.replace( - team=owner_team, - season=Current.get_by_id(1).season, - card1=current_roster[0], - card2=current_roster[1], - card3=current_roster[2], - card4=current_roster[3], - card5=current_roster[4], - card6=current_roster[5], - card7=current_roster[6], - card8=current_roster[7], - card9=current_roster[8], - card10=current_roster[9], - card11=current_roster[10], - card12=current_roster[11], - card13=current_roster[12], - card14=current_roster[13], - card15=current_roster[14], - card16=current_roster[15], - card17=current_roster[16], - card18=current_roster[17], - card19=current_roster[18], - card20=current_roster[19], - card21=current_roster[20], - card22=current_roster[21], - card23=current_roster[22], - card24=current_roster[23], - card25=current_roster[24], - card26=current_roster[25], - ) - print(f'roster_query: {roster_query}') - roster_query.execute() - except Exception as e: - error = f'**INSERT ERROR (rosters):** {type(e).__name__} - {e}' - print(error) - await op_ch.send(f'Jinkies, guys. I\'ve got an ugly error:\n\n{e}') - return - - str_sp = '' - str_rp = '' - str_if = '' - str_of = '' - for x in roster_list: - if x.primary == 'SP': - str_sp += f'{x.cardset} {x.name} ({x.primary})\n' - if x.primary == 'RP' or x.primary == 'CP': - str_rp += f'{x.cardset} {x.name} ({x.primary})\n' - if x.primary == 'C' or x.primary == '1B' or x.primary == '2B' or x.primary == '3B' or x.primary == 'SS': - str_if += f'{x.cardset} {x.name} ({x.primary})\n' - if x.primary == 'LF' or x.primary == 'CF' or x.primary == 'RF': - str_of += f'{x.cardset} {x.name} ({x.primary})\n' - - embed = discord.Embed(title=f'{owner_team.lname} Starting Roster') - embed.add_field(name='Starting Pitchers', value=str_sp, inline=False) - embed.add_field(name='Relief Pitchers', value=str_rp, inline=False) - embed.add_field(name='Infielders', value=str_if, inline=False) - embed.add_field(name='Outfielders', value=str_of, inline=False) - embed.add_field(name='Roster Sheet', value=self.helpers.get_roster_sheet(owner_team)) - await op_ch.send(content=None, embed=embed) - - print(f'roster_count: {roster_counts}') if logging else True - - await self.helpers.pause_then_type(op_ch, 'This is your personal bot channel.') - await self.write_collection(op_ch, owner_team, 0) - - await asyncio.sleep(3600) - await op_ch.delete() - - @commands.command(name='deleteteam', help='IN TESTING, remove your starter team') - @commands.has_any_role('Paper Dynasty Players') - async def delete_team(self, ctx): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') - return - - team = Team.get_by_owner(ctx.author.id) - if not team: - await ctx.send('Now you wait just a second. You don\'t have a team!') - return - - await ctx.send('Are you sure you want to delete your team and full collection?\n\nType \'YES\' to confirm.') - - def confirmation_check(mes): - return mes.author == ctx.author and mes.content.upper() == 'YES' - - try: - confirm_resp = await self.bot.wait_for('message', check=confirmation_check, timeout=10.0) - all_packs = Pack.delete().where(Pack.team == team) - all_cards = Card.delete().where(Card.team == team) - - all_packs.execute() - all_cards.execute() - team.delete_instance() - - await self.helpers.pause_then_type(ctx, 'All done. It\'s like the you were never here.') - except TimeoutError: - return - - @commands.command(name='update', help='Pull team from Sheets for cuts and roster update') - @commands.has_any_role('Paper Dynasty Players') - async def update_team(self, ctx): - if not await self.helpers.check_if_pdhole(ctx): - return - - team = Team.get_by_owner(ctx.author.id) - if not team: - await ctx.send('Now you wait just a second. You don\'t have a team!') - return - - await self.get_collection(ctx, team) - - @staticmethod - def give_pack(team: Team, num=1): - for x in range(num): - new_pack = Pack(team=team) - new_pack.save() - print(f'starting weeklypacks: {team.weeklypacks}') - team.weeklypacks += 1 - team.save() - print(f'ending weeklypacks: {team.weeklypacks}') - return Pack.select().where((Pack.team == team) & (Pack.card1.is_null())).count() - - async def present_player(self, ctx, channel, card): - mvp_flag = False - if card.player.rarity.name == 'Starter': - await channel.send(f'Next up from the {card.player.cardset} set...') - await asyncio.sleep(2) - elif card.player.rarity.name == 'All-Star': - await channel.send(f'Now we\'ve got a {card.player.primary} from the {card.player.cardset} set...') - await asyncio.sleep(2) - await channel.send(f'He played for the {card.player.mlbclub}...') - await asyncio.sleep(2) - elif card.player.rarity.name == 'MVP': - mvp_flag = True - await channel.send(f'@here \n' - f'Oh wow...this card comes from the {card.player.cardset} set.') - await asyncio.sleep(2) - await channel.send(f'This guy played for the {card.player.mlbclub} - maybe you\'ve heard of him...') - await asyncio.sleep(2) - await channel.send(f'You\'ve got MVP {card.player.primary}...') - await asyncio.sleep(2) - - await channel.send(content=None, embed=await self.helpers.get_player_embed(card.player)) - - if mvp_flag: - await self.helpers.send_to_news( - ctx, - f'The **{card.team.lname}** just pulled {card.player.cardset} MVP **{card.player.name}** ' - f'of the {card.player.mlbclub}!', - embed=None + done_anc = await display_cards( + [{'player': x, 'team': team} for x in anchor_players], team, op_ch, interaction.user, self.bot, + cust_message=f'Let\'s take a look at your three {team_choice} anchor players.\n' + f'Press `Close Pack` to continue.', + add_roster=False ) - async def expand_pack(self, ctx, team: Team, pack: Pack): - pack_pos = [] - card_pack = [] - c1_roll = random.randint(1, 100) - c2_roll = random.randint(1, 100) - c3_roll = random.randint(1, 100) - c4_roll = random.randint(1, 100) - c5_roll = random.randint(1, 100) - c6_roll = random.randint(1, 100) + error_text = f'Yikes - I can\'t display the rest of your team. {get_cal_user(interaction).mention} plz halp' + if not done_anc: + await op_ch.send(error_text) - # Cards 1 - 4: 50% Reserve / 50% Replacement - # Card 5: 2% All - Star / 28% Starter / 70% Reserve - # Card 6: 2% MVP / 13% All - Star / 85% Starter + async with op_ch.typing(): + done_sp = await display_cards( + [{'player': x, 'team': team} for x in team_sp], team, op_ch, interaction.user, self.bot, + cust_message=f'Here are your starting pitchers.\n' + f'Press `Close Pack` to continue.', + add_roster=False + ) - # Grab card 1 - try: - if c1_roll > 50: - rarity = Rarity.get(Rarity.name == 'Reserve') - else: - rarity = Rarity.get(Rarity.name == 'Replacement') - chosen = Player.get_random_or_none(rarity) - if chosen: - thiscard = Card(player=chosen, team=team) - card_pack.append(thiscard) - if chosen.primary not in pack_pos: - pack_pos.append(chosen.primary) - else: - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return - except Exception as e: - print(f'**Error** (create_pack card1): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + if not done_sp: + await op_ch.send(error_text) - # Grab card 2 - try: - if c2_roll > 50: - rarity = Rarity.get(Rarity.name == 'Reserve') - else: - rarity = Rarity.get(Rarity.name == 'Replacement') - chosen = Player.get_random_or_none(rarity) - if chosen: - thiscard = Card(player=chosen, team=team) - card_pack.append(thiscard) - if chosen.primary not in pack_pos: - pack_pos.append(chosen.primary) - else: - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return - except Exception as e: - print(f'**Error** (create_pack card2): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + async with op_ch.typing(): + done_rp = await display_cards( + [{'player': x, 'team': team} for x in team_rp], team, op_ch, interaction.user, self.bot, + cust_message=f'And now for your bullpen.\n' + f'Press `Close Pack` to continue.', + add_roster=False + ) - # Grab card 3 - try: - if c3_roll > 50: - rarity = Rarity.get(Rarity.name == 'Reserve') - else: - rarity = Rarity.get(Rarity.name == 'Replacement') - chosen = Player.get_random_or_none(rarity) - if chosen: - thiscard = Card(player=chosen, team=team) - card_pack.append(thiscard) - if chosen.primary not in pack_pos: - pack_pos.append(chosen.primary) - else: - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return - except Exception as e: - print(f'**Error** (create_pack card3): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + if not done_rp: + await op_ch.send(error_text) - # Grab card 4 - try: - if c4_roll > 50: - rarity = Rarity.get(Rarity.name == 'Reserve') - else: - rarity = Rarity.get(Rarity.name == 'Replacement') - chosen = Player.get_random_or_none(rarity) - if chosen: - thiscard = Card(player=chosen, team=team) - card_pack.append(thiscard) - if chosen.primary not in pack_pos: - pack_pos.append(chosen.primary) - else: - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return - except Exception as e: - print(f'**Error** (create_pack card4): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + async with op_ch.typing(): + done_inf = await display_cards( + [{'player': x, 'team': team} for x in team_infielders], team, op_ch, interaction.user, self.bot, + cust_message=f'Next let\'s take a look at your infielders.\n' + f'Press `Close Pack` to continue.', + add_roster=False + ) - # Grab card 5 - try: - if c5_roll > 98: - rarity = Rarity.get(Rarity.name == 'All-Star') - elif c5_roll > 70: - rarity = Rarity.get(Rarity.name == 'Starter') - else: - rarity = Rarity.get(Rarity.name == 'Reserve') - chosen = Player.get_random_or_none(rarity) - if chosen: - thiscard = Card(player=chosen, team=team) - card_pack.append(thiscard) - if chosen.primary not in pack_pos: - pack_pos.append(chosen.primary) - else: - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return - except Exception as e: - print(f'**Error** (create_pack card5): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + if not done_inf: + await op_ch.send(error_text) - # Grab card 6 - try: - if c6_roll > 98: - rarity = Rarity.get(Rarity.name == 'MVP') - elif c6_roll > 85: - rarity = Rarity.get(Rarity.name == 'All-Star') - else: - rarity = Rarity.get(Rarity.name == 'Starter') - chosen = Player.get_random_or_none(rarity) - if chosen: - thiscard = Card(player=chosen, team=team) - card_pack.append(thiscard) - if chosen.primary not in pack_pos: - pack_pos.append(chosen.primary) - else: - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return - except Exception as e: - print(f'**Error** (create_pack card6): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + async with op_ch.typing(): + done_out = await display_cards( + [{'player': x, 'team': team} for x in team_outfielders], team, op_ch, interaction.user, self.bot, + cust_message=f'Now let\'s take a look at your outfielders.\n' + f'Press `Close Pack` to continue.', + add_roster=False + ) - # Save cards - with db.atomic(): - try: - for c in card_pack: - c.save() - except Exception as e: - print(f'**Error** (create_pack insert_cards): {e}') - await ctx.send(f'Oh, jeez. I made an oopsie with this pack. Please go find me an adult. Now! Please!?') - return False + if not done_out: + await op_ch.send(error_text) - # Save pack - pack.card1 = card_pack[0] - pack.card2 = card_pack[1] - pack.card3 = card_pack[2] - pack.card4 = card_pack[3] - pack.card5 = card_pack[4] - pack.card6 = card_pack[5] - pack.save() - - return True - - async def get_collection(self, ctx, team): - await self.helpers.pause_then_type(ctx, 'Let me go check your roster sheet...') - - # Get data from Sheets - async with ctx.typing(): - sheets = pygsheets.authorize() - try: - roster_sheet = sheets.open_by_key(team.gsheet).worksheet_by_title('Full Collection') - raw_data = roster_sheet.get_values('A2', 'B1000') - except Exception as e: - print(f'**ERROR**: {e}') - await ctx.send(f'Yikes. I need a grown-up to read this. I don\'t know what it means:\n\n{e}') - return - else: - await ctx.send(f'Noice - got it! Give me just a sec to go through it...') - - current_collection = self.helpers.get_sorted_collection(team) - roster_length_start = len(current_collection) - rostered_list = [] - cut_list = [] - errors = [] - error_helper = '' - - # Parse raw_data - try: - count = 0 - for line in raw_data: - print(f'Counter: {count}') - print(f'Line: {line}') - error_helper = f'row {count + 2}' - if line[0] != '' and line[1] != '': - errors.append(f'You have {current_collection[count].player.name} set to both cut and active ' - f'- can only be one or the other.') - elif line[0] != '': - cut_list.append(current_collection[count]) - elif line[1] != '': - rostered_list.append(current_collection[count]) - count += 1 - except Exception as e: - print(f'**ERROR**: {e}') - await self.helpers.pause_then_type( - ctx, - f'Shart. I had an accident. I hate Sheets. Please go get me an adult. I don\'t ' - f'know what this means while I was reading {error_helper}:\n\n{e}') - return - - if self.logging: - print(f'Cut List: {cut_list}') - print(f'Roster List: {rostered_list}') - print(f'Errors: {errors}') - - # Check for 26 players on the roster - if len(rostered_list) != 26: - await self.helpers.pause_then_type( - ctx, - f'Will you go double check your roster? You should have 26 rostered players, ' - f'but I see {len(rostered_list)}.') - return - - # Confirm players to cut with player - if len(cut_list) > 0: - cut_message = f'Looks like you want to trade-in the following cards:\n\n' - tradeins = {'MVP': 0, 'All-Star': 0, 'Starter': 0, 'Reserve': 0, 'Replacement': 0} - for x in cut_list: - if x.player.rarity.name == 'MVP': - tradeins['MVP'] += 1 - elif x.player.rarity.name == 'All-Star': - tradeins['All-Star'] += 1 - elif x.player.rarity.name == 'Starter': - tradeins['Starter'] += 1 - elif x.player.rarity.name == 'Reserve': - tradeins['Reserve'] += 1 - else: - tradeins['Replacement'] += 1 - tradein_value = tradeins["MVP"] * 500 + tradeins["All-Star"] * 50 + tradeins["Starter"] * 5 +\ - tradeins["Reserve"] * 2 + tradeins["Replacement"] - cut_message += f'- {tradeins["MVP"]} MVPs: {tradeins["MVP"] * 500} points\n' \ - f'- {tradeins["All-Star"]} All-Stars: {tradeins["All-Star"] * 50} points\n' \ - f'- {tradeins["Starter"]} Starters: {tradeins["Starter"] * 5} points\n' \ - f'- {tradeins["Reserve"]} Reserves: {tradeins["Reserve"] * 2} points\n' \ - f'- {tradeins["Replacement"]} Replacements: {tradeins["Replacement"]} points\n'\ - f'This is worth {tradein_value} points / {int(tradein_value/50)} packs.\n\n' \ - f'If this is correct, please type \'YES\' to confirm.' - await self.helpers.pause_then_type(ctx, cut_message) - - def check(mes): - return mes.author == ctx.author and mes.content.lower() == 'yes' - try: - resp = await self.bot.wait_for('message', check=check, timeout=15.0) - await self.helpers.pause_then_type(ctx, 'They gone!') - self.give_pack(team, int(tradein_value/50)) - except Exception as e: - print(f'**ERROR**: {e}') - await self.helpers.pause_then_type(ctx, - 'I will hold off for now. Let me know if you want to try again.') - return - - # Delete cut list cards - for x in cut_list: - x.team = None - x.save() - - # Set rostered players - roster_query = Roster.replace( - team=team, - season=Current.get_by_id(1).season, - card1=rostered_list[0], - card2=rostered_list[1], - card3=rostered_list[2], - card4=rostered_list[3], - card5=rostered_list[4], - card6=rostered_list[5], - card7=rostered_list[6], - card8=rostered_list[7], - card9=rostered_list[8], - card10=rostered_list[9], - card11=rostered_list[10], - card12=rostered_list[11], - card13=rostered_list[12], - card14=rostered_list[13], - card15=rostered_list[14], - card16=rostered_list[15], - card17=rostered_list[16], - card18=rostered_list[17], - card19=rostered_list[18], - card20=rostered_list[19], - card21=rostered_list[20], - card22=rostered_list[21], - card23=rostered_list[22], - card24=rostered_list[23], - card25=rostered_list[24], - card26=rostered_list[25], + give_packs(team, 1) + await op_ch.send( + f'To get you started, I\'ve spotted you 100₼ and a pack of cards. You can rip that with the ' + f'`/open` command once your google sheet is set up!' ) - roster_query.execute() - await ctx.send('Okay, I will note these changes on your Sheet now. Please hold...') - async with ctx.typing(): - if await self.write_collection(ctx, team, extra=len(cut_list)): - await self.helpers.pause_then_type(ctx, 'There we go - your roster is up to date!') + await op_ch.send( + f'{t_role.mention}\n\n' + f'There\'s your roster! We have one more step and you will be ready to play.\n\n{SHEET_SHARE_STEPS}\n\n' + f'{get_roster_sheet({"gsheet": current["gsheet_template"]}, allow_embed=True)}' + ) - async def write_collection(self, ctx, team, extra): - async with ctx.typing(): - sheets = pygsheets.authorize() - roster_sheet = sheets.open_by_key(team.gsheet).worksheet_by_title('Full Collection') - collection = self.helpers.get_sorted_collection(team) - rostered_cards = Roster.get_cards(team=team) + new_team_embed = team_summary_embed(team, interaction, include_roster=False) + await send_to_channel( + self.bot, "pd-network-news", content='A new challenger approaches...', embed=new_team_embed + ) - write_data = [] - for x in collection: - write_data.append(['', f'{"x" if x in rostered_cards else ""}', - x.player.rarity.name, x.player.name, x.player.mlbclub, x.player.cardset, - x.player.wara, x.player.primary, x.player.url, x.player.url2, x.player.pos1, - x.player.pos2, x.player.pos3, x.player.pos4, x.player.pos5, x.player.pos6, - x.player.pos7, x.player.pos8]) - for x in range(extra): - write_data.append(['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']) - try: - roster_sheet.update_values( - crange='A2', values=write_data + @commands.command(name='mlbteam', help='Mod: Load MLB team data') + @commands.is_owner() + async def mlb_team_command( + self, ctx: commands.Context, abbrev: str, sname: str, lname: str, gmid: int, gmname: str, gsheet: str, + logo: str, color: str, ranking: int): + # Check for duplicate team data + dupes = db_get('teams', params=[('abbrev', abbrev)]) + if dupes['count']: + await ctx.send( + f'Yikes! {abbrev.upper()} is a popular abbreviation - it\'s already in use by the ' + f'{dupes["teams"][0]["sname"]}. No worries, though, you can run the `/newteam` command again to get ' + f'started!' + ) + return + + # Check for duplicate team data + dupes = db_get('teams', params=[('lname', lname)]) + if dupes['count']: + await ctx.send( + f'Yikes! {lname.title()} is a popular name - it\'s already in use by ' + f'{dupes["teams"][0]["abbrev"]}. No worries, though, you can run the `/newteam` command again to get ' + f'started!' + ) + return + + current = db_get('current') + + team = db_post('teams', payload={ + 'abbrev': abbrev.upper(), + 'sname': sname, + 'lname': lname, + 'gmid': gmid, + 'gmname': gmname, + 'gsheet': gsheet, + 'season': current['season'], + 'wallet': 100, + 'ranking': ranking, + 'color': color if color else 'a6ce39', + 'logo': logo if logo else None, + 'is_ai': True + }) + + p_query = db_get('players', params=[('franchise', lname)]) + + this_pack = db_post( + 'packs/one', + payload={'team_id': team['id'], 'pack_type_id': 2, + 'open_time': datetime.datetime.timestamp(datetime.datetime.now())*1000} + ) + + team_players = p_query['players'] + p_query['players'] + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in team_players] + }, timeout=10) + + embed = get_team_embed(f'{team["lname"]}', team) + await ctx.send(content=None, embed=embed) + + @commands.hybrid_command(name='mlb-update', help='Distribute MLB cards to AI teams') + @commands.is_owner() + async def mlb_update_command(self, ctx: commands.Context): + ai_teams = db_get('teams', params=[('is_ai', True)]) + if ai_teams['count'] == 0: + await ctx.send(f'I could not find any AI teams.') + return + + total_cards = 0 + total_teams = 0 + for team in ai_teams['teams']: + all_players = db_get('players', params=[('franchise', team['lname'])]) + + new_players = [] + if all_players: + for player in all_players['players']: + owned_by_team_ids = [entry['team'] for entry in player['paperdex']['paperdex']] + + if team['id'] not in owned_by_team_ids: + new_players.append(player) + + if new_players: + await ctx.send(f'Posting {len(new_players)} new cards for {team["gmname"]}\'s {team["sname"]}...') + total_cards += len(new_players) + total_teams += 1 + this_pack = db_post( + 'packs/one', + payload={'team_id': team['id'], 'pack_type_id': 2, + 'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000} ) - except Exception as e: - print(f'**ERROR**: {e}') - await ctx.send('Barf. I tried to write your roster to the sheet and it didn\'t take. Will you go ' - 'get me some help, please?') - return False - return True + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in + new_players + ]}, timeout=10) + await refresh_sheet(team, self.bot) + + await ctx.send(f'All done! I added {total_cards} across {total_teams} teams.') + + # @app_commands.command(name='sharepd', description='Have your Paper Dynasty team sheet shared with you') + # @app_commands.checks.has_any_role(PD_PLAYERS) + # async def share_sheet_slash(self, interaction: discord.Interaction): + # team = get_team_by_owner(interaction.user.id) + # if not team: + # await interaction.user.send_message( + # f'You...do I know you? I don\'t think I do. Go on and git.', + # ephemeral=True + # ) + # return + # + # await interaction.response.send_modal( + # Email(title='Google Sheets Email Address', timeout=30, custom_id=team['id']) + # ) + + @commands.hybrid_command(name='newsheet', help='Link a new team sheet with your team') + @commands.has_any_role(PD_PLAYERS) + async def share_sheet_command( + self, ctx, google_sheet_url: str, team_abbrev: Optional[str], copy_rosters: Optional[bool] = True): + team = get_team_by_owner(ctx.author.id) + if not team: + await ctx.send(f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!') + return + + if team_abbrev and team_abbrev != team['abbrev']: + if ctx.author.id != 258104532423147520: + await ctx.send(f'You can only update the team sheet for your own team, you goober.') + return + else: + team = get_team_by_abbrev(team_abbrev) + + current = db_get('current') + if current['gsheet_template'] in google_sheet_url: + await ctx.send(f'Ope, looks like that is the template sheet. Would you please make a copy and then share?') + return + + sheets = get_sheets(self.bot) + response = await ctx.send(f'I\'ll go grab that sheet...') + try: + new_sheet = sheets.open_by_url(google_sheet_url) + except Exception as e: + logging.error(f'Error accessing {team["abbrev"]} sheet: {e}') + current = db_get('current') + await ctx.send(f'I wasn\'t able to access that sheet. Did you remember to share it with my PD email?' + f'\n\nHere\'s a quick refresher:\n{SHEET_SHARE_STEPS}\n\n' + f'{get_roster_sheet({"gsheet": current["gsheet_template"]}, allow_embed=True)}') + return + + team_data = new_sheet.worksheet_by_title('Team Data') + team_data.update_values( + crange='B1:B2', + values=[[f'{team["id"]}'], [f'\'{team_hash(team)}']] + ) + + if copy_rosters and team['gsheet'] != 'None': + old_sheet = sheets.open_by_key(team['gsheet']) + r_sheet = old_sheet.worksheet_by_title(f'My Rosters') + roster_ids = r_sheet.range('B3:B80') + lineups_data = r_sheet.range('H4:M26') + + new_r_data, new_l_data = [], [] + + for row in roster_ids: + if row[0].value != '': + new_r_data.append([int(row[0].value)]) + else: + new_r_data.append([None]) + logging.debug(f'new_r_data: {new_r_data}') + + for row in lineups_data: + logging.debug(f'row: {row}') + new_l_data.append([ + row[0].value if row[0].value != '' else None, + int(row[1].value) if row[1].value != '' else None, + row[2].value if row[2].value != '' else None, + int(row[3].value) if row[3].value != '' else None, + row[4].value if row[4].value != '' else None, + int(row[5].value) if row[5].value != '' else None + ]) + logging.debug(f'new_l_data: {new_l_data}') + + new_r_sheet = new_sheet.worksheet_by_title(f'My Rosters') + new_r_sheet.update_values( + crange='B3:B80', + values=new_r_data + ) + new_r_sheet.update_values( + crange='H4:M26', + values=new_l_data + ) + + if team['has_guide']: + post_ratings_guide(team, self.bot, this_sheet=new_sheet) + + team = db_patch('teams', object_id=team['id'], params=[('gsheet', new_sheet.id)]) + await refresh_sheet(team, self.bot, sheets) + + await response.edit(content=f'Alright, your sheet is linked to your team - good luck this season!\n\n' + f'{HELP_SHEET_SCRIPTS}') + + @commands.hybrid_command(name='refresh', help='Refresh team data in Sheets') + @commands.has_any_role(PD_PLAYERS) + async def update_team(self, ctx): + if not await legal_channel(ctx): + await ctx.send(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') + return + + team = get_team_by_owner(ctx.author.id) + if not team: + await ctx.send( + f'I don\'t see a team for you, yet. You can sign up with the `/newteam` command!' + ) + return + + await refresh_sheet(team, self.bot) + await ctx.send(random_conf_gif()) + + # if abbrev and self.bot.is_owner(ctx.author): + # team = Team.get_season(abbrev[0]) + # else: + # team = Team.get_by_owner(ctx.author.id) + # if not team: + # await ctx.send('Now you wait just a second. You don\'t have a team!') + # return + # + # # Get data from Sheets + # roster_data = await self.get_collection(ctx, team) + # + # # Cut any marked players + # comp_trade = True + # if len(roster_data['cut']) > 0: + # comp_trade = await self.cut_players(ctx, team, roster_data['cut']) + # + # if not comp_trade: + # return + # + # # Set new rostered list + # self.set_rostered_players(team, roster_data['rostered']) + # + # # Send current data to Sheets + # if not await self.write_collection(ctx, team, extra=len(roster_data['cut'])): + # logging.error(f'There was an issue trying to update the {team.sname} roster.') + # await helpers.pause_then_type(ctx, 'Yikes. I had an issue with Sheets. Send help.') + # else: + # await helpers.pause_then_type(ctx, 'Alrighty, your sheet is all up to date!') + # if team.logo: + # thumb = team.logo + # else: + # thumb = self.bot.get_user(team.gmid).avatar_url + # await ctx.send(content=None, embed=helpers.get_active_roster(team, thumb)) + # + # db.close() + + @commands.hybrid_command(name='give-card', help='Mod: Give free card to team') + # @commands.is_owner() + @commands.has_any_role("PD Gift Players") + async def give_card_command(self, ctx, player_ids: str, team_abbrev: str): + if ctx.channel.name in ['paper-dynasty-chat', 'pd-news-ticker', 'pd-network-news']: + await ctx.send(f'Please head to down to {get_channel(ctx, "pd-bot-hole")} to run this command.') + return + + question = await ctx.send(f'I\'ll go put that card on their roster...') + + all_player_ids = player_ids.split(" ") + t_query = db_get('teams', params=[('abbrev', team_abbrev)]) + if not t_query['count']: + await ctx.send(f'I could not find {team_abbrev}') + return + team = t_query['teams'][0] + + this_pack = db_post( + 'packs/one', + payload={ + 'team_id': team['id'], + 'pack_type_id': 4, + 'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000} + ) + + try: + give_cards_to_team(team, player_ids=all_player_ids, pack_id=this_pack['id']) + except Exception as e: + logging.error(f'failed to create cards: {e}') + raise ConnectionError(f'Failed to distribute these cards.') + + await question.edit(content=f'Alrighty, now I\'ll refresh their sheet...') + await refresh_sheet(team, self.bot) + await question.edit(content=f'All done!') + await send_to_channel( + self.bot, + channel_name='commissioners-office', + content=f'Just sent {len(all_player_ids)} players to {ctx.message.author.mention}:\n{all_player_ids}' + ) + + @commands.command(name='cleartest', hidden=True) + @commands.is_owner() + async def clear_test_command(self, ctx): + team = get_team_by_owner(ctx.author.id) + msg = await ctx.send('Alright, let\'s go find your cards...') + all_cards = db_get( + 'cards', + params=[('team_id', team['id'])] + ) + + if all_cards: + await msg.edit(content=f'I found {len(all_cards["cards"])} cards; deleting now...') + for x in all_cards['cards']: + db_delete( + 'cards', + object_id=x['id'] + ) + + await msg.edit(content=f'All done with cards. Now I\'ll wipe out your packs...') + p_query = db_get('packs', params=[('team_id', team['id'])]) + if p_query['count']: + for x in p_query['packs']: + db_delete('packs', object_id=x['id']) + + await msg.edit(content=f'All done with packs. Now I\'ll wipe out your paperdex...') + p_query = db_get('paperdex', params=[('team_id', team['id'])]) + if p_query['count']: + for x in p_query['paperdex']: + db_delete('paperdex', object_id=x['id']) + + await msg.edit(content=f'All done with paperdex. Now I\'ll wipe out your team...') + if db_delete('teams', object_id=team['id']): + await msg.edit(content=f'All done!') + + @commands.command(name='packtest', hidden=True) + @commands.is_owner() + async def pack_test_command(self, ctx): + team = get_team_by_owner(ctx.author.id) + + await display_cards( + await get_test_pack(ctx, team), team, ctx.channel, ctx.author, self.bot, + pack_cover=IMAGES['pack-sta'], + pack_name='Standard Pack' + ) + + # @commands.command(name='deleteteam', help='Remove your team from the league', hidden=True) + # @commands.is_owner() + # async def delete_team(self, ctx): + # team = Team.get_by_owner(ctx.author.id) + # if not team: + # await ctx.send('Now you wait just a second. You don\'t have a team!') + # return + # + # await ctx.send('Are you sure you want to delete your team and full collection?\n\nType \'YES\' to confirm.') + # + # def confirmation_check(mes): + # return mes.author == ctx.author and mes.content.upper() == 'YES' + # + # try: + # confirm_resp = await self.bot.wait_for('message', check=confirmation_check, timeout=10.0) + # roster = Roster.delete().where(Roster.team == team) + # all_packs = Pack.delete().where(Pack.team == team) + # all_cards = Card.delete().where(Card.team == team) + # + # roster.execute() + # all_packs.execute() + # all_cards.execute() + # team.delete_instance() + # + # await helpers.pause_then_type(ctx, 'All done. It\'s like you were never here.') + # except TimeoutError: + # return + # + # db.close() + + # @commands.command(name='addmlbteams', help='Create MLB teams and roster sheets', hidden=True) + # @commands.is_owner() + # async def add_mlb_teams(self, ctx: commands.Context): + # current = db_get('current') + # + # all_teams = [ + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # {'lname': asdfasdf, 'sname': asdfasdf, 'color': asdfasdf, 'abbrev': asdf, 'logo': asdfasdf}, + # # 'Arizona Diamondbacks': ['ARI', 'Diamondbacks'], + # # 'Atlanta Braves': ['ATL', 'MLN', 'Braves'], + # # 'Baltimore Orioles': ['BAL', 'Orioles'], + # # 'Boston Red Sox': ['BOS', 'Red Sox'], + # # 'Chicago Cubs': ['CHC', 'Cubs'], + # # 'Chicago White Sox': ['CHW', 'White Sox'], + # # 'Cincinnati Reds': ['CIN', 'Reds'], + # # 'Cleveland Indians': ['CLE', 'Indians'], + # # 'Colorado Rockies': ['COL', 'Rockies'], + # # 'Detroit Tigers': ['DET', 'Tigers'], + # # 'Houston Astros': ['HOU', 'Astros'], + # # 'Kansas City Royals': ['KCR', 'Royals'], + # # 'Los Angeles Angels': ['LAA', 'CAL', 'Angels'], + # # 'Los Angeles Dodgers': ['LAD', 'Dodgers'], + # # 'Miami Marlins': ['MIA', 'Marlins'], + # # 'Milwaukee Brewers': ['MIL', 'MKE', 'Brewers'], + # # 'Minnesota Twins': ['MIN', 'Twins'], + # # 'New York Mets': ['NYM', 'Mets'], + # # 'New York Yankees': ['NYY', 'Yankees'], + # # 'Oakland Athletics': ['OAK', 'Athletics'], + # # 'Philadelphia Phillies': ['PHI', 'Phillies'], + # # 'Pittsburgh Pirates': ['PIT', 'Pirates'], + # # 'San Diego Padres': ['SDP', 'Padres'], + # # 'Seattle Mariners': ['SEA', 'Mariners'], + # # 'San Francisco Giants': ['SFG', 'Giants'], + # # 'St Louis Cardinals': ['STL', 'Cardinals'], + # # 'Tampa Bay Rays': ['TBR', 'Rays'], + # # 'Texas Rangers': ['TEX', 'Senators', 'Rangers'], + # # 'Toronto Blue Jays': ['TOR', 'Jays'], + # # 'Washington Nationals': ['WSN', 'WAS', 'Nationals'], + # ] + # + # for team in all_teams: + # async with ctx.typing(): + # this_sheet = create_team_sheet(team, current, self.bot) + # + # try: + # new_team = db_post('teams', payload={ + # 'abbrev': team['abbrev'], + # 'sname': team['sname'], + # 'lname': team['lname'], + # 'gmid': 69420, + # 'gmname': team['sname'], + # 'gsheet': this_sheet['id'], + # 'season': current['season'], + # 'wallet': 100, + # 'color': team['color'], + # 'logo': team['logo'], + # }) + # # TODO: Create cards for team players + # except ValueError as e: + # await ctx.send(f'Oh man, I wasn\'t able to create the {team["sname"]}.') + # return + + # @commands.command(name='redeem', help='Redeem a special deal') + # @commands.has_any_role('Paper Dynasty Players') + # async def redeem_special_command(self, ctx, special_id): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to the pd-bot-hole ;)') + # return + # + # team = Team.get_by_owner(ctx.author.id) + # if not team: + # await ctx.send('Now you wait just a second. You don\'t have a team!') + # return + # + # try: + # this_special = Special.get_by_id(special_id) + # except: + # await ctx.send('I don\'t see a special with that ID available right now.') + # return + # + # await ctx.send(content=None, embed=helpers.get_special_embed(this_special)) + # await ctx.send('Please type \'YES\' to confirm:') + # + # def confirmation(mes): + # logging.error(f'Content: {mes.content} / Author: {mes.author} / Match?: {mes.author == ctx.author}') + # return mes.author == ctx.author and mes.content.upper() == 'YES' + # + # try: + # resp = await self.bot.wait_for('message', check=confirmation, timeout=10.0) + # except TimeoutError: + # await ctx.send('Maybe next time :)') + # return + # except Exception as e: + # await ctx.send(f'Uhh...do you know what this means?\n\n{e}') + # return + # + # # Manual stuff down here to run redemptions + # # TODO: Automate this + # + # if 'Weekly Standings' in this_special.name and team.dailyclaim: + # await ctx.send('Congrats! You have an All-Star coming your way. Let\'s see who we\'ve got...') + # this_player = Player.get_random_or_none(Rarity.get_or_none(Rarity.name == 'All-Star')) + # new_card = Card(player=this_player, team=team) + # new_card.save() + # + # team.dailyclaim = 0 + # team.save() + # + # await self.present_player(ctx, ctx, new_card) + # await helpers.send_to_news( + # ctx, + # f'The {team.sname} just got {this_player.cardset} {this_player.name} ' + # f'for their week {Current.get().week - 1} standings win!', + # None + # ) + # + # db.close() -def setup(bot): - bot.add_cog(Economy(bot)) +async def setup(bot): + await bot.add_cog(Economy(bot)) diff --git a/cogs/gameplay.py b/cogs/gameplay.py new file mode 100644 index 0000000..550de48 --- /dev/null +++ b/cogs/gameplay.py @@ -0,0 +1,3488 @@ +import asyncio +import logging +import math +import copy +import os + +import ai_manager +import discord +from discord import app_commands +from discord.app_commands import Choice +from discord.ext import commands, tasks +from peewee import IntegrityError + +from typing import Optional, Literal + +from dice import sa_fielding_roll +from helpers import SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME, random_conf_gif, SBA_SEASON, PD_SEASON, IMAGES, \ + get_team_embed, Confirm, get_pos_abbrev, SBA_COLOR, get_roster_lineups, Question, give_packs, send_to_channel, \ + get_channel, get_or_create_role, team_role, get_cal_user, get_card_embeds, ButtonOptions, get_ratings_guide +from gameplay_helpers import * +from db_calls import db_get, db_patch, db_post, db_delete, get_team_by_abbrev +from db_calls_gameplay import StratGame, StratPlay, StratLineup, StratManagerAi, get_sba_team, get_sba_player, \ + post_game, patch_game, get_game_team, post_lineups, make_sub, get_player, player_link, get_team_lineups, \ + get_current_play, post_play, get_one_lineup, advance_runners, patch_play, complete_play, get_batting_stats, \ + get_pitching_stats, undo_play, get_latest_play, advance_one_runner, count_team_games, get_final_scorebug, \ + get_fielding_stats, get_pitching_decisions, get_or_create_bullpen, get_last_inning_end_play, patch_lineup, \ + get_last_game_ids, get_plays, get_manager, get_one_game, load_ai, ai_batting + + +class Gameplay(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.batter_ratings = None + self.pitcher_ratings = None + self.live_scoreboard.start() + + @tasks.loop(minutes=1) + async def live_scoreboard(self): + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Cannot access guild; pausing for 15 seconds') + await asyncio.sleep(15) + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Still cannot access guild; trying again in 10 minutes') + return + + all_games = get_last_game_ids(6) + + games_active = False + for game_id in all_games: + g_query = get_one_game(game_id) + if g_query.active: + games_active = True + + if games_active: + embed = get_team_embed(f'Live Scoreboard') + for game_id in all_games: + try: + gs = await self.get_game_state(get_one_game(game_id=game_id)) + logging.debug(f'curr_play: {gs["curr_play"]}') + g_message = gs['scorebug'] + + if not gs['curr_play'].game.active: + g_dec = get_pitching_decisions(gs['curr_play'].game) + winner = await get_player(gs["curr_play"].game, g_dec["w_lineup"]) + loser = await get_player(gs["curr_play"].game, g_dec["l_lineup"]) + if g_dec['s_lineup']: + saver = await get_player(gs['curr_play'].game, g_dec['s_lineup']) + s_string = f'Save: {saver["name"]}\n' \ + f'Location: {guild.get_channel(gs["channel_id"]).mention}\n' \ + f'\n-------------------------' + else: + s_string = f'Location: {guild.get_channel(gs["channel_id"]).mention}\n' \ + f'-------------------------' + + g_message += f'Win: {winner["name"]}\n' \ + f'Loss: {loser["name"]}\n' \ + f'{s_string}' + else: + games_active = True + g_message += f'Pitcher: {gs["pitcher"]["name"]}\n' \ + f'Batter: {gs["batter"]["name"]}\n' \ + f'Location: {guild.get_channel(gs["channel_id"]).mention}\n' \ + f'-------------------------' + + embed.add_field( + name=f'{gs["away_team"]["sname"]} @ {gs["home_team"]["sname"]}', + value=g_message, + inline=False + ) + + except Exception as e: + logging.error( + f'Skipping Game {game_id} in scoreboard: {e}' + ) + + current = db_get('current') + + try: + s_channel = self.bot.get_channel(int(os.environ.get('SCOREBOARD_CHANNEL'))) + message_id = current['live_scoreboard'] + if message_id == 0: + s_message = await send_to_channel( + self.bot, + 'live-scoreboard', + content=None, + embed=embed + ) + db_patch('current', object_id=current['id'], params=[('live_scoreboard', s_message.id)]) + + else: + s_message = await s_channel.fetch_message(message_id) + await s_message.edit(content=None, embed=embed) + + except Exception as e: + logging.error(f'Unable to post scoreboard: {e}') + + @tasks.loop(hours=36) + async def update_ratings_guides(self): + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Cannot access guild; pausing ratings guide for 20 seconds') + await asyncio.sleep(20) + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Still cannot access guild; trying again in 1 minutes') + await asyncio.sleep(60) + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Still cannot access guild; dueces') + return + + data = get_ratings_guide(get_sheets(self.bot)) + if data['valid']: + self.batter_ratings = data['batter_ratings'] + self.pitcher_ratings = data['pitcher_ratings'] + else: + logging.error(f'gameplay - pulled bad ratings guide data') + + async def cog_command_error(self, ctx, error): + await ctx.send(f'{error}\n\nRun !help to see the command requirements') + + async def slash_error(self, ctx, error): + await ctx.send(f'{error}') + + def get_scorebug(self, home_team, away_team, curr_play): + occupied = '●' + unoccupied = '○' + + first_base = unoccupied if not curr_play.on_first else occupied + second_base = unoccupied if not curr_play.on_second else occupied + third_base = unoccupied if not curr_play.on_third else occupied + half = '▲' if curr_play.inning_half == 'Top' else '▼' + if curr_play.game.active: + inning = f'{half} {curr_play.inning_num}' + outs = f'{curr_play.starting_outs} Out{"s" if curr_play.starting_outs != 1 else ""}' + else: + inning = f'F/{curr_play.inning_num if curr_play.inning_half == "Bot" else curr_play.inning_num - 1}' + outs = '' + + game_string = f'```\n' \ + f'{away_team["abbrev"]: ^4}{curr_play.away_score: ^3} {second_base}' \ + f'{inning: >10}\n' \ + f'{home_team["abbrev"]: ^4}{curr_play.home_score: ^3} {third_base} {first_base}' \ + f'{outs: >8}\n```' + + return game_string + + async def get_game_state(self, game: StratGame) -> dict: + away_team = await get_game_team(game, team_id=game.away_team_id) + home_team = await get_game_team(game, team_id=game.home_team_id) + + curr_play = get_current_play(game.id) + if curr_play is None: + away_lineup = await get_team_lineups(game.id, game.away_team_id) + home_lineup = await get_team_lineups(game.id, game.home_team_id) + + if not away_lineup or not home_lineup: + game_state = { + 'error': True, + 'away_lineup': away_lineup, + 'home_lineup': home_lineup, + 'away_team': away_team, + 'home_team': home_team + } + logging.error(f'One ore more lineups not submitted in Game {game.id}\n\ngame_state: {game_state}') + return game_state + else: + logging.info(f'looking for home ({game.home_team_id}) pitcher in Game {game.id}') + pitcher = get_one_lineup(game.id, team_id=game.home_team_id, position='P') + logging.info(f'pitcher: {pitcher}') + curr_play = post_play({ + 'game_id': game.id, + 'play_num': 1, + 'batter_id': get_one_lineup(game.id, team_id=game.away_team_id, batting_order=1).id, + 'pitcher_id': pitcher.id if pitcher else None, + 'on_base_code': 0, + 'inning_half': 'Top', + 'inning_num': 1, + 'batting_order': 1, + 'starting_outs': 0, + 'away_score': 0, + 'home_score': 0 + }) + + game_state = {'error': False, 'curr_play': curr_play, 'away_team': away_team, 'home_team': home_team} + + away_team = await get_game_team(game, team_id=game.away_team_id) + home_team = await get_game_team(game, team_id=game.home_team_id) + game_state['away_team'] = away_team + game_state['home_team'] = home_team + + scorebug = self.get_scorebug(home_team, away_team, game_state['curr_play']) + game_state['scorebug'] = scorebug + + batter = await get_player(game, game_state['curr_play'].batter) + litmus = 0 + try: + if not game_state['curr_play'].pitcher: + p_search = get_one_lineup( + game.id, + team_id=game.away_team_id if game_state['curr_play'].inning_half == 'Bot' else game.home_team_id, + position='P' + ) + if p_search: + patch_play(game_state['curr_play'].id, pitcher_id=p_search.id) + pitcher = await get_player(game, game_state['curr_play'].pitcher) + litmus = 1 + catcher = await get_player( + game, + get_one_lineup( + game.id, + team_id=game.away_team_id if game_state['curr_play'].inning_half == 'Bot' else game.home_team_id, + position='C' + ) + ) + except Exception as e: + logging.error(f'ERROR: {e} / TYPE: {type(e)}') + away_lineup = await get_team_lineups(game.id, game.away_team_id) + home_lineup = await get_team_lineups(game.id, game.home_team_id) + if litmus == 0: + error_message = f'Please sub in a pitcher to continue' + else: + error_message = f'Please sub in a catcher to continue' + + game_state['error'] = True + game_state['error_message'] = error_message + game_state['away_lineup'] = away_lineup + game_state['home_lineup'] = home_lineup + return game_state + + game_state['batter'] = batter + game_state['pitcher'] = pitcher + game_state['catcher'] = catcher + game_state['channel_id'] = game.channel_id + + return game_state + + async def get_game_state_embed(self, game: StratGame, full_length=True, for_liveboard=False): + game_state = await self.get_game_state(game) + + logging.debug(f'game_state: {game_state}') + + embed = discord.Embed( + title=f'{game_state["away_team"]["sname"]} @ {game_state["home_team"]["sname"]}', + color=int(SBA_COLOR, 16) + ) + + if game.is_pd: + footer_text = f'PD Season {PD_SEASON}' + if game.short_game: + footer_text += f' - Reminder: all pitchers have POW(1) in 3-inning games' + embed.set_footer(text=footer_text, icon_url=IMAGES['logo']) + else: + embed.set_footer(text=f'SBa Season {SBA_SEASON}', icon_url=IMAGES['logo']) + + if game_state['error']: + # embed = discord.Embed( + # title='Current Lineups', + # color=int(SBA_COLOR, 16) + # ) + embed.add_field(name='Away Team', + value=game_state['away_lineup'] if game_state['away_lineup'] else 'None, yet') + embed.add_field(name='Home Team', + value=game_state['home_lineup'] if game_state['home_lineup'] else 'None, yet') + if 'error_message' in game_state: + embed.set_footer(text=game_state['error_message'], icon_url=IMAGES['logo']) + + return embed + + embed.add_field(name='Game State', value=game_state['scorebug'], inline=False) + + if abs(game_state['curr_play'].home_score - game_state['curr_play'].away_score) >= 10: + embed.description = '***Mercy rule in effect***' + + pitcher_string = f'{player_link(game, game_state["pitcher"])}' + batter_string = f'{player_link(game, game_state["batter"])}' + + all_bat_stats = get_batting_stats(game.id, lineup_id=game_state['curr_play'].batter.id) + if len(all_bat_stats): + b_s = all_bat_stats[0] + batter_string += f'\n{b_s["pl_hit"]}-{b_s["pl_ab"]}' + for num, stat in [ + (b_s['pl_double'], '2B'), (b_s['pl_triple'], '3B'), (b_s['pl_homerun'], 'HR'), (b_s['pl_rbi'], 'RBI'), + (b_s['pl_bb'], 'BB'), (b_s['pl_hbp'], 'HBP'), (b_s['pl_ibb'], 'IBB'), (b_s['pl_so'], 'K'), + (b_s['pl_gidp'], 'GIDP'), (b_s['pl_bpfo'], 'BPFO'), (b_s['pl_bplo'], 'BPLO') + ]: + if num: + batter_string += f', {num if num > 1 else ""}{" " if num > 1 else ""}{stat}' + + all_pit_stats = get_pitching_stats(game.id, lineup_id=game_state['curr_play'].pitcher.id) + if len(all_pit_stats): + p_s = all_pit_stats[0] + pitcher_string += f'\n{math.floor(p_s["pl_outs"]/3)}.{p_s["pl_outs"] % 3} IP' + for num, stat in [ + (p_s['pl_runs'], 'R'), (p_s['pl_hit'], 'H'), (p_s['pl_homerun'], 'HR'), (p_s['pl_so'], 'K'), + (p_s['pl_bb'], 'BB'), (p_s['pl_hbp'], 'HBP'), (p_s['pl_wild_pitch'], 'WP'), (p_s['pl_balk'], 'BK'), + ]: + if num: + pitcher_string += f', {num} {stat}' + if stat == 'R' and num != p_s['pl_eruns']: + pitcher_string += f', {p_s["pl_eruns"]} ER' + if game.short_game and p_s["pl_outs"] >= 3: + pitcher_string += f'\n***F A T I G U E D***' + + # embed.add_field(name='Matchup', value=f'Pitcher: \nvs\nBatter: ', inline=False) + embed.add_field(name='Pitcher', value=f'{pitcher_string}') + embed.add_field(name='Batter', value=f'{batter_string}') + embed.set_thumbnail(url=game_state["pitcher"]["image"]) + if game_state['batter']['image2']: + embed.set_image(url=game_state["batter"]["image2"]) + else: + embed.set_image(url=game_state["batter"]["image"]) + + baserunner_string = '' + if game_state['curr_play'].on_first: + runner = await get_player(game, game_state['curr_play'].on_first) + baserunner_string += f'On First: {player_link(game, runner)}\n' + if game_state['curr_play'].on_second: + runner = await get_player(game, game_state['curr_play'].on_second) + baserunner_string += f'On Second: {player_link(game, runner)}\n' + if game_state['curr_play'].on_third: + runner = await get_player(game, game_state['curr_play'].on_third) + baserunner_string += f'On Third: {player_link(game, runner)}\n' + embed.add_field( + name='Baserunners', value=baserunner_string if len(baserunner_string) > 0 else 'None', inline=False + ) + + # If an AI team is playing + if True in [game_state['pitcher']['team']['is_ai'], game_state['batter']['team']['is_ai']]: + ai_note = '' + gm_name = '' + logging.debug(f'Checking AI stuff') + + # AI Team is pitching + if game_state['pitcher']['team']['is_ai']: + this_ai = get_manager(game) + logging.debug(f'Pitching team is an AI') + gm_name = f'{game_state["pitcher"]["team"]["gmname"]}' + n_pitcher = await next_pitcher(game_state['curr_play'], game_state["pitcher"]["team"], self.bot) + logging.debug(f'n_pitcher: {n_pitcher}') + + last_inning_ender = get_last_inning_end_play( + game.id, + game_state['curr_play'].inning_half, + game_state['curr_play'].inning_num - 1 + ) + logging.debug(f'last_inning_ender: {last_inning_ender}') + + if last_inning_ender and last_inning_ender.pitcher != game_state['curr_play'].pitcher: + logging.debug(f'{game_state["pitcher"]["team"]["sname"]} not making a change.') + + ai_note += f'- have {game_state["pitcher"]["p_name"]} finish the inning\n' + + elif game_state['curr_play'].is_new_inning and game.short_game and game_state['curr_play'].inning_num != 1: + logging.debug(f'{game_state["pitcher"]["team"]["sname"]} going the to pen.') + + if 'card_id' in n_pitcher: + replace_pitcher(game_state['pitcher']['team'], game_state['curr_play'], n_pitcher['card_id']) + embed.set_thumbnail(url=n_pitcher['player']['image']) + embed.add_field( + name=f'SUBSTITUTION', + value=f'The {game_state["pitcher"]["team"]["sname"]} have brought in ' + f'{n_pitcher["player"]["p_name"]} to pitch.' + ) + else: + ai_note += f'- continue with auto-fatigued {game_state["pitcher"]["p_name"]}\n' + + else: + if 'card_id' in n_pitcher: + logging.debug(f'{game_state["pitcher"]["team"]["sname"]} are warming up {n_pitcher}.') + ai_note += f'- go to the pen if the pitcher fatigues (`/log ai-pitcher-sub`)\n' + else: + logging.warning(f'{game_state["pitcher"]["team"]["sname"]} are out of pitchers.') + ai_note += f' - continue with {game_state["pitcher"]["p_name"]}\n' + + logging.debug(f'past the first conditional in AI stuff') + + if game_state['curr_play'].on_base_code > 0 and game_state['curr_play'].starting_outs == 2: + ai_note += f'- hold the runners' + elif game_state['curr_play'].on_base_code == 1: + ai_note += f'- only hold baserunners with an ***** auto-jump\n' + elif game_state['curr_play'].on_base_code == 2: + ai_note += f'- only hold baserunners with a safe range of 14+\n' + + if game_state['curr_play'].on_third and game_state['curr_play'].starting_outs < 2: + if game_state['curr_play'].on_first: + ai_note += f'- play the corners in\n' + + elif abs(game_state['curr_play'].away_score - game_state['curr_play'].home_score) <= 3: + ai_note += f'- play the whole infield in\n' + + else: + ai_note += f'- play the corners in\n' + + # AI Team is batting + elif game_state['batter']['team']['is_ai']: + this_ai = get_manager(game) + logging.debug(f'Batting team is an AI') + gm_name = f'{game_state["batter"]["team"]["gmname"]}' + if game_state['curr_play'].on_first and not game_state['curr_play'].on_second: + ai_note += f'- {this_ai.check_jump(2, game_state["curr_play"])}\n' + + elif game_state['curr_play'].on_second and not game_state['curr_play'].on_third: + ai_note += f'- {this_ai.check_jump(3, game_state["curr_play"])}\n' + + if ai_note: + embed.add_field( + name=f'{gm_name} will...', + value=ai_note, + inline=False + ) + + if not full_length: + return embed + + away_lineup = await get_team_lineups(game.id, game.away_team_id) + home_lineup = await get_team_lineups(game.id, game.home_team_id) + embed.add_field(name=f'{game_state["away_team"]["abbrev"]} Lineup', value=away_lineup) + embed.add_field(name=f'{game_state["home_team"]["abbrev"]} Lineup', value=home_lineup) + return embed + + async def groundballs( + self, interaction, this_game, this_play: StratPlay, groundball_type: str, comp_play: bool = True): + batter_to_base = None + bases = ['third', 'second', 'first'] + + if this_play.starting_outs == 2 or this_play.on_base_code == 0: + patch_play(this_play.id, pa=1, ab=1, outs=1) + + else: + if groundball_type == 'a': + if this_play.on_base_code == 1: + patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=2) + + elif this_play.on_base_code == 4: + if this_play.starting_outs == 1: + patch_play(this_play.id, pa=1, ab=1, outs=2) + else: + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is the double play at second and first?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + patch_play( + this_play.id, + pa=1, + ab=1, + outs=2, + on_second_final=3, + on_first_final=False, + ) + + else: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is the double play at third and second?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + patch_play( + this_play.id, + pa=1, + ab=1, + outs=2, + on_second_final=False, + on_first_final=False + ) + batter_to_base = 1 + + else: + await question.edit( + content=f'Hmm...not sure what else this could be. If you expected a different ' + f'result, let Cal know.', + view=None + ) + return + + elif this_play.on_base_code == 7: + if this_play.starting_outs == 1: + patch_play(this_play.id, pa=1, ab=1, outs=2) + else: + runner = await get_player(this_game, this_play.on_third) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {runner["name"]} out on the home-to-first double play?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + advance_runners(this_play.id, 1) + patch_play(this_play.id, on_third_final=False, pa=1, ab=1, outs=2, rbi=0) + else: + await question.delete() + advance_runners(this_play.id, 1) + patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=2, rbi=0) + + else: + num_outs = 1 + + for count, x in enumerate([this_play.on_third, this_play.on_second, this_play.on_first]): + if x: + runner = await get_player(this_game, x) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} sent from {bases[count]} on the play?', view=view + ) + await view.wait() + + num_bases = 0 + if view.value: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} thrown out?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=False) + elif count == 1: + patch_play(this_play.id, on_second_final=False) + else: + patch_play(this_play.id, on_first_final=False) + num_outs += 1 + else: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=4) + elif count == 1: + patch_play(this_play.id, on_second_final=3) + else: + patch_play(this_play.id, on_first_final=2) + else: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=3) + elif count == 1: + patch_play(this_play.id, on_second_final=2) + else: + patch_play(this_play.id, on_first_final=1) + + if this_play.on_third: + batter = await get_player(this_game, this_play.batter) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {batter["name"]} out at first?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + else: + await question.delete() + num_outs -= 1 + batter_to_base = 1 + + patch_play(this_play.id, pa=1, ab=1, outs=num_outs) + if num_outs + this_play.starting_outs == 3: + advance_runners(this_play.id, 0) + + elif groundball_type == 'b': + if this_play.on_base_code == 3 or this_play.on_base_code == 6 or this_play.on_base_code == 2: + if this_play.on_base_code == 2: + runner = await get_player(this_game, this_play.on_second) + from_base = 'second' + else: + runner = await get_player(this_game, this_play.on_third) + from_base = 'third' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} sent from {from_base} on the play?', view=view + ) + await view.wait() + + if view.value: + advance_runners(this_play.id, 1) + + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} thrown out?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if from_base == 'third': + patch_play(this_play.id, on_third_final=False) + + # from second + else: + patch_play(this_play.id, on_second_final=False) + else: + await question.delete() + else: + await question.delete() + advance_runners(this_play.id, 0) + + patch_play(this_play.id, pa=1, ab=1, outs=1) + + elif this_play.on_base_code == 1 or this_play.on_base_code == 4: + advance_runners(this_play.id, 1) + patch_play(this_play.id, on_first_final=False, pa=1, ab=1, outs=1) + batter_to_base = 1 + + else: + num_outs = 0 + for count, x in enumerate([this_play.on_third, this_play.on_second, this_play.on_first]): + if x: + runner = await get_player(this_game, x) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} sent from {bases[count]} on the play?', view=view + ) + await view.wait() + + num_bases = 0 + if view.value: + await question.delete() + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} thrown out?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=False) + elif count == 1: + patch_play(this_play.id, on_second_final=False) + else: + patch_play(this_play.id, on_first_final=False) + num_outs += 1 + else: + await question.delete() + advance_one_runner(this_play.id, from_base=3 - count, num_bases=1) + else: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=3) + elif count == 1: + patch_play(this_play.id, on_second_final=2) + else: + patch_play(this_play.id, on_first_final=1) + + if num_outs == 0: + batter = await get_player(this_game, this_play.batter) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {batter["name"]} out at first?', view=view + ) + + await view.wait() + + if view.value: + await question.delete() + num_outs += 1 + else: + await question.delete() + await interaction.edit_original_response( + content=f'Okay so it wasn\'t a gb B then? Go ahead and log a new play.' + ) + patch_play(this_play.id, locked=False) + return + else: + batter_to_base = 1 + + patch_play(this_play.id, pa=1, ab=1, outs=num_outs) + + elif groundball_type == 'c': + if this_play.on_base_code == 7: + runner = await get_player(this_game, this_play.on_third) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {runner["name"]} forced out at home?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + advance_runners(this_play.id, 1) + patch_play(this_play.id, on_third_final=False, pa=1, ab=1, outs=1, rbi=0) + batter_to_base = 1 + else: + await question.delete() + advance_runners(this_play.id, 1) + patch_play(this_play.id, pa=1, ab=1, outs=1) + + elif this_play.on_third: + num_outs = 0 + for count, x in enumerate([this_play.on_third, this_play.on_second, this_play.on_first]): + if x: + runner = await get_player(this_game, x) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} sent from {bases[count]} on the play?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} thrown out?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=False) + elif count == 1: + patch_play(this_play.id, on_second_final=False) + else: + patch_play(this_play.id, on_first_final=False) + num_outs += 1 + else: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=4) + elif count == 1: + patch_play(this_play.id, on_second_final=3) + else: + patch_play(this_play.id, on_first_final=2) + + else: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=3) + elif count == 1: + patch_play(this_play.id, on_second_final=2) + else: + patch_play(this_play.id, on_first_final=1) + + if not num_outs: + num_outs += 1 + + logging.debug(f'should be patching the gb C now...') + patch_play(this_play.id, pa=1, ab=1, outs=num_outs) + + else: + advance_runners(this_play.id, 1) + patch_play(this_play.id, pa=1, ab=1, outs=1) + + if comp_play: + complete_play(this_play.id, batter_to_base=batter_to_base) + + async def flyballs(self, interaction, this_game, this_play, flyball_type, comp_play: bool = True): + num_outs = 1 + + if flyball_type == 'a': + patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) + if this_play.starting_outs < 2: + advance_runners(this_play.id, 1) + if this_play.on_third: + patch_play(this_play.id, ab=0) + + elif flyball_type == 'b' or flyball_type == 'ballpark': + patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1, bpfo=1 if flyball_type == 'ballpark' else 0) + advance_runners(this_play.id, num_bases=0) + if this_play.starting_outs < 2 and this_play.on_third: + patch_play(this_play.id, ab=0, rbi=1) + advance_one_runner(this_play.id, from_base=3, num_bases=1) + + if this_play.starting_outs < 2 and this_play.on_second: + ai_hint = '' + if this_game.ai_team and ai_batting(this_game, this_play): + ai_hint = f'*The runner will {get_manager(this_game).tag_from_second(this_play.starting_outs + 1)}*' + runner = await get_player(this_game, this_play.on_second) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} sent from second on the play?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + + view = ButtonOptions( + responders=[interaction.user], timeout=60, + labels=['Tagged Up', 'Hold at 2nd', 'Out at 3rd', None, None] + ) + question = await interaction.channel.send( + f'What was the result of {runner["name"]} tagging from second?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if view.value == 'Tagged Up': + advance_one_runner(this_play.id, from_base=2, num_bases=1) + elif view.value == 'Out at 3rd': + num_outs += 1 + patch_play(this_play.id, on_second_final=False, outs=num_outs) + else: + await question.delete() + else: + await question.delete() + + elif flyball_type == 'b?': + patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) + advance_runners(this_play.id, num_bases=0) + if this_play.starting_outs < 2 and this_play.on_third: + ai_hint = '' + if ai_batting(this_game, this_play): + ai_hint = f'*The runner will {get_manager(this_game).tag_from_third(this_play.starting_outs + 1)}*' + runner = await get_player(this_game, this_play.on_third) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} sent from third on the play?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Was {runner["name"]} thrown out?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + patch_play(this_play.id, on_third_final=False) + num_outs += 1 + patch_play(this_play.id, outs=num_outs) + else: + await question.delete() + advance_one_runner(this_play.id, from_base=3, num_bases=1) + else: + await question.delete() + + elif flyball_type == 'c': + patch_play(this_play.id, locked=True, pa=1, ab=1, outs=1) + advance_runners(this_play.id, num_bases=0) + + if comp_play: + complete_play(this_play.id) + + @commands.command(name='tl') + @commands.is_owner() + async def test_lineup_command(self, ctx, team_abbrev: str): + t_query = db_get('teams', params=[('abbrev', team_abbrev)]) + if t_query['count'] > 0: + team = t_query['teams'][0] + await ctx.send(f'Pulling a lineup for the {team["lname"]}...') + lineups = ai_manager.build_lineup(team, 69420, 'l', 'minor-league') + l_output = '' + for line in lineups: + l_output += f'{line["batting_order"]} - {line["player_name"]} - {line["position"]}\n' + await ctx.send(f'Lineups:\n\n{l_output}') + else: + await ctx.send(f'No team found with abbrev {team_abbrev}') + + @commands.command(name='tr') + @commands.is_owner() + async def test_reliever_command(self, ctx, game_id: int): + this_game = get_one_game(game_id=game_id) + ai_id = this_game.away_team_id if this_game.ai_team == 'away' else this_game.home_team_id + ai_team = db_get('teams', object_id=ai_id) + used_pitchers = await get_team_lineups( + game_id=game_id, team_id=ai_team['id'], inc_inactive=True, pitchers_only=True, as_string=False + ) + reliever = ai_manager.get_relief_pitcher(get_current_play(game_id), ai_team, used_pitchers) + await ctx.send(f'Reliever selected:\n\n{reliever}') + + group_new_game = app_commands.Group(name='new-game', description='Start a new baseball game') + + # @group_new_game.command(name='sba', description='Start a new SBa league game') + # @commands.has_any_role(SBA_PLAYERS_ROLE_NAME) + # async def new_game_command( + # self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str, week_num: int, + # game_num: int): + # await interaction.response.defer() + # + # conflict = get_one_game(channel_id=interaction.channel.id, active=True) + # if conflict: + # await interaction.edit_original_response( + # content=f'Ope. There is already a game going on in this channel. Please wait for it to complete ' + # f'before starting a new one.') + # return + # + # away_team = get_sba_team(away_team_abbrev) + # home_team = get_sba_team(home_team_abbrev) + # for x in [away_team, home_team]: + # conflict = count_team_games(x['id']) + # if conflict['count']: + # await interaction.edit_original_response( + # content=f'Ope. The {x["sname"]} are already playing over in ' + # f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' + # ) + # return + # + # if interaction.user.id not in [away_team['gmid'], away_team['gmid2'], home_team['gmid'], home_team['gmid2']]: + # await interaction.edit_original_response( + # content='You can only start a new game if you GM one of the teams.' + # ) + # return + # + # post_game({ + # 'away_team_id': away_team['id'], + # 'home_team_id': home_team['id'], + # 'season': SBA_SEASON, + # 'week_num': week_num, + # 'game_num': game_num, + # 'channel_id': interaction.channel.id, + # 'active': True, + # 'is_pd': False, + # }) + # logging.info(f'game is posted!') + # + # await interaction.edit_original_response( + # content=f'The game is set! Go ahead and set lineups with `/setlineup`' + # ) + + @group_new_game.command(name='mlb-campaign', description='Start a new MLB Campaign game against an AI') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + async def new_game_command( + self, interaction: discord.Interaction, league: Literal['Minor League', 'Major League', 'Hall of Fame'], + away_team_abbrev: str, home_team_abbrev: str, num_innings: Literal[9, 3]): + await interaction.response.defer() + + conflict = get_one_game(channel_id=interaction.channel.id, active=True) + if conflict: + await interaction.edit_original_response( + content=f'Ope. There is already a game going on in this channel. Please wait for it to complete ' + f'before starting a new one.') + return + + try: + if interaction.channel.category.name != 'Public Fields': + await interaction.response.send_message( + f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' + f'pops up?' + ) + return + except Exception as e: + logging.error(f'Could not check channel category: {e}') + + away_team = get_team_by_abbrev(away_team_abbrev) + home_team = get_team_by_abbrev(home_team_abbrev) + + if not away_team: + await interaction.edit_original_response( + content=f'Sorry, I don\'t know who **{away_team_abbrev.upper()}** is.' + ) + return + if not home_team: + await interaction.edit_original_response( + content=f'Sorry, I don\'t know who **{home_team_abbrev.upper()}** is.' + ) + return + + # if True in [away_team['is_ai'], home_team['is_ai']] and is_ranked: + # await interaction.edit_original_response( + # content=f'Sorry, ranked games can only be played against human teams. Run `/new-game` again with ' + # f'`is_ranked` set to False to play against the ' + # f'{away_team["sname"] if away_team["is_ai"] else home_team["sname"]}.' + # ) + # return + + for x in [away_team, home_team]: + if not x['is_ai']: + conflict = count_team_games(x['id']) + if conflict['count']: + await interaction.edit_original_response( + content=f'Ope. The {x["sname"]} are already playing over in ' + f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' + ) + return + + current = db_get('current') + week_num = current['week'] + # logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: {is_ranked}') + logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num}') + + if not away_team['is_ai'] and not home_team['is_ai']: + logging.error(f'MLB Campaign game between {away_team["abbrev"]} and {home_team["abbrev"]} has no AI') + await interaction.edit_original_response( + content=f'I don\'t see an AI team in this MLB Campaign game. Run `/new-game mlb-campaign` again with ' + f'an AI for a campaign game or `/new-game ` for a human game.' + ) + return + + ai_team = away_team if away_team['is_ai'] else home_team + + if interaction.user.id not in [away_team['gmid'], home_team['gmid']]: + await interaction.edit_original_response( + content='You can only start a new game if you GM one of the teams.' + ) + return + + if 'Minor' in league: + league_name = 'minor-league' + elif 'Major' in league: + await interaction.edit_original_response( + content='Major League games are coming soon! For now, you can play an Unlimited Minor League game or ' + 'against other humans!' + ) + return + league_name = 'major-league' + else: + await interaction.edit_original_response( + content='Hall of Fame games are coming next season! For now, you can play an Unlimited Minor League ' + 'game or against other humans!' + ) + return + league_name = 'hall-of-fame' + + this_game = post_game({ + 'away_team_id': away_team['id'], + 'home_team_id': home_team['id'], + 'week_num': week_num, + 'channel_id': interaction.channel.id, + 'active': True, + 'is_pd': True, + 'ranked': False, + 'season': current['season'], + 'short_game': True if num_innings == 3 else False, + 'game_type': league_name + }) + logging.info( + f'Game {this_game.id} between {away_team_abbrev.upper()} and {home_team_abbrev.upper()} is posted!' + ) + away_role = await team_role(interaction, away_team) + home_role = await team_role(interaction, home_team) + + await interaction.edit_original_response( + content=f'I\'m getting bullpen data from ' + f'{away_team["gmname"] if away_team["is_ai"] else home_team["gmname"]}...' + ) + this_pen = get_or_create_bullpen(ai_team, self.bot) + if None in [this_pen.closer_id, this_pen.setup_id, this_pen.middle_one_id, this_pen.middle_two_id, + this_pen.middle_three_id]: + await interaction.edit_original_response( + content=f'Looks like the {ai_team["sname"]} bullpen didn\'t come through clearly. I\'ll sort ' + f'this out with {ai_team["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' + f'this game - why don\'t you play against somebody else for now?' + ) + patch_game(this_game.id, active=False) + return + + try: + # view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + # view.confirm.label = 'Lefty' + # view.confirm.style = discord.ButtonStyle.primary + # view.cancel.label = 'Righty' + # view.cancel.style = discord.ButtonStyle.blurple + # question = await interaction.channel.send( + # f'**{ai_team["gmname"]}** asks, "is your starting pitcher a lefty or righty?"', + # view=view + # ) + # await view.wait() + # + # roster_num = 1 + # if view.value: + # lineup_num = 2 + # else: + # lineup_num = 1 + # await question.delete() + + await interaction.edit_original_response( + content=f'I am getting a lineup card from the {ai_team["sname"]}...' + ) + + # Google Sheets Method ################# + # lineup_cells = get_roster_lineups(ai_team, self.bot, roster_num, lineup_num) + # await asyncio.sleep(1) + # + # all_lineups = [] + # all_pos = [] + # for index, row in enumerate(lineup_cells): + # if '' in row: + # break + # + # if row[0].upper() not in all_pos: + # all_pos.append(row[0].upper()) + # else: + # raise SyntaxError(f'You have a duplicate card {row[0].upper()} in this lineup. Please ' + # f'update and set the lineup again.') + # + # this_card = db_get(f'cards', object_id=int(row[1])) + # if this_card['team']['id'] != ai_team['id']: + # raise SyntaxError(f'Easy there, champ. Looks like card ID {row[1]} belongs to the ' + # f'{this_card["team"]["sname"]}. Try again with only cards you own.') + # player_id = this_card['player']['player_id'] + # card_id = row[1] + # + # this_lineup = { + # 'game_id': this_game.id, + # 'team_id': ai_team['id'], + # 'player_id': player_id, + # 'card_id': card_id, + # 'position': row[0].upper(), + # 'batting_order': index + 1, + # 'after_play': 0 + # } + # + # all_lineups.append(this_lineup) + + logging.info(f'new-game - calling lineup for {ai_team["abbrev"]}') + all_lineups = ai_manager.build_lineup(ai_team, this_game.id, league_name) + logging.info(f'new-game - got lineup for {ai_team["abbrev"]}') + + except Exception as e: + patch_game(this_game.id, active=False) + logging.error(f'could not start an AI game with {ai_team["sname"]}: {e}') + await interaction.edit_original_response( + content=f'Looks like the {ai_team["sname"]} lineup card didn\'t come through clearly. I\'ll sort ' + f'this out with {ai_team["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' + f'this game - why don\'t you play against somebody else for now?' + ) + return + + try: + await interaction.edit_original_response( + content=f'Now to decide on a Starting Pitcher...' + ) + if ai_team['id'] == this_game.away_team_id: + patch_game(this_game.id, away_roster_num=69, ai_team='away') + else: + patch_game(this_game.id, home_roster_num=69, ai_team='home') + + # starter = starting_pitcher(ai_team, self.bot, True if home_team['is_ai'] else False) + starter = ai_manager.get_starting_pitcher(ai_team, this_game.id, True if home_team['is_ai'] else False) + all_lineups.append(starter) + + this_card = db_get(f'cards', object_id=starter['card_id']) + await interaction.channel.send( + content=f'The {ai_team["sname"]} are starting **{this_card["player"]["description"]}**:\n\n' + f'{this_card["player"]["image"]}' + ) + + except Exception as e: + patch_game(this_game.id, active=False) + logging.error(f'could not start an AI game with {ai_team["sname"]}: {e}') + await interaction.edit_original_response( + content=f'Looks like the {ai_team["sname"]} rotation didn\'t come through clearly. I\'ll sort ' + f'this out with {ai_team["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' + f'this game - why don\'t you play against somebody else for now?' + ) + return + + logging.debug(f'Setting lineup for {ai_team["sname"]} in PD game') + post_lineups(all_lineups) + + await interaction.channel.send( + content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' + f'Go ahead and set lineups with the `/readlineup` command!', + embed=await self.get_game_state_embed(this_game, full_length=False) + ) + return + + # if not this_game.first_message: + # logging.debug(f'saving first_message: {interaction.channel_id}') + # patch_game(this_game.id, first_message=interaction.channel_id) + + @group_new_game.command(name='ranked', description='Start a new Ranked game against another human') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + async def new_game_command( + self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str, + num_innings: Literal[9, 3]): + await interaction.response.defer() + + conflict = get_one_game(channel_id=interaction.channel.id, active=True) + if conflict: + await interaction.edit_original_response( + content=f'Ope. There is already a game going on in this channel. Please wait for it to complete ' + f'before starting a new one.') + return + + try: + if interaction.channel.category.name != 'Public Fields': + await interaction.response.send_message( + f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' + f'pops up?' + ) + return + except Exception as e: + logging.error(f'Could not check channel category: {e}') + + away_team = get_team_by_abbrev(away_team_abbrev) + home_team = get_team_by_abbrev(home_team_abbrev) + + if not away_team: + await interaction.edit_original_response( + content=f'Sorry, I don\'t know who **{away_team_abbrev.upper()}** is.' + ) + return + if not home_team: + await interaction.edit_original_response( + content=f'Sorry, I don\'t know who **{home_team_abbrev.upper()}** is.' + ) + return + + if away_team['is_ai'] or home_team['is_ai']: + logging.error(f'Ranked game between {away_team["abbrev"]} and {home_team["abbrev"]} has an AI') + await interaction.edit_original_response( + content=f'Only human vs human games can be ranked - run `/new-game` again and double-check the ' + f'game type you want! If you have questions, feel free to post up in #paper-dynasty-chat' + ) + return + + for x in [away_team, home_team]: + if not x['is_ai']: + conflict = count_team_games(x['id']) + if conflict['count']: + await interaction.edit_original_response( + content=f'Ope. The {x["sname"]} are already playing over in ' + f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' + ) + return + + current = db_get('current') + week_num = current['week'] + logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: True') + + if interaction.user.id not in [away_team['gmid'], home_team['gmid']]: + await interaction.edit_original_response( + content='You can only start a new game if you GM one of the teams.' + ) + return + + this_game = post_game({ + 'away_team_id': away_team['id'], + 'home_team_id': home_team['id'], + 'week_num': week_num, + 'channel_id': interaction.channel.id, + 'active': True, + 'is_pd': True, + 'ranked': True, + 'season': current['season'], + 'short_game': True if num_innings == 3 else False, + 'game_type': 'ranked' + }) + logging.info( + f'Game {this_game.id} between {away_team_abbrev.upper()} and {home_team_abbrev.upper()} is posted!' + ) + away_role = await team_role(interaction, away_team) + home_role = await team_role(interaction, home_team) + + await interaction.channel.send( + content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' + f'Go ahead and set lineups with the `/readlineup` command!', + embed=await self.get_game_state_embed(this_game, full_length=False) + ) + return + + @group_new_game.command(name='unlimited', description='Start a new Unlimited game against another human') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + async def new_game_command( + self, interaction: discord.Interaction, away_team_abbrev: str, home_team_abbrev: str, + num_innings: Literal[9, 3]): + await interaction.response.defer() + + conflict = get_one_game(channel_id=interaction.channel.id, active=True) + if conflict: + await interaction.edit_original_response( + content=f'Ope. There is already a game going on in this channel. Please wait for it to complete ' + f'before starting a new one.') + return + + try: + if interaction.channel.category.name != 'Public Fields': + await interaction.response.send_message( + f'Why don\'t you head down to one of the Public Fields that way other humans can help if anything ' + f'pops up?' + ) + return + except Exception as e: + logging.error(f'Could not check channel category: {e}') + + away_team = get_team_by_abbrev(away_team_abbrev) + home_team = get_team_by_abbrev(home_team_abbrev) + + if not away_team: + await interaction.edit_original_response( + content=f'Sorry, I don\'t know who **{away_team_abbrev.upper()}** is.' + ) + return + if not home_team: + await interaction.edit_original_response( + content=f'Sorry, I don\'t know who **{home_team_abbrev.upper()}** is.' + ) + return + + if away_team['is_ai'] or home_team['is_ai']: + logging.error(f'Ranked game between {away_team["abbrev"]} and {home_team["abbrev"]} has an AI') + await interaction.edit_original_response( + content=f'This command is for human v human games - run `/new-game` again and double-check the ' + f'game type you want! If you have questions, feel free to post up in #paper-dynasty-chat' + ) + return + + for x in [away_team, home_team]: + if not x['is_ai']: + conflict = count_team_games(x['id']) + if conflict['count']: + await interaction.edit_original_response( + content=f'Ope. The {x["sname"]} are already playing over in ' + f'{interaction.guild.get_channel(conflict["games"][0]["channel_id"]).mention}' + ) + return + + current = db_get('current') + week_num = current['week'] + logging.debug(f'away: {away_team} / home: {home_team} / week: {week_num} / ranked: True') + + if interaction.user.id not in [away_team['gmid'], home_team['gmid']]: + await interaction.edit_original_response( + content='You can only start a new game if you GM one of the teams.' + ) + return + + this_game = post_game({ + 'away_team_id': away_team['id'], + 'home_team_id': home_team['id'], + 'week_num': week_num, + 'channel_id': interaction.channel.id, + 'active': True, + 'is_pd': True, + 'ranked': False, + 'season': current['season'], + 'short_game': True if num_innings == 3 else False, + 'game_type': 'unlimited' + }) + logging.info( + f'Game {this_game.id} between {away_team_abbrev.upper()} and {home_team_abbrev.upper()} is posted!' + ) + away_role = await team_role(interaction, away_team) + home_role = await team_role(interaction, home_team) + + await interaction.channel.send( + content=f'{away_role.mention} @ {home_role.mention} is set!\n\n' + f'Go ahead and set lineups with the `/readlineup` command!', + embed=await self.get_game_state_embed(this_game, full_length=False) + ) + return + + @commands.command(name='force-endgame', help='Mod: Force a game to end without stats') + @commands.is_owner() + async def force_end_game_command(self, ctx: commands.Context): + this_game = get_one_game(channel_id=ctx.channel.id, active=True) + + try: + await ctx.send(content=None, embed=await self.get_game_state_embed(this_game)) + except Exception as e: + logging.error(f'could not post game state embed: {e}') + question = await ctx.send( + f'Something is very borked here and I can\'t post the embed. Imma nuke this game now...' + ) + patch_game(this_game.id, active=False) + await question.edit(content='Done and dusted.', view=None) + return + + view = Confirm(responders=[ctx.author], timeout=60, label_type='yes') + question = await ctx.send(f'Should I nuke this game?', view=view) + await view.wait() + + if view.value: + patch_game(this_game.id, active=False) + await question.edit(content='It\'s gone.', view=None) + else: + await question.edit(content='It stays.', view=None) + + @commands.command(name='check-decisions', help='Mod: Calculate pitching decisions of current game') + @commands.is_owner() + async def check_decisions_command(self, ctx: commands.Context): + this_game = get_one_game(channel_id=ctx.channel.id, active=True) + get_pitching_decisions(this_game) + await ctx.send(random_conf_gif()) + + @commands.hybrid_command(name='endgame', help='End game in this channel') + @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) + async def end_game_command(self, ctx: commands.Context): + this_game = get_one_game(channel_id=ctx.channel.id, active=True) + if not this_game: + await ctx.send(f'Ope, I don\'t see a game in this channel.') + return + logging.info(f'Ending Game {this_game.id}') + + response = await ctx.send(f'Let\'s see what we\'ve got here...') + + owner_team = await get_game_team(this_game, ctx.author.id) + if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id] and \ + ctx.author.id != self.bot.owner_id: + await ctx.send('Bruh. Only GMs of the active teams can end games.') + return + + await response.edit(content=None, embed=await self.get_game_state_embed(this_game)) + view = Confirm(responders=[ctx.author], timeout=60, label_type='yes') + question = await ctx.send(f'Should I end this game?', view=view) + await view.wait() + + if view.value: + await question.edit(content='I\'ll tally the scorecard now...', view=None) + else: + await question.edit(content='It stays.', view=None) + return + + away_team = db_get('teams', object_id=this_game.away_team_id) + home_team = db_get('teams', object_id=this_game.home_team_id) + + away_stats = { + # 'p_lines': get_pitching_stats(this_game.id, team_id=away_team['id']), + 'p_lines': [], + 'b_lines': get_batting_stats(this_game.id, team_id=away_team['id']), + 'f_lines': get_fielding_stats(this_game.id, team_id=away_team['id']) + } + home_stats = { + # 'p_lines': get_pitching_stats(this_game.id, team_id=home_team['id']), + 'p_lines': [], + 'b_lines': get_batting_stats(this_game.id, team_id=home_team['id']), + 'f_lines': get_fielding_stats(this_game.id, team_id=home_team['id']), + # 'score': away_stats['p_lines'][0]['tm_runs'] + } + + logging.debug(f'away_stats: {away_stats}\n\nhome_stats: {home_stats}') + + away_pitchers = await get_team_lineups( + this_game.id, team_id=away_team['id'], inc_inactive=True, pitchers_only=True, as_string=False + ) + for line in away_pitchers: + try: + # logging.info(f'line: {line}') + this_stats = get_pitching_stats(this_game.id, lineup_id=line.id) + # logging.info(f'away / this_stats: {this_stats}') + away_stats['p_lines'].extend(this_stats) + if 'score' not in home_stats: + # logging.info(f'score not in home_stats') + home_stats['score'] = this_stats[0]['pl_runs'] + else: + # logging.info(f'score is in home_stats') + home_stats['score'] += this_stats[0]['pl_runs'] + if 'hits' not in home_stats: + # logging.info(f'hits not in home_stats') + home_stats['hits'] = this_stats[0]['pl_hit'] + else: + # logging.info(f'hits is in home_stats') + home_stats['hits'] += this_stats[0]['pl_hit'] + except Exception as e: + bad_player = await get_player(this_game, line) + logging.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: ' + f'{type(e)}: {e}') + await ctx.send(f'I was not able to process stats for {bad_player["name"]} - {get_cal_user(ctx).mention}' + f' help!') + + home_pitchers = await get_team_lineups( + this_game.id, team_id=home_team['id'], inc_inactive=True, pitchers_only=True, as_string=False + ) + for line in home_pitchers: + try: + # logging.info(f'line: {line}') + this_stats = get_pitching_stats(this_game.id, lineup_id=line.id) + # logging.info(f'home / this_stats: {this_stats}') + home_stats['p_lines'].extend(this_stats) + if 'score' not in away_stats: + # logging.info(f'score not in away_stats') + away_stats['score'] = this_stats[0]['pl_runs'] + else: + # logging.info(f'score is in away_stats') + away_stats['score'] += this_stats[0]['pl_runs'] + if 'hits' not in away_stats: + # logging.info(f'hits not in away_stats') + away_stats['hits'] = this_stats[0]['pl_hit'] + else: + # logging.info(f'hits is in away_stats') + away_stats['hits'] += this_stats[0]['pl_hit'] + except Exception as e: + bad_player = await get_player(this_game, line) + logging.error(f'Unable to process stats for card_id {line.card_id} in Game {this_game.id}: ' + f'{type(e)}: {e}') + await ctx.send(f'I was not able to process stats for {bad_player["name"]} - {get_cal_user(ctx).mention}' + f' help!') + + logging.debug(f'finished tallying pitcher stats') + + # away_stats['score'] = home_stats['p_lines'][0]['tm_runs'] + try: + decisions = get_pitching_decisions(this_game) + except AttributeError as e: + logging.error(f'Could not pull decisions for Game {this_game.id}: {e}') + await ctx.send(f'I was not able to calculate the Winning and Losing Pitcher for this game. Is the game ' + f'ending early? {get_cal_user(ctx).mention} can probably help.') + return + logging.debug(f'decisions: {decisions}') + + wr_query = db_get( + 'gamerewards', params=[('name', f'{"Short" if this_game.short_game else "Full"} Game Win')]) + lr_query = db_get( + 'gamerewards', params=[('name', f'{"Short" if this_game.short_game else "Full"} Game Loss')]) + if not wr_query['count'] or not lr_query['count']: + raise KeyError(f'Game Rewards were not found. Leaving this game active.') + win_reward = wr_query['gamerewards'][0] + loss_reward = lr_query['gamerewards'][0] + winning_team = away_team if away_stats['score'] > home_stats['score'] else home_team + losing_team = away_team if away_stats['score'] < home_stats['score'] else home_team + + logging.debug(f'away_stats (in /endgame function)\n\n{away_stats}') + logging.debug(f'home_stats (in /endgame function)\n\n{home_stats}') + logging.debug(f'winning_team: {winning_team}\nlosing_team: {losing_team}') + + if this_game.is_pd: + logging.debug(f'Time to build statlines and submit the scorecard for this PD game!') + # Post result + success = db_post( + 'results', + payload={ + 'away_team_id': this_game.away_team_id, + 'home_team_id': this_game.home_team_id, + 'away_score': away_stats['score'], + 'home_score': home_stats['score'], + 'away_team_ranking': away_team['ranking'], + 'home_team_ranking': home_team['ranking'], + 'scorecard': f'Bot Game {this_game.id}', + 'week': this_game.week_num, + 'season': this_game.season, + 'ranked': this_game.ranked, + 'short_game': this_game.short_game, + 'game_type': this_game.game_type + } + ) + # Submit the stats + batter_stats = [] + pitcher_stats = [] + doubles = '' + triples = '' + homers = '' + s_bases = '' + caught_s = '' + + for line in [*away_stats['b_lines'], *home_stats['b_lines']]: + if line['pl_double']: + if len(doubles): + doubles += ', ' + doubles += f'{db_get("cards", object_id=line["card_id"])["player"]["p_name"]}' \ + f'{" " if line["pl_double"] > 1 else ""}' \ + f'{line["pl_double"] if line["pl_double"] > 1 else ""}' + if line['pl_triple']: + if len(triples): + triples += ', ' + triples += f'{db_get("cards", object_id=line["card_id"])["player"]["p_name"]}' \ + f'{" " if line["pl_triple"] > 1 else ""}' \ + f'{line["pl_triple"] if line["pl_triple"] > 1 else ""}' + if line['pl_homerun']: + if len(homers): + homers += ', ' + homers += f'{db_get("cards", object_id=line["card_id"])["player"]["p_name"]}' \ + f'{" " if line["pl_homerun"] > 1 else ""}' \ + f'{line["pl_homerun"] if line["pl_homerun"] > 1 else ""}' + if line['pl_sb']: + if len(s_bases): + s_bases += ', ' + s_bases += f'{db_get("cards", object_id=line["card_id"])["player"]["p_name"]}' \ + f'{" " if line["pl_sb"] > 1 else ""}' \ + f'{line["pl_sb"] if line["pl_sb"] > 1 else ""}' + batter_stats.append( + { + 'card_id': line['card_id'], + 'team_id': line['team_id'], + 'roster_num': + this_game.away_roster_num if this_game.away_team_id == line['team_id'] + else this_game.home_roster_num, + 'vs_team_id': + home_team['id'] if this_game.away_team_id == line['team_id'] + else away_team['id'], + 'pos': line['pos'], + 'pa': line['pl_pa'], + 'ab': line['pl_ab'], + 'run': line['pl_run'], + 'rbi': line['pl_rbi'], + 'hit': line['pl_hit'], + 'double': line['pl_double'], + 'triple': line['pl_triple'], + 'hr': line['pl_homerun'], + 'bb': line['pl_bb'], + 'so': line['pl_so'], + 'hbp': line['pl_hbp'], + 'sac': line['pl_sac'], + 'ibb': line['pl_ibb'], + 'gidp': line['pl_gidp'], + 'sb': line['pl_sb'], + 'cs': line['pl_cs'], + 'bphr': line['pl_bphr'], + 'bpfo': line['pl_bpfo'], + 'bp1b': line['pl_bp1b'], + 'bplo': line['pl_bplo'], + 'week': this_game.week_num, + 'season': this_game.season, + 'game_id': this_game.id, + } + ) + + for line in [*away_stats['f_lines'], *home_stats['f_lines']]: + if line['pl_csc']: + if len(caught_s): + caught_s += ', ' + caught_s += f'{db_get("cards", object_id=line["card_id"])["player"]["p_name"]}' \ + f'{" " if line["pl_csc"] > 1 else ""}' \ + f'{line["pl_csc"] if line["pl_csc"] > 1 else ""}' + batter_stats.append( + { + 'card_id': line['card_id'], + 'team_id': line['team_id'], + 'roster_num': + this_game.away_roster_num if this_game.away_team_id == line['team_id'] + else this_game.home_roster_num, + 'vs_team_id': + home_team['id'] if this_game.away_team_id == line['team_id'] + else away_team['id'], + 'pos': line['pos'], + 'xch': line['pl_xch'], + 'xhit': line['pl_xhit'], + 'error': line['pl_error'], + 'pb': line['pl_pb'], + 'sbc': line['pl_sbc'], + 'csc': line['pl_csc'], + 'week': this_game.week_num, + 'season': this_game.season, + 'game_id': this_game.id + } + ) + + for line in [*away_stats['p_lines'], *home_stats['p_lines']]: + pitcher_stats.append( + { + 'card_id': line['card_id'], + 'team_id': line['team_id'], + 'roster_num': + this_game.away_roster_num if this_game.away_team_id == line['team_id'] + else this_game.home_roster_num, + 'vs_team_id': + home_team['id'] if this_game.away_team_id == line['team_id'] + else away_team['id'], + 'ip': (math.floor(line['pl_outs'] / 3) * 1.0) + ((line['pl_outs'] % 3) / 3.0), + 'hit': line['pl_hit'], + 'run': line['pl_runs'], + 'erun': line['pl_eruns'], + 'so': line['pl_so'], + 'bb': line['pl_bb'], + 'hbp': line['pl_hbp'], + 'wp': line['pl_wild_pitch'], + 'balk': line['pl_balk'], + 'hr': line['pl_homerun'], + 'ir': 0, + 'irs': 0, + 'gs': 1 if line['card_id'] in decisions['starters'] else 0, + 'win': 1 if line['card_id'] == decisions['winner'] else 0, + 'loss': 1 if line['card_id'] == decisions['loser'] else 0, + 'hold': 1 if line['card_id'] in decisions['holds'] else 0, + 'sv': 1 if line['card_id'] == decisions['save'] else 0, + 'bsv': 1 if line['card_id'] in decisions['b_save'] else 0, + 'week': this_game.week_num, + 'season': this_game.season, + 'game_id': this_game.id + } + ) + + doubles += '\n' if len(doubles) else '' + triples += '\n' if len(triples) else '' + homers += '\n' if len(homers) else '' + s_bases += '\n' if len(s_bases) else '' + caught_s += '\n' if len(caught_s) else '' + + db_post('batstats', payload={'stats': batter_stats}) + db_post('pitstats', payload={'stats': pitcher_stats}) + + # Post a notification to PD + last_play = get_current_play(this_game.id) + inning = f'{last_play.inning_num if last_play.inning_half == "Bot" else last_play.inning_num - 1}' + embed = get_team_embed( + f'{away_team["lname"]} {away_stats["score"]} @ {home_stats["score"]} {home_team["lname"]} - F/' + f'{inning}', + winning_team + ) + embed.description = f'Score Report - {"Ranked" if this_game.ranked else "Unlimited"} ' \ + f'{"- 3-Inning Game" if this_game.short_game else " - 9-Inning Game"}' + embed.add_field( + name='Box Score', + value=get_final_scorebug(away_team, home_team, away_stats, home_stats), + inline=False + ) + embed.add_field( + name='Location', + value=f'{ctx.guild.get_channel(this_game.channel_id).mention}' + ) + embed.add_field( + name='Pitching', + value=f'Win: {db_get("cards", object_id=decisions["winner"])["player"]["p_name"]}\n' + f'Loss: {db_get("cards", object_id=decisions["loser"])["player"]["p_name"]}\n' + f'{"Save: " if decisions["save"] else ""}' + f'{db_get("cards", object_id=decisions["save"])["player"]["p_name"] if decisions["save"] else ""}', + inline=False + ) + if len(doubles) + len(triples) + len(homers) > 0: + embed.add_field( + name='Batting', + value=f'{"2B: " if len(doubles) else ""}{doubles if len(doubles) else ""}' + f'{"3B: " if len(triples) else ""}{triples if len(triples) else ""}' + f'{"HR: " if len(homers) else ""}{homers if len(homers) else ""}', + inline=False + ) + if len(s_bases) + len(caught_s) > 0: + embed.add_field( + name='Baserunning', + value=f'{"SB: " if len(s_bases) else ""}{s_bases if len(s_bases) else ""}' + f'{"CSc: " if len(caught_s) else ""}{caught_s if len(caught_s) else ""}', + inline=False + ) + embed.add_field( + name=f'{winning_team["abbrev"]} Rewards', + value=f'1x {win_reward["pack_type"]["name"]} Pack\n{win_reward["money"]}₼' + ) + embed.add_field( + name=f'{losing_team["abbrev"]} Rewards', + value=f'{loss_reward["money"]}₼' + ) + embed.add_field( + name='Highlights', + value=f'Please share the highlights in {get_channel(ctx, "pd-news-ticker").mention}!', + inline=False + ) + await send_to_channel(self.bot, 'pd-network-news', embed=embed) + + # Post rewards + give_packs(winning_team, num_packs=1, pack_type=win_reward['pack_type']) + db_post(f'teams/{winning_team["id"]}/money/{win_reward["money"]}') + db_post(f'teams/{losing_team["id"]}/money/{loss_reward["money"]}') + else: + logging.debug(f'Time to build statlines and share the scorecard for this SBa game!') + # Send stats to sheets + # Post sheets link + pass + + patch_game(this_game.id, active=False) + + logging.info(f'Game {this_game.id} is complete') + await ctx.send(f'Good game! Go share the highlights in {get_channel(ctx, "pd-news-ticker").mention}!') + + @app_commands.command( + name='setlineup', + description='Manually set lineups for for SBa games', + ) + @app_commands.describe( + order_1_player='SBa: Player Name; PD: Card #', + order_1_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_2_player='SBa: Player Name; PD: Card #', + order_2_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_3_player='SBa: Player Name; PD: Card #', + order_3_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_4_player='SBa: Player Name; PD: Card #', + order_4_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_5_player='SBa: Player Name; PD: Card #', + order_5_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_6_player='SBa: Player Name; PD: Card #', + order_6_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_7_player='SBa: Player Name; PD: Card #', + order_7_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_8_player='SBa: Player Name; PD: Card #', + order_8_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_9_player='SBa: Player Name; PD: Card #', + order_9_pos='C for Catcher, 1B for first base, P for pitcher, etc.', + order_10_player='SBa: Player Name; PD: Card #; only used with a DH', + order_10_pos='P for pitcher; only used with a DH' + ) + @app_commands.checks.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) + async def set_lineup_command( + self, interaction: discord.Interaction, team_abbrev: str, order_1_player: str, order_1_pos: str, + order_2_player: str, order_2_pos: str, order_3_player: str, order_3_pos: str, order_4_player: str, + order_4_pos: str, order_5_player: str, order_5_pos: str, order_6_player: str, order_6_pos: str, + order_7_player: str, order_7_pos: str, order_8_player: str, order_8_pos: str, order_9_player: str, + order_9_pos: str, order_10_player: Optional[str] = None, order_10_pos: Optional[str] = 'P'): + this_game = get_one_game(channel_id=interaction.channel_id, active=True) + if not this_game: + await interaction.response.send_message(f'I dont\'t see an active game in this channel!') + return + + owner_team = await get_game_team(this_game, interaction.user.id) + lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) + + if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + await interaction.response.send_message('Bruh. Only GMs of the active teams can set lineups.') + return + if not lineup_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + await interaction.response.send_message( + f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and try again' + ) + return + + existing_lineups = await get_team_lineups(this_game.id, lineup_team['id']) + if not existing_lineups or not len(existing_lineups): + await interaction.response.send_message( + f'It looks like the {lineup_team["sname"]} already have a lineup. Run `/substitution` to make changes.' + ) + return + + await interaction.response.defer() + + all_lineups = [] + all_pos = [] + lineup_data = [ + (order_1_pos, order_1_player), (order_2_pos, order_2_player), (order_3_pos, order_3_player), + (order_4_pos, order_4_player), (order_5_pos, order_5_player), (order_6_pos, order_6_player), + (order_7_pos, order_7_player), (order_8_pos, order_8_player), (order_9_pos, order_9_player), + (order_10_pos, order_10_player) + ] + for index, pair in enumerate(lineup_data): + if not pair[1]: + break + + if pair[0].upper() not in all_pos: + all_pos.append(pair[0].upper()) + else: + raise SyntaxError(f'You have more than one {pair[0].upper()} in this lineup. Please update and set the ' + f'lineup again.') + + if this_game.is_pd: + this_card = db_get(f'cards', object_id=int(pair[1])) + if this_card['team']['id'] != lineup_team['id']: + raise SyntaxError(f'Easy there, champ. Looks like card ID {pair[1]} belongs to the ' + f'{this_card["team"]["sname"]}. Try again with only cards you own.') + player_id = this_card['player']['player_id'] + card_id = pair[1] + else: + this_player = get_sba_player(pair[1], season=SBA_SEASON) + if this_player['team']['id'] != lineup_team['id']: + raise SyntaxError(f'Easy there, champ. Looks like {pair[1]} is on ' + f'{this_player["team"]["sname"]}. Try again with only your own players.') + player_id = this_player['id'] + card_id = None + + this_lineup = { + 'game_id': this_game.id, + 'team_id': lineup_team['id'], + 'player_id': player_id, + 'card_id': card_id, + 'position': pair[0].upper(), + 'batting_order': index + 1, + 'after_play': 0 + } + + all_lineups.append(this_lineup) + + if len(all_lineups) < 9: + await interaction.edit_original_response( + content=f'Looks like the {lineup_team["sname"]} lineup card didn\'t come through clearly. I\'ll sort ' + f'this out with {lineup_team["gmname"]} and {get_cal_user(interaction).mention}. I\'ll end ' + f'this game - why don\'t you play against somebody else for now?' + ) + patch_game(this_game.id, active=False) + return + + logging.info(f'Setting lineup for {owner_team["sname"]} in {"PD" if this_game.is_pd else "SBa"} game') + post_lineups(all_lineups) + + try: + await interaction.edit_original_response(content=None, embed=await self.get_game_state_embed(this_game)) + except IntegrityError as e: + logging.debug(f'Unable to pull game_state for game_id {this_game.id} until both lineups are in: {e}') + await interaction.response.send_message(f'Game state will be posted once both lineups are in') + return + + # @app_commands.command(name='starting-pitcher') + # @app_commands.checks.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) + # async def starting_pitcher_command( + # self, interaction: discord.Interaction, team_abbrev: str, sp_card_id: int, batting_order: int = 10): + # this_game = get_one_game(channel_id=interaction.channel_id, active=True) + # if not this_game: + # await interaction.response.send_message(f'I dont\'t see an active game in this channel!') + # return + # + # await interaction.response.defer() + # + # owner_team = await get_game_team(this_game, interaction.user.id) + # lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) + # this_card = db_get(f'cards', object_id=sp_card_id) + # if this_card['team']['id'] != lineup_team['id']: + # await interaction.edit_original_response( + # content=f'Uh oh, it looks like that copy of {this_card["player"]["p_name"]} doesn\'t belong to you. ' + # f'Please enter the card_id of a card from your collection.' + # ) + # return + # + # this_lineup = { + # 'game_id': this_game.id, + # 'team_id': lineup_team['id'], + # 'player_id': this_card['player']['player_id'], + # 'card_id': sp_card_id, + # 'position': 'P', + # 'batting_order': batting_order, + # 'after_play': 0 + # } + # post_lineups([this_lineup]) + # + # try: + # await interaction.edit_original_response(content=None, embed=await self.get_game_state_embed(this_game)) + # except IntegrityError as e: + # logging.debug(f'Unable to pull game_state for game_id {this_game.id} until both lineups are in: {e}') + # await interaction.response.send_message(f'Game state will be posted once both lineups are in') + # return + + @app_commands.command( + name='readlineup', + description='Import a saved lineup directly from the team sheet for PD games' + ) + @app_commands.describe( + team_abbrev='The 2- to 4-digit abbreviation for the team being set', + roster_num='1, 2, or 3: 1 is Primary, 2 is Secondary, 3 is Tertiary; Enter 1 for AI team', + lineup_num='1 or 2: 1 is top lineup in sheet, 2 is bottom; Enter 1 for AI team', + sp_card_id='Light gray number to the left of the pitcher\'s name on your depth chart' + ) + @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) + async def read_lineup_command( + self, interaction: discord.Interaction, team_abbrev: str, roster_num: Literal[1, 2, 3], + lineup_num: Literal[1, 2], sp_card_id: int): + this_game = get_one_game(channel_id=interaction.channel_id, active=True) + if not this_game: + await interaction.response.send_message(f'I dont\'t see an active game in this channel!') + return + + if not this_game.is_pd: + await interaction.response.send_message(f'Lineup imports are only supported for PD games right now.' + f'Please use the `/setlineup` command.') + return + + owner_team = await get_game_team(this_game, interaction.user.id) + lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) + + if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + await interaction.response.send_message('Bruh. Only GMs of the active teams can set lineups.') + return + if not lineup_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + await interaction.response.send_message( + f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and try again' + ) + return + + existing_lineups = await get_team_lineups(this_game.id, lineup_team['id']) + if existing_lineups: + await interaction.response.send_message( + f'It looks like the {lineup_team["sname"]} already have a lineup. Run `/substitution` to make changes.' + ) + return + + await interaction.response.send_message(f'Let\'s put this lineup card together...') + lineup_cells = get_roster_lineups( + lineup_team, + self.bot, + roster_num, + lineup_num if not lineup_team['is_ai'] else 1 + ) + + all_lineups = [] + all_pos = [] + for index, row in enumerate(lineup_cells): + if '' in row: + break + + if row[0].upper() not in all_pos: + all_pos.append(row[0].upper()) + else: + raise SyntaxError(f'You have more than one {row[0].upper()} in this lineup. Please ' + f'update and set the lineup again.') + + this_card = db_get(f'cards', object_id=int(row[1])) + if this_card['team']['id'] != lineup_team['id']: + raise SyntaxError(f'Easy there, champ. Looks like card ID {row[1]} belongs to the ' + f'{this_card["team"]["sname"]}. Try again with only cards you own.') + player_id = this_card['player']['player_id'] + card_id = row[1] + + this_lineup = { + 'game_id': this_game.id, + 'team_id': lineup_team['id'], + 'player_id': player_id, + 'card_id': card_id, + 'position': row[0].upper(), + 'batting_order': index + 1, + 'after_play': 0 + } + + all_lineups.append(this_lineup) + + if lineup_team['id'] == this_game.away_team_id: + patch_game(this_game.id, away_roster_num=roster_num) + else: + patch_game(this_game.id, home_roster_num=roster_num) + + if lineup_team['is_ai']: + starter = starting_pitcher(lineup_team, self.bot, True if this_game.ai_team == 'home' else False) + this_card = db_get(f'cards', object_id=int(starter)) + all_lineups.append({ + 'game_id': this_game.id, + 'team_id': lineup_team['id'], + 'player_id': this_card['player']['player_id'], + 'card_id': starter, + 'position': 'P', + 'batting_order': 10, + 'after_play': 0 + }) + all_pos.append('P') + else: + this_card = db_get(f'cards', object_id=sp_card_id) + logging.debug(f'this_card: {this_card}') + + if this_card['team']['id'] != lineup_team['id']: + logging.error( + f'Card_id {sp_card_id} does not belong to {lineup_team["abbrev"]} in Game {this_game.id}' + ) + await interaction.channel.send( + f'Uh oh. Card ID {sp_card_id} is {this_card["player"]["p_name"]} and belongs to ' + f'{this_card["team"]["sname"]}. Will you double check that before we get started?') + return + + else: + all_lineups.append({ + 'game_id': this_game.id, + 'team_id': lineup_team['id'], + 'player_id': this_card['player']['player_id'], + 'card_id': sp_card_id, + 'position': 'P', + 'batting_order': 10, + 'after_play': 0 + }) + all_pos.append('P') + + logging.debug(f'Setting lineup for {lineup_team["sname"]} in PD game') + post_lineups(all_lineups) + + # while True: + # # Get Starting Pitcher + # this_q = Question( + # self.bot, interaction.channel, f'Please enter the card_id of the {lineup_team["sname"]} SP', + # 'int', 45 + # ) + # resp = await this_q.ask([interaction.user]) + # + # if not resp: + # await interaction.edit_original_response( + # content='Please enter the Starting Pitcher using the `/substitution` command' + # ) + # return + # else: + + if 'P' not in all_pos: + await interaction.channel.send( + content=f'Please enter the Starting pitcher using the `/starting-pitcher` command' + ) + return + + try: + await interaction.channel.send(content=None, embed=await self.get_game_state_embed(this_game)) + except IntegrityError as e: + logging.debug(f'Unable to pull game_state for game_id {this_game.id} until both lineups are in: {e}') + await interaction.response.send_message(f'Game state will be posted once both lineups are in') + return + + @commands.command(name='get-bullpen', help='Mod: Sync an AI bullpen') + @commands.is_owner() + async def get_bullpen_command(self, ctx: commands.Context, team_id): + team = db_get('teams', object_id=team_id) + if not team: + await ctx.send(f'I did not find a team with id {team_id}') + return + + await ctx.send(f'I\'m getting bullpen data from {team["gmname"]}...') + get_or_create_bullpen(team, self.bot) + + await ctx.send(f'Got it!') + + @commands.hybrid_command( + name='substitution', + help='Make a lineup substitution; Player Name for SBa games / Card ID for PD games', + aliases=['sub'] + ) + @app_commands.describe( + order_number='Batting order of new player; 10 for pitchers if there is a DH', + new_player='For PD game: enter the Card ID (a number); or SBa game: enter the Player Name') + @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) + async def substitution_command( + self, ctx: commands.Context, team_abbrev: str, new_player: str, order_number: Literal[ + 'this-spot', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + new_pos: Literal['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH', 'PH', 'PR']): + this_game = get_one_game(channel_id=ctx.channel.id, active=True) + if not this_game: + await ctx.send(f'I dont\'t see an active game in this channel!') + return + + owner_team = await get_game_team(this_game, ctx.author.id) + lineup_team = await get_game_team(this_game, team_abbrev=team_abbrev) + + if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + await ctx.send('Bruh. Only GMs of the active teams can set lineups.') + return + + if not lineup_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + await ctx.send(f'I do not see {lineup_team["sname"]} in this game. Please check the team abbrev and ' + f'try again') + return + + try: + if this_game.is_pd: + this_card = db_get(f'cards', object_id=int(new_player)) + if this_card["team"]["id"] != lineup_team['id']: + raise SyntaxError(f'Easy there, champ. Looks like card ID {new_player} belongs to the ' + f'{this_card["team"]["sname"]}. Try again with only cards you own.') + player_id = this_card['player']['player_id'] + card_id = new_player + else: + this_player = get_sba_player(new_player, season=SBA_SEASON) + if this_player['team']['id'] != lineup_team['id']: + raise SyntaxError(f'Easy there, champ. Looks like {new_player} is on ' + f'{this_player["team"]["sname"]}. Try again with only your own players.') + player_id = this_player['id'] + card_id = None + except Exception as e: + logging.error(f'Game {this_game} - Could not find player {new_player}') + if this_game.is_pd: + await ctx.send(f'I could not find card_id {new_player}.') + else: + await ctx.send(f'I could not find {new_player.title()}.') + return + + this_play = get_current_play(this_game.id) + batting_order = int(order_number) if order_number != 'this-spot' else this_play.batting_order + + # Check for simple position change + in_lineup = get_one_lineup(this_game.id, team_id=lineup_team['id'], active=True, batting_order=batting_order) + if in_lineup.card_id == int(card_id): + post_pos = patch_lineup(in_lineup.id, position=new_pos) + else: + curr_play = get_current_play(this_game.id) + this_lineup = { + 'game_id': this_game.id, + 'team_id': lineup_team['id'], + 'player_id': player_id, + 'card_id': card_id, + 'position': new_pos.upper(), + 'batting_order': batting_order, + 'after_play': curr_play.play_num - 1 if curr_play else 0 + } + + make_sub(this_lineup) + + await ctx.send(content=None, embed=await self.get_game_state_embed(this_game)) + + @commands.hybrid_command(name='gamestate', help='Post the current game state', aliases=['gs']) + @commands.has_any_role(SBA_PLAYERS_ROLE_NAME, PD_PLAYERS_ROLE_NAME) + async def game_state_command(self, ctx: commands.Context, include_lineups: bool = True): + this_game = get_one_game(channel_id=ctx.channel.id, active=True) + if not this_game: + await ctx.send(f'I dont\'t see an active game in this channel!') + return + + response = await ctx.send(f'Building the scorebug now...') + + await response.edit(content=None, embed=await self.get_game_state_embed(this_game, full_length=include_lineups)) + + async def checks_log_interaction(self, interaction: discord.Interaction, block_rollback: Optional[bool] = False) \ + -> (Optional[StratGame], Optional[dict], Optional[StratPlay]): + await interaction.response.defer() + + this_game = get_one_game(channel_id=interaction.channel.id, active=True) + if not this_game: + await interaction.response.send_message('I don\'t see a game in this channel.') + return False, False, False + + owner_team = await get_game_team(this_game, interaction.user.id) + if not owner_team['id'] in [this_game.away_team_id, this_game.home_team_id]: + logging.info(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} when they ' + f'aren\'t a GM in the game.') + await interaction.response.send_message('Bruh. Only GMs of the active teams can log plays.') + # return this_game, False, False + + this_play = get_current_play(this_game.id) + # Allow specific commands to not rollback the play (e.g. /show-card) + if this_play.locked and not block_rollback: + logging.info(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} while the ' + f'play was locked.') + undo_play(this_play.id) + await interaction.edit_original_response( + content=f'Looks like your game got hung up there for a second. I just rolled it back a play to ' + f'release it. If you have any more issues, let Cal know.' + ) + this_play = get_current_play(this_game.id) + + if not this_play.pitcher: + logging.info(f'{interaction.user.display_name} tried to run a command in Game {this_game.id} without a ' + f'pitcher in the lineup.') + await interaction.response.send_message(f'Please sub in a pitcher before logging a new play.') + this_play = None + + return this_game, owner_team, this_play + + group_log = app_commands.Group(name='log', description='Log a play in this channel\'s game') + + @group_log.command(name='ai-pitcher-sub', description='Run automated substitution when the AI wants a change') + async def ai_pitcher_sub(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + away_team = await get_game_team(this_game, team_id=this_game.away_team_id) + home_team = await get_game_team(this_game, team_id=this_game.home_team_id) + ai_team = away_team if owner_team == home_team else home_team + + if this_play.batter.team_id == ai_team['id']: + await interaction.edit_original_response( + content=f'It looks like the {ai_team["sname"]} are batting now. If they need a sub, please do so when ' + f'they are back on defense.' + ) + return + + curr_pitcher = get_one_lineup(this_game.id, team_id=ai_team['id'], position='P') + pit_plays = get_plays(this_game.id, curr_pitcher.id) + logging.debug(f'pit_plays for sub in Game {this_game.id}: {pit_plays}') + if pit_plays['count'] < 2: + pitcher = await get_player(this_game, curr_pitcher) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'It doesn\'t look like {pitcher["name"]} is fatigued - are you sure they should be subbed out?', + view=view + ) + await view.wait() + + if view.value: + await question.delete() + await interaction.channel.send( + content=f'Thanks! I let {get_cal_user(interaction).mention} know there was an issue with ' + f'{ai_team["abbrev"]} pitchers.' + ) + else: + await question.delete() + await interaction.edit_original_response(content=f'Okay, I\'ll leave them in!') + return + + # new_pitcher = await next_pitcher(this_play, ai_team, self.bot) + # logging.debug(f'new_pitcher: {new_pitcher}') + # + # this_lineup = { + # 'game_id': this_game.id, + # 'team_id': ai_team['id'], + # 'player_id': new_pitcher['player']['player_id'], + # 'card_id': new_pitcher['card_id'], + # 'position': 'P', + # 'batting_order': 10, + # 'after_play': this_play.play_num - 1 + # } + # make_sub(this_lineup) + + used_pitchers = await get_team_lineups( + game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, pitchers_only=True, as_string=False + ) + make_sub(ai_manager.get_relief_pitcher(this_play, ai_team, used_pitchers)) + await interaction.edit_original_response( + content=None, + embed=await self.get_game_state_embed(this_game) + ) + + @group_log.command(name='flyball', description='Flyballs: a, b, ballpark, bq, c') + async def log_flyball( + self, interaction: discord.Interaction, flyball_type: Literal['a', 'b', 'ballpark', 'b?', 'c']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + await self.flyballs(interaction, this_game, this_play, flyball_type) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='groundball', description='Groundballs: a, b, c') + async def log_groundball(self, interaction: discord.Interaction, groundball_type: Literal['a', 'b', 'c']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + await self.groundballs(interaction, this_game, this_play, groundball_type) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='single', description='Singles: *, **, ballpark, uncapped') + async def log_single( + self, interaction: discord.Interaction, single_type: Literal['*', '**', 'ballpark', 'uncapped']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + + if single_type == '**': + single_wellhit(this_play) + elif single_type == '*': + single_onestar(this_play) + elif single_type == 'ballpark': + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=1) + patch_play(this_play.id, pa=1, ab=1, hit=1, bp1b=1) + complete_play(this_play.id, batter_to_base=1) + elif single_type == 'uncapped': + patch_play(this_play.id, locked=True, pa=1, ab=1, hit=1) + advance_runners(this_play.id, 1) + this_play = get_current_play(this_game.id) + batter_to_base = 1 + + if this_play.on_base_code in [1, 2, 4, 5, 6, 7]: + ai_manager = get_manager(this_game) + ai_is_batting = ai_batting(this_game, this_play) + to_bases = [None, None, 'to second', 'to third', 'home'] + at_bases = [None, None, 'at second', 'at third', 'at home'] + + if this_play.on_second: + lead_runner = await get_player(this_game, this_play.on_second) + lead_base = 4 + if this_play.on_first: + trail_runner = await get_player(this_game, this_play.on_first) + trail_base = 3 + else: + trail_runner = await get_player(this_game, this_play.batter) + trail_base = 2 + else: + lead_runner = await get_player(this_game, this_play.on_first) + lead_base = 3 + trail_runner = await get_player(this_game, this_play.batter) + trail_base = 2 + + ai_hint = '' + if this_game.ai_team and ai_is_batting: + ai_hint = f'*The runner will ' \ + f'{ai_manager.uncapped_advance(lead_base, this_play.starting_outs)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {lead_runner["name"]} being sent {to_bases[lead_base]}?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + ai_hint = '' + if this_game.ai_team and not ai_is_batting: + ai_hint = f'*The defense will ' \ + f'{ai_manager.throw_lead_runner(lead_base, this_play.starting_outs)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is the defense throwing {to_bases[lead_base]}?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + ai_hint = '' + if this_game.ai_team and ai_is_batting: + ai_hint = f'*The runner will ' \ + f'{ai_manager.trail_advance(trail_base, this_play.starting_outs, True if lead_base == 4 else False)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {trail_runner["name"]} being sent {to_bases[trail_base]}?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + ai_hint = '' + if this_game.ai_team and not ai_is_batting: + ai_hint = f'*The defense will ' \ + f'{ai_manager.throw_which_runner(lead_base, this_play.starting_outs)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + view.confirm.label = 'Home Plate' if lead_base == 4 else 'Third Base' + view.cancel.label = 'Third Base' if trail_base == 3 else 'Second Base' + question = await interaction.channel.send( + f'Is the throw going {to_bases[lead_base]} or {to_bases[trail_base]}?\n\n{ai_hint}', + view=view + ) + await view.wait() + + # Throw to lead runner + if view.value: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {lead_runner["name"]} thrown out {at_bases[lead_base]}?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + advance_runners(this_play.id, 2) + batter_to_base = 2 + if lead_base == 4: + patch_play(this_play.id, on_second_final=False, outs=1) + else: + patch_play(this_play.id, on_first_final=False, outs=1) + + else: + await question.delete() + advance_runners(this_play.id, 2) + batter_to_base = 2 + + # Throw to trail runner + else: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {trail_runner["name"]} thrown out {at_bases[trail_base]}?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + advance_runners(this_play.id, 2) + if trail_base == 3: + patch_play(this_play.id, on_first_final=False, outs=1) + batter_to_base = 2 + else: + patch_play(this_play.id, outs=1) + batter_to_base = None + + else: + await question.delete() + advance_runners(this_play.id, 2) + batter_to_base = 2 + + else: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {lead_runner["name"]} thrown out at {to_bases[lead_base]}?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if lead_base == 4: + patch_play(this_play.id, on_second_final=False, outs=1) + else: + patch_play(this_play.id, on_first_final=False, outs=1) + + else: + await question.delete() + advance_one_runner(this_play.id, from_base=lead_base - 2, num_bases=2) + + else: + await question.delete() + advance_one_runner(this_play.id, from_base=lead_base - 2, num_bases=2) + + else: + await question.delete() + + logging.debug(f'post process batter runner on_second_final: {this_play.on_second_final}') + complete_play(this_play.id, batter_to_base=batter_to_base) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='double', description='Doubles: **, ***, uncapped') + async def log_double(self, interaction: discord.Interaction, double_type: Literal['**', '***', 'uncapped']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + + if double_type == '**': + double_twostar(this_play) + elif double_type == '***': + double_threestar(this_play) + elif double_type == 'uncapped': + advance_runners(this_play.id, 2) + this_play = patch_play(this_play.id, locked=True, pa=1, ab=1, hit=1, double=1) + + batter_to_base = 2 + ai_is_batting = ai_batting(this_game, this_play) + ai_manager = get_manager(this_game) + + if this_play.on_first: + ai_hint = '' + if this_game.ai_team and ai_is_batting: + if this_play.on_first: + ai_hint = f'*The runner will attempt to advance*' + else: + ai_hint = f'*The runner will ' \ + f'{ai_manager.uncapped_advance(4, this_play.starting_outs)}*' + + runner_to_home = await get_player(this_game, this_play.on_first) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {runner_to_home["name"]} being sent home?\n\n{ai_hint}', view=view + ) + await view.wait() + + if view.value: + await question.delete() + ai_hint = '' + if this_game.ai_team and not ai_is_batting: + ai_hint = f'*The defense will {ai_manager.throw_lead_runner(4, this_play.starting_outs)}*' + + # Throw for lead runner? + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is the defense throwing home?\n\n{ai_hint}', view=view + ) + await view.wait() + + # Yes: Send Trail runner? + if view.value: + await question.delete() + ai_hint = '' + if this_game.ai_team and ai_is_batting: + ai_hint = f'*The trail runner will ' \ + f'{ai_manager.trail_advance(3, this_play.starting_outs, True)}*' + + batter_runner = await get_player(this_game, this_play.batter) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {batter_runner["name"]} being sent to third?\n\n{ai_hint}', view=view + ) + await view.wait() + + # Yes: Throw lead or trail? + if view.value: + await question.delete() + ai_hint = '' + if this_game.ai_team and not ai_is_batting: + ai_hint = f'*The defense will ' \ + f'{ai_manager.throw_which_runner(4, this_play.starting_outs)}*' + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + view.confirm.label = 'Home Plate' + view.cancel.label = 'Third Base' + question = await interaction.channel.send( + f'Is the throw going to home plate or to third base?\n\n{ai_hint}', view=view + ) + await view.wait() + + # Throw home + if view.value: + batter_to_base = 3 + + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {runner_to_home["name"]} thrown out at the plate?', view=view + ) + await view.wait() + + # Out at the plate + if view.value: + await question.delete() + patch_play(this_play.id, on_first_final=False, outs=1) + + # Safe at the plate + else: + await question.delete() + advance_runners(this_play.id, 3) + + # Throw to third + else: + await question.delete() + advance_runners(this_play.id, num_bases=3) + + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {batter_runner["name"]} thrown out at third?', view=view + ) + await view.wait() + + # Out at third + if view.value: + await question.delete() + batter_to_base = None + advance_runners(this_play.id, num_bases=3) + patch_play(this_play.id, outs=1) + + # Safe at home and third + else: + await question.delete() + + # No: Safe at home? + else: + await question.delete() + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + content=f'Was {runner_to_home["name"]} thrown out at the plate?', view=view + ) + await view.wait() + + # Out at the plate + if view.value: + await question.delete() + patch_play(this_play.id, on_first_final=False, outs=1) + + # Safe at the plate + else: + await question.delete() + advance_runners(this_play.id, num_bases=3) + + # No: End of play + else: + await question.delete() + advance_runners(this_play.id, num_bases=3) + else: + await question.delete() + + complete_play(this_play.id, batter_to_base=batter_to_base) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='triple', description='Triples: no sub-types') + async def log_triple(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + triple(this_play) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='homerun', description='Home Runs: ballpark, no-doubt') + async def log_homerun(self, interaction: discord.Interaction, homerun_type: Literal['ballpark', 'no-doubt']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + + advance_runners(this_play.id, num_bases=4) + if homerun_type == 'ballpark': + patch_play(this_play.id, pa=1, ab=1, hit=1, homerun=1, bphr=1) + elif homerun_type == 'no-doubt': + patch_play(this_play.id, pa=1, ab=1, hit=1, homerun=1) + complete_play(this_play.id, batter_to_base=4) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='walk', description='Walks: unintentional, intentional') + async def log_walk(self, interaction: discord.Interaction, walk_type: Literal['unintentional', 'intentional']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=1, only_forced=True) + + if walk_type == 'unintentional': + patch_play(this_play.id, pa=1, walk=1) + elif walk_type == 'intentional': + patch_play(this_play.id, pa=1, ibb=1) + + complete_play(this_play.id, batter_to_base=1) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='chaos', description='Chaos: wild-pitch, passed-ball, balk, pickoff') + async def log_chaos(self, interaction: discord.Interaction, + chaos_type: Literal['wild-pitch', 'passed-ball', 'balk', 'pickoff']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, 0) + + if chaos_type == 'wild-pitch': + advance_runners(this_play.id, 1) + patch_play(this_play.id, rbi=0, wp=1) + elif chaos_type == 'passed-ball': + advance_runners(this_play.id, 1) + patch_play(this_play.id, rbi=0, pb=1) + elif chaos_type == 'balk': + advance_runners(this_play.id, 1) + patch_play(this_play.id, rbi=0, balk=1) + elif chaos_type == 'pickoff': + # Get from base + runner_flag = False + patch_play(this_play.id, pick=1) + + if this_play.on_base_code in [1, 2, 3]: + if this_play.on_first: + patch_play(this_play.id, on_first_final=False, runner_id=this_play.on_first.id, outs=1) + elif this_play.on_second: + patch_play(this_play.id, on_second_final=False, runner_id=this_play.on_second.id, outs=1) + elif this_play.on_third: + patch_play(this_play.id, on_third_final=False, runner_id=this_play.on_third.id, outs=1) + + else: + if this_play.on_first: + this_runner = await get_player(this_game, this_play.on_first) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send(f'Was {this_runner["name"]} picked off?', view=view) + await view.wait() + + if view.value: + patch_play(this_play.id, on_first_final=False, runner_id=this_play.on_first.id, outs=1) + runner_flag = True + + await question.delete() + + if this_play.on_second and not runner_flag: + this_runner = await get_player(this_game, this_play.on_second) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send(f'Was {this_runner["name"]} picked off?', view=view) + await view.wait() + + if view.value: + patch_play(this_play.id, on_second_final=False, runner_id=this_play.on_second.id, outs=1) + runner_flag = True + + await question.delete() + + if this_play.on_third and not runner_flag: + this_runner = await get_player(this_game, this_play.on_third) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send(f'Was {this_runner["name"]} picked off?', view=view) + await view.wait() + + if view.value: + patch_play(this_play.id, on_third_final=False, runner_id=this_play.on_third.id, outs=1) + runner_flag = True + + await question.delete() + + if not runner_flag: + await interaction.edit_original_response(content=f'So I guess nobody got picked off?') + patch_play(this_play.id, locked=False) + return + + complete_play(this_play.id) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='stealing', description='Running: stolen-base, caught-stealing') + @app_commands.describe(to_base='Base the runner is advancing to; 2 for 2nd, 3 for 3rd, 4 for Home') + async def log_stealing( + self, interaction: discord.Interaction, + running_type: Literal['stolen-base', 'caught-stealing', 'steal-plus-overthrow'], + to_base: Literal[2, 3, 4]): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + catcher = get_one_lineup( + this_game.id, team_id=this_play.pitcher.team_id, position='C' + ) + advance_runners(this_play.id, 0) + + if running_type == 'stolen-base': + if to_base == 'Home' and this_play.on_third: + patch_play( + this_play.id, sb=1, on_third_final=4, runner_id=this_play.on_third.id, catcher_id=catcher.id + ) + elif to_base == 3 and this_play.on_second: + if not this_play.on_third: + patch_play( + this_play.id, sb=1, on_second_final=3, runner_id=this_play.on_second.id, catcher_id=catcher.id + ) + else: + this_runner = await get_player(this_game, this_play.on_second) + await interaction.edit_original_response( + content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.' + ) + patch_play(this_play.id, locked=False) + return + elif to_base == 2 and this_play.on_first: + if not this_play.on_second: + patch_play( + this_play.id, sb=1, on_first_final=2, runner_id=this_play.on_first.id, catcher_id=catcher.id + ) + else: + this_runner = await get_player(this_game, this_play.on_first) + await interaction.edit_original_response( + content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.' + ) + patch_play(this_play.id, locked=False) + return + else: + await interaction.edit_original_response( + content=f'Uh oh - I don\'t see a runner there to steal the bag.' + ) + patch_play(this_play.id, locked=False) + return + + if running_type == 'steal-plus-overthrow': + if to_base == 'Home' and this_play.on_third: + patch_play( + this_play.id, sb=1, on_third_final=4, runner_id=this_play.on_third.id, catcher_id=catcher.id + ) + elif to_base == 3 and this_play.on_second: + advance_runners(this_play.id, 1) + if not this_play.on_third: + patch_play( + this_play.id, sb=1, on_second_final=4, runner_id=this_play.on_second.id, catcher_id=catcher.id + ) + else: + this_runner = await get_player(this_game, this_play.on_second) + await interaction.edit_original_response( + content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.' + ) + patch_play(this_play.id, locked=False) + return + elif to_base == 2 and this_play.on_first: + if not this_play.on_second: + advance_runners(this_play.id, 1) + patch_play( + this_play.id, sb=1, on_first_final=3, runner_id=this_play.on_first.id, catcher_id=catcher.id + ) + else: + this_runner = await get_player(this_game, this_play.on_first) + await interaction.edit_original_response( + content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.' + ) + patch_play(this_play.id, locked=False) + return + else: + await interaction.edit_original_response( + content=f'Uh oh - I don\'t see a runner there to steal the bag.' + ) + patch_play(this_play.id, locked=False) + return + + patch_play(this_play.id, error=1) + + elif running_type == 'caught-stealing': + if to_base == 'Home' and this_play.on_third: + patch_play( + this_play.id, cs=1, on_third_final=False, runner_id=this_play.on_third.id, + catcher_id=catcher.id, outs=1 + ) + elif to_base == 3 and this_play.on_second: + if not this_play.on_third: + patch_play( + this_play.id, cs=1, on_second_final=False, runner_id=this_play.on_second.id, + catcher_id=catcher.id, outs=1 + ) + else: + this_runner = await get_player(this_game, this_play.on_second) + await interaction.edit_original_response( + content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at third.' + ) + patch_play(this_play.id, locked=False) + return + elif to_base == 2 and this_play.on_first: + if not this_play.on_second: + patch_play( + this_play.id, cs=1, on_first_final=False, runner_id=this_play.on_first.id, + catcher_id=catcher.id, outs=1 + ) + else: + this_runner = await get_player(this_game, this_play.on_first) + await interaction.edit_original_response( + content=f'Ope. Looks like {this_runner["name"]} is blocked by the runner at second.' + ) + patch_play(this_play.id, locked=False) + return + else: + await interaction.edit_original_response( + content=f'Uh oh - I don\'t see a runner there to steal the bag.' + ) + patch_play(this_play.id, locked=False) + return + + complete_play(this_play.id) + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='strikeout', description='Strikeout') + async def log_strikeout(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + + patch_play(this_play.id, pa=1, ab=1, outs=1, so=1) + advance_runners(this_play.id, 0) + + complete_play(this_play.id) + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='popout', description='Popout') + async def log_popout(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + + patch_play(this_play.id, pa=1, ab=1, outs=1) + advance_runners(this_play.id, 0) + + complete_play(this_play.id) + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='lineout', description='Lineouts: one out, ballpark, max outs') + async def log_lineout( + self, interaction: discord.Interaction, lineout_type: Literal['one-out', 'ballpark', 'max-outs']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, 0) + + if lineout_type == 'one-out' or this_play.starting_outs == 2 or this_play.on_base_code == 0: + patch_play(this_play.id, pa=1, ab=1, outs=1) + + elif lineout_type == 'ballpark': + patch_play(this_play.id, pa=1, ab=1, outs=1, bplo=1) + + elif lineout_type == 'max-outs': + bases = ['third', 'second', 'first'] + num_outs = 1 + + for count, x in enumerate([this_play.on_third, this_play.on_second, this_play.on_first]): + if x: + runner = await get_player(this_game, x) + view = Confirm(responders=[interaction.user], timeout=60, label_type='yes') + question = await interaction.channel.send( + f'Is {runner["name"]} out at {bases[count]} on the play?', view=view + ) + await view.wait() + + if view.value: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=False) + elif count == 1: + patch_play(this_play.id, on_second_final=False) + else: + patch_play(this_play.id, on_first_final=False) + num_outs += 1 + else: + await question.delete() + if count == 0: + patch_play(this_play.id, on_third_final=3) + elif count == 1: + patch_play(this_play.id, on_second_final=2) + else: + patch_play(this_play.id, on_first_final=1) + patch_play(this_play.id, pa=1, ab=1, outs=num_outs) + + complete_play(this_play.id) + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='hit-by-pitch', description='Batter to first; runners advance if forced') + async def log_hit_by_pitch_command(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=False) + + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=1, only_forced=True) + patch_play(this_play.id, pa=1, hbp=1) + complete_play(this_play.id, batter_to_base=1) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='sac-bunt', description='Batter out; runners advance one base') + async def log_sac_bunt_command(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=False) + + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=1) + patch_play(this_play.id, pa=1, sac=1, outs=1) + complete_play(this_play.id) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='undo-play', description='Remove the most recent play from the log') + async def log_undo_play_command(self, interaction: discord.Interaction): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=False) + + try: + logging.debug(f'Undoing play {this_play.id} in Game {this_game.id}: Batter {this_play.batter}') + undo_play(this_game.id) + except AttributeError as e: + logging.error(f'Could not undo play {this_play.id} in game {this_game.id}') + + try: + last_completed = get_latest_play(this_game.id) + logging.debug(f'Undoing play {last_completed.id} in Game {this_game.id}') + undo_play(this_game.id) + + latest_play = get_latest_play(this_game.id) + logging.debug(f'Latest completed play is Play {latest_play.id}; batter_to_base: {latest_play.batter_final}') + except AttributeError as e: + logging.error(f'Could not undo second play in game {this_game.id}') + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + @group_log.command(name='xcheck', description='Defender makes an x-check') + @app_commands.choices(position=[ + Choice(name='Pitcher', value='P'), + Choice(name='Catcher', value='C'), + Choice(name='First Base', value='1B'), + Choice(name='Second Base', value='2B'), + Choice(name='Third Base', value='3B'), + Choice(name='Shortstop', value='SS'), + Choice(name='Left Field', value='LF'), + Choice(name='Center Field', value='CF'), + Choice(name='Right Field', value='RF'), + ]) + async def log_xcheck_command(self, interaction: discord.Interaction, position: Choice[str]): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + patch_play(this_play.id, locked=False) + + def_team_id = this_game.away_team_id if this_play.inning_half == 'Bot' else this_game.home_team_id + def_team = db_get('teams', object_id=def_team_id) + d_lineup = get_one_lineup(this_game.id, team_id=this_play.pitcher.team_id, position=position.value) + defender = await get_player(this_game, d_lineup) + patch_play(this_play.id, defender_id=d_lineup.id, check_pos=position.value) + + defender_embed = get_team_embed(f'{def_team["sname"]} {position.value}', def_team) + defender_embed.description = f'{defender["name"]}' + defender_embed.set_image(url=defender['image']) + embeds = [defender_embed] + + dice_embeds = sa_fielding_roll(position.value, def_team) + embeds.extend(dice_embeds) + all_embeds = [x for x in embeds if x is not None] + + await interaction.edit_original_response( + content=None, + embeds=all_embeds + ) + + hit_allowed, error_allowed, chaos_allowed = None, None, None + + if position.value == 'C': + view = Confirm([interaction.user], label_type='yes') + question = await interaction.channel.send(f'Did {defender["name"]} give up a chaos result?', view=view) + await view.wait() + + if view.value: + await question.delete() + view = ButtonOptions(responders=[interaction.user], labels=['WP', 'X (PB)', None, None, None]) + question = await interaction.channel.send( + f'Which chaos result did {defender["name"]} allow?', view=view + ) + await view.wait() + + if view.value == 'WP' or view.value == 'X (PB)': + await question.delete() + if view.value == 'WP': + advance_runners(this_play.id, 1) + patch_play(this_play.id, rbi=0, wp=1) + elif view.value == 'X (PB)': + advance_runners(this_play.id, 1) + patch_play(this_play.id, rbi=0, pb=1) + + complete_play(this_play.id) + + await interaction.edit_original_response( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + return + else: + await question.delete() + + else: + await question.delete() + + view = Confirm([interaction.user], label_type='yes') + question = await interaction.channel.send(f'Did {defender["name"]} give up a hit?', view=view) + await view.wait() + + if view.value: + await question.delete() + view = ButtonOptions( + responders=[interaction.user], + labels=['single*', 'single**', 'double**', 'double***', 'triple'] + ) + question = await interaction.channel.send(f'Which hit did {defender["name"]} allow?', view=view) + + await view.wait() + + if not view.value: + await question.edit(f'Hmm...you keep thinking on it and get back to me when you\'re ready.') + return + else: + await question.delete() + + hit_allowed = view.value + else: + await question.delete() + hit_allowed = 'out' + + view = Confirm([interaction.user], label_type='yes') + question = await interaction.channel.send(f'Did {defender["name"]} give up an error?', view=view) + await view.wait() + + if view.value: + await question.delete() + view = ButtonOptions( + responders=[interaction.user], + labels=['1 base', '2 bases', '3 bases', None, None] + ) + question = await interaction.channel.send(f'How many bases did {defender["name"]} allow?', view=view) + await view.wait() + + if not view.value: + await question.edit(f'Hmm...you keep thinking on it and get back to me when you\'re ready.') + return + else: + await question.delete() + + error_allowed = view.value + else: + await question.delete() + error_allowed = 'no error' + + # Not hit and no error + if hit_allowed == 'out' and error_allowed == 'no error': + if position.value not in ['LF', 'CF', 'RF']: + view = ButtonOptions( + responders=[interaction.user], + labels=['gb A', 'gb B', 'gb C', None if position.value != 'C' else 'FO', + None if position.value != 'C' else 'PO'] + ) + question = await interaction.channel.send(f'What was the result of the play?', view=view) + await view.wait() + + if not view.value: + await question.delete() + + if view.value == 'gb A': + await self.groundballs(interaction, this_game, this_play, groundball_type='a') + elif view.value == 'gb B': + await self.groundballs(interaction, this_game, this_play, groundball_type='b') + elif view.value == 'gb C': + await self.groundballs(interaction, this_game, this_play, groundball_type='c') + else: + patch_play(this_play.id, pa=1, ab=1, outs=1) + advance_runners(this_play.id, 0) + complete_play(this_play.id) + + patch_play(this_play.id, locked=False) + return + + else: + await question.delete() + if view.value in ['FO', 'PO']: + patch_play(this_play.id, pa=1, ab=1, outs=1) + advance_runners(this_play.id, 0) + complete_play(this_play.id) + + else: + if view.value == 'gb A': + gb_code = 'a' + elif view.value == 'gb B': + gb_code = 'b' + else: + gb_code = 'c' + + await self.groundballs(interaction, this_game, this_play, gb_code) + + else: + view = ButtonOptions(responders=[interaction.user], labels=['fly A', 'fly B', 'fly C', None, None]) + question = await interaction.channel.send(f'What was the result of the play?', view=view) + await view.wait() + + if not view.value: + await question.delete() + await interaction.channel.send( + content=f'Just logged the x-check! Please log the resulting play to continue (e.g. ' + f'\'flyball-b\' or \'flyball-c\')' + ) + patch_play(this_play.id, locked=False) + return + + else: + await question.delete() + if view.value == 'fly A': + fly_code = 'a' + elif view.value == 'fly B': + fly_code = 'b' + else: + fly_code = 'c' + + await self.flyballs(interaction, this_game, this_play, fly_code) + + # Hit and error + elif hit_allowed != 'out' and error_allowed != 'no error': + patch_play(this_play.id, error=1) + if hit_allowed == 'triple': + triple(this_play, comp_play=False) + advance_runners(this_play.id, num_bases=4) + batter_to_base = 4 + elif 'double' in hit_allowed: + double_threestar(this_play, comp_play=False) + if error_allowed == '1 base': + batter_to_base = 3 + elif error_allowed == '3 bases': + batter_to_base = 4 + # 2 base error is the only one handled differently between doubles + elif hit_allowed == 'double***': + batter_to_base = 4 + else: + batter_to_base = 3 + # Both singles are handled the same + else: + single_wellhit(this_play, comp_play=False) + if error_allowed == '1 base': + batter_to_base = 2 + else: + advance_runners(this_play.id, 3) + batter_to_base = 3 + + complete_play(this_play.id, batter_to_base=batter_to_base) + + # Either hit or error + else: + num_bases = None + if error_allowed == 'no error': + if hit_allowed == 'single*': + single_onestar(this_play) + elif hit_allowed == 'single**': + single_wellhit(this_play) + elif hit_allowed == 'double**': + double_twostar(this_play) + elif hit_allowed == 'double***': + double_threestar(this_play) + elif hit_allowed == 'triple': + triple(this_play) + else: + patch_play(this_play.id, error=1) + if error_allowed == '1 base': + num_bases = 1 + elif error_allowed == '2 bases': + num_bases = 2 + elif error_allowed == '3 bases': + num_bases = 3 + + advance_runners(this_play.id, num_bases=num_bases, is_error=True) + complete_play(this_play.id, batter_to_base=num_bases) + + patch_play(this_play.id, locked=False) + await interaction.channel.send( + content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + ) + + # @group_log.command(name='xcheck', description='Defender makes an x-check') + # async def log_xcheck_command(self, interaction: discord.Interaction, position: Literal[ + # 'Pitcher', 'Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', + # 'Center Field', 'Right Field'], hit_allowed: Literal['out', 'single*', 'single**', 'double**', + # 'double***', 'triple'], error_allowed: Literal['no error', '1 base', '2 bases', '3 bases']): + # this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + # if False in (this_game, owner_team, this_play): + # return + # + # patch_play(this_play.id, locked=False) + # + # pos = get_pos_abbrev(position) + # defender = get_one_lineup( + # this_game.id, team_id=this_play.pitcher.team_id, position=pos + # ) + # logging.info(f'defender: {defender}') + # patch_play(this_play.id, defender_id=defender.id, error=1 if error_allowed != 'no-error' else 0, check_pos=pos) + # + # # Not hit and no error + # if hit_allowed == 'out' and error_allowed == 'no error': + # await interaction.edit_original_response( + # content=f'Just logged the x-check! Please log the resulting play to continue (e.g. \'flyball-b\' or ' + # f'\'groundball-a\')' + # ) + # patch_play(this_play.id, locked=False) + # return + # + # # Hit and error + # elif hit_allowed != 'out' and error_allowed != 'no error': + # if hit_allowed == 'triple': + # triple(this_play, comp_play=False) + # batter_to_base = 4 + # elif 'double' in hit_allowed: + # double_threestar(this_play, comp_play=False) + # if error_allowed == '1 base': + # batter_to_base = 3 + # elif error_allowed == '3 bases': + # batter_to_base = 4 + # # 2 base error is the only one handled differently between doubles + # elif hit_allowed == 'double***': + # batter_to_base = 4 + # else: + # batter_to_base = 3 + # # Both singles are handled the same + # else: + # single_wellhit(this_play, comp_play=False) + # if error_allowed == '1 base': + # batter_to_base = 2 + # else: + # batter_to_base = 3 + # + # complete_play(this_play.id, batter_to_base=batter_to_base) + # + # # Either hit or error + # else: + # num_bases = None + # if error_allowed == 'no error': + # if hit_allowed == 'single*': + # single_onestar(this_play) + # elif hit_allowed == 'single**': + # single_wellhit(this_play) + # elif hit_allowed == 'double**': + # double_twostar(this_play) + # elif hit_allowed == 'double***': + # double_threestar(this_play) + # elif hit_allowed == 'triple': + # triple(this_play) + # else: + # if error_allowed == '1 base': + # num_bases = 1 + # elif error_allowed == '2 bases': + # num_bases = 2 + # elif error_allowed == '3 bases': + # num_bases = 3 + # + # advance_runners(this_play.id, num_bases=num_bases, is_error=True) + # complete_play(this_play.id, batter_to_base=num_bases) + # + # patch_play(this_play.id, locked=False) + # await interaction.edit_original_response( + # content=None, embed=await self.get_game_state_embed(this_game, full_length=False) + # ) + + group_show = app_commands.Group(name='show-card', description='Display the player card for an active player') + + @group_show.command(name='defense', description='Display a defender\'s player card') + async def show_defense_command( + self, interaction: discord.Interaction, position: Literal[ + 'Catcher', 'First Base', 'Second Base', 'Third Base', 'Shortstop', 'Left Field', 'Center Field', + 'Right Field']): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction, block_rollback=True) + if False in (this_game, owner_team, this_play): + return + + defender = await get_player( + game=this_game, + lineup_member=get_one_lineup( + this_game.id, team_id=this_play.pitcher.team_id, position=get_pos_abbrev(position) + ) + ) + + embed = get_team_embed(f'{defender["team"]["sname"]} {position}', defender['team']) + embed.description = f'{defender["name"]}' + embed.set_image(url=defender['image']) + if this_game.is_pd: + embed.set_footer(text=f'PD Season {PD_SEASON}', icon_url=IMAGES['logo']) + + await interaction.edit_original_response(content=None, embed=embed) + + @group_show.command(name='lineup', description='Display a batting team member\'s player card') + async def show_batting_team_command( + self, interaction: discord.Interaction, batting_order: Optional[int] = None, + relative_order: Optional[Literal['on-deck', 'in-the-hole']] = None): + this_game, owner_team, this_play = await self.checks_log_interaction(interaction) + if False in (this_game, owner_team, this_play): + return + + if batting_order: + lineup_member = get_one_lineup( + this_game.id, team_id=this_play.batter.team_id, batting_order=batting_order + ) + elif relative_order: + modifier = 1 if relative_order == 'on-deck' else 2 + b_order = this_play.batter.batting_order + modifier + if b_order == 10: + b_order = 1 + if b_order == 11: + b_order = 2 + lineup_member = get_one_lineup( + this_game.id, team_id=this_play.batter.team_id, batting_order=b_order + ) + else: + await interaction.edit_original_response( + content=f'You need to select either a batting order or relative order.' + ) + patch_play(this_play.id, locked=False) + return + + this_player = await get_player(game=this_game, lineup_member=lineup_member) + + embed = get_team_embed(f'{this_player["team"]["sname"]} #{lineup_member.batting_order} Batter', + this_player['team']) + embed.description = f'{this_player["name"]}' + embed.set_image(url=this_player['image']) + if this_game.is_pd: + embed.set_footer(text=f'PD Season {PD_SEASON}', icon_url=IMAGES['logo']) + + await interaction.edit_original_response(content=None, embed=embed) + + @commands.command(name='load-ai', hidden=True) + @commands.is_owner() + async def load_ai_command(self, ctx): + await ctx.send(f'{load_ai()}') + + # @commands.command(name='pentest', hidden=True) + # @commands.is_owner() + # async def pen_test_command(self, ctx, team_id: int = 3): + # this_pen = get_or_create_bullpen({'id': team_id}, self.bot) + # await ctx.send(f'{this_pen}') + + +async def setup(bot): + await bot.add_cog(Gameplay(bot)) diff --git a/cogs/owner.py b/cogs/owner.py index df1268c..33e2cd9 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -1,5 +1,19 @@ +import datetime +import logging +import os + import discord +from discord import Object from discord.ext import commands +from discord.ext.commands import Greedy, Context +from typing import Optional, Literal + +date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' +logging.basicConfig( + filename=f'logs/{date}.log', + format='%(asctime)s - %(levelname)s - %(message)s', + level=logging.WARNING +) class Owner(commands.Cog): @@ -10,9 +24,8 @@ class Owner(commands.Cog): @commands.is_owner() async def load(self, ctx, *, cog: str): try: - self.bot.load_extension(f'cogs.{cog}') - print(f'Loaded {cog}') - print('------') + await self.bot.load_extension(f'cogs.{cog}') + logging.warning(f'Loaded {cog}') except Exception as e: await ctx.send(f'**ERROR:** {type(e).__name__} - {e}') else: @@ -22,9 +35,8 @@ class Owner(commands.Cog): @commands.is_owner() async def unload(self, ctx, *, cog: str): try: - self.bot.unload_extension(f'cogs.{cog}') - print(f'Unloaded {cog}') - print('------') + await self.bot.unload_extension(f'cogs.{cog}') + logging.warning(f'Unloaded {cog}') except Exception as e: await ctx.send(f'**ERROR:** {type(e).__name__} - {e}') else: @@ -34,16 +46,86 @@ class Owner(commands.Cog): @commands.is_owner() async def reload(self, ctx, *, cog: str): try: - self.bot.unload_extension(f'cogs.{cog}') - print(f'Unloaded {cog}') - self.bot.load_extension(f'cogs.{cog}') - print(f'Reloaded {cog}') - print('------') + await self.bot.unload_extension(f'cogs.{cog}') + logging.warning(f'Unloaded {cog}') + await self.bot.load_extension(f'cogs.{cog}') + logging.warning(f'Reloaded {cog}') except Exception as e: await ctx.send(f'**ERROR:** {type(e).__name__} - {e}') else: await ctx.send('**SUCCESS**') + @commands.command(hidden=True) + @commands.is_owner() + async def fullreset(self, ctx): + cogs = ['helpers', 'admins', 'economy', 'players'] -def setup(bot): - bot.add_cog(Owner(bot)) + for x in cogs: + try: + await self.bot.unload_extension(f'cogs.{x}') + logging.warning(f'Unloaded {x}') + except Exception as e: + await ctx.send(f'Failed to unload **{x}**') + + for x in cogs: + try: + await self.bot.load_extension(f'cogs.{x}') + logging.warning(f'Loaded {x}') + except Exception as e: + await ctx.send(f'Failed to load **{x}**') + + await ctx.send('**SUCCESS**') + + # @commands.command(name='sync', hidden=True) + # @commands.is_owner() + # async def sync_command(self, ctx, sync_type: str = 'local'): + # sync = None + # await ctx.send('I will try to sync slash commands...') + # try: + # if sync_type == 'global': + # sync = await self.bot.tree.sync() + # else: + # sync = await self.bot.tree.sync(guild=discord.Object(os.environ.get('GUILD_ID'))) + # logging.warning(f'sync: {sync}') + # except Exception as e: + # logging.error(f'failed to sync: {e}') + # + # await ctx.send(f'Just ran the sync. Here is the output:\n{sync}') + + @commands.command() + @commands.is_owner() + async def sync(self, ctx: Context, guilds: Greedy[Object], spec: Optional[Literal['~', "*"]] = None) -> None: + """ + !sync -> global sync + !sync ~ -> sync current guild + !sync * -> copies all global app commands to current guild and syncs + !sync id_1 id_2 -> syncs guilds with id 1 and 2 + """ + if not guilds: + if spec == "~": + fmt = await ctx.bot.tree.sync(guild=ctx.guild) + elif spec == "*": + ctx.bot.tree.copy_global_to(guild=ctx.guild) + fmt = await ctx.bot.tree.sync(guild=ctx.guild) + else: + fmt = await ctx.bot.tree.sync() + + await ctx.send( + f"Synced {len(fmt)} commands {'globally' if spec is None else 'to the current guild.'}" + ) + return + + fmt = 0 + for guild in guilds: + try: + await ctx.bot.tree.sync(guild=guild) + except discord.HTTPException: + pass + else: + fmt += 1 + + await ctx.send(f"Synced the tree to {fmt}/{len(guilds)} guilds.") + + +async def setup(bot): + await bot.add_cog(Owner(bot)) diff --git a/cogs/players.py b/cogs/players.py index 8be54d2..83d03b2 100644 --- a/cogs/players.py +++ b/cogs/players.py @@ -1,67 +1,882 @@ +import asyncio +import math +import os +import random + +import requests + import discord import pygsheets -from db_engine import * -from discord.ext import commands +import logging +import datetime +from discord import app_commands, Member +from discord.ext import commands, tasks from difflib import get_close_matches +from discord.ext.commands import Greedy +from db_calls import db_get, db_post, db_patch +from helpers import PD_PLAYERS_ROLE_NAME, IMAGES, PD_SEASON, random_conf_gif, fuzzy_player_search, ALL_MLB_TEAMS, \ + fuzzy_search, get_channel, display_cards, get_card_embeds, get_team_embed, cardset_search, get_blank_team_card, \ + get_team_by_owner, get_rosters, get_roster_sheet, legal_channel, random_conf_word, embed_pagination, get_cal_user, \ + team_summary_embed +from typing import Optional, Literal + +# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' +# logging.basicConfig( +# filename=f'logs/{date}.log', +# format='%(asctime)s - %(levelname)s - %(message)s', +# level=logging.WARNING +# ) + + +def get_ai_records(short_games, long_games): + all_results = { + 'ARI': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'ATL': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'BAL': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'BOS': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'CHC': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'CHW': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'CIN': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'CLE': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'COL': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'DET': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'HOU': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'KCR': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'LAA': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'LAD': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'MIA': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'MIL': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'MIN': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'NYM': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'NYY': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'OAK': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'PHI': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'PIT': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'SDP': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'SEA': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'SFG': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'STL': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'TBR': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'TEX': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'TOR': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + 'WSN': { + 'short': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'minor': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, + 'major': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}, 'hof': {'w': 0, 'l': 0, 'rd': 0, 'points': 0}}, + } + + logging.debug(f'running short games...') + for line in short_games: + home_win = True if line['home_score'] > line['away_score'] else False + + if line['away_team']['is_ai']: + all_results[line['away_team']['abbrev']]['short']['w'] += 1 if home_win else 0 + all_results[line['away_team']['abbrev']]['short']['l'] += 1 if not home_win else 0 + all_results[line['away_team']['abbrev']]['short']['points'] += 2 if home_win else 1 + all_results[line['away_team']['abbrev']]['short']['rd'] += line['home_score'] - line['away_score'] + elif line['home_team']['is_ai']: + all_results[line['home_team']['abbrev']]['short']['w'] += 1 if not home_win else 0 + all_results[line['home_team']['abbrev']]['short']['l'] += 1 if home_win else 0 + all_results[line['home_team']['abbrev']]['short']['points'] += 2 if not home_win else 1 + all_results[line['home_team']['abbrev']]['short']['rd'] += line['away_score'] - line['home_score'] + logging.debug(f'done short games') + + logging.debug(f'running league games...') + league = {None: 'minor', 'minor-league': 'minor', 'major-league': 'major', 'hall-of-fame': 'hof'} + for line in long_games: + home_win = True if line['home_score'] > line['away_score'] else False + + if line['away_team']['is_ai']: + all_results[line['away_team']['abbrev']][league[line['game_type']]]['w'] += 1 if home_win else 0 + all_results[line['away_team']['abbrev']][league[line['game_type']]]['l'] += 1 if not home_win else 0 + all_results[line['away_team']['abbrev']][league[line['game_type']]]['points'] += 2 if home_win else 1 + all_results[line['away_team']['abbrev']][league[line['game_type']]]['rd'] += \ + line['home_score'] - line['away_score'] + elif line['home_team']['is_ai']: + all_results[line['home_team']['abbrev']][league[line['game_type']]]['w'] += 1 if not home_win else 0 + all_results[line['home_team']['abbrev']][league[line['game_type']]]['l'] += 1 if home_win else 0 + all_results[line['home_team']['abbrev']][league[line['game_type']]]['points'] += 2 if not home_win else 1 + all_results[line['home_team']['abbrev']][league[line['game_type']]]['rd'] += \ + line['away_score'] - line['home_score'] + logging.debug(f'done league games') + + return all_results + + +def get_record_embed(embed: discord.Embed, results: dict, league: str): + ale_points = results["BAL"][league]["points"] + results["BOS"][league]["points"] + \ + results["NYY"][league]["points"] + results["TBR"][league]["points"] + results["TOR"][league]["points"] + alc_points = results["CLE"][league]["points"] + results["CHW"][league]["points"] + \ + results["DET"][league]["points"] + results["KCR"][league]["points"] + results["MIN"][league]["points"] + alw_points = results["HOU"][league]["points"] + results["LAA"][league]["points"] + \ + results["OAK"][league]["points"] + results["SEA"][league]["points"] + results["TEX"][league]["points"] + nle_points = results["ATL"][league]["points"] + results["MIA"][league]["points"] + \ + results["NYM"][league]["points"] + results["PHI"][league]["points"] + results["WSN"][league]["points"] + nlc_points = results["CHC"][league]["points"] + results["CIN"][league]["points"] + \ + results["MIL"][league]["points"] + results["PIT"][league]["points"] + results["STL"][league]["points"] + nlw_points = results["ARI"][league]["points"] + results["COL"][league]["points"] + \ + results["LAD"][league]["points"] + results["SDP"][league]["points"] + results["SFG"][league]["points"] + + embed.add_field( + name=f'AL East ({ale_points} pts)', + value=f'BAL: {results["BAL"][league]["w"]} - {results["BAL"][league]["l"]} ({results["BAL"][league]["rd"]} RD)\n' + f'BOS: {results["BOS"][league]["w"]} - {results["BOS"][league]["l"]} ({results["BOS"][league]["rd"]} RD)\n' + f'NYY: {results["NYY"][league]["w"]} - {results["NYY"][league]["l"]} ({results["NYY"][league]["rd"]} RD)\n' + f'TBR: {results["TBR"][league]["w"]} - {results["TBR"][league]["l"]} ({results["TBR"][league]["rd"]} RD)\n' + f'TOR: {results["TOR"][league]["w"]} - {results["TOR"][league]["l"]} ({results["TOR"][league]["rd"]} RD)\n' + ) + embed.add_field( + name=f'AL Central ({alc_points} pts)', + value=f'CLE: {results["CLE"][league]["w"]} - {results["CLE"][league]["l"]} ({results["CLE"][league]["rd"]} RD)\n' + f'CHW: {results["CHW"][league]["w"]} - {results["CHW"][league]["l"]} ({results["CHW"][league]["rd"]} RD)\n' + f'DET: {results["DET"][league]["w"]} - {results["DET"][league]["l"]} ({results["DET"][league]["rd"]} RD)\n' + f'KCR: {results["KCR"][league]["w"]} - {results["KCR"][league]["l"]} ({results["KCR"][league]["rd"]} RD)\n' + f'MIN: {results["MIN"][league]["w"]} - {results["MIN"][league]["l"]} ({results["MIN"][league]["rd"]} RD)\n' + ) + embed.add_field( + name=f'AL West ({alw_points} pts)', + value=f'HOU: {results["HOU"][league]["w"]} - {results["HOU"][league]["l"]} ({results["HOU"][league]["rd"]} RD)\n' + f'LAA: {results["LAA"][league]["w"]} - {results["LAA"][league]["l"]} ({results["LAA"][league]["rd"]} RD)\n' + f'OAK: {results["OAK"][league]["w"]} - {results["OAK"][league]["l"]} ({results["OAK"][league]["rd"]} RD)\n' + f'SEA: {results["SEA"][league]["w"]} - {results["SEA"][league]["l"]} ({results["SEA"][league]["rd"]} RD)\n' + f'TEX: {results["TEX"][league]["w"]} - {results["TEX"][league]["l"]} ({results["TEX"][league]["rd"]} RD)\n' + ) + embed.add_field( + name=f'NL East ({nle_points} pts)', + value=f'ATL: {results["ATL"][league]["w"]} - {results["ATL"][league]["l"]} ({results["ATL"][league]["rd"]} RD)\n' + f'MIA: {results["MIA"][league]["w"]} - {results["MIA"][league]["l"]} ({results["MIA"][league]["rd"]} RD)\n' + f'NYM: {results["NYM"][league]["w"]} - {results["NYM"][league]["l"]} ({results["NYM"][league]["rd"]} RD)\n' + f'PHI: {results["PHI"][league]["w"]} - {results["PHI"][league]["l"]} ({results["PHI"][league]["rd"]} RD)\n' + f'WSN: {results["WSN"][league]["w"]} - {results["WSN"][league]["l"]} ({results["WSN"][league]["rd"]} RD)\n' + ) + embed.add_field( + name=f'NL Central ({nlc_points} pts)', + value=f'CHC: {results["CHC"][league]["w"]} - {results["CHC"][league]["l"]} ({results["CHC"][league]["rd"]} RD)\n' + f'CHW: {results["CIN"][league]["w"]} - {results["CIN"][league]["l"]} ({results["CIN"][league]["rd"]} RD)\n' + f'MIL: {results["MIL"][league]["w"]} - {results["MIL"][league]["l"]} ({results["MIL"][league]["rd"]} RD)\n' + f'PIT: {results["PIT"][league]["w"]} - {results["PIT"][league]["l"]} ({results["PIT"][league]["rd"]} RD)\n' + f'STL: {results["STL"][league]["w"]} - {results["STL"][league]["l"]} ({results["STL"][league]["rd"]} RD)\n' + ) + embed.add_field( + name=f'NL West ({nlw_points} pts)', + value=f'ARI: {results["ARI"][league]["w"]} - {results["ARI"][league]["l"]} ({results["ARI"][league]["rd"]} RD)\n' + f'COL: {results["COL"][league]["w"]} - {results["COL"][league]["l"]} ({results["COL"][league]["rd"]} RD)\n' + f'LAD: {results["LAD"][league]["w"]} - {results["LAD"][league]["l"]} ({results["LAD"][league]["rd"]} RD)\n' + f'SDP: {results["SDP"][league]["w"]} - {results["SDP"][league]["l"]} ({results["SDP"][league]["rd"]} RD)\n' + f'SFG: {results["SFG"][league]["w"]} - {results["SFG"][league]["l"]} ({results["SFG"][league]["rd"]} RD)\n' + ) + + return embed + class Players(commands.Cog): def __init__(self, bot): self.bot = bot - self.helpers = self.bot.get_cog('Helpers') + self.sheets = pygsheets.authorize(service_file='storage/paper-dynasty-service-creds.json', retries=1) + self.player_list = [] + self.cardset_list = [] - @commands.command(name='show', aliases=['card', 'player'], help='show ') - @commands.has_any_role('Paper Dynasty Players') - async def display_player(self, ctx, cardset, *, name): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') + self.build_player_list.start() + + async def cog_command_error(self, ctx, error): + await ctx.send(f'{error}') + + @tasks.loop(hours=18) + async def build_player_list(self): + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Cannot access guild; pausing for 5 seconds') + await asyncio.sleep(5) + guild = self.bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error(f'Still cannot access guild; trying again in 18 hours') + return + + all_players = db_get('players', params=[('flat', True)], timeout=5) + all_cardsets = db_get('cardsets', params=[('flat', True)]) + + [self.player_list.append(x['p_name'].lower()) for x in all_players['players'] if x['p_name'].lower() + not in self.player_list] + logging.info(f'There are now {len(self.player_list)} player names in the fuzzy search list.') + + self.cardset_list = [x['name'].lower() for x in all_cardsets['cardsets']] + logging.info(f'There are now {len(self.cardset_list)} cardsets in the fuzzy search list.') + + # def get_standings_embeds(self, current, which: str, title: str): + # all_embeds = [ + # discord.Embed(title=title), discord.Embed(title=title), discord.Embed(title=title), + # discord.Embed(title=title), discord.Embed(title=title), discord.Embed(title=title) + # ] + # + # if which == 'week': + # weekly_games = Result.select_season(current.season).where( + # (Result.week == current.week) & (Result.game_type == "baseball") + # ) + # logging.info(f'weekly_games: {weekly_games}') + # + # if weekly_games.count() == 0: + # return None + # + # active_teams = [] + # for game in weekly_games: + # if game.awayteam.abbrev not in active_teams: + # active_teams.append(game.awayteam.abbrev) + # if game.hometeam.abbrev not in active_teams: + # active_teams.append(game.hometeam.abbrev) + # + # records = [] + # for abbrev in active_teams: + # team = Team.get_season(abbrev) + # record = team.get_record(current.week, game_type='baseball') + # points = record['w'] * 2.0 + record['l'] + # this_record = [ + # record, + # points, + # record['w'] / (record['w'] + record['l']), + # team + # ] + # records.append(this_record) + # + # else: + # records = [] + # for this_team in Team.select_season(): + # record = this_team.get_record() + # points = record['w'] * 2.0 + record['l'] + # if record['w'] + record['l'] > 0: + # records.append([ + # record, + # points, + # record['w'] / (record['w'] + record['l']), + # this_team + # ]) + # + # records.sort(key=lambda x: x[1] + x[2], reverse=True) + # + # standings_message = '' + # count = 1 + # embed_count = 0 + # for team in records: + # standings_message += f'**{count}**: {team[3].sname} - {team[1]:.0f} Pts ({team[0]["w"]}-{team[0]["l"]})\n' + # if count % 24 == 0 or count >= len(records): + # logging.info(f'standings_message: {standings_message}') + # all_embeds[embed_count].add_field(name='Standings', value=standings_message) + # all_embeds[embed_count].set_thumbnail(url=self.logo) + # + # standings_message = '' + # embed_count += 1 + # count += 1 + # + # return_embeds = [] + # for x in range(embed_count): + # return_embeds.append(all_embeds[x]) + # + # db.close() + # return return_embeds + + @commands.command(name='build_list', help='Mod: Synchronize fuzzy player list') + async def build_player_command(self, ctx): + self.build_player_list.stop() + self.build_player_list.start() + await ctx.send(f'Just kicked off the build...') + await asyncio.sleep(10) + await ctx.send(f'There are now {len(self.player_list)} player names in the fuzzy search list.') + + @commands.command(name='player', help='For specific cardset, run /player', aliases=['show', 'card']) + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def player_card_command(self, ctx, *, player_name: str): + this_player = fuzzy_search(player_name, self.player_list) + if not this_player: + await ctx.send(f'No clue who that is.') return - yp_query = Player.select(Player.name).where(Player.cardset == cardset) - yearly_players = [] - for x in yp_query: - yearly_players.append(x.name.lower()) + all_players = db_get('players', params=[('name', this_player)]) + all_cards = [ + {'player': x, 'team': {'lname': 'Paper Dynasty', 'logo': IMAGES['logo'], 'season': PD_SEASON}} + for x in all_players['players'] + ] + all_cards.sort(key=lambda x: x['player']['rarity']['value'], reverse=True) - try: - great_match = get_close_matches(name.lower(), yearly_players, cutoff=0.75)[0] - this_guy = Player.get((fn.Lower(Player.name) == great_match.lower()), Player.cardset == cardset) + all_embeds = [] + for x in all_cards: + all_embeds.extend(await get_card_embeds(x)) + await ctx.send(content=None, embeds=all_embeds) - embed = await self.helpers.get_player_embed(this_guy) + @app_commands.command(name='player', description='Display one or more of the player\'s cards') + @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) + async def player_slash_command( + self, interaction: discord.Interaction, player_name: str, + cardset: Literal['All', '2021 Season', '2022 Season', '2022 Promos', 'Sams Choice'] = 'All'): + # min_rarity: Literal['Replacement', 'Reserve', 'Starter', 'All-Star', 'MVP'] = None): + ephemeral = False + if interaction.channel.name in ['paper-dynasty-chat', 'pd-news-ticker']: + ephemeral = True - await self.helpers.send_to_bothole(ctx, None, embed) - - except Exception as e: - await ctx.send(f'I could not find {name.title()}. Is that the right year?') - print(f'**ERROR** (display_player): {e}') - - @commands.command(name='roster', aliases=['team'], help='Show your active roster') - @commands.has_any_role('Paper Dynasty Players') - async def get_inventory(self, ctx, *abbrev): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') + this_player = fuzzy_search(player_name, self.player_list) + if not this_player: + await interaction.response.send_message(f'No clue who that is.') return - if abbrev: - team = Team.get_or_none(Team.abbrev == abbrev[0].upper()) - if not team: - await ctx.send(f'I couldn\'t find **{abbrev}**. Is that the team\'s abbreviation?') + if cardset and cardset != 'All': + this_cardset = cardset_search(cardset, self.cardset_list) + if this_cardset: + all_params = [('name', this_player), ('cardset_id', this_cardset['id'])] + else: + await interaction.response.send_message(f'I couldn\'t find {cardset} cardset.') return else: - team = Team.get_by_owner(ctx.author.id) - if not team: - await ctx.send(f'What team are you searching for?') - return + all_params = [('name', this_player)] - embed = self.helpers.get_active_roster(team, f'{self.bot.get_user(team.gmid).avatar_url}') + all_players = db_get('players', params=all_params) + all_cards = [get_blank_team_card(x) for x in all_players['players']] + all_cards.sort(key=lambda x: x['player']['rarity']['value'], reverse=True) - await self.helpers.send_to_bothole(ctx, content=f'{ctx.author.mention}', embed=embed) + all_embeds = [] + for x in all_cards: + all_embeds.extend(await get_card_embeds(x)) + logging.debug(f'embeds: {all_embeds}') + await interaction.response.send_message(content=None, embeds=all_embeds, ephemeral=ephemeral) + + @app_commands.command(name='record', description='Display team record against AI teams') + @app_commands.checks.has_any_role(PD_PLAYERS_ROLE_NAME) + async def record_slash_command( + self, interaction: discord.Interaction, + league: Literal['All', 'Minor League', 'Major League', 'Hall of Fame'], + team_abbrev: Optional[str] = None): + ephemeral = False + if interaction.channel.name in ['paper-dynasty-chat', 'pd-news-ticker']: + ephemeral = True + + if team_abbrev: + t_query = db_get('teams', params=[('abbrev', team_abbrev)]) + else: + t_query = db_get('teams', params=[('gm_id', interaction.user.id)]) + current = db_get('current') + + if t_query['count'] == 0: + await interaction.response.send_message( + f'Hmm...I can\'t find the team you looking for.', ephemeral=ephemeral + ) + return + team = t_query['teams'][0] + + await interaction.response.send_message( + f'I\'m tallying the {team["lname"]} results now...', ephemeral=ephemeral + ) + + rs_query = db_get( + 'results', + params=[('team_one_id', team['id']), ('season', current['season']), ('short_game', True)] + ) + rl_query = db_get( + 'results', + params=[('team_one_id', team['id']), ('season', current['season']), ('short_game', False)] + ) + + logging.debug(f'getting ai records...') + all_results = get_ai_records(rs_query['results'], rl_query['results']) + logging.debug(f'received ai records') + + logging.debug(f'getting embeds...') + short_embed = get_team_embed(team['lname'], team) + short_embed.description = '3-Inning Games' + minor_embed = get_team_embed(team['lname'], team) + minor_embed.description = 'Minor League Record' + major_embed = get_team_embed(team['lname'], team) + major_embed.description = 'Major League Record' + hof_embed = get_team_embed(team['lname'], team) + hof_embed.description = 'Hall of Fame Record' + logging.debug(f'received embeds') + + logging.debug(f'getting short game embed...') + short_embed = get_record_embed(short_embed, all_results, 'short') + minor_embed = get_record_embed(minor_embed, all_results, 'minor') + major_embed = get_record_embed(major_embed, all_results, 'major') + hof_embed = get_record_embed(hof_embed, all_results, 'hof') + logging.debug(f'received short game embed') + + if league == 'All': + start_page = 0 + elif league == 'Minor League': + start_page = 1 + elif league == 'Major League': + start_page = 2 + else: + start_page = 3 + + await embed_pagination( + [short_embed, minor_embed, major_embed, hof_embed], + interaction.channel, + interaction.user, + timeout=20, + start_page=start_page + ) + + @commands.hybrid_command(name='team', help='Show team overview and rosters') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def team_command(self, ctx: commands.Context, team_abbrev: Optional[str] = None): + if team_abbrev: + t_query = db_get('teams', params=[('abbrev', team_abbrev)]) + else: + t_query = db_get('teams', params=[('gm_id', ctx.author.id)]) + current = db_get('current') + + if t_query['count'] == 0: + await ctx.send(f'Hmm...I can\'t find the team you looking for.') + return + + team = t_query['teams'][0] + embed = team_summary_embed(team, ctx) + + await ctx.send(content=None, embed=embed) + + @commands.hybrid_command(name='branding-pd', help='Update your team branding') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def branding_command( + self, ctx, team_logo_url: str = None, color: str = None, short_name: str = None, full_name: str = None): + owner_team = get_team_by_owner(ctx.author.id) + if not owner_team: + await ctx.send(f'Hmm...I don\'t see a team for you, yet. You can create one with `/newteam`!') + return + + params = [] + if team_logo_url is not None: + params.append(('logo', team_logo_url)) + if color is not None: + params.append(('color', color)) + if short_name is not None: + params.append(('sname', short_name)) + if full_name is not None: + params.append(('lname', full_name)) + + if not params: + await ctx.send(f'You keep thinking on it - I can\'t make updates if you don\'t provide them.') + return + + team = db_patch('teams', object_id=owner_team['id'], params=params) + embed = team_summary_embed(team, ctx) + + await ctx.send(content=None, embed=embed) + + @commands.hybrid_command(name='fuck', help='You know') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def fuck_command(self, ctx, gm: Member): + t_query = db_get('teams', params=[('gm_id', gm.id)]) + if t_query['count'] == 0: + await ctx.send(f'Who?') + return + + await ctx.send(f'{t_query["teams"][0]["sname"]} are a bunch of cuties!') + + @commands.hybrid_command(name='random', help='Check out a random card') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def random_card_command(self, ctx: commands.Context): + this_player = db_get('players/random', params=[('limit', 1)])['players'][0] + this_embed = await get_card_embeds( + {'player': this_player, 'team': {'lname': 'Paper Dynasty', 'logo': IMAGES['logo'], 'season': PD_SEASON}} + ) + await ctx.send(content=None, embeds=this_embed) + + @commands.hybrid_group(name='paperdex', help='Check your collection counts') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def paperdex_command(self, ctx: commands.Context): + if ctx.invoked_subcommand is None: + await ctx.send(f'The available dex commands are: `cardset`') + + @paperdex_command.command(name='cardset', help='Check your collection of a specific cardset') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def paperdex_cardset( + self, ctx: commands.Context, + cardset_name: Literal['2021 Season', '2022 Season', '2022 Promos', 'Sams Choice']): + team = get_team_by_owner(ctx.author.id) + + c_query = db_get('cardsets', params=[('name', cardset_name)]) + if c_query['count'] == 0: + await ctx.send(f'Ope, I couldn\'t find that cardset. {get_cal_user(ctx).mention} halp.') + return + this_cardset = c_query['cardsets'][0] + + all_dex = db_get( + 'paperdex', + params=[('team_id', team['id']), ('cardset_id', this_cardset['id']), ('flat', True)] + ) + dex_player_list = [x['player'] for x in all_dex['paperdex']] + + hof_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + mvp_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + as_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + sta_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + res_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + rep_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + + coll_data = { + 99: { + 'name': 'Hall of Fame', + 'owned': 0, + 'players': [], + 'embeds': [hof_embed] + }, + 1: { + 'name': 'MVP', + 'owned': 0, + 'players': [], + 'embeds': [mvp_embed] + }, + 2: { + 'name': 'All-Star', + 'owned': 0, + 'players': [], + 'embeds': [as_embed] + }, + 3: { + 'name': 'Starter', + 'owned': 0, + 'players': [], + 'embeds': [sta_embed] + }, + 4: { + 'name': 'Reserve', + 'owned': 0, + 'players': [], + 'embeds': [res_embed] + }, + 5: { + 'name': 'Replacement', + 'owned': 0, + 'players': [], + 'embeds': [rep_embed] + }, + 'total_owned': 0 + } + + response = await ctx.send(f'Okay, sifting through your cards...') + + set_players = db_get( + 'players', + params=[('cardset_id', this_cardset['id']), ('flat', True), ('inc_dex', False)], + timeout=5 + ) + if not set_players: + await ctx.send('Yikes, I don\'t see any players for that set.') + return + + for player in set_players['players']: + if player['player_id'] in dex_player_list: + coll_data[player['rarity']]['owned'] += 1 + coll_data['total_owned'] += 1 + player['owned'] = True + else: + player['owned'] = False + + logging.debug(f'player: {player} / type: {type(player)}') + coll_data[player['rarity']]['players'].append(player) + await response.delete() + + cover_embed = get_team_embed(f'{team["lname"]} Collection', team=team) + cover_embed.description = this_cardset['name'] + cover_embed.add_field(name='# Total Cards', value=f'{set_players["count"]}') + cover_embed.add_field(name='# Collected', value=f'{coll_data["total_owned"]}') + display_embeds = [cover_embed] + + for rarity_id in coll_data: + if rarity_id != 'total_owned': + if coll_data[rarity_id]['players']: + coll_data[rarity_id]['embeds'][0].description = f'Rarity: {coll_data[rarity_id]["name"]}' + coll_data[rarity_id]['embeds'][0].add_field( + name='# Collected / # Total Cards', + value=f'{coll_data[rarity_id]["owned"]} / {len(coll_data[rarity_id]["players"])}', + inline=False + ) + + chunk_string = '' + for index, this_player in enumerate(coll_data[rarity_id]['players']): + logging.debug(f'this_player: {this_player}') + chunk_string += '☑ ' if this_player['owned'] else '⬜ ' + chunk_string += f'{this_player["p_name"]}\n' + + if (index + 1) == len(coll_data[rarity_id]["players"]): + coll_data[rarity_id]['embeds'][0].add_field( + name=f'Group {math.ceil((index + 1) / 20)} / ' + f'{math.ceil(len(coll_data[rarity_id]["players"]) / 20)}', + value=chunk_string + ) + + elif (index + 1) % 20 == 0: + coll_data[rarity_id]['embeds'][0].add_field( + name=f'Group {math.floor((index + 1) / 20)} / ' + f'{math.ceil(len(coll_data[rarity_id]["players"]) / 20)}', + value=chunk_string + ) + chunk_string = '' + + display_embeds.append(coll_data[rarity_id]['embeds'][0]) + + await embed_pagination(display_embeds, ctx.channel, ctx.author, timeout=30) + + @paperdex_command.command(name='team', help='Check your collection of a specific MLB team') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def paperdex_team(self, ctx: commands.Context, team_name: str): + team = get_team_by_owner(ctx.author.id) + + team_choice = None + if team_name.title() in ALL_MLB_TEAMS.keys(): + team_choice = team_name.title() + else: + for x in ALL_MLB_TEAMS: + if team_name.upper() in ALL_MLB_TEAMS[x] or team_name.title() in ALL_MLB_TEAMS[x]: + team_choice = x + break + + p_query = db_get('players', params=[('franchise', team_choice)]) + if p_query['count'] == 0: + await ctx.send(f'Hmm...I don\'t see any players from the {team_choice}. What\'s up with that ' + f'{get_cal_user(ctx).mention}?') + + @commands.hybrid_command(name='ai-teams', help='Get list of AI teams and abbreviations') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def ai_teams_command(self, ctx: commands.Context): + embed = get_team_embed(f'Paper Dynasty AI Teams') + embed.description = 'Teams Available for Solo Play' + embed.add_field( + name='AL East', + value=f'BAL - Baltimore Orioles\nBOS - Boston Red Sox\nNYY - New York Yankees\nTBR - Tampa Bay Rays\nTOR - ' + f'Toronto Blue Jays' + ) + embed.add_field( + name='AL Central', + value=f'CLE - Cleveland Guardians\nCHW - Chicago White Sox\nDET - Detroit Tigers\nKCR - Kansas City ' + f'Royals\nMIN - Minnesota Twins' + ) + embed.add_field( + name='NL West', + value=f'HOU - Houston Astros\nLAA - Los Angeles Angels\nOAK - Oakland Athletics\nSEA - Seattle Mariners' + f'\nTEX - Texas Rangers' + ) + embed.add_field( + name='NL East', + value=f'ATL - Atlanta Braves\nMIA - Miami Marlins\nNYM - New York Mets\nPHI - Philadelphia Phillies\n' + f'WSN - Washington Nationals' + ) + embed.add_field( + name='NL Central', + value=f'CHC - Chicago Cubs\nCIN - Cincinnati Reds\nMIL - Milwaukee Brewers\nPIT - Pittsburgh Pirates\n' + f'STL - St Louis Cardinals' + ) + embed.add_field( + name='NL West', + value=f'ARI - Arizona Diamondbacks\nCOL - Colorado Rockies\nLAD - Los Angeles Dodgers\nSDP - San Diego ' + f'Padres\nSFG - San Francisco Giants' + ) + await ctx.send(content=None, embed=embed) + + @commands.hybrid_command(name='standings', help='Check weekly or season-long standings') + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def standings_command(self, ctx: commands.Context, which: Literal['week', 'season']): + current = db_get('current') + params = [('season', current['season']), ('ranked', True)] + + if which == 'week': + params.append(('week', current['week'])) + + r_query = db_get('results', params=params) + if not r_query['count']: + await ctx.send(f'There are no Ranked games on record this {"week" if which == "week" else "season"}.') + return + + all_records = {} + for line in r_query['results']: + home_win = True if line['home_score'] > line['away_score'] else False + + if line['away_team']['id'] not in all_records: + all_records[line['away_team']['id']] = { + 'wins': 1 if not home_win else 0, + 'losses': 1 if home_win else 0, + 'points': 2 if not home_win else 1 + } + else: + all_records[line['away_team']['id']]['wins'] += 1 if not home_win else 0 + all_records[line['away_team']['id']]['losses'] += 1 if home_win else 0 + all_records[line['away_team']['id']]['points'] += 2 if not home_win else 1 + + if line['home_team']['id'] not in all_records: + all_records[line['home_team']['id']] = { + 'wins': 1 if home_win else 0, + 'losses': 1 if not home_win else 0, + 'points': 2 if home_win else 1 + } + else: + all_records[line['home_team']['id']]['wins'] += 1 if home_win else 0 + all_records[line['home_team']['id']]['losses'] += 1 if not home_win else 0 + all_records[line['home_team']['id']]['points'] += 2 if home_win else 0 + + # logging.info(f'all_records:\n\n{all_records}') + sorted_records = sorted(all_records.items(), key=lambda k_v: k_v[1]['points'], reverse=True) + # logging.info(f'sorted_records: {sorted_records}') + + # await ctx.send(f'sorted: {sorted_records}') + embed = get_team_embed( + title=f'{"Season" if which == "season" else "Week"} ' + f'{current["season"] if which == "season" else current["week"]} Standings' + ) + + chunk_string = '' + for index, record in enumerate(sorted_records): + # logging.info(f'index: {index} / record: {record}') + team = db_get('teams', object_id=record[0]) + if team: + chunk_string += f'{record[1]["points"]} pt{"s" if record[1]["points"] != 1 else ""} ' \ + f'({record[1]["wins"]}-{record[1]["losses"]}) - {team["sname"]} [{team["ranking"]}]\n' + + else: + logging.error(f'Could not find team {record[0]} when running standings.') + + if (index + 1) == len(sorted_records): + embed.add_field( + name=f'Group {math.ceil((index + 1) / 20)} / ' + f'{math.ceil(len(sorted_records) / 20)}', + value=chunk_string + ) + elif (index + 1) % 20 == 0: + embed.add_field( + name=f'Group {math.ceil((index + 1) / 20)} / ' + f'{math.floor(len(sorted_records) / 20)}', + value=chunk_string + ) + + await ctx.send(content=None, embed=embed) + + # @paperdex_command.command(name='teams', help='Check your collection based on team') + # @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + # @commands.check(legal_channel) + # async def paperdex_team(self, ctx: commands.Context, team_abbrev: Optional[str]): + + @commands.hybrid_command(name='pullroster', help='Pull saved rosters from your team Sheet', + aliases=['roster', 'rosters', 'pullrosters']) + @app_commands.describe( + specific_roster_num='Enter 1, 2, or 3 to only pull one roster; leave blank to pull all 3', + ) + @commands.has_any_role(PD_PLAYERS_ROLE_NAME) + @commands.check(legal_channel) + async def pull_roster_command(self, ctx: commands.Context, specific_roster_num: Optional[int] = None): + team = get_team_by_owner(ctx.author.id) + if not team: + await ctx.send(f'Do you even have a team? I don\'t know you.') + return + + # Pull data from Sheets + async with ctx.typing(): + roster_data = get_rosters(team, self.bot) + logging.debug(f'roster_data: {roster_data}') + + # Post roster team/card ids and throw error if db says no + for index, roster in enumerate(roster_data): + logging.debug(f'index: {index} / roster: {roster}') + if (not specific_roster_num or specific_roster_num == index + 1) and roster: + this_roster = db_post( + 'rosters', + payload={ + 'team_id': team['id'], 'name': roster['name'], + 'roster_num': roster['roster_num'], 'card_ids': roster['cards'] + } + ) + + await ctx.send(random_conf_gif()) + + # @commands.command(name='standings', aliases=['leaders', 'points', 'weekly'], help='Weekly standings') + # async def standings_command(self, ctx, *week_or_season): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # current = Current.get() + # which = None + # + # if not week_or_season: + # which = 'week' + # title = f'Week {current.week} Standings' + # elif 'season' in week_or_season: + # which = 'season' + # title = f'Season {current.season} Standings' + # else: + # which = 'week' + # title = f'Week {current.week} Standings' + # + # all_embeds = self.get_standings_embeds(current, which, title) + # for embed in all_embeds: + # await ctx.send(content=None, embed=embed) @commands.command(name='in', help='Get Paper Dynasty Players role') async def give_role(self, ctx, *args): await ctx.author.add_roles(discord.utils.get(ctx.guild.roles, name='Paper Dynasty Players')) - await ctx.send('I got u, boo. ;)') + await ctx.send('I got u, boo. ;)\n\nNow that you\'ve got the PD role, you can run all of the Paper Dynasty ' + 'bot commands. For help, check out `/help-pd`') @commands.command(name='out', help='Remove Paper Dynasty Players role') @commands.has_any_role('Paper Dynasty Players') @@ -69,172 +884,553 @@ class Players(commands.Cog): await ctx.author.remove_roles(discord.utils.get(ctx.guild.roles, name='Paper Dynasty Players')) await ctx.send('Oh no! I\'m so sad to see you go! What are we going to do without you?') - @commands.command(name='teams', help='List all teams') - @commands.has_any_role('Paper Dynasty Players') - async def list_teams(self, ctx, *args): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') - return + # @commands.command(name='teams', help='List all teams') + # @commands.has_any_role('Paper Dynasty Players') + # async def list_teams(self, ctx): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # all_teams = Team.select_season() + # team_list = [] + # + # for x in all_teams: + # team_list.append(x) + # team_list.sort(key=lambda y: y.collection_value, reverse=True) + # + # # Collect rarity objects + # # try: + # # rar_mvp = Rarity.get(Rarity.name == 'MVP') + # # rar_als = Rarity.get(Rarity.name == 'All-Star') + # # rar_sta = Rarity.get(Rarity.name == 'Starter') + # # rar_res = Rarity.get(Rarity.name == 'Reserve') + # # rar_rpl = Rarity.get(Rarity.name == 'Replacement') + # # except Exception as e: + # # logging.error(f'**Error**: (players inv getrars) - {e}') + # # return + # + # all_embeds = [ + # discord.Embed(title='All Teams', color=0xdeeadd), discord.Embed(title='All Teams', color=0xdeeadd), + # discord.Embed(title='All Teams', color=0xdeeadd), discord.Embed(title='All Teams', color=0xdeeadd), + # discord.Embed(title='All Teams', color=0xdeeadd), discord.Embed(title='All Teams', color=0xdeeadd) + # ] + # + # # Build embed + # count = 0 + # async with ctx.typing(): + # for x in team_list: + # embed_index = math.floor(count / 24) + # all_embeds[embed_index] = helpers.get_team_blurb(ctx, all_embeds[embed_index], x) + # count += 1 + # + # for x in range(math.ceil(len(all_teams) / 24)): + # await ctx.send(content=None, embed=all_embeds[x]) + # + # db.close() + # + # @commands.command(name='compare', aliases=['vs'], help='Compare two teams') + # @commands.has_any_role('Paper Dynasty Players') + # async def compare_command(self, ctx, team1_abbrev, team2_abbrev): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # away_team = Team.get_season(team1_abbrev) + # if not away_team: + # await ctx.send(f'I couldn\'t find **{team1_abbrev}**. Is that the team\'s abbreviation?') + # return + # home_team = Team.get_season(team2_abbrev) + # if not home_team: + # await ctx.send(f'I couldn\'t find **{team2_abbrev}**. Is that the team\'s abbreviation?') + # return + # + # embed = discord.Embed(title=f'{away_team.abbrev} vs {home_team.abbrev}', color=0xdeeadd) + # embed = helpers.get_team_blurb(ctx, embed, away_team) + # embed = helpers.get_team_blurb(ctx, embed, home_team) + # + # away_tv = away_team.team_value + # home_tv = home_team.team_value + # diff = abs(away_tv - home_tv) + # + # if diff > 12: + # embed.add_field(name='Both Teams Eligible for Packs?', value=f'No, diff is {diff}', inline=False) + # else: + # embed.add_field(name='Both Teams Eligible for Packs?', value='Yes!', inline=False) + # + # await ctx.send(content=None, embed=embed) + # + # db.close() + # + # @commands.command(name='result', help='Log your game results') + # @commands.has_any_role('Paper Dynasty Players') + # async def result_command(self, ctx, awayabbrev: str, awayscore: int, homeabbrev: str, + # homescore: int, scorecard_url, *game_type: str): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # # Check access on the scorecard + # try: + # await ctx.send('Alright, let me go open that Sheet...') + # scorecard = self.sheets.open_by_url(scorecard_url).worksheet_by_title('Results') + # except Exception as e: + # logging.error(f'Unable to access sheet ({scorecard_url}) submitted by {ctx.author.name}') + # await ctx.message.add_reaction('❌') + # await ctx.send(f'{ctx.message.author.mention}, I can\'t access that sheet.') + # return + # + # # Validate teams listed + # try: + # awayteam = Team.get_season(awayabbrev) + # hometeam = Team.get_season(homeabbrev) + # logging.info(f'Final: {awayabbrev} {awayscore} - {homescore} {homeabbrev}') + # if awayteam == hometeam: + # await ctx.message.add_reaction('❌') + # await helpers.send_to_news( + # ctx, + # f'{self.bot.get_user(ctx.author.id).mention} just tried to log ' + # f'a game result played against themselves...', + # embed=None) + # return + # except Exception as e: + # error = f'**ERROR:** {type(e).__name__} - {e}' + # logging.error(error) + # await ctx.message.add_reaction('❌') + # await ctx.send(f'Hey, {ctx.author.mention}, I couldn\'t find the teams you mentioned. You put ' + # f'**{awayabbrev}** as the away team and **{homeabbrev}** as the home team.') + # return + # + # # Check for duplicate scorecard + # dupes = Result.select().where(Result.scorecard == scorecard_url) + # if dupes.count() > 0: + # await ctx.message.add_reaction('❌') + # await ctx.send(f'Bruh. This scorecard was already submitted for credit.') + # return + # + # if not game_type: + # this_q = helpers.Question(self.bot, ctx.channel, 'Was this a wiffleball game?', 'yesno', 15) + # resp = await this_q.ask([ctx.author]) + # + # if resp is None: + # await helpers.react_and_reply(ctx, '❌', 'You think on it and get back to me.') + # return + # elif not resp: + # game_type = 'baseball' + # else: + # game_type = 'wiffleball' + # elif game_type[0] in ['b', 'base', 'baseball', 'standard', 'regular']: + # game_type = 'baseball' + # elif game_type[0] in ['w', 'wif', 'wiff', 'wiffleball']: + # game_type = 'wiffleball' + # else: + # this_q = helpers.Question(self.bot, ctx.channel, 'Was this a wiffleball game?', 'yesno', 15) + # resp = await this_q.ask([ctx.author]) + # + # if resp is None: + # await helpers.react_and_reply(ctx, '❌', 'You think on it and get back to me.') + # return + # elif not resp: + # game_type = 'baseball' + # else: + # game_type = 'wiffleball' + # + # earnings = { + # 'away': 'None', + # 'home': 'None', + # } + # + # if game_type == 'wiffleball': + # away_team_value = 10 + # home_team_value = 10 + # else: + # away_team_value = awayteam.team_value + # home_team_value = hometeam.team_value + # + # # Check author then log result + # if ctx.author.id in [awayteam.gmid, awayteam.gmid2, hometeam.gmid, hometeam.gmid2] \ + # or ctx.author.id == self.bot.owner_id: + # this_result = Result(week=Current.get_by_id(1).week, + # awayteam=awayteam, hometeam=hometeam, + # awayscore=awayscore, homescore=homescore, + # home_team_value=home_team_value, away_team_value=away_team_value, + # scorecard=scorecard_url, season=Current.get_by_id(1).season, game_type=game_type) + # this_result.save() + # await helpers.pause_then_type( + # ctx, + # f'Just logged {awayteam.abbrev.upper()} {awayscore} - ' + # f'{homescore} {hometeam.abbrev.upper()}' + # ) + # await ctx.message.add_reaction('✅') + # + # logging.info('Checking for credit') + # # Credit pack for win + # economy = self.bot.get_cog('Economy') + # if awayscore > homescore: + # # Set embed logo + # if awayteam.logo: + # winner_avatar = awayteam.logo + # else: + # winner_avatar = self.bot.get_user(awayteam.gmid).avatar_url + # + # # Check values and distribute earnings + # if awayteam.team_value - hometeam.team_value <= 12: + # earnings['away'] = '1 Premium Pack' + # logging.info(f'{awayteam.sname} earns 1 Premium pack for the win') + # economy.give_pack(awayteam, 1, 'Premium') + # else: + # logging.info(f'{awayteam.sname} earns nothing for the win - team value {awayteam.team_value} vs ' + # f'{hometeam.team_value}') + # earnings['away'] = f'None - Team was {awayteam.team_value - hometeam.team_value} points higher' + # + # if hometeam.team_value - awayteam.team_value <= 12: + # earnings['home'] = '1 Standard Pack' + # logging.info(f'{hometeam.sname} earns 1 Standard pack for the loss') + # economy.give_pack(hometeam, 1) + # else: + # logging.info(f'{hometeam.sname} earns nothing for the loss - team value {hometeam.team_value} vs ' + # f'{awayteam.team_value}') + # earnings['home'] = f'None - Team was {hometeam.team_value - awayteam.team_value} points higher' + # else: + # if hometeam.logo: + # winner_avatar = hometeam.logo + # else: + # winner_avatar = self.bot.get_user(hometeam.gmid).avatar_url + # + # # Check values and distribute earnings + # if hometeam.team_value - awayteam.team_value <= 12: + # earnings['home'] = '1 Premium Pack' + # logging.info(f'{hometeam.sname} earns 1 Premium pack for the win') + # economy.give_pack(hometeam, 1, 'Premium') + # else: + # logging.info(f'{hometeam.sname} earns nothing for the win - team value {hometeam.team_value} vs ' + # f'{awayteam.team_value}') + # earnings['home'] = f'None - Team was {hometeam.team_value - awayteam.team_value} points higher' + # + # if awayteam.team_value - hometeam.team_value <= 12: + # earnings['away'] = '1 Standard Pack' + # logging.info(f'{awayteam.sname} earns 1 Standard pack for the loss') + # economy.give_pack(awayteam, 1) + # else: + # logging.info(f'{awayteam.sname} earns nothing for the loss - team value {awayteam.team_value} vs ' + # f'{hometeam.team_value}') + # earnings['away'] = f'None - Team was {awayteam.team_value - hometeam.team_value} points higher' + # + # # Get team records + # away_record = awayteam.get_record() + # home_record = hometeam.get_record() + # + # # away_team_value = helpers.get_collection_value(awayteam) + # # home_team_value = helpers.get_collection_value(hometeam) + # # delta = away_team_value - home_team_value + # # if delta < 0: + # # increments = divmod(-delta, helpers.TEAM_DELTA_CONSTANT) + # # # logging.info(f'increments: {increments}') + # # packs = min(increments[0], 5) + # # if packs > 0: + # # earnings['away'] += packs + # # earnings_away.append(f'- {packs} pack{"s" if packs > 1 else ""} for underdog\n') + # # else: + # # increments = divmod(delta, helpers.TEAM_DELTA_CONSTANT) + # # # logging.info(f'increments: {increments}') + # # packs = min(increments[0], 5) + # # if packs > 0: + # # earnings['home'] += packs + # # earnings_home.append(f'- {packs} pack{"s" if packs > 1 else ""} for underdog\n') + # + # # logging.info(f'earn away: {earnings["away"]} / earn home: {earnings["home"]}') + # # away_packs_remaining = Current.get_by_id(1).packlimit - awayteam.weeklypacks + # # home_packs_remaining = Current.get_by_id(1).packlimit - hometeam.weeklypacks + # # away_final_earnings = earnings["away"] if away_packs_remaining >= earnings["away"] else max(away_packs_remaining, 0) + # # home_final_earnings = earnings["home"] if home_packs_remaining >= earnings["home"] else max(home_packs_remaining, 0) + # # ogging.info(f'away_final_earnings: {away_final_earnings}') + # # ogging.info(f'home_final_earnings: {home_final_earnings}') + # + # # economy = self.bot.get_cog('Economy') + # # if away_final_earnings > 0: + # # logging.info(f'away_final_earnings: {away_final_earnings}') + # # economy.give_pack(awayteam, away_final_earnings, True) + # # else: + # # away_final_earnings = 0 + # # if home_final_earnings > 0: + # # logging.info(f'home_final_earnings: {home_final_earnings}') + # # economy.give_pack(hometeam, home_final_earnings, True) + # # else: + # # home_final_earnings = 0 + # + # embed = discord.Embed(title=f'{awayteam.sname} {awayscore} - {homescore} {hometeam.sname}', + # description=f'Score Report - {game_type.title()}') + # embed.add_field(name=awayteam.lname, + # value=f'Team Value: {awayteam.team_value}\n\n' + # f'Earn: {earnings["away"]}\n' + # f'Record: {away_record["w"]}-{away_record["l"]}', + # inline=False) + # embed.add_field(name=hometeam.lname, + # value=f'Team Value: {hometeam.team_value}\n\n' + # f'Earn: {earnings["home"]}\n' + # f'Record: {home_record["w"]}-{home_record["l"]}', + # inline=False) + # embed.add_field(name='Scorecard', + # value=scorecard_url, + # inline=False) + # embed.set_thumbnail(url=winner_avatar) + # await helpers.send_to_news(ctx, None, embed) + # + # db.close() + # + # @result_command.error + # async def result_command_error(self, ctx, error): + # if isinstance(error, commands.MissingRequiredArgument): + # await ctx.send('The syntax is .result ' + # '') + # else: + # await ctx.send(f'Error: {error}') + # + # db.close() + # + # @commands.command(name='sheet', aliases=['google'], help='Link to your roster sheet') + # @commands.has_any_role('Paper Dynasty Players') + # async def get_roster_command(self, ctx): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # team = Team.get_by_owner(ctx.author.id) + # if not team: + # await ctx.send(f'Do you have a team? I don\'t see your name here...') + # return + # + # await ctx.send(f'{ctx.author.mention}\n{team.lname} Roster Sheet: <{helpers.get_roster_sheet_legacy(team)}>') + # + # db.close() + # + # @commands.command(name='setthumbnail', help='Set your team\'s thumbnail image') + # @commands.has_any_role('Paper Dynasty Players') + # async def set_thumbnail_command(self, ctx, url): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # team = Team.get_by_owner(ctx.author.id) + # if not team: + # await ctx.send(f'I cannot find a team that you manage. Are you registered for Paper Dynasty?') + # return + # + # try: + # team.logo = url + # team.save() + # embed = discord.Embed(title=f'{team.lname} Test') + # embed.set_thumbnail(url=team.logo if team.logo else self.logo) + # await ctx.send(content='Got it! What do you think?', embed=embed) + # except Exception as e: + # await ctx.send(f'Huh. Do you know what this means?\n\n{e}') + # + # db.close() + # + # @commands.command(name='rates', help='Check current pull rates') + # @commands.has_any_role('Paper Dynasty Players') + # async def all_card_pulls(self, ctx): + # await self.bot.change_presence(activity=discord.Game(name='strat | .help')) + # total_count = Card.select().count() + # mvp_count = (Card + # .select() + # .join(Player) + # .join(Rarity) + # .where(Card.player.rarity.value == 10)).count() + # als_count = (Card + # .select() + # .join(Player) + # .join(Rarity) + # .where(Card.player.rarity.value == 7)).count() + # sta_count = (Card + # .select() + # .join(Player) + # .join(Rarity) + # .where(Card.player.rarity.value == 5)).count() + # res_count = (Card + # .select() + # .join(Player) + # .join(Rarity) + # .where(Card.player.rarity.value == 3)).count() + # rep_count = (Card + # .select() + # .join(Player) + # .join(Rarity) + # .where(Card.player.rarity.value == 0)).count() + # + # embed = discord.Embed(title='Current Pull Rates', color=0x800080) + # embed.add_field(name='Total Pulls', value=f'{total_count}') + # embed.add_field(name='MVPs', value=f'{mvp_count} ({(mvp_count / total_count)*100:.2f}%)\n' + # f'Target: 0.33%', inline=False) + # embed.add_field(name='All-Stars', value=f'{als_count} ({(als_count / total_count)*100:.2f}%)\n' + # f'Target: 2.50%', inline=False) + # embed.add_field(name='Starters', value=f'{sta_count} ({(sta_count / total_count)*100:.2f}%)\n' + # f'Target: 18.83%', inline=False) + # embed.add_field(name='Reserves', value=f'{res_count} ({(res_count / total_count)*100:.2f}%)\n' + # f'Target: 45.00%', inline=False) + # embed.add_field(name='Replacements', value=f'{rep_count} ({(rep_count / total_count)*100:.2f}%)\n' + # f'Target: 33.33%', inline=False) + # await ctx.send(content=None, embed=embed) + # + # db.close() + # + # @commands.command(name='paperdex', aliases=['collection', 'pokedex'], help='See collection counts') + # @commands.has_any_role('Paper Dynasty Players') + # async def collection_command(self, ctx, *team_or_league): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # league = False + # team = None + # + # if team_or_league: + # if team_or_league[0].lower() in ['l', 'lg', 'league']: + # league = True + # else: + # team = Team.get_season(team_or_league[0]) + # + # if not team: + # team = Team.get_by_owner(ctx.author.id) + # if not team: + # await ctx.send(f'I cannot find a team that you manage. Are you registered for Paper Dynasty?') + # return + # + # if league: + # thumb = 'https://sombaseball.ddns.net/static/images/sba-logo.png' + # title = 'League Paperdex' + # elif team.logo: + # thumb = team.logo + # title = f'{team.lname} Paperdex' + # else: + # thumb = self.bot.get_user(team.gmid).avatar_url + # title = f'{team.lname} Paperdex' + # + # embed = helpers.get_random_embed(title, thumb) + # embed.description = '(Seen / Owned / Total)' + # + # cardsets = Player.select(Player.cardset).distinct().order_by(-Player.cardset) + # overall_total = 0 + # overall_owned = 0 + # overall_seen = 0 + # + # for x in cardsets: + # total_players = Player.select().where((Player.cardset == x.cardset) & (Player.pos1 != 'Park')).count() + # total_parks = Player.select().where((Player.cardset == x.cardset) & (Player.pos1 == 'Park')).count() + # + # if league: + # owned_cards = Card.select().join(Player).distinct() + # seen_cards = len(get_pokedex(cardset=x.cardset, is_park=False)) + # seen_parks = len(get_pokedex(cardset=x.cardset, is_park=True)) + # else: + # owned_cards = Card.select().join(Player).where(Card.team == team) + # seen_cards = len(get_pokedex(team, cardset=x.cardset, is_park=False)) + # seen_parks = len(get_pokedex(team, cardset=x.cardset, is_park=True)) + # + # owned_players = owned_cards.select(Card.player).where( + # (Card.player.cardset == x.cardset) & (Card.player.pos1 != 'Park') + # ).distinct().count() + # + # owned_parks = owned_cards.select(Card.player).where( + # (Card.player.cardset == x.cardset) & (Card.player.pos1 == 'Park') + # ).distinct().count() + # + # set_string = f'Players: {seen_cards} / {owned_players} / {total_players}\n' \ + # f'Parks: {seen_parks} / {owned_parks} / {total_parks}\n' + # ratio = f'{((seen_cards + seen_parks) / (total_players + total_parks)) * 100:.0f}' + # field_name = f'{x.cardset} Set ({ratio}%)' + # + # embed.add_field(name=field_name, value=set_string, inline=False) + # overall_total += total_players + total_parks + # overall_owned += owned_players + owned_parks + # overall_seen += seen_cards + seen_parks + # + # overall_ratio = (overall_seen / overall_total) * 100 + # embed.add_field(name=f'Paper Dynasty Universe ({overall_ratio:.0f}%)', + # value=f'{overall_seen} / {overall_owned} / {overall_total}\n', + # inline=False) + # + # await ctx.send(content=None, embed=embed) + # + # @commands.command(name='gms', aliases=['allgms', 'list'], help='List team/gm info') + # @commands.has_any_role('Paper Dynasty Players') + # async def gms_command(self, ctx): + # if not await legal_channel(ctx): + # await ctx.send('Slide on down to my #pd-bot-hole ;)') + # return + # + # all_teams = Team.select_season() + # team_list = [] + # + # for x in all_teams: + # team_list.append(x) + # team_list.sort(key=lambda y: y.abbrev) + # + # this_color = discord.Color.random() + # all_embeds = [ + # discord.Embed(title='All Teams', color=this_color), discord.Embed(title='All Teams', color=this_color), + # discord.Embed(title='All Teams', color=this_color), discord.Embed(title='All Teams', color=this_color), + # discord.Embed(title='All Teams', color=this_color), discord.Embed(title='All Teams', color=this_color) + # ] + # team_strings = [ + # '', '', '', '', '', '' + # ] + # + # count = 0 + # for x in team_list: + # index = math.floor(count / 18) + # team_strings[index] += f'**{x.abbrev}** - **{x.lname}** - {x.gmname}\n' + # count += 1 + # + # for x in range(math.ceil(len(team_list) / 18)): + # all_embeds[x].set_thumbnail(url=self.logo) + # all_embeds[x].add_field(name='Abbrev - Name - GM', value=team_strings[x], inline=False) + # await ctx.send(content=None, embed=all_embeds[x]) - all_teams = Team.select() + @commands.command(name='c', aliases=['chaos', 'choas'], help='c, chaos, or choas') + async def chaos_roll(self, ctx): + """ + Have the pitcher check for chaos with a runner on base. + """ + d_twenty = random.randint(1, 20) + d_twenty_two = random.randint(1, 20) + flag = None - # Collect rarity objects - try: - rar_mvp = Rarity.get(Rarity.name == 'MVP') - rar_als = Rarity.get(Rarity.name == 'All-Star') - rar_sta = Rarity.get(Rarity.name == 'Starter') - rar_res = Rarity.get(Rarity.name == 'Reserve') - rar_rpl = Rarity.get(Rarity.name == 'Replacement') - except Exception as e: - print(f'**Error**: (players inv getrars) - {e}') - return + if ctx: + if ctx.author.id == 258104532423147520: + d_twenty_three = random.randint(5, 16) + if d_twenty_two < d_twenty_three: + d_twenty_two = d_twenty_three - embed = discord.Embed(title='All Teams', color=0xdeeadd) + if d_twenty == 1: + flag = 'wild pitch' + elif d_twenty == 2: + if random.randint(1, 2) == 1: + flag = 'balk' + else: + flag = 'passed ball' - # Build embed - for x in all_teams: - mvps, alss, stas, ress, reps = 0, 0, 0, 0, 0 - roster = Roster.get_cards(team=x) - - for p in roster: - if p.player.rarity == rar_mvp: - mvps += 1 - elif p.player.rarity == rar_als: - alss += 1 - elif p.player.rarity == rar_sta: - stas += 1 - elif p.player.rarity == rar_res: - ress += 1 - else: - reps += 1 - - un_packs = Pack.select(Pack.id).where((Pack.team == x) & (Pack.card1.is_null())).count() - op_packs = Pack.select(Pack.id).where((Pack.team == x) & (Pack.card1.is_null(False))).count() - - embed.add_field( - name=f'{x.lname}', - value=f'GM: {x.gmname}\n' - f'Packs (Unopen): {op_packs + un_packs} ({un_packs})\n\n' - f'MVPs: {mvps}\n' - f'All-Stars: {alss}\n' - f'Starters: {stas}\n' - f'Reserves: {ress}\n' - f'Replacements: {reps}\n------\n' - f'Collection Value: {self.helpers.get_team_value(x)}') - - await self.helpers.send_to_bothole(ctx, content=f'{ctx.author.mention}', embed=embed) - - @commands.command(name='result', help='Log your game results') - @commands.has_any_role('Paper Dynasty Players') - async def result(self, ctx, awayabbrev: str, awayscore: int, homeabbrev: str, homescore: int): - # Validate teams listed - try: - awayteam = Team.get(Team.abbrev == awayabbrev.upper()) - hometeam = Team.get(Team.abbrev == homeabbrev.upper()) - print(f'Final: {awayabbrev} {awayscore} - {homescore} {homeabbrev}') - except Exception as e: - error = f'**ERROR:** {type(e).__name__} - {e}' - print(error) - await ctx.message.add_reaction('❌') - await ctx.send(f'Hey, {ctx.author.mention}, I couldn\'t find the teams you mentioned. You put ' - f'**{awayabbrev}** as the away team and **{homeabbrev}** as the home team.') - return - - earnings = {'away': 0, 'home': 0} - earnings_away = [] - earnings_home = [] - - # Check author then log result - if ctx.author.id in [awayteam.gmid, awayteam.gmid2, hometeam.gmid, hometeam.gmid2] \ - or ctx.author.id == self.bot.owner_id: - this_result = Result(week=Current.get_by_id(1).week, - awayteam=awayteam, hometeam=hometeam, - awayscore=awayscore, homescore=homescore, - season=Current.get_by_id(1).season) - this_result.save() - await self.helpers.pause_then_type(ctx, f'Just logged {awayteam.abbrev.upper()} {awayscore} - ' - f'{homescore} {hometeam.abbrev.upper()}') - await ctx.message.add_reaction('✅') - - # Credit pack for win - if awayscore > homescore: - earnings['away'] += 1 - earnings_away.append('- 1 pack for the win\n') + if not flag: + roll_message = f'Chaos roll for {ctx.author.name}\n```md\nNo Chaos```' else: - earnings['home'] += 1 - earnings_home.append('- 1 pack for the win\n') + roll_message = f'Chaos roll for {ctx.author.name}\n```md\nCheck {flag}```\n'\ + f'{flag.title()} roll```md\n# {d_twenty_two}\nDetails: [1d20 ({d_twenty_two})]```' - away_team_value = self.helpers.get_team_value(awayteam) - home_team_value = self.helpers.get_team_value(hometeam) - delta = away_team_value - home_team_value - if delta < 0: - increments = divmod(-delta, self.helpers.TEAM_DELTA_CONSTANT) - print(f'increments: {increments}') - packs = min(increments[0], 5) - if packs > 0: - earnings['away'] += packs - earnings_away.append(f'- {packs} pack{"s" if packs > 1 else ""} for underdog\n') - else: - increments = divmod(delta, self.helpers.TEAM_DELTA_CONSTANT) - print(f'increments: {increments}') - packs = min(increments[0], 5) - if packs > 0: - earnings['home'] += packs - earnings_home.append(f'- {packs} pack{"s" if packs > 1 else ""} for underdog\n') + await ctx.send(roll_message) - print(f'earn away: {earnings["away"]} / earn home: {earnings["home"]}') - away_packs_remaining = Current.get_by_id(1).packlimit - awayteam.weeklypacks - home_packs_remaining = Current.get_by_id(1).packlimit - hometeam.weeklypacks - away_final_earnings = away_packs_remaining if away_packs_remaining >= earnings["away"] else earnings["away"] - home_final_earnings = home_packs_remaining if home_packs_remaining >= earnings["home"] else earnings["home"] - print(f'away_final_earnings: {away_final_earnings}') - print(f'home_final_earnings: {home_final_earnings}') + @commands.command(name='sba', hidden=True) + async def sba_command(self, ctx, *, player_name): + async def get_one_player(id_or_name): + req_url = f'http://database/api/v1/players/{id_or_name}' - # TODO: Seems to be giving underdog the square of their earnings - economy = self.bot.get_cog('Economy') - if earnings["away"] > 0: - print(f'away_final_earnings: {away_final_earnings}') - economy.give_pack(awayteam, away_final_earnings) - if earnings["home"] > 0: - print(f'home_final_earnings: {home_final_earnings}') - economy.give_pack(hometeam, home_final_earnings) + resp = requests.get(req_url, timeout=3) + if resp.status_code == 200: + return resp.json() + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') - embed = discord.Embed(title=f'{awayteam.sname} {awayscore} - {homescore} {hometeam.sname}', - description='Score Report / Post Game Earnings') - embed.add_field(name=awayteam.lname, - value=f'Team Value: {away_team_value}\n\n' - f'**Earn: {earnings["away"]} pack{"s" if earnings["away"] != 1 else ""}**' - f' (limit {away_final_earnings})\n' - f'{"Summary:" if len(earnings_away) > 0 else ""}\n' - f'{earnings_away[0] if len(earnings_away) > 0 else ""}' - f'{earnings_away[1] if len(earnings_away) > 1 else ""}', - inline=False) - embed.add_field(name=hometeam.lname, - value=f'Team Value: {home_team_value}\n\n' - f'**Earn: {earnings["home"]} pack{"s" if earnings["home"] != 1 else ""}**' - f' (limit {home_final_earnings})\n' - f'{"Summary:" if len(earnings_home) > 0 else ""}\n' - f'{earnings_home[0] if len(earnings_home) > 0 else ""}' - f'{earnings_home[1] if len(earnings_home) > 1 else ""}', - inline=False) - await self.helpers.send_to_news(ctx, None, embed) - - @commands.command(name='sheet', aliases=['google'], help='Link to your roster sheet') - @commands.has_any_role('Paper Dynasty Players') - async def get_roster_command(self, ctx): - if ctx.message.channel.name != 'pd-bot-hole': - await ctx.send('Slide on down to my bot-hole for running commands.') - await ctx.message.add_reaction('❌') - return - - team = Team.get_by_owner(ctx.author.id) - if not team: - await ctx.send(f'Do you have a team? I don\'t see your name here...') - return - - await ctx.send(f'{ctx.author.mention}\n{team.lname} Roster Sheet: <{self.helpers.get_roster_sheet(team)}>') + this_player = await get_one_player(player_name) + logging.debug(f'this_player: {this_player}') -def setup(bot): - bot.add_cog(Players(bot)) +async def setup(bot): + await bot.add_cog(Players(bot)) diff --git a/db_calls.py b/db_calls.py new file mode 100644 index 0000000..a7cf961 --- /dev/null +++ b/db_calls.py @@ -0,0 +1,161 @@ +import requests +import logging +import json +import os + +AUTH_TOKEN = {'Authorization': f'Bearer {os.environ.get("API_TOKEN")}'} +DB_URL = 'http://pd-database/api' +master_debug = True +alt_database = None + +if alt_database == 'prod': + DB_URL = 'https://sombaseball.ddns.net/pd/api' +elif alt_database == 'local': + DB_URL = 'https://manticorum.ddns.net/pd/api' + + +def param_char(other_params): + if other_params: + return '&' + else: + return '?' + + +def get_req_url(endpoint: str, api_ver: int = 1, object_id: int = None, params: list = None): + req_url = f'{DB_URL}/v{api_ver}/{endpoint}{"/" if object_id is not None else ""}{object_id if object_id is not None else ""}' + + if params: + other_params = False + for x in params: + req_url += f'{param_char(other_params)}{x[0]}={x[1]}' + other_params = True + + return req_url + + +def db_get(endpoint: str, api_ver: int = 1, object_id: int = None, params: list = None, none_okay: bool = True, + timeout: int = 3): + req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params) + log_string = f'get:\n{endpoint} id: {object_id} params: {params}' + logging.info(log_string) if master_debug else logging.debug(log_string) + + try: + resp = requests.get(req_url, timeout=timeout) + except requests.ReadTimeout as e: + logging.error(f'Read Timeout: {e}') + raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please ' + f'hang on a few extra seconds and try again.') + + if resp.status_code == 200: + data = resp.json() + log_string = f'{data}' + if master_debug: + logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + else: + logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + return data + elif none_okay: + data = resp.json() + log_string = f'{data}' + if master_debug: + logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + else: + logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + return None + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +def db_patch(endpoint: str, object_id: int, params: list, api_ver: int = 1, timeout: int = 3): + req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params) + log_string = f'patch:\n{endpoint} {params}' + logging.info(log_string) if master_debug else logging.debug(log_string) + + try: + resp = requests.patch(req_url, headers=AUTH_TOKEN, timeout=timeout) + except requests.ReadTimeout as e: + logging.error(f'Read Timeout: {e}') + raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please ' + f'hang on a few extra seconds and try again.') + if resp.status_code == 200: + data = resp.json() + log_string = f'{data}' + if master_debug: + logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + else: + logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + return data + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +def db_post(endpoint: str, api_ver: int = 1, payload: dict = None, timeout: int = 3): + req_url = get_req_url(endpoint, api_ver=api_ver) + log_string = f'post:\n{endpoint} payload: {payload}\ntype: {type(payload)}' + logging.info(log_string) if master_debug else logging.debug(log_string) + + try: + resp = requests.post(req_url, json=payload, headers=AUTH_TOKEN, timeout=timeout) + except requests.ReadTimeout as e: + logging.error(f'Read Timeout: {e}') + raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please ' + f'hang on a few extra seconds and try again.') + if resp.status_code == 200: + data = resp.json() + log_string = f'{data}' + if master_debug: + logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + else: + logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + return data + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +def db_delete(endpoint: str, object_id: int, api_ver: int = 3): + req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id) + log_string = f'delete:\n{endpoint} {object_id}' + logging.info(log_string) if master_debug else logging.debug(log_string) + + try: + resp = requests.delete(req_url, headers=AUTH_TOKEN, timeout=3) + except requests.ReadTimeout as e: + logging.error(f'Read Timeout: {e}') + raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please ' + f'hang on a few extra seconds and try again.') + if resp.status_code == 200: + data = resp.json() + log_string = f'{data}' + if master_debug: + logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + else: + logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}') + return True + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +def get_team_by_abbrev(abbrev: str): + all_teams = db_get('teams', params=[('abbrev', abbrev)]) + + if not all_teams or not all_teams['count']: + return None + + return all_teams['teams'][0] + + +def post_to_dex(player, team): + return db_post('paperdex', payload={'team_id': team['id'], 'player_id': player['id']}) + + +def team_hash(team): + hash_string = f'{team["abbrev"]}{team["gmid"] / 123:.0f}' + return hash(hash_string) + + + + diff --git a/db_calls_gameplay.py b/db_calls_gameplay.py new file mode 100644 index 0000000..07bc04a --- /dev/null +++ b/db_calls_gameplay.py @@ -0,0 +1,2128 @@ +import datetime +import logging +import random + +import requests +import pydantic + +from typing import Optional +from peewee import * +from playhouse.shortcuts import model_to_dict +from dataclasses import dataclass + +from helpers import SBA_SEASON, PD_SEASON, get_player_url, get_sheets +from db_calls import db_get + +db = SqliteDatabase( + 'storage/gameplay.db', + pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1 * 64000, + 'synchronous': 0 + } +) +SBA_DB_URL = 'http://database/api' + + +def param_char(other_params): + if other_params: + return '&' + else: + return '?' + + +def get_sba_team(id_or_abbrev, season=None): + req_url = f'{SBA_DB_URL}/v1/teams/{id_or_abbrev}' + if season: + req_url += f'?season={season}' + + resp = requests.get(req_url, timeout=3) + + if resp.status_code == 200: + return resp.json() + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +def get_sba_player(id_or_name, season=None): + req_url = f'{SBA_DB_URL}/v2/players/{id_or_name}' + if season is not None: + req_url += f'?season={season}' + + resp = requests.get(req_url, timeout=3) + if resp.status_code == 200: + return resp.json() + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +def get_sba_team_by_owner(season, owner_id): + resp = requests.get(f'{SBA_DB_URL}/v1/teams?season={season}&owner_id={owner_id}&active_only=True', timeout=3) + if resp.status_code == 200: + full_resp = resp.json() + if len(full_resp['teams']) != 1: + raise ValueError(f'One team requested, but {len(full_resp)} were returned') + else: + return full_resp['teams'][0] + else: + logging.warning(resp.text) + raise ValueError(f'DB: {resp.text}') + + +# def pd_db_get(endpoint: str, api_ver: int = 1, object_id: int = None, params: list = None, none_okay: bool = True): +# req_url = pd_get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params) +# logging.info(f'get:\n{endpoint} id: {object_id} params: {params}') +# +# resp = requests.get(req_url, timeout=3) +# if resp.status_code == 200: +# data = resp.json() +# logging.info(f'return: {data}') +# return data +# elif none_okay: +# data = resp.json() +# logging.info(f'return: {data}') +# return None +# else: +# logging.warning(resp.text) +# raise ValueError(f'DB: {resp.text}') + + +# def pd_get_one_team(team_abbrev: str): +# team = pd_db_get('teams', params=[('abbrev', team_abbrev), ('season', PD_SEASON)], none_okay=False)['teams'][0] +# return team +# +# # req_url = pd_get_req_url('teams', ) +# # +# # resp = requests.get(req_url, timeout=3) +# # if resp.status_code == 200: +# # data = resp.json() +# # logging.info(f'return: {data}') +# # return data['teams'][0] +# # else: +# # logging.warning(resp.text) +# # raise ValueError(f'PD DB: {resp.text}') + + +# def pd_get_card_by_id(card_id: int): +# return pd_db_get('cards', object_id=card_id, none_okay=False) + + +class BaseModel(Model): + class Meta: + database = db + + +class ManagerAi(BaseModel): + name = CharField() + steal = IntegerField(default=5) + running = IntegerField(default=5) + hold = IntegerField(default=5) + catcher_throw = IntegerField(default=5) + uncapped_home = IntegerField(default=5) + uncapped_third = IntegerField(default=5) + uncapped_trail = IntegerField(default=5) + bullpen_matchup = IntegerField(default=5) + + +class StratManagerAi(pydantic.BaseModel): + id: int + name: str + steal: int = 5 + running: int = 5 + hold: int = 5 + catcher_throw: int = 5 + uncapped_home: int = 5 + uncapped_third: int = 5 + uncapped_trail: int = 5 + bullpen_matchup: int = 5 + behind_aggression: int = 5 + ahead_aggression: int = 5 + + """ + Rating Rule of Thumb: + 1: Least Aggressive + 5: Average + 10: Most Aggressive + """ + + # def __init__(self, **data) -> None: + # super().__init__(**data) + # + # seed = random.randint(1, 100) + # if seed > 95: + # self.steal = 10 + # self.running = 10 + # self.uncapped_third = 10 + # self.uncapped_home = 10 + # self.uncapped_trail = 10 + # self.behind_aggression = 10 + # self.ahead_aggression = 10 + # elif seed > 80: + # self.steal = 8 + # self.running = 8 + # self.uncapped_third = 8 + # self.uncapped_home = 8 + # self.uncapped_trail = 8 + # self.behind_aggression = 8 + # self.ahead_aggression = 5 + # elif seed <= 40: + # self.steal = 3 + # self.running = 3 + # self.uncapped_third = 3 + # self.uncapped_home = 3 + # self.uncapped_trail = 3 + # self.behind_aggression = 5 + # self.ahead_aggression = 3 + # elif seed <= 15: + # self.steal = 1 + # self.running = 1 + # self.uncapped_third = 1 + # self.uncapped_home = 1 + # self.uncapped_trail = 1 + # self.behind_aggression = 3 + # self.ahead_aggression = 1 + + def check_jump(self, to_base: int, outs: int) -> Optional[str]: + """Returns a string to be appended to the AI note""" + + steal_base = f'attempt to steal' + if to_base == 2 or to_base == 3: + if self.steal == 10: + if to_base == 2: + return f'{steal_base} second if the runner has an ***** auto-jump or the safe range is 13+' + else: + steal_range = 13 + elif self.steal >= 8: + steal_range = 14 + elif self.steal >= 5: + steal_range = 15 + elif self.steal >= 3: + steal_range = 16 + else: + steal_range = 17 + + if outs == 2: + steal_range += 1 + elif outs == 0: + steal_range -= 1 + + return f'{steal_base} {"second" if to_base == 2 else "third"} if their safe range is {steal_range}+' + + else: + return None + + def tag_from_second(self, outs: int) -> str: + """Returns a string to be posted ahead of tag up message""" + + tag_base = f'attempt to tag up if their safe range is' + if self.running >= 8: + tag_range = 5 + elif self.running >= 5: + tag_range = 10 + else: + tag_range = 12 + + if outs == 2: + tag_range += 3 + elif outs == 0: + tag_range -= 2 + + return f'{tag_base} {tag_range}+' + + def tag_from_third(self, outs: int) -> str: + """Returns a string to be posted with the tag up message""" + + tag_base = f'attempt to tag up if their safe range is' + if self.running >= 8: + tag_range = 8 + elif self.running >= 5: + tag_range = 10 + else: + tag_range = 12 + + if outs == 2: + tag_range -= 2 + elif outs == 0: + tag_range += 2 + + return f'{tag_base} {tag_range}+' + + def uncapped_advance(self, to_base: int, outs: int) -> str: + """Returns a string to be posted with the advancement message""" + + advance_base = f'attempt to advance if their safe range is' + + if to_base == 3: + if self.uncapped_third >= 8: + advance_range = 14 + elif self.uncapped_third >= 5: + advance_range = 18 + else: + return f'not attempt to advance' + + if outs == 2: + advance_range += 2 + + else: + if self.uncapped_home >= 8: + advance_range = 8 + elif self.uncapped_home >= 5: + advance_range = 10 + else: + advance_range = 12 + + if outs == 2: + advance_range -= 2 + elif outs == 0: + advance_range += 3 + + return f'{advance_base} {advance_range}+' + + def trail_advance(self, to_base: int, outs: int, sent_home: bool = False) -> str: + """Returns a string to be posted with the advancement message""" + + advance_base = f'attempt to advance if their safe range is' + if sent_home: + if self.uncapped_trail >= 8: + return 'attempt to advance' + elif self.uncapped_trail >= 5: + if outs == 2: + return 'attempt to advance' + else: + advance_range = 14 + else: + return 'not attempt to advance' + + else: + if self.uncapped_trail >= 8: + advance_range = 14 + else: + advance_range = 16 + + return f'{advance_base} {advance_range}+' + + def throw_lead_runner(self, to_base: int, outs: int) -> str: + """Returns a string to be posted with the throw message""" + + return 'throw for the lead runner' + + def throw_which_runner(self, to_base: int, outs: int) -> str: + """Returns a string to be posted with the throw message""" + + if to_base == 4: + return 'throw for the lead runner' + else: + return 'throw for the lead runner if their safe range is 14-' + + +def convert_strat_manager(manager: ManagerAi) -> StratManagerAi: + manager_dict = model_to_dict(manager) + return StratManagerAi(**manager_dict) + + +def get_manager(game) -> Optional[StratManagerAi]: + if not game.ai_team: + return None + + # manager_ai_id = game.home_team_id if game.ai_team == 'home' else game.away_team_id + # manager_ai_id = 1 + team_id = game.home_team_id if game.ai_team == 'home' else game.away_team_id + manager_ai_id = ((datetime.datetime.now().day * team_id) % 3) + 1 + if manager_ai_id > 3 or manager_ai_id < 1: + manager_ai_id = 1 + logging.info(f'manager id: {manager_ai_id} for game {game}') + + try: + this_manager = ManagerAi.get_by_id(manager_ai_id) + except Exception as e: + e_message = f'Could not find manager id {manager_ai_id}' + logging.error(f'{e_message}: {type(e)}: {e}') + raise KeyError(f'Could not find this AI manager\'s playbook') + + return convert_strat_manager(this_manager) + + +def load_ai(): + all_ai = [ + {'name': 'Basic'} + ] + for x in all_ai: + ManagerAi.create(**x) + + return True + + +def ai_batting(this_game, this_play) -> bool: + return (this_play.inning_half == 'Top' and this_game.ai_team == 'away') or \ + (this_play.inning_half == 'Bot' and this_game.ai_team == 'home') + + +db.create_tables([ManagerAi]) + + +class Game(BaseModel): + away_team_id = IntegerField() + home_team_id = IntegerField() + week_num = IntegerField(null=True) + game_num = IntegerField(null=True) + channel_id = IntegerField() + season = IntegerField() + active = BooleanField(default=True) + is_pd = BooleanField(default=False) + ranked = BooleanField(default=True) + short_game = BooleanField(default=False) + away_roster_num = IntegerField(null=True) + home_roster_num = IntegerField(null=True) + first_message = IntegerField(null=True) + ai_team = CharField(null=True) + game_type = CharField(default='minor-league') + + # TODO: add get_away_team and get_home_team that deals with SBa/PD and returns Team object + + +@dataclass +class StratGame: + id: int + away_team_id: int + home_team_id: int + channel_id: int + season: int + active: bool = True + is_pd: bool = False + ranked: bool = True + short_game: bool = False + week_num: int = None + game_num: int = None + away_roster_num: int = None + home_roster_num: int = None + first_message: int = None + ai_team: str = None + game_type: str = None + + +db.create_tables([Game]) + + +# class GameModel(pydantic.BaseModel): +# away_team_id: int +# home_team_id: int +# week_num: Optional[int] = None +# game_num: Optional[int] = None +# channel_id: int +# active: Optional[bool] = True +# is_pd: Optional[bool] = True + + +def count_team_games(team_id: int, active: bool = True): + all_games = Game.select().where( + ((Game.away_team_id == team_id) | (Game.home_team_id == team_id)) & (Game.active == active) + ) + return {'count': all_games.count(), 'games': [model_to_dict(x) for x in all_games]} + + +def post_game(game_dict: dict): + # game_model = GameModel.parse_obj(game_dict) + # new_game = Game( + # away_team_id=game_model.away_team_id, + # home_team_id=game_model.home_team_id, + # week_num=game_model.week_num, + # game_num=game_model.game_num, + # channel_id=game_model.channel_id, + # active=game_model.active, + # is_pd=game_model.is_pd + # ) + # new_game.save() + new_game = Game.create(**game_dict) + # return_game = model_to_dict(new_game) + return_game = StratGame( + new_game.id, new_game.away_team_id, new_game.home_team_id, new_game.channel_id, new_game.season, + new_game.active, new_game.is_pd, new_game.ranked, new_game.short_game, new_game.week_num, new_game.game_num, + new_game.away_roster_num, new_game.home_roster_num, new_game.first_message, new_game.ai_team, new_game.game_type + ) + db.close() + + return return_game + + +def get_one_game(game_id=None, away_team_id=None, home_team_id=None, week_num=None, game_num=None, + channel_id=None, active=None) -> Optional[StratGame]: + single_game, this_game = None, None + if game_id is not None: + single_game = Game.get_by_id(game_id) + + else: + this_game = Game.select() + if away_team_id is not None: + this_game = this_game.where(Game.away_team_id == away_team_id) + if home_team_id is not None: + this_game = this_game.where(Game.home_team_id == home_team_id) + if week_num is not None: + this_game = this_game.where(Game.week_num == week_num) + if game_num is not None: + this_game = this_game.where(Game.game_num == game_num) + if channel_id is not None: + this_game = this_game.where(Game.channel_id == channel_id) + if active is not None: + this_game = this_game.where(Game.active == active) + + if single_game or this_game.count() > 0: + # return_game = model_to_dict(this_game[0]) + r_game = this_game[0] if this_game else single_game + return_game = StratGame(**model_to_dict(r_game)) + db.close() + return return_game + else: + return None + + +async def get_game_team(game: StratGame, gm_id: int = None, team_abbrev: str = None, team_id: int = None) -> dict: + if not gm_id and not team_abbrev and not team_id: + raise KeyError(f'get_game_team requires either one of gm_id, team_abbrev, or team_id to not be None') + + logging.debug(f'getting game team for game {game.id} / gm_id: {gm_id} / ' + f'tm_abbrev: {team_abbrev} / team_id: {team_id} / game: {game}') + if game.is_pd: + if gm_id: + return db_get('teams', params=[('season', PD_SEASON), ('gm_id', gm_id)])['teams'][0] + elif team_id: + return db_get('teams', object_id=team_id) + else: + return db_get('teams', params=[('season', PD_SEASON), ('abbrev', team_abbrev)])['teams'][0] + else: + if gm_id: + return get_sba_team_by_owner(season=SBA_SEASON, owner_id=gm_id) + elif team_id: + return get_sba_team(team_id) + else: + return get_sba_team(team_abbrev, season=SBA_SEASON) + + +def patch_game( + game_id, away_team_id=None, home_team_id=None, week_num=None, game_num=None, channel_id=None, active=None, + first_message=None, home_roster_num=None, away_roster_num=None, ai_team=None): + this_game = Game.get_by_id(game_id) + if away_team_id is not None: + this_game.away_team_id = away_team_id + if home_team_id is not None: + this_game.home_team_id = home_team_id + if week_num is not None: + this_game.week_num = week_num + if game_num is not None: + this_game.game_num = game_num + if channel_id is not None: + this_game.channel_id = channel_id + if active is not None: + this_game.active = active + if first_message is not None: + this_game.first_message = first_message + if home_roster_num is not None: + this_game.home_roster_num = home_roster_num + if away_roster_num is not None: + this_game.away_roster_num = away_roster_num + if ai_team is not None: + this_game.ai_team = ai_team + this_game.save() + # return_game = model_to_dict(this_game) + return_game = StratGame( + this_game.id, this_game.away_team_id, this_game.home_team_id, this_game.channel_id, this_game.season, + this_game.active, this_game.is_pd, this_game.ranked, this_game.short_game, this_game.week_num, + this_game.game_num, this_game.away_roster_num, this_game.home_roster_num, this_game.first_message, + this_game.ai_team + ) + db.close() + return return_game + + +def get_last_game_ids(num_games: int = 10) -> list: + last_games = Game.select(Game.id).order_by(-Game.id).limit(num_games) + game_list = [x.id for x in last_games] + return game_list + + +class Lineup(BaseModel): + game = ForeignKeyField(Game) + team_id = IntegerField() + player_id = IntegerField() + card_id = IntegerField(null=True) # Only needed for Paper Dynasty games + position = CharField() + batting_order = IntegerField() + after_play = IntegerField() + active = BooleanField(default=True) + + +@dataclass +class StratLineup: + id: int + game: StratGame + team_id: int + player_id: int + position: str + batting_order: int + after_play: int + active: bool = True + card_id: int = None + + +def convert_stratlineup(lineup: Lineup) -> StratLineup: + lineup_dict = model_to_dict(lineup) + lineup_dict['game'] = StratGame(**lineup_dict['game']) + return StratLineup(**lineup_dict) + + +db.create_tables([Lineup]) + + +def get_one_lineup( + game_id: int, lineup_id: int = None, team_id: int = None, batting_order: int = None, position: str = None, + active: bool = True, as_obj: bool = False) -> Optional[StratLineup]: + if not batting_order and not position and not lineup_id: + raise KeyError(f'One of batting_order, position, or lineup_id must not be None') + + if lineup_id: + this_lineup = Lineup.get_by_id(lineup_id) + + elif batting_order: + this_lineup = Lineup.get_or_none( + Lineup.game_id == game_id, Lineup.team_id == team_id, Lineup.batting_order == batting_order, + Lineup.active == active + ) + else: + this_lineup = Lineup.get_or_none( + Lineup.game_id == game_id, Lineup.team_id == team_id, Lineup.position == position, + Lineup.active == active + ) + logging.debug(f'get_one_lineup / this_lineup: {this_lineup}') + + if as_obj: + return this_lineup + if not this_lineup: + return None + + # return_value = model_to_dict(this_lineup) + # return_value = StratLineup(**vars(this_lineup)["__data__"]) + return_value = convert_stratlineup(this_lineup) + + # return_value = StratLineup( + # game=StratGame(**model_to_dict(this_lineup.game)), + # team_id=this_lineup.team_id, + # player_id=this_lineup.player_id, + # position=this_lineup.position, + # batting_order=this_lineup.batting_order, + # after_play=this_lineup.after_play, + # active= + # ) + db.close() + return return_value + + +async def get_team_lineups( + game_id: int, team_id: int, inc_inactive: bool = False, pitchers_only: bool = False, as_string: bool = True): + all_lineups = Lineup.select().where( + (Lineup.game_id == game_id) & (Lineup.team_id == team_id) + ).order_by(Lineup.batting_order) + + if not inc_inactive: + all_lineups = all_lineups.where(Lineup.active == True) + + if pitchers_only: + all_lineups = all_lineups.where(Lineup.position == 'P') + + if all_lineups.count() == 0: + return None + + if as_string: + l_string = '' + this_game = Game.get_by_id(game_id) + for x in all_lineups: + l_string += f'{x.batting_order}. {player_link(this_game, await get_player(this_game, x))} - {x.position}\n' + + db.close() + return l_string + + return_lineups = [] + for x in all_lineups: + # return_lineups.append(model_to_dict(x)) + return_lineups.append(convert_stratlineup(x)) + + db.close() + return return_lineups + + +def post_lineups(lineups: list): + with db.atomic(): + Lineup.insert_many(lineups).execute() + db.close() + + +def patch_lineup(lineup_id, active: Optional[bool] = None, position: Optional[str] = None) -> StratLineup: + this_lineup = Lineup.get_by_id(lineup_id) + if active is not None: + this_lineup.active = active + if position is not None: + this_lineup.position = position + this_lineup.save() + + # return_lineup = model_to_dict(this_lineup) + return_value = convert_stratlineup(this_lineup) + db.close() + + return return_value + + +def make_sub(lineup_dict: dict): + # Check for dupe player / card + this_game = Game.get_by_id(lineup_dict['game_id']) + + if this_game.is_pd: + player_conflict = Lineup.select().where( + (Lineup.game_id == lineup_dict['game_id']) & (Lineup.team_id == lineup_dict['team_id']) & + (Lineup.card_id == lineup_dict['card_id']) + ) + db_error = f'This card is already in the lineup' + else: + player_conflict = Lineup.select().where( + (Lineup.game_id == lineup_dict['game_id']) & (Lineup.team_id == lineup_dict['team_id']) & + (Lineup.player_id == lineup_dict['player_id']) + ) + db_error = f'This player is already in the lineup' + + if player_conflict.count(): + db.close() + raise DatabaseError(db_error) + + subbed_player = Lineup.get_or_none( + Lineup.game_id == lineup_dict['game_id'], Lineup.team_id == lineup_dict['team_id'], + Lineup.batting_order == lineup_dict['batting_order'], Lineup.active == True + ) + logging.debug(f'subbed_player: {subbed_player}') + + if subbed_player: + subbed_player = patch_lineup(subbed_player.id, active=False) + + new_lineup = Lineup.create(**lineup_dict) + # return_lineup = model_to_dict(new_lineup) + return_value = convert_stratlineup(new_lineup) + + curr_play = get_current_play(lineup_dict['game_id']) + logging.debug(f'\n\nreturn_value: {return_value}\n\ncurr_play: {curr_play}\n\n') + + # Check current play for updates + if curr_play: + if not curr_play.pitcher and curr_play.batter.team_id != return_value.team_id: + patch_play(curr_play.id, pitcher_id=return_value.id) + + if subbed_player: + if curr_play.batter == subbed_player: + patch_play(curr_play.id, batter_id=return_value.id) + elif curr_play.pitcher == subbed_player: + patch_play(curr_play.id, pitcher_id=return_value.id) + elif curr_play.catcher == subbed_player: + patch_play(curr_play.id, catcher_id=return_value.id) + elif curr_play.on_first == subbed_player: + patch_play(curr_play.id, on_first_id=return_value.id) + elif curr_play.on_second == subbed_player: + patch_play(curr_play.id, on_second_id=return_value.id) + elif curr_play.on_third == subbed_player: + patch_play(curr_play.id, on_third_id=return_value.id) + + db.close() + + return return_value + + +async def get_player(game, lineup_member) -> dict: + if isinstance(game, Game): + if game.is_pd: + this_card = db_get(f'cards', object_id=lineup_member.card_id) + player = this_card['player'] + player['name'] = player['p_name'] + player['team'] = this_card['team'] + return player + else: + return get_sba_player(lineup_member.player_id) + elif isinstance(game, StratGame): + if game.is_pd: + # card_id = lineup_member.card_id if isinstance(lineup_member, Lineup) else lineup_member['card_id'] + card_id = lineup_member['card_id'] if isinstance(lineup_member, dict) else lineup_member.card_id + this_card = db_get(f'cards', object_id=card_id) + player = this_card['player'] + player['name'] = player['p_name'] + player['team'] = this_card['team'] + logging.debug(f'player: {player}') + return player + else: + return get_sba_player(lineup_member.player_id) + else: + raise TypeError(f'Cannot get player; game is not a valid object') + + +def player_link(game, player): + if isinstance(game, Game): + if game.is_pd: + return f'[{player["p_name"]}]({player["image"]})' + else: + return get_player_url(player) + elif isinstance(game, StratGame): + if game.is_pd: + return f'[{player["p_name"]}]({player["image"]})' + else: + return get_player_url(player) + else: + raise TypeError(f'Cannot get player link; game is not a valid object') + + +class Play(BaseModel): + game = ForeignKeyField(Game) + play_num = IntegerField() + batter = ForeignKeyField(Lineup) + pitcher = ForeignKeyField(Lineup, null=True) + on_base_code = IntegerField() + inning_half = CharField() + inning_num = IntegerField() + batting_order = IntegerField() + starting_outs = IntegerField() + away_score = IntegerField() + home_score = IntegerField() + on_first = ForeignKeyField(Lineup, null=True) + on_first_final = IntegerField(null=True) + on_second = ForeignKeyField(Lineup, null=True) + on_second_final = IntegerField(null=True) + on_third = ForeignKeyField(Lineup, null=True) + on_third_final = IntegerField(null=True) + batter_final = IntegerField(null=True) + pa = IntegerField(default=0) + ab = IntegerField(default=0) + # run = IntegerField(null=True) + hit = IntegerField(default=0) + rbi = IntegerField(default=0) + double = IntegerField(default=0) + triple = IntegerField(default=0) + homerun = IntegerField(default=0) + bb = IntegerField(default=0) + so = IntegerField(default=0) + hbp = IntegerField(default=0) + sac = IntegerField(default=0) + ibb = IntegerField(default=0) + gidp = IntegerField(default=0) + bphr = IntegerField(default=0) + bpfo = IntegerField(default=0) + bp1b = IntegerField(default=0) + bplo = IntegerField(default=0) + sb = IntegerField(default=0) + cs = IntegerField(default=0) + outs = IntegerField(default=0) + catcher = ForeignKeyField(Lineup, null=True) + defender = ForeignKeyField(Lineup, null=True) + runner = ForeignKeyField(Lineup, null=True) + check_pos = CharField(null=True) + error = IntegerField(default=0) + wild_pitch = IntegerField(default=0) + passed_ball = IntegerField(default=0) + pick_off = IntegerField(default=0) + balk = IntegerField(default=0) + complete = BooleanField(default=False) + locked = BooleanField(default=False) + is_go_ahead = BooleanField(default=False) + is_tied = BooleanField(default=False) + is_new_inning = BooleanField(default=False) + + +@dataclass +class StratPlay: + id: int + game: StratGame + play_num: int + batter: StratLineup + pitcher: StratLineup + on_base_code: int + inning_half: str + inning_num: int + batting_order: int + starting_outs: int + away_score: int + home_score: int + on_first: StratLineup = None + on_first_final: int = None + on_second: StratLineup = None + on_second_final: int = None + on_third: StratLineup = None + on_third_final: int = None + batter_final: int = None + pa: int = 0 + ab: int = 0 + # run: int = 0 + hit: int = 0 + rbi: int = 0 + double: int = 0 + triple: int = 0 + homerun: int = 0 + bb: int = 0 + so: int = 0 + hbp: int = 0 + sac: int = 0 + ibb: int = 0 + gidp: int = 0 + bphr: int = 0 + bpfo: int = 0 + bp1b: int = 0 + bplo: int = 0 + sb: int = 0 + cs: int = 0 + outs: int = 0 + catcher: StratLineup = None + defender: StratLineup = None + runner: StratLineup = None + check_pos: str = None + error: int = 0 + wild_pitch: int = 0 + passed_ball: int = 0 + pick_off: int = 0 + balk: int = 0 + complete: bool = False + locked: bool = False + is_go_ahead: bool = False + is_tied: bool = False + is_new_inning: bool = False + + +def convert_stratplay(play: Play) -> StratPlay: + play_dict = model_to_dict(play) + play_dict['game'] = StratGame(**play_dict['game']) + if play_dict['batter']: + play_dict['batter'] = convert_stratlineup(play.batter) + if play_dict['pitcher']: + play_dict['pitcher'] = convert_stratlineup(play.pitcher) + if play_dict['on_first']: + play_dict['on_first'] = convert_stratlineup(play.on_first) + if play_dict['on_second']: + play_dict['on_second'] = convert_stratlineup(play.on_second) + if play_dict['on_third']: + play_dict['on_third'] = convert_stratlineup(play.on_third) + if play_dict['catcher']: + play_dict['catcher'] = convert_stratlineup(play.catcher) + if play_dict['defender']: + play_dict['defender'] = convert_stratlineup(play.defender) + if play_dict['runner']: + play_dict['runner'] = convert_stratlineup(play.runner) + + return StratPlay(**play_dict) + + +db.create_tables([Play]) + + +def post_play(play_dict: dict) -> StratPlay: + logging.debug(f'play_dict: {play_dict}') + new_play = Play.create(**play_dict) + # return_play = model_to_dict(new_play) + return_play = convert_stratplay(new_play) + db.close() + + return return_play + + +def patch_play( + play_id, batter_id: int = None, pitcher_id: int = None, catcher_id: int = None, locked: bool = None, + pa: int = None, ab: int = None, hit: int = None, double: int = None, triple: int = None, homerun: int = None, + outs: int = None, so: int = None, bp1b: int = None, bplo: int = None, bphr: int = None, bpfo: int = None, + walk: int = None, hbp: int = None, ibb: int = None, sac: int = None, sb: int = None, is_go_ahead: bool = None, + defender_id: int = None, check_pos: str = None, error: int = None, play_num: int = None, cs: int = None, + on_first_id: int = None, on_first_final: int = None, on_second_id: int = None, on_second_final: int = None, + on_third_id: int = None, on_third_final: int = None, starting_outs: int = None, runner_id: int = None, + complete: bool = None, rbi: int = None, wp: int = None, pb: int = None, pick: int = None, balk: int = None, + is_new_inning: bool = None, batter_final: int = None): + this_play = Play.get_by_id(play_id) + + if batter_id is not None: + this_play.batter_id = batter_id + if pitcher_id is not None: + this_play.pitcher_id = pitcher_id + if catcher_id is not None: + this_play.catcher_id = catcher_id + if runner_id is not None: + this_play.runner_id = runner_id + if locked is not None: + this_play.locked = locked + if complete is not None: + this_play.complete = complete + if pa is not None: + this_play.pa = pa + if ab is not None: + this_play.ab = ab + if hit is not None: + this_play.hit = hit + if rbi is not None: + this_play.rbi = rbi + if double is not None: + this_play.double = double + if triple is not None: + this_play.triple = triple + if homerun is not None: + this_play.homerun = homerun + if starting_outs is not None: + this_play.starting_outs = starting_outs + if outs is not None: + this_play.outs = outs + if so is not None: + this_play.so = so + if bp1b is not None: + this_play.bp1b = bp1b + if bplo is not None: + this_play.bplo = bplo + if bphr is not None: + this_play.bphr = bphr + if bpfo is not None: + this_play.bpfo = bpfo + if walk is not None: + this_play.bb = walk + if hbp is not None: + this_play.hbp = hbp + if ibb is not None: + this_play.ibb = ibb + if sac is not None: + this_play.sac = sac + if sb is not None: + this_play.sb = sb + if cs is not None: + this_play.cs = cs + if wp is not None: + this_play.wild_pitch = wp + if pb is not None: + this_play.passed_ball = pb + if pick is not None: + this_play.pick_off = pick + if balk is not None: + this_play.balk = balk + if defender_id is not None: + this_play.defender_id = defender_id + if check_pos is not None: + this_play.check_pos = check_pos + if error is not None: + this_play.error = error + if play_num is not None: + this_play.play_num = play_num + if on_first_id is not None: + if not on_first_id: + this_play.on_first = None + else: + this_play.on_first_id = on_first_id + if on_first_final is not None: + if not on_first_final: + this_play.on_first_final = None + else: + this_play.on_first_final = on_first_final + if on_second_id is not None: + this_play.on_second_id = on_second_id + if not on_second_id: + this_play.on_second = None + else: + this_play.on_second_id = on_second_id + if on_second_final is not None: + if not on_second_final: + this_play.on_second_final = None + else: + this_play.on_second_final = on_second_final + if on_third_id is not None: + this_play.on_third_id = on_third_id + if not on_third_id: + this_play.on_third = None + else: + this_play.on_third_id = on_third_id + if on_third_final is not None: + if not on_third_final: + this_play.on_third_final = None + else: + this_play.on_third_final = on_third_final + if batter_final is not None: + if not batter_final: + this_play.batter_final = None + else: + this_play.batter_final = batter_final + if is_go_ahead is not None: + if not is_go_ahead: + this_play.is_go_ahead = 0 + else: + this_play.is_go_ahead = 1 + if is_new_inning is not None: + if not is_new_inning: + this_play.is_new_inning = 0 + else: + this_play.is_new_inning = 1 + + this_play.save() + # return_play = model_to_dict(this_play) + return_play = convert_stratplay(this_play) + db.close() + return return_play + + +def get_plays(game_id: int = None, pitcher_id: int = None): + all_plays = Play.select().where(Play.game_id == game_id) + + if pitcher_id is not None: + all_plays = all_plays.where(Play.pitcher_id == pitcher_id) + + return_plays = {'count': all_plays.count(), 'plays': []} + for line in all_plays: + return_plays['plays'].append(convert_stratplay(line)) + db.close() + return return_plays + + +def get_play_by_num(game_id, play_num: int): + latest_play = Play.get_or_none(Play.game_id == game_id, Play.play_num == play_num) + if not latest_play: + return None + + # return_play = model_to_dict(latest_play) + return_play = convert_stratplay(latest_play) + db.close() + return return_play + + +def get_latest_play(game_id): + latest_play = Play.select().where(Play.game_id == game_id).order_by(-Play.id).limit(1) + if not latest_play: + return None + + # return_play = model_to_dict(latest_play[0]) + return_play = convert_stratplay(latest_play[0]) + db.close() + return return_play + + +def undo_play(game_id): + p_query = Play.delete().where(Play.game_id == game_id).order_by(-Play.id).limit(1) + logging.debug(f'p_query: {p_query}') + count = p_query.execute() + db.close() + + return count + + +def get_current_play(game_id) -> Optional[StratPlay]: + curr_play = Play.get_or_none(Play.game_id == game_id, Play.complete == False) + if not curr_play: + latest_play = Play.select().where(Play.game_id == game_id).order_by(-Play.id).limit(1) + if not latest_play: + return None + else: + complete_play(latest_play[0].id, batter_to_base=latest_play[0].batter_final) + curr_play = Play.get_or_none(Play.game_id == game_id, Play.complete == False) + + # return_play = model_to_dict(curr_play) + return_play = convert_stratplay(curr_play) + db.close() + return return_play + + +def get_last_inning_end_play(game_id, inning_half, inning_num): + this_play = Play.select().where( + (Play.game_id == game_id) & (Play.inning_half == inning_half) & (Play.inning_num == inning_num) + ).order_by(-Play.id) + + # return_play = model_to_dict(this_play[0]) + if this_play.count() > 0: + return_play = convert_stratplay(this_play[0]) + else: + return_play = None + db.close() + return return_play + + +class Bullpen(BaseModel): + ai_team_id = IntegerField() + closer_id = IntegerField() + setup_id = IntegerField() + middle_one_id = IntegerField(null=True) + middle_two_id = IntegerField(null=True) + middle_three_id = IntegerField(null=True) + long_one_id = IntegerField(null=True) + long_two_id = IntegerField(null=True) + long_three_id = IntegerField(null=True) + long_four_id = IntegerField(null=True) + last_updated = DateTimeField() + + +db.create_tables([Bullpen]) + + +@dataclass +class StratBullpen: + id: int + ai_team_id: int + closer_id: int + setup_id: int + last_updated: int + middle_one_id: Optional[int] = None + middle_two_id: Optional[int] = None + middle_three_id: Optional[int] = None + long_one_id: Optional[int] = None + long_two_id: Optional[int] = None + long_three_id: Optional[int] = None + long_four_id: Optional[int] = None + + +def convert_bullpen_to_strat(bullpen: Bullpen) -> StratBullpen: + bullpen_dict = model_to_dict(bullpen) + return StratBullpen(**bullpen_dict) + + +def get_or_create_bullpen(ai_team, bot): + this_pen = Bullpen.get_or_none(Bullpen.ai_team_id == ai_team['id']) + + if this_pen: + return convert_bullpen_to_strat(this_pen) + three_days_ago = int(datetime.datetime.timestamp(datetime.datetime.now() - datetime.timedelta(days=3))) * 1000 + logging.debug(f'3da: {three_days_ago} / last_up: {this_pen.last_updated} / L > 3: ' + f'{this_pen.last_updated > three_days_ago}') + + if this_pen and this_pen.last_updated > three_days_ago: + return convert_bullpen_to_strat(this_pen) + else: + this_pen.delete_instance() + + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(ai_team['gsheet']) + r_sheet = this_sheet.worksheet_by_title('My Rosters') + + bullpen_range = f'N30:N41' + raw_cells = r_sheet.range(bullpen_range) + logging.debug(f'raw_cells: {raw_cells}') + + bullpen = Bullpen( + ai_team_id=ai_team['id'], + closer_id=raw_cells[0][0].value, + setup_id=raw_cells[1][0].value, + middle_one_id=raw_cells[2][0].value if raw_cells[2][0].value != '' else None, + middle_two_id=raw_cells[3][0].value if raw_cells[3][0].value != '' else None, + middle_three_id=raw_cells[4][0].value if raw_cells[4][0].value != '' else None, + long_one_id=raw_cells[5][0].value if raw_cells[5][0].value != '' else None, + long_two_id=raw_cells[6][0].value if raw_cells[6][0].value != '' else None, + long_three_id=raw_cells[7][0].value if raw_cells[7][0].value != '' else None, + long_four_id=raw_cells[8][0].value if raw_cells[8][0].value != '' else None, + last_updated=int(datetime.datetime.timestamp(datetime.datetime.now())*1000) + ) + bullpen.save() + logging.debug(f'bullpen: {bullpen}') + + return convert_bullpen_to_strat(bullpen) + + +def advance_runners(play_id: int, num_bases: int, is_error: bool = False, only_forced: bool = False): + """ + Advances runners and tallies RBIs + """ + this_play = Play.get_by_id(play_id) + this_play.rbi = 0 + + if only_forced: + if not this_play.on_first: + if this_play.on_second: + this_play.on_second_final = 2 + if this_play.on_third: + this_play.on_third_final = 3 + this_play.save() + db.close() + return + if this_play.on_second: + if this_play.on_third: + if num_bases > 0: + this_play.on_third_final = 4 + this_play.rbi += 1 if is_error else 0 + + if num_bases > 1: + this_play.on_second_final = 4 + this_play.rbi += 1 if is_error else 0 + elif num_bases == 1: + this_play.on_second_final = 3 + else: + this_play.on_second_final = 2 + else: + if this_play.on_third: + this_play.on_third_final = 3 + + if num_bases > 2: + this_play.on_first_final = 4 + this_play.rbi += 1 if is_error else 0 + elif num_bases == 2: + this_play.on_first_final = 3 + elif num_bases == 1: + this_play.on_first_final = 2 + else: + this_play.on_first_final = 1 + + else: + if this_play.on_third: + if num_bases > 0: + this_play.on_third_final = 4 + this_play.rbi += 1 if not is_error else 0 + else: + this_play.on_third_final = 3 + + if this_play.on_second: + if num_bases > 1: + this_play.on_second_final = 4 + this_play.rbi += 1 if not is_error else 0 + elif num_bases == 1: + this_play.on_second_final = 3 + else: + this_play.on_second_final = 2 + + if this_play.on_first: + if num_bases > 2: + this_play.on_first_final = 4 + this_play.rbi += 1 if not is_error else 0 + elif num_bases == 2: + this_play.on_first_final = 3 + elif num_bases == 1: + this_play.on_first_final = 2 + else: + this_play.on_first_final = 1 + + if num_bases == 4: + this_play.batter_final = 4 + this_play.rbi += 1 + + this_play.save() + db.close() + + +def advance_one_runner(play_id: int, from_base: int, num_bases: int): + """ + Advances runner and excludes RBIs + """ + this_play = Play.get_by_id(play_id) + + if from_base == 1: + if num_bases > 2: + this_play.on_first_final = 4 + elif num_bases == 2: + this_play.on_first_final = 3 + elif num_bases == 1: + this_play.on_first_final = 2 + elif from_base == 2: + if num_bases > 1: + this_play.on_second_final = 4 + elif num_bases == 1: + this_play.on_second_final = 3 + elif from_base == 3: + if num_bases > 0: + this_play.on_third_final = 4 + + if this_play.on_first and not this_play.on_first_final: + this_play.on_first_final = 1 + if this_play.on_second and not this_play.on_second_final: + this_play.on_second_final = 2 + if this_play.on_third and not this_play.on_third_final: + this_play.on_third_final = 3 + + this_play.save() + db.close() + + +def complete_play(play_id, batter_to_base: int = None): + """ + Finalizes current play and sets base values for next play + """ + this_play = Play.get_by_id(play_id) + + this_play.locked = False + this_play.complete = True + this_play.save() + + logging.debug(f'starting the inning calc') + new_inning_half = this_play.inning_half + new_inning_num = this_play.inning_num + if this_play.runner or this_play.wild_pitch or this_play.passed_ball or this_play.pick_off or this_play.balk: + new_batting_order = this_play.batting_order + else: + new_batting_order = this_play.batting_order + 1 if this_play.batting_order < 9 else 1 + new_bteam_id = this_play.batter.team_id + new_pteam_id = this_play.pitcher.team_id + new_starting_outs = this_play.starting_outs + this_play.outs + new_on_first = None + new_on_second = None + new_on_third = None + # score_increment = this_play.homerun + # patch to handle little league home runs TODO: standardize on just _on_final for these + logging.info(f'complete_play - this_play: {this_play}') + if this_play.batter_final == 4 or batter_to_base == 4: + score_increment = 1 + else: + score_increment = 0 + logging.info(f'complete_play - score_increment: {score_increment}') + + if this_play.on_first_final == 99: + this_play.on_first_final = None + elif this_play.on_first_final == 4: + score_increment += 1 + + if this_play.on_second_final == 99: + this_play.on_second_final = None + elif this_play.on_second_final == 4: + score_increment += 1 + + if this_play.on_third_final == 99: + this_play.on_third_final = None + elif this_play.on_third_final == 4: + score_increment += 1 + + this_play.save() + # for runner in [this_play.on_first_final, this_play.on_second_final, this_play.on_third_final]: + # if runner == 4: + # score_increment += 1 + + if this_play.starting_outs + this_play.outs > 2: + new_starting_outs = 0 + new_obc = 0 + if this_play.inning_half == 'Top': + new_inning_half = 'Bot' + new_bteam_id = this_play.game.home_team_id + new_pteam_id = this_play.game.away_team_id + else: + new_inning_half = 'Top' + new_inning_num += 1 + new_bteam_id = this_play.game.away_team_id + new_pteam_id = this_play.game.home_team_id + + if new_inning_num > 1: + last_inning_play = get_last_inning_end_play(this_play.game.id, new_inning_half, new_inning_num - 1) + if last_inning_play.runner or last_inning_play.pick_off: + new_batting_order = last_inning_play.batting_order + else: + new_batting_order = last_inning_play.batting_order + 1 if last_inning_play.batting_order < 9 else 1 + else: + new_batting_order = 1 + # Not an inning-ending play + else: + logging.debug(f'starting the obc calc') + bases_occ = [False, False, False, False] + + # Set the occupied bases for the next play and lineup member occupying it + for runner, base in [ + (this_play.on_first, this_play.on_first_final), (this_play.on_second, this_play.on_second_final), + (this_play.on_third, this_play.on_third_final) + ]: + if base: + bases_occ[base - 1] = True + if base == 1: + new_on_first = runner + elif base == 2: + new_on_second = runner + elif base == 3: + new_on_third = runner + # elif base == 4: + # score_increment += 1 + + # Set the batter-runner occupied base for the next play + if batter_to_base: + if batter_to_base == 1: + bases_occ[0] = True + new_on_first = this_play.batter + elif batter_to_base == 2: + bases_occ[1] = True + new_on_second = this_play.batter + elif batter_to_base == 3: + bases_occ[2] = True + new_on_third = this_play.batter + # elif batter_to_base == 4: + # score_increment += 1 + this_play.batter_final = batter_to_base + this_play.save() + # Set the OBC based on the bases occupied + if bases_occ[2]: + if bases_occ[1]: + if bases_occ[0]: + new_obc = 7 + else: + new_obc = 6 + elif bases_occ[0]: + new_obc = 5 + else: + new_obc = 3 + elif bases_occ[1]: + if bases_occ[0]: + new_obc = 4 + else: + new_obc = 2 + elif bases_occ[0]: + new_obc = 1 + else: + new_obc = 0 + + if this_play.inning_half == 'Top': + new_away_score = this_play.away_score + score_increment + new_home_score = this_play.home_score + else: + new_away_score = this_play.away_score + new_home_score = this_play.home_score + score_increment + + # A team score + if score_increment: + logging.debug(f'complete_play: \n\nscore_increment: {score_increment}\n\nnew home score: {new_home_score}\n\n' + f'new_away_score: {new_away_score}\n\nthis_play.away_score: {this_play.away_score}\n\n' + f'this_player.home_score: {this_play.home_score}') + # Game is now tied + if new_home_score == new_away_score: + logging.debug(f'\n\nGame {this_play.game} is now tied\n\n') + this_play.is_tied = 1 + + # One team took the lead + elif (this_play.away_score <= this_play.home_score) and (new_away_score > new_home_score): + logging.debug(f'\n\nTeam {this_play.batter.team_id} took the lead\n\n') + this_play.is_go_ahead = 1 + this_play.save() + + elif (this_play.home_score <= this_play.away_score) and (new_home_score > new_away_score): + logging.debug(f'\n\nteam {this_play.batter.team_id} took the lead\n\n') + this_play.is_go_ahead = 1 + this_play.save() + + batter = get_one_lineup(this_play.game.id, team_id=new_bteam_id, batting_order=new_batting_order) + batter_id = batter.id if batter else None + pitcher = get_one_lineup(this_play.game_id, team_id=new_pteam_id, position='P') + pitcher_id = pitcher.id if pitcher else None + + logging.debug(f'done the obc calc') + next_play = Play.create(**{ + 'game_id': this_play.game.id, + 'play_num': this_play.play_num + 1, + 'batter_id': batter_id, + 'pitcher_id': pitcher_id, + 'on_base_code': new_obc, + 'inning_half': new_inning_half, + 'inning_num': new_inning_num, + 'next_inning_num': new_inning_num, + 'batting_order': new_batting_order, + 'starting_outs': new_starting_outs, + 'away_score': new_away_score, + 'home_score': new_home_score, + 'on_first': new_on_first, + 'on_second': new_on_second, + 'on_third': new_on_third, + 'is_new_inning': 1 if new_inning_half != this_play.inning_half else 0 + }) + # return_play = model_to_dict(next_play) + return_play = convert_stratplay(next_play) + db.close() + return return_play + + +def get_batting_stats(game_id, lineup_id: int = None, team_id: int = None): + batting_stats = Play.select( + Play.batter, + fn.SUM(Play.pa).over(partition_by=[Play.batter_id]).alias('pl_pa'), + fn.SUM(Play.ab).over(partition_by=[Play.batter_id]).alias('pl_ab'), + fn.SUM(Play.hit).over(partition_by=[Play.batter_id]).alias('pl_hit'), + fn.SUM(Play.rbi).over(partition_by=[Play.batter_id]).alias('pl_rbi'), + # fn.COUNT(Play.on_first_final).filter( + # Play.on_first_final == 4).over(partition_by=[Play.batter_id]).alias('pl_run_first'), + # fn.COUNT(Play.on_second_final).filter( + # Play.on_second_final == 4).over(partition_by=[Play.batter_id]).alias('pl_run_second'), + # fn.COUNT(Play.on_third_final).filter( + # Play.on_third_final == 4).over(partition_by=[Play.batter_id]).alias('pl_run_third'), + # fn.COUNT(Play.batter_final).filter( + # Play.batter_final == 4).over(partition_by=[Play.batter_id]).alias('pl_run_batter'), + fn.SUM(Play.double).over(partition_by=[Play.batter_id]).alias('pl_double'), + fn.SUM(Play.triple).over(partition_by=[Play.batter_id]).alias('pl_triple'), + fn.SUM(Play.homerun).over(partition_by=[Play.batter_id]).alias('pl_homerun'), + fn.SUM(Play.bb).over(partition_by=[Play.batter_id]).alias('pl_bb'), + fn.SUM(Play.so).over(partition_by=[Play.batter_id]).alias('pl_so'), + fn.SUM(Play.hbp).over(partition_by=[Play.batter_id]).alias('pl_hbp'), + fn.SUM(Play.sac).over(partition_by=[Play.batter_id]).alias('pl_sac'), + fn.SUM(Play.ibb).over(partition_by=[Play.batter_id]).alias('pl_ibb'), + fn.SUM(Play.gidp).over(partition_by=[Play.batter_id]).alias('pl_gidp'), + fn.SUM(Play.sb).over(partition_by=[Play.runner_id]).alias('pl_sb'), + fn.SUM(Play.cs).over(partition_by=[Play.runner_id]).alias('pl_cs'), + fn.SUM(Play.bphr).over(partition_by=[Play.batter_id]).alias('pl_bphr'), + fn.SUM(Play.bpfo).over(partition_by=[Play.batter_id]).alias('pl_bpfo'), + fn.SUM(Play.bp1b).over(partition_by=[Play.batter_id]).alias('pl_bp1b'), + fn.SUM(Play.bplo).over(partition_by=[Play.batter_id]).alias('pl_bplo'), + fn.SUM(Play.pa).over(partition_by=[Play.batter.team_id]).alias('tm_pa'), + fn.SUM(Play.ab).over(partition_by=[Play.batter.team_id]).alias('tm_ab'), + fn.SUM(Play.hit).over(partition_by=[Play.batter.team_id]).alias('tm_hit'), + fn.SUM(Play.rbi).over(partition_by=[Play.batter.team_id]).alias('tm_rbi'), + fn.SUM(Play.double).over(partition_by=[Play.batter.team_id]).alias('tm_double'), + fn.SUM(Play.triple).over(partition_by=[Play.batter.team_id]).alias('tm_triple'), + fn.SUM(Play.homerun).over(partition_by=[Play.batter.team_id]).alias('tm_homerun'), + fn.SUM(Play.bb).over(partition_by=[Play.batter.team_id]).alias('tm_bb'), + fn.SUM(Play.so).over(partition_by=[Play.batter.team_id]).alias('tm_so'), + fn.SUM(Play.hbp).over(partition_by=[Play.batter.team_id]).alias('tm_hbp'), + fn.SUM(Play.sac).over(partition_by=[Play.batter.team_id]).alias('tm_sac'), + fn.SUM(Play.ibb).over(partition_by=[Play.batter.team_id]).alias('tm_ibb'), + fn.SUM(Play.gidp).over(partition_by=[Play.batter.team_id]).alias('tm_gidp'), + fn.SUM(Play.sb).over(partition_by=[Play.batter.team_id]).alias('tm_sb'), + fn.SUM(Play.cs).over(partition_by=[Play.batter.team_id]).alias('tm_cs'), + fn.SUM(Play.bphr).over(partition_by=[Play.batter.team_id]).alias('tm_bphr'), + fn.SUM(Play.bpfo).over(partition_by=[Play.batter.team_id]).alias('tm_bpfo'), + fn.SUM(Play.bp1b).over(partition_by=[Play.batter.team_id]).alias('tm_bp1b'), + fn.SUM(Play.bplo).over(partition_by=[Play.batter.team_id]).alias('tm_bplo'), + ).join(Lineup, on=Play.batter).where(Play.game_id == game_id) + + if lineup_id is not None: + batting_stats = batting_stats.where(Play.batter_id == lineup_id) + elif team_id is not None: + batting_stats = batting_stats.where(Play.batter.team_id == team_id) + + done_batters = [] + return_batters = [] + for x in batting_stats: + if x.batter.id not in done_batters: + runs_scored = Play.select(Play.pa).where( + ((Play.on_first == x.batter) & (Play.on_first_final == 4)) | + ((Play.on_second == x.batter) & (Play.on_second_final == 4)) | + ((Play.on_third == x.batter) & (Play.on_third_final == 4)) | + ((Play.batter == x.batter) & (Play.batter_final == 4)) + ).count() + stolen_bases = Play.select(Play.pa).where( + (Play.runner == x.batter) & (Play.sb == 1) + ).count() + return_batters.append({ + 'batter_id': x.batter_id, + 'card_id': x.batter.card_id, + 'team_id': x.batter.team_id, + 'pos': x.batter.position, + 'pl_run': runs_scored, + 'pl_pa': x.pl_pa, + 'pl_ab': x.pl_ab, + 'pl_hit': x.pl_hit, + 'pl_rbi': x.pl_rbi, + 'pl_double': x.pl_double, + 'pl_triple': x.pl_triple, + 'pl_homerun': x.pl_homerun, + 'pl_bb': x.pl_bb, + 'pl_so': x.pl_so, + 'pl_hbp': x.pl_hbp, + 'pl_sac': x.pl_sac, + 'pl_ibb': x.pl_ibb, + 'pl_gidp': x.pl_gidp, + 'pl_sb': stolen_bases, + 'pl_cs': x.pl_cs, + 'pl_bphr': x.pl_bphr, + 'pl_bpfo': x.pl_bpfo, + 'pl_bp1b': x.pl_bp1b, + 'pl_bplo': x.pl_bplo, + 'tm_pa': x.tm_pa, + 'tm_ab': x.tm_ab, + 'tm_hit': x.tm_hit, + 'tm_rbi': x.tm_rbi, + 'tm_double': x.tm_double, + 'tm_triple': x.tm_triple, + 'tm_homerun': x.tm_homerun, + 'tm_bb': x.tm_bb, + 'tm_so': x.tm_so, + 'tm_hbp': x.tm_hbp, + 'tm_sac': x.tm_sac, + 'tm_ibb': x.tm_ibb, + 'tm_gidp': x.tm_gidp, + 'tm_sb': x.tm_sb, + 'tm_cs': x.tm_cs, + 'tm_bphr': x.tm_bphr, + 'tm_bpfo': x.tm_bpfo, + 'tm_bp1b': x.tm_bp1b, + 'tm_bplo': x.tm_bplo, + }) + done_batters.append(x.batter.id) + + db.close() + return return_batters + + +def get_fielding_stats(game_id, lineup_id: int = None, team_id: int = None): + fielding_stats = Play.select( + Play.defender, + Play.check_pos, + fn.SUM(Play.error).over(partition_by=[Play.defender_id]).alias('pl_error'), + fn.SUM(Play.hit).over(partition_by=[Play.defender_id]).alias('pl_xhit'), + fn.COUNT(Play.defender).over(partition_by=[Play.defender_id]).alias('pl_xch'), + fn.SUM(Play.error).over(partition_by=[Play.defender.team_id]).alias('tm_error'), + ).join(Lineup, on=Play.defender).where(Play.game_id == game_id) + + if lineup_id is not None: + fielding_stats = fielding_stats.where(Play.defender_id == lineup_id) + elif team_id is not None: + fielding_stats = fielding_stats.where(Play.defender.team_id == team_id) + + added_card_ids = [] + all_stats = [] + for x in fielding_stats: + if x.defender.card_id not in added_card_ids: + added_card_ids.append(x.defender.card_id) + all_stats.append({ + 'defender_id': x.defender_id, + 'card_id': x.defender.card_id, + 'team_id': x.defender.team_id, + 'pos': x.check_pos, + 'pl_error': x.pl_error, + 'pl_xhit': x.pl_xhit, + 'pl_xch': x.pl_xch, + 'tm_error': x.pl_error, + 'pl_pb': 0, + 'pl_sbc': 0, + 'pl_csc': 0 + }) + + catching_stats = Play.select( + Play.catcher, + fn.SUM(Play.passed_ball).over(partition_by=[Play.catcher_id]).alias('pl_pb'), + fn.SUM(Play.sb).over(partition_by=[Play.catcher_id]).alias('pl_sbc'), + fn.SUM(Play.cs).over(partition_by=[Play.catcher_id]).alias('pl_csc') + ).join(Lineup, on=Play.catcher).where(Play.game_id == game_id) + + if lineup_id is not None: + catching_stats = catching_stats.where(Play.defender_id == lineup_id) + elif team_id is not None: + catching_stats = catching_stats.where(Play.defender.team_id == team_id) + + for x in catching_stats: + all_stats.append({ + 'defender_id': x.catcher_id, + 'card_id': x.catcher.card_id, + 'team_id': x.catcher.team_id, + 'pos': 'C', + 'pl_error': 0, + 'pl_xhit': 0, + 'pl_xch': 0, + 'tm_error': 0, + 'pl_pb': x.pl_pb, + 'pl_sbc': x.pl_sbc, + 'pl_csc': x.pl_csc + }) + + logging.debug(f'fielding_stats: {all_stats}') + db.close() + return all_stats + +# def new_get_batting_stats(game_id, lineup_id: int = None, team_id: int = None): +# return_stats = [] +# +# if lineup_id is not None: +# batting_stats = Play.select( +# Play.batter, +# fn.SUM(Play.pa).over(partition_by=[Play.batter_id]).alias('pl_pa'), +# fn.SUM(Play.ab).over(partition_by=[Play.batter_id]).alias('pl_ab'), +# fn.SUM(Play.hit).over(partition_by=[Play.batter_id]).alias('pl_hit'), +# fn.SUM(Play.rbi).over(partition_by=[Play.batter_id]).alias('pl_rbi'), +# fn.SUM(Play.double).over(partition_by=[Play.batter_id]).alias('pl_double'), +# fn.SUM(Play.triple).over(partition_by=[Play.batter_id]).alias('pl_triple'), +# fn.SUM(Play.homerun).over(partition_by=[Play.batter_id]).alias('pl_homerun'), +# fn.SUM(Play.bb).over(partition_by=[Play.batter_id]).alias('pl_bb'), +# fn.SUM(Play.so).over(partition_by=[Play.batter_id]).alias('pl_so'), +# fn.SUM(Play.hbp).over(partition_by=[Play.batter_id]).alias('pl_hbp'), +# fn.SUM(Play.sac).over(partition_by=[Play.batter_id]).alias('pl_sac'), +# fn.SUM(Play.ibb).over(partition_by=[Play.batter_id]).alias('pl_ibb'), +# fn.SUM(Play.gidp).over(partition_by=[Play.batter_id]).alias('pl_gidp'), +# fn.SUM(Play.sb).over(partition_by=[Play.batter_id]).alias('pl_sb'), +# fn.SUM(Play.cs).over(partition_by=[Play.batter_id]).alias('pl_cs'), +# fn.SUM(Play.bphr).over(partition_by=[Play.batter_id]).alias('pl_bphr'), +# fn.SUM(Play.bpfo).over(partition_by=[Play.batter_id]).alias('pl_bpfo'), +# fn.SUM(Play.bp1b).over(partition_by=[Play.batter_id]).alias('pl_bp1b'), +# fn.SUM(Play.bplo).over(partition_by=[Play.batter_id]).alias('pl_bplo') +# ).join(Lineup, on=Play.batter).where((Play.game_id == game_id) & (Play.batter_id == lineup_id)) +# +# done_batters = [] +# for x in batting_stats: +# if x.batter.id not in done_batters: +# return_stats.append({ +# 'batter_id': x.batter_id, +# 'pl_pa': x.pl_pa, +# 'pl_ab': x.pl_ab, +# 'pl_hit': x.pl_hit, +# 'pl_rbi': x.pl_rbi, +# 'pl_double': x.pl_double, +# 'pl_triple': x.pl_triple, +# 'pl_homerun': x.pl_homerun, +# 'pl_bb': x.pl_bb, +# 'pl_so': x.pl_so, +# 'pl_hbp': x.pl_hbp, +# 'pl_sac': x.pl_sac, +# 'pl_ibb': x.pl_ibb, +# 'pl_gidp': x.pl_gidp, +# 'pl_sb': x.pl_sb, +# 'pl_cs': x.pl_cs, +# 'pl_bphr': x.pl_bphr, +# 'pl_bpfo': x.pl_bpfo, +# 'pl_bp1b': x.pl_bp1b, +# 'pl_bplo': x.pl_bplo, +# }) +# done_batters.append(x.batter.id) +# logging.info(f'batting stats: {return_stats}') +# +# elif team_id is not None: +# batting_stats = Play.select( +# fn.SUM(Play.pa).over(partition_by=[Play.batter.team_id]).alias('tm_pa'), +# fn.SUM(Play.ab).over(partition_by=[Play.batter.team_id]).alias('tm_ab'), +# fn.SUM(Play.hit).over(partition_by=[Play.batter.team_id]).alias('tm_hit'), +# fn.SUM(Play.rbi).over(partition_by=[Play.batter.team_id]).alias('tm_rbi'), +# fn.SUM(Play.double).over(partition_by=[Play.batter.team_id]).alias('tm_double'), +# fn.SUM(Play.triple).over(partition_by=[Play.batter.team_id]).alias('tm_triple'), +# fn.SUM(Play.homerun).over(partition_by=[Play.batter.team_id]).alias('tm_homerun'), +# fn.SUM(Play.bb).over(partition_by=[Play.batter.team_id]).alias('tm_bb'), +# fn.SUM(Play.so).over(partition_by=[Play.batter.team_id]).alias('tm_so'), +# fn.SUM(Play.hbp).over(partition_by=[Play.batter.team_id]).alias('tm_hbp'), +# fn.SUM(Play.sac).over(partition_by=[Play.batter.team_id]).alias('tm_sac'), +# fn.SUM(Play.ibb).over(partition_by=[Play.batter.team_id]).alias('tm_ibb'), +# fn.SUM(Play.gidp).over(partition_by=[Play.batter.team_id]).alias('tm_gidp'), +# fn.SUM(Play.sb).over(partition_by=[Play.batter.team_id]).alias('tm_sb'), +# fn.SUM(Play.cs).over(partition_by=[Play.batter.team_id]).alias('tm_ccs'), +# fn.SUM(Play.bphr).over(partition_by=[Play.batter.team_id]).alias('tm_bphr'), +# fn.SUM(Play.bpfo).over(partition_by=[Play.batter.team_id]).alias('tm_bpfo'), +# fn.SUM(Play.bp1b).over(partition_by=[Play.batter.team_id]).alias('tm_bp1b'), +# fn.SUM(Play.bplo).over(partition_by=[Play.batter.team_id]).alias('tm_bplo') +# ).join(Lineup, on=Play.batter).where((Play.game_id == game_id) & (Play.batter.team_id == team_id)) +# +# logging.info(f'batting_stats count: {batting_stats.count()}') +# done_batters = [] +# for x in batting_stats: +# if x.batter.id not in done_batters: +# return_stats.append({ +# 'tm_pa': x.tm_pa, +# 'tm_ab': x.tm_ab, +# 'tm_hit': x.tm_hit, +# 'tm_rbi': x.tm_rbi, +# 'tm_double': x.tm_double, +# 'tm_triple': x.tm_triple, +# 'tm_homerun': x.tm_homerun, +# 'tm_bb': x.tm_bb, +# 'tm_so': x.tm_so, +# 'tm_hbp': x.tm_hbp, +# 'tm_sac': x.tm_sac, +# 'tm_ibb': x.tm_ibb, +# 'tm_gidp': x.tm_gidp, +# 'tm_sb': x.tm_sb, +# 'tm_cs': x.tm_cs, +# 'tm_bphr': x.tm_bphr, +# 'tm_bpfo': x.tm_bpfo, +# 'tm_bp1b': x.tm_bp1b, +# 'tm_bplo': x.tm_bplo, +# }) +# done_batters.append(x.batter.id) +# +# else: +# raise DatabaseError(f'Either lineup_id or team_id must be specified.') +# +# db.close() +# return return_stats + + +# def get_final_batting_stats(game_id: int, team_id: int): +# team_lineups = Lineup.select().where( +# (Lineup.game_id == game_id) & (Lineup.team_id == team_id) & (Lineup.batting_order < 10) +# ).order_by(Lineup.batting_order) +# logging.info(f'team_lineups count: {team_lineups.count()} / lineups: {team_lineups}') +# +# all_stats = [] +# for line in team_lineups: +# all_stats.append(get_batting_stats(game_id, lineup_id=line.id)) +# +# return all_stats + + +def get_pitching_stats(game_id, lineup_id: int = None, team_id: int = None): + pitching_stats = Play.select( + Play.pitcher, + fn.SUM(Play.outs).over(partition_by=[Play.pitcher_id]).alias('pl_outs'), + fn.SUM(Play.hit).over(partition_by=[Play.pitcher_id]).alias('pl_hit'), + fn.COUNT(Play.on_first_final).filter( + Play.on_first_final == 4).over(partition_by=[Play.pitcher_id]).alias('pl_run_first'), + fn.COUNT(Play.on_second_final).filter( + Play.on_second_final == 4).over(partition_by=[Play.pitcher_id]).alias('pl_run_second'), + fn.COUNT(Play.on_third_final).filter( + Play.on_third_final == 4).over(partition_by=[Play.pitcher_id]).alias('pl_run_third'), + fn.COUNT(Play.batter_final).filter( + Play.batter_final == 4).over(partition_by=[Play.pitcher_id]).alias('pl_run_batter'), + fn.SUM(Play.so).over(partition_by=[Play.pitcher_id]).alias('pl_so'), + fn.SUM(Play.bb).over(partition_by=[Play.pitcher_id]).alias('pl_bb'), + fn.SUM(Play.hbp).over(partition_by=[Play.pitcher_id]).alias('pl_hbp'), + fn.SUM(Play.wild_pitch).over(partition_by=[Play.pitcher_id]).alias('pl_wild_pitch'), + fn.SUM(Play.balk).over(partition_by=[Play.pitcher_id]).alias('pl_balk'), + fn.SUM(Play.homerun).over(partition_by=[Play.pitcher_id]).alias('pl_homerun'), + fn.SUM(Play.outs).over(partition_by=[Play.pitcher.team_id]).alias('tm_outs'), + fn.SUM(Play.hit).over(partition_by=[Play.pitcher.team_id]).alias('tm_hit'), + fn.COUNT(Play.on_first_final).filter( + Play.on_first_final == 4).over(partition_by=[Play.pitcher.team_id]).alias('tm_run_first'), + fn.COUNT(Play.on_second_final).filter( + Play.on_second_final == 4).over(partition_by=[Play.pitcher.team_id]).alias('tm_run_second'), + fn.COUNT(Play.on_third_final).filter( + Play.on_third_final == 4).over(partition_by=[Play.pitcher.team_id]).alias('tm_run_third'), + fn.COUNT(Play.batter_final).filter( + Play.batter_final == 4).over(partition_by=[Play.pitcher.team_id]).alias('tm_run_batter'), + fn.SUM(Play.so).over(partition_by=[Play.pitcher.team_id]).alias('tm_so'), + fn.SUM(Play.bb).over(partition_by=[Play.pitcher.team_id]).alias('tm_bb'), + fn.SUM(Play.hbp).over(partition_by=[Play.pitcher.team_id]).alias('tm_hbp'), + fn.SUM(Play.wild_pitch).over(partition_by=[Play.pitcher.team_id]).alias('tm_wild_pitch'), + fn.SUM(Play.balk).over(partition_by=[Play.pitcher.team_id]).alias('tm_balk'), + fn.SUM(Play.homerun).over(partition_by=[Play.pitcher.team_id]).alias('tm_homerun'), + ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + + # This is counging plays with multiple runs scored on 1 ER and the rest unearned + # earned_runs_pl = Play.select().where( + # ((Play.on_first_final == 4) | (Play.on_second_final == 4) | (Play.on_third_final == 4) | + # (Play.batter_final == 4)) & (Play.error == 0) + # ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + # logging.info(f'earned_runs: {earned_runs_pl}') + + er_first = Play.select().where( + (Play.on_first_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where((Play.game_id == game_id) & (Play.pitcher_id == lineup_id)) + er_second = Play.select().where( + (Play.on_second_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where((Play.game_id == game_id) & (Play.pitcher_id == lineup_id)) + er_third = Play.select().where( + (Play.on_third_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where((Play.game_id == game_id) & (Play.pitcher_id == lineup_id)) + er_batter = Play.select().where( + (Play.batter_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where((Play.game_id == game_id) & (Play.pitcher_id == lineup_id)) + + # earned_runs_tm = Play.select().where( + # ((Play.on_first_final == 4) | (Play.on_second_final == 4) | (Play.on_third_final == 4) | + # (Play.batter_final == 4)) & (Play.error == 0) + # ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + + if lineup_id is not None: + pitching_stats = pitching_stats.where(Play.pitcher_id == lineup_id) + + tm_earned_runs = None + if team_id is not None: + tm_er_first = Play.select().where( + (Play.on_first_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + tm_er_second = Play.select().where( + (Play.on_second_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + tm_er_third = Play.select().where( + (Play.on_third_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + tm_er_batter = Play.select().where( + (Play.batter_final == 4) & (Play.error == 0) + ).join(Lineup, on=Play.pitcher).where(Play.game_id == game_id) + + # er_first = er_first.where(Play.pitcher.team_id == team_id) + # er_second = er_second.where(Play.pitcher.team_id == team_id) + # er_third = er_third.where(Play.pitcher.team_id == team_id) + # er_batter = er_batter.where(Play.pitcher.team_id == team_id) + + pitching_stats = pitching_stats.where(Play.pitcher.team_id == team_id) + + tm_er_first = tm_er_first.where(Play.pitcher.team_id == team_id) + tm_er_second = tm_er_second.where(Play.pitcher.team_id == team_id) + tm_er_third = tm_er_third.where(Play.pitcher.team_id == team_id) + tm_er_batter = tm_er_batter.where(Play.pitcher.team_id == team_id) + + tm_earned_runs = tm_er_first.count() + tm_er_second.count() + tm_er_third.count() + tm_er_batter.count() + + pl_earned_runs = er_first.count() + er_second.count() + er_third.count() + er_batter.count() + + done_pitchers = [] + return_pitchers = [] + for x in pitching_stats: + if x.pitcher.id not in done_pitchers: + return_pitchers.append({ + 'pitcher_id': x.pitcher_id, + 'card_id': x.pitcher.card_id, + 'team_id': x.pitcher.team_id, + 'pl_outs': x.pl_outs, + 'pl_hit': x.pl_hit, + 'pl_eruns': pl_earned_runs, + 'pl_runs': x.pl_run_first + x.pl_run_second + x.pl_run_third + x.pl_run_batter, + 'pl_so': x.pl_so, + 'pl_bb': x.pl_bb, + 'pl_hbp': x.pl_hbp, + 'pl_homerun': x.pl_homerun, + 'pl_wild_pitch': x.pl_wild_pitch, + 'pl_balk': x.pl_balk, + 'tm_outs': x.tm_outs, + 'tm_hit': x.tm_hit, + 'tm_eruns': tm_earned_runs, + 'tm_runs': x.tm_run_first + x.tm_run_second + x.tm_run_third + x.tm_run_batter, + 'tm_so': x.tm_so, + 'tm_bb': x.tm_bb, + 'tm_hbp': x.tm_hbp, + 'tm_homerun': x.tm_homerun, + 'tm_wild_pitch': x.tm_wild_pitch, + 'tm_balk': x.tm_balk, + 'pl_gs': 1 if x.pitcher.after_play == 0 else 0 + }) + done_pitchers.append(x.pitcher_id) + + db.close() + logging.debug(f'pitching stats: {return_pitchers}') + return return_pitchers + + +def get_pitching_decisions(game: StratGame): + # is_win, is_hold, is_loss = False, False, False + # away_lineups = get_team_lineups(game.id, game.away_team_id) + # home_lineups = get_team_lineups(game.id, game.home_team_id) + # away_pitchers = [] # [(pitcher, first_play), (pitcher2, their_first_play)] + # home_pitchers = [] # [(pitcher, first_play), (pitcher2, their_first_play)] + # last_play = get_current_play(game.id) + + logging.debug(f'this game: {game}') + winner = None + loser = None + save = None + gs = [] + b_save = [] + holds = [] + + # Get starting pitchers and update this as a pointer for the play crawl + away_pitcher = Lineup.get(Lineup.game_id == game.id, Lineup.team_id == game.away_team_id, Lineup.position == 'P') + home_pitcher = Lineup.get(Lineup.game_id == game.id, Lineup.team_id == game.home_team_id, Lineup.position == 'P') + gs.extend([away_pitcher.card_id, home_pitcher.card_id]) + logging.debug(f'SPs: {away_pitcher} / {home_pitcher}') + + for x in Play.select().where(Play.game_id == game.id): + logging.debug(f'checking play num {x.play_num}') + if x.inning_half == 'Top' and home_pitcher != x.pitcher: + if save == home_pitcher: + if x.home_score > x.away_score: + holds.append(save.card_id) + else: + b_save.append(save.card_id) + save = None + + home_pitcher = x.pitcher + + if x.home_score > x.away_score and x.home_score - x.away_score <= 3: + save = home_pitcher + + elif x.inning_half == 'Bot' and away_pitcher != x.pitcher: + if save == away_pitcher: + if x.away_score > x.home_score: + holds.append(save.card_id) + else: + b_save.append(save.card_id) + save = None + + away_pitcher = x.pitcher + + if x.away_score > x.home_score and x.away_score - x.home_score <= 3: + save = away_pitcher + + if x.is_go_ahead: + logging.debug(f'is go ahead: {x}') + if x.on_third_final == 4: + # winning_run = x.on_third + # + # # Get winning run's last PA + # last_pa = Play.select().where( + # Play.game_id == game.id, Play.batter == x.on_third + # ).order_by(-Play.id).limit(1) + # loser = Lineup.get_by_id(last_pa[0].pitcher_id) # pitcher from winning_run's last PA + loser = x.pitcher + if save == loser: + b_save.append(save.card_id) + save = None + winner = home_pitcher if x.inning_half == 'Bot' else away_pitcher + + elif x.on_second_final == 4: + # winning_run = x.on_second + # + # # Get winning run's last PA + # last_pa = Play.select().where( + # Play.game_id == game.id, Play.batter == x.on_second + # ).order_by(-Play.id).limit(1) + # loser = Lineup.get_by_id(last_pa[0].pitcher_id) # pitcher from winning_run's last PA + loser = x.pitcher + if save == loser: + b_save.append(save.card_id) + save = None + winner = home_pitcher if x.inning_half == 'Bot' else away_pitcher + + elif x.on_first_final == 4: + # winning_run = x.on_first + # + # # Get winning run's last PA + # last_pa = Play.select().where( + # Play.game_id == game.id, Play.batter == x.on_first + # ).order_by(-Play.id).limit(1) + # loser = Lineup.get_by_id(last_pa[0].pitcher_id) # pitcher from winning_run's last PA + loser = x.pitcher + if save == loser: + b_save.append(save.card_id) + save = None + winner = home_pitcher if x.inning_half == 'Bot' else away_pitcher + + elif x.batter_final == 4: + # winning_run = x.batter + # + # # Get winning run's last PA + # last_pa = Play.select().where( + # Play.game_id == game.id, Play.batter == x.batter + # ).order_by(-Play.id).limit(1) + # loser = Lineup.get_by_id(last_pa[0].pitcher_id) # pitcher from winning_run's last PA + loser = x.pitcher + if save == loser: + b_save.append(save.card_id) + save = None + winner = home_pitcher if x.inning_half == 'Bot' else away_pitcher + + if x.is_tied: + logging.debug(f'is tied: {x}') + winner, loser = None, None + if save: + b_save.append(save.card_id) + save = None + + logging.debug(f'\n\nWin: {winner}\nLose: {loser}\nSave: {save}\nBlown Save: {b_save}\nHolds: {holds}') + return { + 'winner': winner.card_id, + 'loser': loser.card_id, + 'save': save.card_id if save else None, + 'b_save': b_save, + 'holds': holds, + 'starters': gs, + 'w_lineup': winner, + 'l_lineup': loser, + 's_lineup': save + } + + # for count, pit_pair in enumerate(away_pitchers): + # """ + # If starter & team won + # check for win condition + # if starter & team lost + # check for loss condition + # if reliever & team won + # check for save situation + # if save situation, check for blown save + # credit either BS or Save; if Save is not None, add Save to holds and replace with this pitcher + # if reliever & team lost + # check for loss condition + # """ + # # If starter & team won + # if pit_pair[0].after_play == 0 and (last_play.away_score > last_play.home_score): + # if len(away_pitchers) == 1: + # winner = pit_pair[0] + # else: + # next_p_first = away_pitchers[count + 1][1] + # if next_p_first.away_score > next_p_first.home_score: + # winner = pit_pair[0] + # else: + # winner = away_pitchers[count + 1][0] + # # if starter & team lost + # elif pit_pair[0].after_play == 0: + # if len(away_pitchers) == 1: + # loser = pit_pair[0] + # else: + # next_p_first = away_pitchers[count + 1][1] + # if next_p_first.away_score < next_p_first.home_score: + + +def get_final_scorebug(away_team, home_team, away_stats, home_stats): + return f'```' \ + f'Team | R | H | E |\n' \ + f'{away_team["abbrev"]: <4} | {away_stats["score"]: >2} | {away_stats["hits"]: >2} | ' \ + f'{away_stats["f_lines"][0]["tm_error"] if away_stats["f_lines"] else 0: >2} |\n' \ + f'{home_team["abbrev"]: <4} | {home_stats["score"]: >2} | {home_stats["hits"]: >2} | ' \ + f'{home_stats["f_lines"][0]["tm_error"] if home_stats["f_lines"] else 0: >2} |\n' \ + f'```' + + +db.close() + diff --git a/dice.py b/dice.py new file mode 100644 index 0000000..1f46bdf --- /dev/null +++ b/dice.py @@ -0,0 +1,917 @@ +import discord +import random + +from helpers import INFIELD_X_CHART, OUTFIELD_X_CHART + + +def get_dice_embed(team: dict = None, embed_title: str = None): + if team: + embed = discord.Embed( + color=int(team["dice_color"], 16) if 'dice_color' in team else int(team["color"], 16) + ) + else: + embed = discord.Embed( + color=int('0x000000', 16) + ) + + if embed_title: + embed.title = embed_title + + return embed + + +def sa_fielding_roll(pos_code: str, team: dict) -> [discord.Embed]: + """ + Make a Super Advanced fielding check. + """ + d_twenty = random.randint(1, 20) + d_six_one = random.randint(1, 6) + d_six_two = random.randint(1, 6) + d_six_three = random.randint(1, 6) + error_dice = d_six_one + d_six_two + d_six_three + x_chart = None + error_chart = 'https://sombaseball.ddns.net/static/images/season04/error-' + symbol_link = None + range_note = None + error_note = None + error_note1 = None + error_note2 = None + + if pos_code in ['1B', '2B', '3B', 'SS']: + x_chart = 'https://sombaseball.ddns.net/static/images/season04/range-infield.png' + symbol_link = 'https://docs.google.com/document/d/1p8Y2On0301C1yq4ktyPxE4lXxrIALYaF1vbW6RkRpII/edit' \ + '#heading=h.qgqyxe5xb77m' + + # Build range note + if d_twenty == 1: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G3# SI1 ----SI2----\n' \ + '```\n\n' + elif d_twenty == 2: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G2# SI1 ----SI2----\n' \ + '```\n\n' + elif d_twenty <= 4: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G2# G3# SI1 --SI2--\n' \ + '```\n\n' + elif d_twenty == 5: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G1 --G3#-- SI1 SI2\n' \ + '```\n\n' + elif d_twenty == 6: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G1 G2# G3# SI1 SI2\n' \ + '```\n\n' + elif d_twenty <= 8: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G1 G2 --G3#-- SI1\n' \ + '```\n\n' + elif d_twenty == 9: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G1 G2 G3 --G3#--\n' \ + '```\n\n' + elif d_twenty == 10: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- G2 --G3#--\n' \ + '```\n\n' + elif d_twenty <= 12: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- G2 G3 G3#\n' \ + '```\n\n' + elif d_twenty == 13: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- G2 --G3---\n' \ + '```\n\n' + elif d_twenty == 14: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- --G2--- G3\n' \ + '```\n\n' + elif d_twenty <= 16: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----G1----- G2 G3\n' \ + '```\n\n' + elif d_twenty == 17: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------G1------- G3\n' \ + '```\n\n' + elif d_twenty <= 19: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------G1------- G2\n' \ + '```\n\n' + elif d_twenty == 20: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------G1---------\n' \ + '```\n\n' + + if pos_code == '1B': + error_chart += 'first-base.png' + + if error_dice == 18: + error_note = '2-base error for e3 -> e12, e19 -> e28\n' \ + '1-base error for e1, e2, e30' + elif error_dice == 17: + error_note = '2-base error for e13 -> e28\n' \ + '1-base error for e1, e5, e8, e9, e29' + elif error_dice == 16: + error_note = '2-base error for e29, e30\n' \ + '1-base error for e2, e8, e16, e19, e23' + elif error_dice == 15: + error_note = '1-base error for e3, e8, e10 -> e12, e20, e26, e30' + elif error_dice == 14: + error_note = '1-base error for e4, e5, e9, e15, e18, e22, e24 -> e28' + elif error_dice == 13: + error_note = '1-base error for e6, e13, e24, e26 -> e28, e30' + elif error_dice == 12: + error_note = '1-base error for e14 -> e18, e21 -> e26, e28 -> e30' + elif error_dice == 11: + error_note = '1-base error for e10, e13, e16 -> e20, e23 -> e25, e27 -> e30' + elif error_dice == 10: + error_note = '1-base error for e19 -> e21, e23, e29' + elif error_dice == 9: + error_note = '1-base error for e7, e12, e14, e21, e25, e26, e29' + elif error_dice == 8: + error_note = '1-base error for e11, e27' + elif error_dice == 7: + error_note = '1-base error for e9, e15, e22, e27, e28' + elif error_dice == 6: + error_note = '1-base error for e8, e11, e12, e17, e20' + elif error_dice == 5: + error_note = f'Rare play!\n\n' \ + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' \ + f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' + elif error_dice == 4: + error_note = 'No error' + elif error_dice == 3: + error_note = '2-base error for e8 -> e12, e24 -> e28\n' \ + '1-base error for e2, e3, e6, e7, e14, e16, e17, e21' + if pos_code == '2B': + error_chart += 'second-base.png' + + if error_dice == 18: + error_note = '2-base error for e4 -> e19, e28 -> e41, e53 -> e65\n' \ + '1-base error for e22, e24, e25, e27, e44, e50' + elif error_dice == 17: + error_note = '2-base error for e20 -> e41, e68, e71\n' \ + '1-base error for e3, e4, e8 -> e12, e15, e16, e19' + elif error_dice == 16: + error_note = '2-base error for e53 -> 71\n' \ + '1-base error for e5 -> 10, e14, e16, e29, e37' + elif error_dice == 15: + error_note = '1-base error for e11, e12, e14, e16, e17, e19, e26 -> e28, e30, e32, e37, ' \ + 'e50 -> e62, e71' + elif error_dice == 14: + error_note = '1-base error for e13, e15, e34, e47, e65' + elif error_dice == 13: + error_note = '1-base error for e18, e20, e21, e26 -> e28, e39, e41, e50, e56, e59, e65, e71' + elif error_dice == 12: + error_note = '1-base error for e22, e30, e34, e39, e44, e47, e53, e56, e62, e68, e71' + elif error_dice == 11: + error_note = '1-base error for e23 -> e25, e29, e32, e37, e41, e50, e53, e59, e62, e68' + elif error_dice == 10: + error_note = '1-base error for e68' + elif error_dice == 9: + error_note = '1-base error for e44' + elif error_dice == 8: + error_note = 'No error' + elif error_dice == 7: + error_note = '1-base error for e47, e65' + elif error_dice == 6: + error_note = '1-base error for e17, e19, e56 -> 62' + elif error_dice == 5: + error_note = f'Rare play!\n\n' \ + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' \ + f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' + elif error_dice == 4: + error_note = '1-base error for e10, e21' + elif error_dice == 3: + error_note = '2-base error for e12 -> e19, e37 -> e41, e59 -> e65\n' \ + '1-base error for e2 -> e4, e6, e20, e25, e28, e29' + if pos_code == '3B': + error_chart += 'third-base.png' + + if error_dice == 18: + error_note = '2-base error for e1, e2, e8, e10, e13 -> e16, e21 -> e24, e27, e30 -> e33, e50, ' \ + 'e56, e62, e65\n' \ + '1-base error for e39' + elif error_dice == 17: + error_note = '2-base error for e3 -> e10, e17, e18, e25 -> e27, e34 -> e37, e44, e47\n' \ + '1-base error for e11, e19, e32, e56' + elif error_dice == 16: + error_note = '2-base error for e11 -> e18, e32, e33, e37, e53, e62, e65\n' \ + '1-base error for e4, e8, e19, e21, e22, e27, e41' + elif error_dice == 15: + error_note = '2-base error for e19 -> 27, e32, e33, e37, e39, e44, e50, e59\n' \ + '1-base error for e5 -> e8, e11, e14, e15, e17, e18, e28 -> e31, e34' + elif error_dice == 14: + error_note = '2-base error for e28 -> e31, e34, e35, e50\n' \ + '1-base error for e14, e16, e19, e20, e22, e32, e39, e44, e56, e62' + elif error_dice == 13: + error_note = '2-base error for e41, e47, e53, e59\n' \ + '1-base error for e10, e15, e23, e25, e28, e30, e32, e33, e35, e44, e65' + elif error_dice == 12: + error_note = '2-base error for e62\n' \ + '1-base error for e12, e17, e22, e24, e27, e29, e34 -> e50, e56 -> e59, e65' + elif error_dice == 11: + error_note = '2-base error for e56, e65\n' \ + '1-base error for e13, e18, e20, e21, e23, e26, e28, e31 -> e33, e35, e37, ' \ + 'e41 -> e53, e59, e65' + elif error_dice == 10: + error_note = '1-base error for e26, e31, e41, e53 -> 65' + elif error_dice == 9: + error_note = '1-base error for e24, e27, e29, e34, e37, e39, e47 -> e65' + elif error_dice == 8: + error_note = '1-base error for e25, e30, e33, e47, e53, e56, e62, e65' + elif error_dice == 7: + error_note = '1-base error for e16, e19, e39, e59 -> e65' + elif error_dice == 6: + error_note = '1-base error for e21, e25, e30, e34, e53' + elif error_dice == 5: + error_note = f'Rare play!\n\n' \ + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' \ + f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' + elif error_dice == 4: + error_note = '1-base error for e2, e3, e6, e14, e16, e44' + elif error_dice == 3: + error_note = '2-base error for e10, e15, e16, e23, e24, e56\n' \ + '1-base error for e1 -> e4, e8, e14' + if pos_code == 'SS': + error_chart += 'shortstop.png' + + if error_dice == 18: + error_note = '2-base error for e4 -> e12, e22 -> e32, e40 -> e48, e64, e68\n' \ + '1-base error for e1, e18, e34, e52, e56' + elif error_dice == 17: + error_note = '2-base error for e14 -> 32, e52, e56, e72 -> e84\n' \ + '1-base error for e3 -> e5, e8 ,e10, e36' + elif error_dice == 16: + error_note = '2-base error for e33 -> 56, e72\n' \ + '1-base error for e6 -> e10, e17, e18, e20, e28, e31, e88' + elif error_dice == 15: + error_note = '2-base error for e60 -> e68, e76 -> 84\n' \ + '1-base error for e12, e14, e17, e18, e20 -> e22, e24, e28, e31 -> 36, e40, ' \ + 'e48, e72' + elif error_dice == 14: + error_note = '1-base error for e16, e19, e38, e42, e60, e68' + elif error_dice == 13: + error_note = '1-base error for e23, e25, e32 -> 38, e44, e52, e72 -> 84' + elif error_dice == 12: + error_note = '1-base error for e26, e27, e30, e42, e48, e56, e64, e68, e76 -> e88' + elif error_dice == 11: + error_note = '1-base error for e29, e40, e52 -> e60, e72, e80 -> e88' + elif error_dice == 10: + error_note = '1-base error for e84' + elif error_dice == 9: + error_note = '1-base error for e64, e68, e76, e88' + elif error_dice == 8: + error_note = '1-base error for e44' + elif error_dice == 7: + error_note = '1-base error for e60' + elif error_dice == 6: + error_note = '1-base error for e21, e22, e24, e28, e31, e48, e64, e72' + elif error_dice == 5: + error_note = f'Rare play!\n\n' \ + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' \ + f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' + elif error_dice == 4: + error_note = '2-base error for e72\n' \ + '1-base error for e14, e19, e20, e24, e25, e31, e80' + elif error_dice == 3: + error_note = '2-base error for e10, e12, e28 -> e32, e48, e84\n' \ + '1-base error for e2, e5, e7, e23, e27' + + elif pos_code in ['LF', 'CF', 'RF']: + x_chart = 'https://sombaseball.ddns.net/static/images/season04/range-outfield.png' + symbol_link = 'https://docs.google.com/document/d/1p8Y2On0301C1yq4ktyPxE4lXxrIALYaF1vbW6RkRpII/edit' \ + '#heading=h.drrzdsvu0ij0' + + # Build range note + if d_twenty == 1: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'F1 DO2 DO3 --TR3--\n' \ + '```\n\n' + elif d_twenty == 2: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'F2 SI2 DO2 DO3 TR3\n' \ + '```\n\n' + elif d_twenty == 3: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'F2 SI2 --DO2-- DO3\n' \ + '```\n\n' + elif d_twenty == 4: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'F2 F1 SI2 DO2 DO3\n' \ + '```\n\n' + elif d_twenty <= 6: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--F2--- --SI2-- DO2\n' \ + '```\n\n' + elif d_twenty == 7: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--F2--- F1 SI2 DO2\n' \ + '```\n\n' + elif d_twenty == 8: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--F2--- F1 --SI2--\n' \ + '```\n\n' + elif d_twenty <= 11: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----F2----- --SI2--\n' \ + '```\n\n' + elif d_twenty <= 13: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----F2----- F1 SI2\n' \ + '```\n\n' + elif d_twenty <= 15: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'F3 ----F2----- SI2\n' \ + '```\n\n' + elif d_twenty == 16: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--F3--- --F2--- F1\n' \ + '```\n\n' + elif d_twenty <= 18: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----F3----- F2 F1\n' \ + '```\n\n' + elif d_twenty == 19: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------F3------- F2\n' \ + '```\n\n' + elif d_twenty == 20: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------F3---------\n' \ + '```\n\n' + + if pos_code in ['LF', 'RF']: + error_chart += 'corner-outfield.png' + + if error_dice == 18: + error_note = '3-base error for e4 -> e12, e19 -> e25\n' \ + '2-base error for e18\n' \ + '1-base error for e2, e3, e15' + elif error_dice == 17: + error_note = '3-base error for e13 -> e25\n' \ + '2-base error for e1, e6, e8, e10' + elif error_dice == 16: + error_note = '2-base error for e2\n' \ + '1-base error for e7 -> 12, e22, e24, e25' + elif error_dice == 15: + error_note = '2-base error for e3, e4, e7, e8, e10, e11, e13, e20, e21' + elif error_dice == 14: + error_note = '2-base error for e5, e6, e10, e12, e14, e15, e22, e23' + elif error_dice == 13: + error_note = '2-base error for e11, e12, e16, e20, e24, e25' + elif error_dice == 12: + error_note = '2-base error for e13 -> e18, e21 -> e23, e25' + elif error_dice == 11: + error_note = '2-base error for e9, e18 -> e21, e23 -> e25' + elif error_dice == 10: + error_note = '2-base error for e19' + elif error_dice == 9: + error_note = '2-base error for e22' + elif error_dice == 8: + error_note = '2-base error for e24' + elif error_dice == 7: + error_note = '1-base error for e19 -> e21, e23' + elif error_dice == 6: + error_note = '2-base error for e7, e8\n' \ + '1-base error for e13 -> e18, e22, e24, e25' + elif error_dice == 5: + error_note = f'Rare play!' + error_note1 = f'**F1**: {OUTFIELD_X_CHART["f1"]["rp"]}\n' \ + f'**F2**: {OUTFIELD_X_CHART["f2"]["rp"]}\n' \ + f'**F3**: {OUTFIELD_X_CHART["f3"]["rp"]}\n' + error_note2 = f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' \ + f'**DO2**: {OUTFIELD_X_CHART["do2"]["rp"]}\n' \ + f'**DO3**: {OUTFIELD_X_CHART["do3"]["rp"]}\n' \ + f'**TR3**: {OUTFIELD_X_CHART["tr3"]["rp"]}\n' + elif error_dice == 4: + error_note = '2-base error for e13, e16, e20, e23 -> e25\n' \ + '1-base error for e4 -> e6, e10 -> 12, e16 -> e18' + elif error_dice == 3: + error_note = '3-base error for e8 -> e12, e24, e25\n' \ + '2-base error for e19\n' \ + '1-base error for e1 -> e3, e6, e14, e15, e18, e20 -> e22' + else: + error_chart += 'center-field.png' + + if error_dice == 18: + error_note = '3-base error for e4 -> 19\n' \ + '2-base error for e2, e25\n' \ + '1-base error for e3, e23' + elif error_dice == 17: + error_note = '3-base error for e20 -> e25\n' \ + '2-base error for e1, e2, e5, e7, e9, e13 -> e15, e17' + elif error_dice == 16: + error_note = '2-base error for e3 -> e5, e8, e23\n' \ + '1-base error for e10 -> e18' + elif error_dice == 15: + error_note = '2-base error for e6 -> e8, e12, e13, e19' + elif error_dice == 14: + error_note = '2-base error for e9, e10, e16 -> e18, e20 -> e23' + elif error_dice == 13: + error_note = '2-base error for e11, e18, e20, e23 -> e25' + elif error_dice == 12: + error_note = '2-base error for e14, e15, e21, e22, e24' + elif error_dice == 11: + error_note = '2-base error for e19, e25' + elif error_dice >= 8: + error_note = 'No error' + elif error_dice == 7: + error_note = '2-base error for e16, e17' + elif error_dice == 6: + error_note = '2-base error for e12, e13\n' \ + '1-base error for e19 -> e25' + elif error_dice == 5: + error_note = f'Rare play!' + error_note1 = f'**F1**: {OUTFIELD_X_CHART["f1"]["rp"]}\n' \ + f'**F2**: {OUTFIELD_X_CHART["f2"]["rp"]}\n' \ + f'**F3**: {OUTFIELD_X_CHART["f3"]["rp"]}\n' + error_note2 = f'**SI2**: {OUTFIELD_X_CHART["si2"]["rp"]}\n' \ + f'**DO2**: {OUTFIELD_X_CHART["do2"]["rp"]}\n' \ + f'**DO3**: {OUTFIELD_X_CHART["do3"]["rp"]}\n' \ + f'**TR3**: {OUTFIELD_X_CHART["tr3"]["rp"]}\n' + elif error_dice == 4: + error_note = '2-base error for e10, e12, e13, e20, e22, e23\n' \ + '1-base error for e3 -> e9, e15 -> e18' + elif error_dice == 3: + error_note = '3-base error for e12 -> e19\n' \ + '2-base error for e10, e11\n' \ + '1-base error for e2, e3, e7 -> e9, e21 -> e23' + + elif pos_code == 'C': + x_chart = 'https://sombaseball.ddns.net/static/images/season04/range-catcher.png' + error_chart += 'catcher.png' + symbol_link = 'https://docs.google.com/document/d/1a_g6apg74ixcwJapNrbOJZAx7gVxGjeNm79c5XLjSF0/' \ + 'edit#heading=h.mcf1vrt5gzr9' + + # Build range note + range_note = '__If Bases Empty__\n' + if d_twenty == 1: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G3 ------SI1------\n' \ + '```\n' + elif d_twenty == 2: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G3--- ----SI1----\n' \ + '```\n' + elif d_twenty == 3: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'PO --G3--- --SI1--\n' \ + '```\n' + elif d_twenty == 4: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--PO--- --G3--- SI1\n' \ + '```\n' + elif d_twenty == 5: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----PO----- --G3---\n' \ + '```\n' + elif d_twenty == 6: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------PO------- G3\n' \ + '```\n' + elif d_twenty <= 10: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------PO---------\n' \ + '```\n' + elif d_twenty == 11: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'FO -------PO------\n' \ + '```\n' + elif d_twenty == 12: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--FO--- -----PO----\n' \ + '```\n' + elif d_twenty == 13: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----FO----- --PO---\n' \ + '```\n' + elif d_twenty == 14: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------FO------- PO\n' \ + '```\n' + elif d_twenty <= 16: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------FO---------\n' \ + '```\n' + elif d_twenty == 17: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G2--- G3 --FO---\n' \ + '```\n' + elif d_twenty == 18: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G2--- --G3--- FO\n' \ + '```\n' + elif d_twenty == 19: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- G2 --G3---\n' \ + '```\n' + elif d_twenty == 20: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- --G2--- G3\n' \ + '```\n' + + range_note += '__If Runners on Base__\n' + if d_twenty <= 2: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------WP---------\n' \ + '```\n' + elif d_twenty == 3: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + ' X -------WP------\n' \ + '```\n' + elif d_twenty == 4: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '---X--- -----WP----\n' \ + '```\n' + elif d_twenty == 5: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '-----X----- --WP---\n' \ + '```\n' + elif d_twenty == 6: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '-------X------- WP\n' \ + '```\n' + elif d_twenty <= 9: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'PO -------X-------\n' \ + '```\n' + elif d_twenty <= 12: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--PO--- -----X-----\n' \ + '```\n' + elif d_twenty == 13: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'FO PO -----X-----\n' \ + '```\n' + elif d_twenty == 14: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--FO--- PO ---X---\n' \ + '```\n' + elif d_twenty <= 16: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----FO----- ---X---\n' \ + '```\n' + elif d_twenty == 17: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G2--- G3 PO X\n' \ + '```\n' + elif d_twenty == 18: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G2--- --G3--- X\n' \ + '```\n' + elif d_twenty == 19: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G1 --G2--- --G3---\n' \ + '```\n' + elif d_twenty == 20: + range_note += '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- --G2--- G3\n' \ + '```\n' + + # Build error note + # error_dice = 5 + if error_dice == 18: + error_note = '2-base error for e4 -> 16\n1-base error for e2, e3' + elif error_dice == 17: + error_note = '1-base error for e1, e2, e4, e5, e12 -> e14, e16' + elif error_dice == 16: + error_note = '1-base error for e3 -> e5, e7, e12 -> e14, e16' + elif error_dice == 15: + error_note = '1-base error for e7, e8, e12, e13, e15' + elif error_dice == 14: + error_note = '1-base error for e6' + elif error_dice == 13: + error_note = '1-base error for e9' + elif error_dice == 12: + error_note = '1-base error for e10, e14' + elif error_dice == 11: + error_note = '1-base error for e11, e15' + elif 8 <= error_dice <= 10: + error_note = 'No error' + elif error_dice == 7: + error_note = '1-base error for e16' + elif error_dice == 6: + error_note = '1-base error for e8, e12, e13' + elif error_dice == 5: + error_note = 'Rare play! Ignore range chart above and consult ranges below\n\n' + error_note1 = '__If Bases Empty__\n' + if d_twenty == 1: + error_note1 += f'**c-1**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-2 -> 5**: {INFIELD_X_CHART["si1"]["rp"]}\n\n' + elif d_twenty == 2: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-3 -> 5**: {INFIELD_X_CHART["si1"]["rp"]}\n\n' + elif d_twenty == 3: + error_note1 += f'**c-1**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-2 -> 3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["si1"]["rp"]}\n\n' + elif d_twenty == 4: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-3 -> 4**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["si1"]["rp"]}\n\n' + elif d_twenty == 5: + error_note1 += f'**c-1 -> 3**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["g3"]["rp"]}\n\n' + elif d_twenty == 6: + error_note1 += f'**c-1 -> 4**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["g3"]["rp"]}\n\n' + elif d_twenty <= 10: + error_note1 += f'**c-1 -> 5**: {INFIELD_X_CHART["po"]["rp"]}\n\n' + elif d_twenty == 11: + error_note1 += f'**c-1**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-2 -> 5**: {INFIELD_X_CHART["po"]["rp"]}\n\n' + elif d_twenty == 12: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-3 -> 5**: {INFIELD_X_CHART["po"]["rp"]}\n\n' + elif d_twenty == 13: + error_note1 += f'**c-1 -> 3**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["po"]["rp"]}\n\n' + elif d_twenty == 14: + error_note1 += f'**c-1 -> 4**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["po"]["rp"]}\n\n' + elif d_twenty <= 16: + error_note1 += f'**c-1 -> 5**: {INFIELD_X_CHART["fo"]["rp"]}\n\n' + elif d_twenty == 17: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["fo"]["rp"]}\n\n' + elif d_twenty == 18: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-3 -> 4**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["fo"]["rp"]}\n\n' + elif d_twenty == 19: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**c-3**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["g3"]["rp"]}\n\n' + elif d_twenty == 20: + error_note1 += f'**c-1 -> 2**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**c-3 -> 4**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["g3"]["rp"]}\n\n' + + error_note2 = '__If Runners on Base__\n' + if d_twenty <= 2: + error_note2 += f'**c-1 -> 5**: {INFIELD_X_CHART["wp"]["rp"]}\n\n' + elif d_twenty == 3: + error_note2 += f'**c-1**: {INFIELD_X_CHART["x"]["rp"]}\n' \ + f'**c-2 -> 5**: {INFIELD_X_CHART["wp"]["rp"]}\n\n' + elif d_twenty == 4: + error_note2 += f'**c-1 -> 2**: {INFIELD_X_CHART["x"]["rp"]}\n' \ + f'**c-3 -> 5**: {INFIELD_X_CHART["wp"]["rp"]}\n\n' + elif d_twenty == 5: + error_note2 += f'**c-1 -> 3**: {INFIELD_X_CHART["x"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["wp"]["rp"]}\n\n' + elif d_twenty == 6: + error_note2 += f'**c-1 -> 4**: {INFIELD_X_CHART["x"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["wp"]["rp"]}\n\n' + elif d_twenty <= 9: + error_note2 += f'**c-1**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-2 -> 5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty <= 12: + error_note2 += f'**c-1 -> 2**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-3 -> 5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty == 13: + error_note2 += f'**c-1**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-2**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-3 -> 5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty == 14: + error_note2 += f'**c-1 -> 2**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-3**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty <= 16: + error_note2 += f'**c-1 -> 3**: {INFIELD_X_CHART["fo"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty == 17: + error_note2 += f'**c-1 -> 2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-4**: {INFIELD_X_CHART["po"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty == 18: + error_note2 += f'**c-1 -> 2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-3 -> 4**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["x"]["rp"]}\n\n' + elif d_twenty == 19: + error_note2 += f'**c-1**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**c-2 -> 3**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-4 -> 5**: {INFIELD_X_CHART["g3"]["rp"]}\n\n' + elif d_twenty == 20: + error_note2 += f'**c-1 -> 2**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**c-3 -> 4**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**c-5**: {INFIELD_X_CHART["g3"]["rp"]}\n\n' + elif error_dice == 4: + error_note = '1-base error for e5, e13' + else: + error_note = '2-base error for e12 -> e16\n1-base error for e2, e3, e7, e11' + elif pos_code == 'P': + x_chart = 'https://sombaseball.ddns.net/static/images/season04/range-pitcher.png' + error_chart += 'pitcher.png' + symbol_link = 'https://docs.google.com/document/d/1a_g6apg74ixcwJapNrbOJZAx7gVxGjeNm79c5XLjSF0/edit' \ + '#heading=h.fpjqmiv10r8l' + + # Build range note + if d_twenty <= 2: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G3 ------SI1------\n' \ + '```\n' + elif d_twenty == 3: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G3--- ----SI1----\n' \ + '```\n' + elif d_twenty == 4: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----G3----- --SI1--\n' \ + '```\n' + elif d_twenty <= 6: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------G3------- SI1\n' \ + '```\n' + elif d_twenty == 7: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------G3---------\n' \ + '```\n' + elif d_twenty <= 9: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G2 ------G3-------\n' \ + '```\n' + elif d_twenty <= 12: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + 'G1 G2 ----G3-----\n' \ + '```\n' + elif d_twenty == 13: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- G2 --G3---\n' \ + '```\n' + elif d_twenty == 14: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- --G2--- G3\n' \ + '```\n' + elif d_twenty <= 16: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--G1--- ----G2-----\n' \ + '```\n' + elif d_twenty <= 18: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '----G1----- --G2---\n' \ + '```\n' + elif d_twenty == 19: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '------G1------- G2\n' \ + '```\n' + elif d_twenty == 20: + range_note = '```\n' \ + ' 1 | 2 | 3 | 4 | 5\n' \ + '--------G1---------\n' \ + '```\n' + + # Build error note + if error_dice == 18: + error_note = '2-base error for e4 -> e12, e19 -> e28, e34 -> e43, e46 -> e48' + elif error_dice == 17: + error_note = '2-base error for e13 -> e28, e44 -> e50' + elif error_dice == 16: + error_note = '2-base error for e34 -> e48, e50, e51\n' \ + '1-base error for e8, e11, e16, e23' + elif error_dice == 15: + error_note = '2-base error for e50, e51\n' \ + '1-base error for e10 -> e12, e19, e20, e24, e26, e30, e35, e38, e40, e46, e47' + elif error_dice == 14: + error_note = '1-base error for e4, e14, e18, e21, e22, e26, e31, e35, e42, e43, e48 -> e51' + elif error_dice == 13: + error_note = '1-base error for e6, e13, e14, e21, e22, e26, e27, e30 -> 34, e38 -> e51' + elif error_dice == 12: + error_note = '1-base error for e7, e11, e12, e15 -> e19, e22 -> e51' + elif error_dice == 11: + error_note = '1-base error for e10, e13, e15, e17, e18, e20, e21, e23, e24, e27 -> 38, e40, e42, ' \ + 'e44 -> e51' + elif error_dice == 10: + error_note = '1-base error for e20, e23, e24, e27 -> e51' + elif error_dice == 9: + error_note = '1-base error for e16, e19, e26, e28, e34 -> e36, e39 -> e51' + elif error_dice == 8: + error_note = '1-base error for e22, e33, e38, e39, e43 -> e51' + elif error_dice == 7: + error_note = '1-base error for e14, e21, e36, e39, e42 -> e44, e47 -> e51' + elif error_dice == 6: + error_note = '1-base error for e8, e22, e38, e39, e43 -> e51' + elif error_dice == 5: + error_note = f'Rare play!\n\n' \ + f'**G3**: {INFIELD_X_CHART["g3"]["rp"]}\n' \ + f'**G2**: {INFIELD_X_CHART["g2"]["rp"]}\n' \ + f'**G1**: {INFIELD_X_CHART["g1"]["rp"]}\n' \ + f'**SI1**: {INFIELD_X_CHART["si1"]["rp"]}\n' + elif error_dice == 4: + error_note = '1-base error for e15, e16, e40' + elif error_dice == 3: + error_note = '2-base error for e8 -> e12, e26 -> e28, e39 -> e43\n' \ + '1-base error for e6, e7, e17, e30, e33, e44' + + roll_message = f'```md\n' \ + f'# {str(d_twenty)},{str(d_six_one + d_six_two + d_six_three)}\n' \ + f'Details:[1d20;3d6 ({d_twenty} - {d_six_one} {d_six_two} {d_six_three})]```' + + roll_embed = get_dice_embed(team) + roll_embed.add_field(name=f'SA Fielding roll for {team["gmname"]}', value=roll_message) + + chart_embed = get_dice_embed(team, f'{pos_code} Fielding Check Summary') + chart_embed.add_field(name='Range Result', value=range_note, inline=False) + chart_embed.add_field(name='Error Result', value=error_note, inline=False) + chart_embed.add_field(name='Help Commands', + value=f'Run ! for full chart readout (e.g. `!g1` or `!do3`)') + chart_embed.add_field(name='Range Chart', value=x_chart, inline=False) + chart_embed.add_field(name='Error Chart', value=error_chart, inline=False) + chart_embed.add_field(name='Result Reference', value=symbol_link, inline=False) + + error1_embed, error2_embed = None, None + if error_note1: + error1_embed = get_dice_embed(team) + error1_embed.add_field(name='Error Results', value=error_note1) + if error_note2: + error2_embed = get_dice_embed(team) + error2_embed.add_field(name='Error Results', value=error_note2) + + return [roll_embed, chart_embed, error1_embed, error2_embed] diff --git a/gameplay_helpers.py b/gameplay_helpers.py new file mode 100644 index 0000000..787eb2c --- /dev/null +++ b/gameplay_helpers.py @@ -0,0 +1,157 @@ +import logging +import random + +from db_calls_gameplay import StratGame, StratPlay, StratLineup, StratManagerAi, patch_play, advance_runners, \ + complete_play, get_team_lineups, get_or_create_bullpen, get_player, get_sheets, make_sub +from db_calls import db_get + + +def single_onestar(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=1) + patch_play(this_play.id, pa=1, ab=1, hit=1) + if comp_play: + complete_play(this_play.id, batter_to_base=1) + + +def single_wellhit(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=2) + patch_play(this_play.id, pa=1, ab=1, hit=1) + if comp_play: + complete_play(this_play.id, batter_to_base=1) + + +def double_twostar(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=2) + patch_play(this_play.id, pa=1, ab=1, hit=1, double=1) + if comp_play: + complete_play(this_play.id, batter_to_base=2) + + +def double_threestar(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=3) + patch_play(this_play.id, pa=1, ab=1, hit=1, double=1) + if comp_play: + complete_play(this_play.id, batter_to_base=2) + + +def triple(this_play: StratPlay, comp_play: bool = True): + patch_play(this_play.id, locked=True) + advance_runners(this_play.id, num_bases=3) + patch_play(this_play.id, pa=1, ab=1, hit=1, triple=1) + if comp_play: + complete_play(this_play.id, batter_to_base=3) + + +async def next_pitcher(this_play: StratPlay, ai_team, bot): + used_pitchers = await get_team_lineups( + game_id=this_play.game.id, team_id=ai_team['id'], inc_inactive=True, pitchers_only=True, as_string=False + ) + logging.debug(f'\n\nused_pitchers: {used_pitchers}') + + used_ids = [x.card_id for x in used_pitchers] + logging.debug(f'used_ids: {used_ids}') + bullpen = get_or_create_bullpen(ai_team, bot) + logging.debug(f'bullpen: {bullpen}') + + if this_play.game.short_game: + if this_play.inning_num == 1: + relievers = [bullpen.middle_two_id, bullpen.middle_one_id, bullpen.middle_three_id] + elif this_play.inning_num == 2: + relievers = [bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] + elif this_play.inning_num == 3: + relievers = [ + bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, + bullpen.middle_three_id + ] + else: + relievers = [ + bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id, + bullpen.middle_three_id, bullpen.middle_two_id, bullpen.middle_one_id, bullpen.setup_id, + bullpen.closer_id + ] + else: + if this_play.inning_num < 6: + relievers = [bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id] + elif this_play.inning_num == 6: + relievers = [bullpen.middle_two_id, bullpen.middle_one_id, bullpen.middle_three_id] + elif this_play.inning_num == 7: + relievers = [bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] + elif this_play.inning_num == 8: + relievers = [bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id] + elif this_play.inning_num == 9: + relievers = [ + bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id + ] + else: + relievers = [ + bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id, + bullpen.middle_three_id, bullpen.middle_two_id, bullpen.middle_one_id, bullpen.setup_id, bullpen.closer_id + ] + + for arm in relievers: + if arm and arm not in used_ids: + return {'player': await get_player(this_play.game, {'card_id': arm}), 'card_id': arm} + + relievers = [ + bullpen.closer_id, bullpen.setup_id, bullpen.middle_one_id, bullpen.middle_two_id, bullpen.middle_three_id, + bullpen.long_one_id, bullpen.long_two_id, bullpen.long_three_id, bullpen.long_four_id + ] + + for arm in relievers: + if arm and arm not in used_ids: + return {'player': await get_player(this_play.game, {'card_id': arm}), 'card_id': arm} + + return {"p_name": "Backup Infielder"} + + +def starting_pitcher(ai_team, bot, is_home): + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(ai_team['gsheet']) + r_sheet = this_sheet.worksheet_by_title('My Rosters') + + rotation_range = f'I4:I8' + raw_cells = r_sheet.range(rotation_range) + d_100 = random.randint(1, 100) + logging.info(f'raw_cells: {raw_cells} / d_100: {d_100}') + + if is_home: + if d_100 <= 30: + return raw_cells[0][0].value + elif d_100 <= 55: + return raw_cells[1][0].value + elif d_100 <= 75: + return raw_cells[2][0].value + elif d_100 <= 90: + return raw_cells[3][0].value + else: + return raw_cells[4][0].value + else: + if d_100 <= 50: + return raw_cells[0][0].value + elif d_100 <= 75: + return raw_cells[1][0].value + elif d_100 <= 85: + return raw_cells[2][0].value + elif d_100 <= 95: + return raw_cells[3][0].value + else: + return raw_cells[4][0].value + + +def replace_pitcher(ai_team, this_play, pitcher_card_id): + this_card = db_get(f'cards', object_id=int(pitcher_card_id)) + new_lineup = { + 'game_id': this_play.game.id, + 'team_id': ai_team['id'], + 'player_id': this_card['player']['player_id'], + 'card_id': pitcher_card_id, + 'position': 'P', + 'batting_order': 10, + 'after_play': this_play.play_num - 1 + } + make_sub(new_lineup) + diff --git a/help_text.py b/help_text.py new file mode 100644 index 0000000..7ee2b29 --- /dev/null +++ b/help_text.py @@ -0,0 +1,146 @@ +SHEET_SHARE_STEPS = ( + f'We have a very easy-to-use Google Sheet for you to manage your team. It has a script attached that ' + f'powers most of the advanced functionality. Google requires you to manually copy a sheet if it ' + f'has scripts so I need you to follow three quick steps to get it shared:\n' + f'1) Make a copy of the embedded sheet below\n' + f'2) Share the sheet with Paper Domo with this email address: `pd-service-account@paper-dynasty.iam.' + f'gserviceaccount.com`\n' + f'3) Run `/newsheet ` and I will assign it to your team!\n\n' + f'__Do not__ share this sheet with anyone else. Access to the sheet grants buying and selling rights over your ' + f'collection.' +) + +HELP_SHEET_SCRIPTS = ( + f'The first time that you use the Paper Dynasty menu, Google Sheets will require that you authorize the script ' + f'the first time you access the Paper Dynasty menu following these steps:\n\n' + f'1. Click **Continue** and select your account\n' + f'2. Google has not verified this app, so click **Advanced** in the lower left corner and scroll down to ' + f'**Go to Paper Dynasty Marketplace (unsafe)**\n' + f'3. When told that the Paper Dynasty Marketplace wants to access your Google Account, scroll down and select ' + f'**Allow**\n\n' + f'The Paper Dynasty menu will now work on your Team Sheet!' +) + +HELP_GAMEMODES = ( + f'- Ranked Play: Play against another PD manager with your ranked roster. The top teams will make ' + f'the playoffs at the end of the season and fight for the Paper Cup.\n' + f'- Unlimited Play: Play against another PD manager or an AI with either of your un-ranked rosters. Great for ' + f'casual play, playtesting rosters, and event games.\n' + f'- Gauntlets: Coming soon' +) + +HELP_NEWGAME = ( + f'- Run `/newgame` and add the prompted info\n' + f'- Set starting lineups with `/read-lineup` for each team\n' + f'- Set your starting pitcher with `/starting-pitcher`\n\n' + f'From here, the game scorebug will be posted and you are ready to start rolling dice!' +) + + +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'- 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' + f'- Run `/gamestate` to see the scorebug with both lineups\n' + f'- Run `/substitution` to make lineup changes (for you and the AI in single player)\n' + f'- Run `/show-card` to see upcoming batters or the current defenders' +) + +HELP_ENDGAME = ( + f'- Run `/endgame`\n' + f'- ???\n' + f'- Profit\n' +) + +HELP_TS_DASH = ( + f'Your dashboard is split into three main columns:\n\n' + f'- The left column has your depth chart and lineups at the top, your roster list in the middle, and ' + f'the bulk marketplace category at the bottom.\n' + f'- The center column is the Paper Dynasty Marketplace. You can search and filter cards in Paper ' + f'Dynasty to purchase, sell, or just view them.\n' + f'- The right column is the player overview. This will display the most recently selected player ' + f'from the Marketplace column.' +) + +HELP_TS_ROSTER = ( + f'Top left of the My Team tab\n\n' + f'You have three rosters available for use: Primary, Secondary, and Ranked. The roster highlighted ' + f'green is the active roster; to swich the active roster, click on its name in the gray cell.\n\n' + f'You can set your rotation and two default lineups in the Lineups section. These are imported ' + f'by Paper Domo when you play a game so it sets your lineup automatically.\n\n' + f'If you don\'t have any players showing up in the Depth Chart, scroll down and add players to your ' + f'roster in the Roster Management section' +) + +HELP_TS_MARKET = ( + f'By default, this column will display all player cards in the Paper Dynasty universe. You can set ' + f'filters at the very top; you can also search for a first or last name in the search bar.\n\n' + f'By checking the box beside a player\'s name, it sets them as the active player. This will display ' + f'their overview on the right of the dashboard and select them for any "Quick" actions.' +) + +HELP_TS_MENU = ( + f'In the top bar (to the far right of File / Edit) is a Paper Dynasty tab. When that is selected, ' + f'you have access to the automations built into your team sheet.\n\n' + f'Data Refresh - resync all data imports across your sheet and is a good first step ' + f'if things arent\'t loading on your sheet\n' + f'Rosters - save or clear your saved rosters and lineups\n' + f'Marketplace - buy it now, add to card, and quick sell cards\n' + f'Shopping Cart - manage your cart and purchase multiple cards at once\n' + f'Sale Barn - bulk sell cards in the bottom-left corner of your team sheet' +) + +HELP_REWARDS_PREMIUM = ( + '- Win a 9-inning game\n' + '- Finish in top half of weekly standings\n' + '- Purchasable for 200₼\n' + '- Purchasable for $3 on the ko-fi shop' +) + +HELP_REWARDS_STANDARD = ( + '- Win a 3-inning game\n' + '- Play at least one game per week\n' + '- Purchasable for 100₼\n' + '- Every fifth check-in (`/comeonmanineedthis`)\n' + '- Purchasable for $2 on the ko-fi shop' +) + +HELP_REWARDS_MONEY = ( + '- 75₼ for playing a 9-inning game\n' + '- 25₼ for playing a 3-inning game\n' + '- Variable per cardset collections (`/paperdex`)' +) + +HELP_REWARDS_SHOP = ( + 'The Ko-fi shop can be [viewed here](https://ko-fi.com/manticorum/shop). In addition to card packs, ' + 'the full ratings guide is available for purchase.' +) + +HELP_START_WHAT = ( + f'Well, whipper snapper, have a seat and I\'ll tell you. We\'re running a diamond dynasty / ' + f'perfect team style game with electronic card and dice baseball!\n\nGet a starter pack, play ' + f'games at your leisure either solo or against another player, and collect cards from the ' + f'custom 2021 player set.' +) + +HELP_START_HOW = ( + f'Run the `.in` command - that\'s a period followed by the word "in". That\'ll get you the ' + f'Paper Dynasty Players role so you can run all of the other PD commands!\n\nOnce you get your ' + f'role, run `/newteam` and follow the prompts to get your starter team.' +) + +HELP_START_PLAY = ( + 'A step-by-step of how to play was written by Riles [starting here](https://discord.com/channels' + '/613880856032968834/633456305830625303/985968300272001054). ' + 'In addition, you can find the Rules Reference [right here](https://docs.google.com/document/d/' + '1yGZcHy9zN2MUi4hnce12dAzlFpIApbn7zR24vCkPl1o).\n\nThere are three key differences from league ' + 'play:\n1) Injuries: there are no injuries in Paper Dynasty!\n2) sWAR: there is no sWAR "salary ' + 'cap" for your team like in league play. Some events will have roster construction rules to ' + 'follow, though!\n3) The Universal DH is in effect; teams may forfeit the DH at their ' + 'discretion.' +) + +HELP_START_ASK = ( + f'Feel free to ask any questions down in ' +) diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..6814307 --- /dev/null +++ b/helpers.py @@ -0,0 +1,1898 @@ +import asyncio +import datetime +import logging +import os +import random +import traceback + +import discord +import pygsheets +from discord.ext import commands +from db_calls import * + +from bs4 import BeautifulSoup +from difflib import get_close_matches +from dataclasses import dataclass +from typing import Optional, Literal + + +SBA_SEASON = 7 +PD_SEASON = 4 +SBA_COLOR = 'a6ce39' +PD_PLAYERS = 'Paper Dynasty Players' +SBA_PLAYERS_ROLE_NAME = f'Season {SBA_SEASON} Players' +PD_PLAYERS_ROLE_NAME = f'Paper Dynasty Players' +PD_CARD_URL = 'https://sombaseball.ddns.net/cards/pd' +RATINGS_BATTER_FORMULA = '=IMPORTRANGE("1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE","guide_Batters!A1:CD")' +RATINGS_PITCHER_FORMULA = '=IMPORTRANGE("1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE","guide_Pitchers!A1:BQ")' +RATINGS_SHEET_KEY = '1zDmlOw94gTzOAjqOpNdDZsg0O6rxNWkL4-XT6-iL2IE' +ALL_MLB_TEAMS = { + 'Arizona Diamondbacks': ['ARI', 'Diamondbacks'], + 'Atlanta Braves': ['ATL', 'MLN', 'Braves'], + 'Baltimore Orioles': ['BAL', 'Orioles'], + 'Boston Red Sox': ['BOS', 'Red Sox'], + 'Chicago Cubs': ['CHC', 'Cubs'], + 'Chicago White Sox': ['CHW', 'White Sox'], + 'Cincinnati Reds': ['CIN', 'Reds'], + 'Cleveland Guardians': ['CLE', 'Guardians'], + 'Colorado Rockies': ['COL', 'Rockies'], + 'Detroit Tigers': ['DET', 'Tigers'], + 'Houston Astros': ['HOU', 'Astros'], + 'Kansas City Royals': ['KCR', 'Royals'], + 'Los Angeles Angels': ['LAA', 'CAL', 'Angels'], + 'Los Angeles Dodgers': ['LAD', 'Dodgers'], + 'Miami Marlins': ['MIA', 'Marlins'], + 'Milwaukee Brewers': ['MIL', 'MKE', 'Brewers'], + 'Minnesota Twins': ['MIN', 'Twins'], + 'New York Mets': ['NYM', 'Mets'], + 'New York Yankees': ['NYY', 'Yankees'], + 'Oakland Athletics': ['OAK', 'Athletics'], + 'Philadelphia Phillies': ['PHI', 'Phillies'], + 'Pittsburgh Pirates': ['PIT', 'Pirates'], + 'San Diego Padres': ['SDP', 'Padres'], + 'Seattle Mariners': ['SEA', 'Mariners'], + 'San Francisco Giants': ['SFG', 'Giants'], + 'St Louis Cardinals': ['STL', 'Cardinals'], + 'Tampa Bay Rays': ['TBR', 'Rays'], + 'Texas Rangers': ['TEX', 'Senators', 'Rangers'], + 'Toronto Blue Jays': ['TOR', 'Jays'], + 'Washington Nationals': ['WSN', 'WAS', 'Nationals'], +} +IMAGES = { + 'logo': 'https://sombaseball.ddns.net/static/images/sba-logo.png', + 'mvp-hype': f'{PD_CARD_URL}/mvp.png', + 'pack-sta': f'{PD_CARD_URL}/pack-standard.png', + 'pack-pre': f'{PD_CARD_URL}/pack-premium.png', + 'mvp': { + 'Arizona Diamondbacks': f'{PD_CARD_URL}/mvp/arizona-diamondbacks.gif', + 'Atlanta Braves': f'{PD_CARD_URL}/mvp/atlanta-braves.gif', + 'Baltimore Orioles': f'{PD_CARD_URL}/mvp/baltimore-orioles.gif', + 'Boston Red Sox': f'{PD_CARD_URL}/mvp/boston-red-sox.gif', + 'Chicago Cubs': f'{PD_CARD_URL}/mvp/chicago-cubs.gif', + 'Chicago White Sox': f'{PD_CARD_URL}/mvp/chicago-white-sox.gif', + 'Cincinnati Reds': f'{PD_CARD_URL}/mvp/cincinnati-reds.gif', + 'Cleveland Indians': f'{PD_CARD_URL}/mvp/cleveland-guardians.gif', + 'Cleveland Guardians': f'{PD_CARD_URL}/mvp/cleveland-guardians.gif', + 'Colorado Rockies': f'{PD_CARD_URL}/mvp/colorado-rockies.gif', + 'Detroit Tigers': f'{PD_CARD_URL}/mvp/detroit-tigers.gif', + 'Houston Astros': f'{PD_CARD_URL}/mvp/houston-astros.gif', + 'Kansas City Royals': f'{PD_CARD_URL}/mvp/kansas-city-royals.gif', + 'Los Angeles Angels': f'{PD_CARD_URL}/mvp/los-angeles-angels.gif', + 'Los Angeles Dodgers': f'{PD_CARD_URL}/mvp/los-angeles-dodgers.gif', + 'Miami Marlins': f'{PD_CARD_URL}/mvp/miami-marlins.gif', + 'Milwaukee Brewers': f'{PD_CARD_URL}/mvp/milwaukee-brewers.gif', + 'Minnesota Twins': f'{PD_CARD_URL}/mvp/minnesota-twins.gif', + 'New York Mets': f'{PD_CARD_URL}/mvp/new-york-mets.gif', + 'New York Yankees': f'{PD_CARD_URL}/mvp/new-york-yankees.gif', + 'Oakland Athletics': f'{PD_CARD_URL}/mvp/oakland-athletics.gif', + 'Philadelphia Phillies': f'{PD_CARD_URL}/mvp/philadelphia-phillies.gif', + 'Pittsburgh Pirates': f'{PD_CARD_URL}/mvp/pittsburgh-pirates.gif', + 'San Diego Padres': f'{PD_CARD_URL}/mvp/san-diego-padres.gif', + 'Seattle Mariners': f'{PD_CARD_URL}/mvp/seattle-mariners.gif', + 'San Francisco Giants': f'{PD_CARD_URL}/mvp/san-francisco-giants.gif', + 'St Louis Cardinals': f'{PD_CARD_URL}/mvp/st-louis-cardinals.gif', + 'St. Louis Cardinals': f'{PD_CARD_URL}/mvp/st-louis-cardinals.gif', + 'Tampa Bay Rays': f'{PD_CARD_URL}/mvp/tampa-bay-rays.gif', + 'Texas Rangers': f'{PD_CARD_URL}/mvp/texas-rangers.gif', + 'Toronto Blue Jays': f'{PD_CARD_URL}/mvp/toronto-blue-jays.gif', + 'Washington Nationals': f'{PD_CARD_URL}/mvp/washington-nationals.gif', + } +} +INFIELD_X_CHART = { + 'si1': { + 'rp': 'Runner on first: Line drive hits the runner! Runner on first is out. Batter goes to first with single ' + 'and all other runners hold.\nNo runner on first: batter singles, runners advance 1 base.', + 'e1': 'Single and Error, batter to second, runners advance 2 bases.', + 'e2': 'Single and Error, batter to third, all runners score.', + 'no': 'Single, runners advance 1 base.' + }, + 'po': { + 'rp': 'The batters hits a popup. None of the fielders take charge on the play and the ball drops in the ' + 'infield for a single! All runners advance 1 base.', + 'e1': 'The catcher drops a popup for an error. All runners advance 1 base.', + 'e2': 'The catcher grabs a squib in front of the plate and throws it into right field. The batter goes to ' + 'second and all runners score.', + 'no': 'The batter pops out to the catcher.' + }, + 'wp': { + 'rp': 'Automatic wild pitch. Catcher has trouble finding it and all base runners advance 2 bases.', + 'no': 'Automatic wild pitch, all runners advance 1 base and batter rolls AB again.' + }, + 'x': { + 'rp': 'Runner(s) on base: pitcher trips during his delivery and the ball sails for automatic wild pitch, ' + 'runners advance 1 base and batter rolls AB again.', + 'no': 'Wild pitch check (credited as a PB). If a passed ball occurs, batter rerolls AB. ' + 'If no passed ball occurs, the batter fouls out to the catcher.' + }, + 'fo': { + 'rp': 'Batter swings and misses, but is awarded first base on a catcher interference call! Baserunners advance ' + 'only if forced.', + 'e1': 'The catcher drops a foul popup for an error. Batter rolls AB again.', + 'e2': 'The catcher drops a foul popup for an error. Batter rolls AB again.', + 'no': 'Runner(s) on base: make a passed ball check. If no passed ball, batter pops out to the catcher. If a ' + 'passed ball occurs, batter roll his AB again.\nNo runners: batter pops out to the catcher' + }, + 'g1': { + 'rp': 'Runner on first: runner on first breaks up the double play, but umpires call runner interference and ' + 'the batter is out on GIDP.\nNo runners: Batter grounds out.', + 'e1': 'Error, batter to first, runners advance 1 base.', + 'e2': 'Error, batter to second, runners advance 2 bases.', + 'no': 'Consult Groundball Chart: `!gbA`' + }, + 'g2': { + 'rp': 'Batter lines the ball off the pitcher to the fielder who makes the play to first for the out! Runners ' + 'advance only if forced.', + 'e1': 'Error, batter to first, runners advance 1 base.', + 'e2': 'Error, batter to second, runners advance 2 bases.', + 'no': 'Consult Groundball Chart: `!gbB`' + }, + 'g3': { + 'rp': 'Batter lines the ball off the mound and deflects to the fielder who makes the play to first for the ' + 'out! Runners advance 1 base.', + 'e1': 'Error, batter to first, runners advance 1 base.', + 'e2': 'Error, batter to second, runners advance 2 bases.', + 'no': 'Consult Groundball Chart: `!gbC`' + }, +} +OUTFIELD_X_CHART = { + 'si2': { + 'rp': 'Batter singles, baserunners advance 2 bases. As the batter rounds first, the fielder throws behind him ' + 'and catches him off the bag for an out!', + 'e1': 'Single and error, batter to second, runners advance 2 bases.', + 'e2': 'Single and error, batter to third, all runners score.', + 'e3': 'Single and error, batter to third, all runners score', + 'no': 'Single, all runners advance 2 bases.' + }, + 'do2': { + 'rp': 'Batter doubles, runners advance 2 bases. The outfielder throws the ball to the shortstop who executes a ' + 'hidden ball trick! Runner on second is called out!', + 'e1': 'Double and error, batter to third, all runners score.', + 'e2': 'Double and error, batter to third, and all runners score.', + 'e3': 'Double and error, batter and all runners score. Little league home run!', + 'no': 'Double, all runners advance 2 bases.' + }, + 'do3': { + 'rp': 'Runner(s) on base: batter doubles and runners advance three bases as the outfielders collide!\n' + 'No runners: Batter doubles, but the play is appealed. The umps rule the batter missed first base so is ' + 'out on the appeal!', + 'e1': 'Double and error, batter to third, all runners score.', + 'e2': 'Double and error, batter and all runners score. Little league home run!', + 'e3': 'Double and error, batter and all runners score. Little league home run!', + 'no': 'Double, all runners score.' + }, + 'tr3': { + 'rp': 'Batter hits a ball into the gap and the outfielders collide trying to make the play! The ball rolls to ' + 'the wall and the batter trots home with an inside-the-park home run!', + 'e1': 'Triple and error, batter and all runners score. Little league home run!', + 'e2': 'Triple and error, batter and all runners score. Little league home run!', + 'e3': 'Triple and error, batter and all runners score. Little league home run!', + 'no': 'Triple, all runners score.' + }, + 'f1': { + 'rp': 'The outfielder races back and makes a diving catch and collides with the wall! In the time he takes to ' + 'recuperate, all baserunners tag-up and advance 2 bases.', + 'e1': '1 base error, runners advance 1 base.', + 'e2': '2 base error, runners advance 2 bases.', + 'e3': '3 base error, batter to third, all runners score.', + 'no': 'Flyball A' + }, + 'f2': { + 'rp': 'The outfielder catches the flyball for an out. If there is a runner on third, he tags-up and scores. ' + 'The play is appealed and the umps rule that the runner left early and is out on the appeal!', + 'e1': '1 base error, runners advance 1 base.', + 'e2': '2 base error, runners advance 2 bases.', + 'e3': '3 base error, batter to third, all runners score.', + 'no': 'Flyball B' + }, + 'f3': { + 'rp': 'The outfielder makes a running catch in the gap! The lead runner lost track of the ball and was ' + 'advancing - he cannot return in time and is doubled off by the outfielder.', + 'e1': '1 base error, runners advance 1 base.', + 'e2': '2 base error, runners advance 2 bases.', + 'e3': '3 base error, batter to third, all runners score.', + 'no': 'Flyball C' + } +} + + +class Question: + def __init__(self, bot, channel, prompt, qtype, timeout, embed=None): + """ + Version 0.4 + + :param bot, discord bot object + :param channel, discord Channel object + :param prompt, string, prompt message to post + :param qtype, string, 'yesno', 'int', 'float', 'text', or 'url' + :param timeout, float, time to wait for a response + """ + if not prompt and not embed: + raise TypeError('prompt and embed may not both be None') + + self.bot = bot + self.channel = channel + self.prompt = prompt + self.qtype = qtype + self.timeout = timeout + self.embed = embed + + async def ask(self, responders: list): + """ + Params: responder, list of discord User objects + Returns: True/False if confirm question; full response otherwise + """ + yes = ['yes', 'y', 'ye', 'yee', 'yerp', 'yep', 'yeet', 'yip', 'yup', 'yarp', 'si'] + no = ['no', 'n', 'nope', 'nah', 'nyet', 'nein'] + + if type(responders) is not list: + raise TypeError('Responders must be a list of Members') + + def yesno(mes): + return mes.channel == self.channel and mes.author in responders and mes.content.lower() in yes + no + + def text(mes): + return mes.channel == self.channel and mes.author in responders + + await self.channel.send(content=self.prompt, embed=self.embed) + + try: + resp = await self.bot.wait_for( + 'message', + timeout=self.timeout, + check=yesno if self.qtype == 'yesno' else text + ) + except asyncio.TimeoutError: + return None + except Exception as e: + await self.channel.send(f'Yuck, do you know what this means?\n\n{e}') + return None + + if self.qtype == 'yesno': + if resp.content.lower() in yes: + return True + else: + return False + elif self.qtype == 'int': + return int(resp.content) + elif self.qtype == 'float': + return float(resp.content) + elif self.qtype == 'url': + if resp.content[:7] == 'http://' or resp.content[:8] == 'https://': + return resp.content + else: + return False + else: + return resp.content + + +class Confirm(discord.ui.View): + def __init__(self, responders: list, timeout: float = 300.0, label_type: Literal['yes', 'confirm'] = None): + super().__init__(timeout=timeout) + if not isinstance(responders, list): + raise TypeError('responders must be a list') + self.value = None + self.responders = responders + if label_type == 'yes': + self.confirm.label = 'Yes' + self.cancel.label = 'No' + + # When the confirm button is pressed, set the inner value to `True` and + # stop the View from listening to more input. + # We also send the user an ephemeral message that we're confirming their choice. + @discord.ui.button(label='Confirm', style=discord.ButtonStyle.green) + async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = True + self.clear_items() + self.stop() + + # This one is similar to the confirmation button except sets the inner value to `False` + @discord.ui.button(label='Cancel', style=discord.ButtonStyle.grey) + async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = False + self.clear_items() + self.stop() + + +class ButtonOptions(discord.ui.View): + def __init__(self, responders: list, timeout: float = 300.0, labels=None): + super().__init__(timeout=timeout) + if not isinstance(responders, list): + raise TypeError('responders must be a list') + self.value = None + self.responders = responders + self.options = labels + for count, x in enumerate(labels): + if count == 0: + self.option1.label = x + if x is None or x.lower() == 'na' or x == 'N/A': + self.remove_item(self.option1) + if count == 1: + self.option2.label = x + if x is None or x.lower() == 'na' or x == 'N/A': + self.remove_item(self.option2) + if count == 2: + self.option3.label = x + if x is None or x.lower() == 'na' or x == 'N/A': + self.remove_item(self.option3) + if count == 3: + self.option4.label = x + if x is None or x.lower() == 'na' or x == 'N/A': + self.remove_item(self.option4) + if count == 4: + self.option5.label = x + if x is None or x.lower() == 'na' or x == 'N/A': + self.remove_item(self.option5) + + @discord.ui.button(label='Option 1', style=discord.ButtonStyle.primary) + async def option1(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = self.options[0] + self.clear_items() + self.stop() + + @discord.ui.button(label='Option 2', style=discord.ButtonStyle.primary) + async def option2(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = self.options[1] + self.clear_items() + self.stop() + + @discord.ui.button(label='Option 3', style=discord.ButtonStyle.primary) + async def option3(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = self.options[2] + self.clear_items() + self.stop() + + @discord.ui.button(label='Option 4', style=discord.ButtonStyle.primary) + async def option4(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = self.options[3] + self.clear_items() + self.stop() + + @discord.ui.button(label='Option 5', style=discord.ButtonStyle.primary) + async def option5(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + return + + self.value = self.options[4] + self.clear_items() + self.stop() + + +class Pagination(discord.ui.View): + def __init__(self, responders: list, timeout: float = 300.0): + super().__init__(timeout=timeout) + if not isinstance(responders, list): + raise TypeError('responders must be a list') + + self.value = None + self.responders = responders + + @discord.ui.button(label='⏮️', style=discord.ButtonStyle.blurple) + async def left_button(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + logging.info(f'{interaction.user} is not in {self.responders}') + return + + self.value = 'left' + await interaction.response.defer() + self.stop() + + @discord.ui.button(label='❌️', style=discord.ButtonStyle.secondary) + async def cancel_button(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + logging.info(f'{interaction.user} is not in {self.responders}') + return + + self.value = 'cancel' + await interaction.response.defer() + self.stop() + + @discord.ui.button(label='⏭️', style=discord.ButtonStyle.blurple) + async def right_button(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user not in self.responders: + logging.info(f'{interaction.user} is not in {self.responders}') + return + + self.value = 'right' + await interaction.response.defer() + self.stop() + + +# class Email(discord.ui.Modal, title='Team Sheet Share'): +# def __init__(self, title: str, timeout: float = 300.0, custom_id: int = None): +# super().__init__(timeout=timeout) +# self.title = title +# self.custom_id = custom_id +# logging.info(f'Modal init') +# +# email = discord.ui.TextInput( +# label='Email address', +# style=discord.TextStyle.short, +# placeholder='paper.domo@gmail.com', +# required=True +# ) +# +# async def on_submit(self, interaction: discord.Interaction): +# logging.info(f'on_submit: {self.email} for team_id: {self.custom_id}') +# team = get_team_by_owner(int(self.custom_id)) +# share_sheet(team, self.email) +# await interaction.response.send_message( +# f'Got it, {team["gmname"]}. Just shared your team sheet with you!' +# ) +# await interaction.followup.send_message(get_roster_sheet(team)) +# +# async def on_error(self, interaction: discord.Interaction, error: Exception) -> None: +# await interaction.response.send_message('Oops! Something went wrong.', ephemeral=True) +# +# # Make sure we know what the error actually is +# traceback.print_tb(error.__traceback__) + + +def random_conf_gif(): + conf_gifs = [ + 'https://tenor.com/view/boom-annakendrick-pitchperfect-pitchperfect2-micdrop-gif-5143507', + 'https://tenor.com/view/boom-annakendrick-pitchperfect-pitchperfect2-micdrop-gif-5143507', + 'https://tenor.com/view/boom-annakendrick-pitchperfect-pitchperfect2-micdrop-gif-5143507', + 'https://tenor.com/view/explosion-boom-iron-man-gif-14282225', + 'https://tenor.com/view/betty-white-dab-consider-it-done-gif-11972415', + 'https://tenor.com/view/done-and-done-spongebob-finished-just-did-it-gif-10843280', + 'https://tenor.com/view/thumbs-up-okay-ok-well-done-gif-13840394', + 'https://tenor.com/view/tinkerbell-peter-pan-all-done-gif-15003723', + 'https://tenor.com/view/done-and-done-ron-swanson-gotchu-gif-10843254', + 'https://tenor.com/view/sponge-bob-thumbs-up-ok-smile-gif-12038157', + 'https://tenor.com/view/thumbs-up-cool-okay-bye-gif-8633196', + 'https://media.giphy.com/media/zCME2Cd20Czvy/giphy.gif', + 'https://media.giphy.com/media/xT0xeJpckCr0qGAFna/giphy.gif', + 'https://media0.giphy.com/media/l0MYw3oeYCUJhj5FC/200.gif', + 'https://media2.giphy.com/media/52FcaTVc9Y1rk7q1NQ/200.gif', + 'https://i0.wp.com/media1.giphy.com/media/iwvuPyfi7z14I/giphy.gif', + 'https://media1.tenor.com/images/859a2d3b201fbacec13904242976b9e0/tenor.gif', + 'https://media.giphy.com/media/3o6ZsZbUukGiYMf2a4/giphy.gif', + 'https://media.giphy.com/media/l0Iyl55kTeh71nTXy/giphy.gif', + 'https://media.giphy.com/media/3o7qDEq2bMbcbPRQ2c/giphy.gif', + 'https://media.giphy.com/media/3orieTbyzN9QNCDANa/giphy.gif', + 'https://media.giphy.com/media/l3V0JGUuz4xght7Py/giphy.gif', + 'https://media.giphy.com/media/o1H5mqZKB0RCE/giphy.gif', + 'https://media.giphy.com/media/Rhf0uSWt1P2TFqVMZK/giphy.gif', + 'https://media.giphy.com/media/UIMAFTRUcf6V71BGsd/giphy.gif', + 'https://media.giphy.com/media/xUOwFYnEb6rv4Oxx6M/giphy.gif', + 'https://media.giphy.com/media/6bdiLSKOwgR6ekaTSS/giphy.gif', + 'https://tenor.com/bc1OJ.gif', + 'https://tenor.com/1EmF.gif', + 'https://tenor.com/ZYCh.gif', + 'https://tenor.com/patd.gif', + 'https://tenor.com/u6mU.gif', + 'https://tenor.com/x2sa.gif', + 'https://tenor.com/bAVeS.gif', + 'https://tenor.com/bxOcj.gif', + 'https://tenor.com/ETJ7.gif', + 'https://tenor.com/bpH3g.gif', + 'https://tenor.com/biF9q.gif', + 'https://tenor.com/OySS.gif', + 'https://tenor.com/bvVFv.gif', + 'https://tenor.com/bFeqA.gif' + ] + return conf_gifs[random.randint(0, len(conf_gifs) - 1)] + + +def random_no_gif(): + no_gifs = [ + 'https://tenor.com/view/youre-not-my-dad-dean-jensen-ackles-supernatural-you-arent-my-dad-gif-19503399', + 'https://tenor.com/view/youre-not-my-dad-kid-gif-8300190', + 'https://tenor.com/view/youre-not-my-supervisor-youre-not-my-boss-gif-12971403', + 'https://tenor.com/view/dont-tell-me-what-to-do-gif-4951202' + ] + return no_gifs[random.randint(0, len(no_gifs) - 1)] + + +def random_salute_gif(): + salute_gifs = [ + 'https://media.giphy.com/media/fSAyceY3BCgtiQGnJs/giphy.gif', + 'https://media.giphy.com/media/bsWDUSFUmJCOk/giphy.gif', + 'https://media.giphy.com/media/hStvd5LiWCFzYNyxR4/giphy.gif', + 'https://media.giphy.com/media/RhSR5xXDsXJ7jbnrRW/giphy.gif', + 'https://media.giphy.com/media/lNQvrlPdbmZUU2wlh9/giphy.gif', + 'https://gfycat.com/skeletaldependableandeancat', + 'https://i.gifer.com/5EJk.gif', + 'https://tenor.com/baJUV.gif', + 'https://tenor.com/bdnQH.gif', + 'https://tenor.com/bikQU.gif', + 'https://i.pinimg.com/originals/04/36/bf/0436bfc9861b4b57ffffda82d3adad6e.gif', + 'https://media.giphy.com/media/6RtOG4Q7v34kw/giphy.gif', + 'https://keyassets-p2.timeincuk.net/wp/prod/wp-content/uploads/sites/42/2017/04/anigif_' + 'enhanced-946-1433453114-7.gif', + 'https://keyassets-p2.timeincuk.net/wp/prod/wp-content/uploads/sites/42/2017/04/100c5d677cc28ea3f15' + '4c70d641f655b_meme-crying-gif-crying-gif-meme_620-340.gif', + 'https://media.giphy.com/media/fnKd6rCHaZoGdzLjjA/giphy.gif', + 'https://media.giphy.com/media/47D5jmVc4f7ylygXYD/giphy.gif', + 'https://media.giphy.com/media/I4wGMXoi2kMDe/giphy.gif', + ] + return salute_gifs[random.randint(0, len(salute_gifs) - 1)] + + +def random_conf_word(): + conf_words = [ + 'dope', + 'cool', + 'got it', + 'noice', + 'ok', + 'lit', + ] + return conf_words[random.randint(0, len(conf_words) - 1)] + + +def random_codename(): + all_names = [ + 'Shong', 'DerekSux', 'JoeSux', 'CalSux', 'Friend', 'Andrea', 'Ent', 'Lindved', 'Camp', 'Idyll', 'Elaphus', + 'Turki', 'Shrimp', 'Primary', 'Anglica', 'Shail', 'Blanket', 'Baffled', 'Deer', 'Thisted', 'Brisk', 'Shy', + 'Table', 'Jorts', 'Renati', 'Gisky', 'Prig', 'Bathtub', 'Gallery', 'Mavas', 'Chird', 'Oxyura', 'Mydal', 'Brown', + 'Vasen', 'Worthy', 'Bivver', 'Cirlus', 'Self', 'Len', 'Sharp', 'Dart', 'Crepis', 'Ferina', 'Curl', 'Lancome', + 'Stuff', 'Glove', 'Consist', 'Smig', 'Egg', 'Pleat', 'Picture', 'Spin', 'Ridgty', 'Ickled', 'Abashed', 'Haul', + 'Cordage', 'Chivery', 'Stointy', 'Baa', 'Here', 'Ulmi', 'Tour', 'Tribe', 'Crunch', 'Used', 'Pigface', 'Audit', + 'Written', 'Once', 'Fickle', 'Drugged', 'Swarm', 'Blimber', 'Torso', 'Retusa', 'Hockey', 'Pusty', 'Sallow', + 'Next', 'Mansion', 'Glass', 'Screen', 'Josiah', 'Bonkey', 'Stuff', 'Sane', 'Blooded', 'Gnat', 'Liparis', + 'Ocean', 'Sway', 'Roband', 'Still', 'Ribba', 'Biryani', 'Halibut', 'Flyn', 'Until', 'Depend', 'Intel', + 'Affinis', 'Chef', 'Trounce', 'Crawl', 'Grab', 'Eggs', 'Malfroy', 'Sitta', 'Cretin', 'May', 'Smithii', + 'Saffron', 'Crummy', 'Powered', 'Rail', 'Trait', 'Koiled', 'Bronze', 'Quickly', 'Vikis', 'Trift', 'Jubilar', + 'Deft', 'Juncus', 'Sodding', 'Distant', 'Poecile', 'Pipe', 'Sell', 'Inops', 'Peusi', 'Sparrow', 'Yams', + 'Kidneys', 'Artery', 'Vuffin', 'Boink', 'Bos', 'Notable', 'Alba', 'Spurge', 'Ruby', 'Cilia', 'Pellow', 'Nox', + 'Woozy', 'Semvik', 'Tyda', 'Season', 'Lychnis', 'Ibestad', 'Bagge', 'Marked', 'Browdie', 'Fisher', 'Tilly', + 'Troll', 'Gypsy', 'Thisted', 'Flirt', 'Stop', 'Radiate', 'Poop', 'Plenty', 'Jeff', 'Magpie', 'Roof', 'Ent', + 'Dumbo', 'Pride', 'Weights', 'Winted', 'Dolden', 'Meotica', 'Yikes', 'Teeny', 'Fizz', 'Eide', 'Foetida', + 'Crash', 'Mann', 'Salong', 'Cetti', 'Balloon', 'Petite', 'Find', 'Sputter', 'Patula', 'Upstage', 'Aurora', + 'Dadson', 'Drate', 'Heidal', 'Robin', 'Auditor', 'Ithil', 'Warmen', 'Pat', 'Muppet', '007', 'Advantage', + 'Alert', 'Backhander', 'Badass', 'Blade', 'Blaze', 'Blockade', 'Blockbuster', 'Boxer', 'Brimstone', 'Broadway', + 'Buccaneer', 'Champion', 'Cliffhanger', 'Coachman', 'Comet', 'Commander', 'Courier', 'Cowboy', 'Crawler', + 'Crossroads', 'DeepSpace', 'Desperado', 'Double-Decker', 'Echelon', 'Edge', 'Encore', 'EnRoute', 'Escape', + 'Eureka', 'Evangelist', 'Excursion', 'Explorer', 'Fantastic', 'Firefight', 'Foray', 'Forge', 'Freeway', + 'Frontier', 'FunMachine', 'Galaxy', 'GameOver', 'Genesis', 'Hacker', 'Hawkeye', 'Haybailer', 'Haystack', + 'Hexagon', 'Hitman', 'Hustler', 'Iceberg', 'Impossible', 'Impulse', 'Invader', 'Inventor', 'IronWolf', + 'Jackrabbit', 'Juniper', 'Keyhole', 'Lancelot', 'Liftoff', 'MadHatter', 'Magnum', 'Majestic', 'Merlin', + 'Multiplier', 'Netiquette', 'Nomad', 'Octagon', 'Offense', 'OliveBranch', 'OlympicTorch', 'Omega', 'Onyx', + 'Orbit', 'OuterSpace', 'Outlaw', 'Patron', 'Patriot', 'Pegasus', 'Pentagon', 'Pilgrim', 'Pinball', 'Pinnacle', + 'Pipeline', 'Pirate', 'Portal', 'Predator', 'Prism', 'RagingBull', 'Ragtime', 'Reunion', 'Ricochet', + 'Roadrunner', 'Rockstar', 'RobinHood', 'Rover', 'Runabout', 'Sapphire', 'Scrappy', 'Seige', 'Shadow', + 'Shakedown', 'Shockwave', 'Shooter', 'Showdown', 'SixPack', 'SlamDunk', 'Slasher', 'Sledgehammer', 'Spirit', + 'Spotlight', 'Starlight', 'Steamroller', 'Stride', 'Sunrise', 'Superhuman', 'Supernova', 'SuperBowl', 'Sunset', + 'Sweetheart', 'TopHand', 'Touchdown', 'Tour', 'Trailblazer', 'Transit', 'Trekker', 'Trio', 'TriplePlay', + 'TripleThreat', 'Universe', 'Unstoppable', 'Utopia', 'Vicinity', 'Vector', 'Vigilance', 'Vigilante', 'Vista', + 'Visage', 'Vis-à-vis', 'VIP', 'Volcano', 'Volley', 'Whizzler', 'Wingman', 'Badger', 'BlackCat', 'Bobcat', + 'Caracal', 'Cheetah', 'Cougar', 'Jaguar', 'Leopard', 'Lion', 'Lynx', 'MountainLion', 'Ocelot', 'Panther', + 'Puma', 'Siamese', 'Serval', 'Tiger', 'Wolverine', 'Abispa', 'Andrena', 'BlackWidow', 'Cataglyphis', + 'Centipede', 'Cephalotes', 'Formica', 'Hornet', 'Jellyfish', 'Scorpion', 'Tarantula', 'Yellowjacket', 'Wasp', + 'Apollo', 'Ares', 'Artemis', 'Athena', 'Hercules', 'Hermes', 'Iris', 'Medusa', 'Nemesis', 'Neptune', 'Perseus', + 'Poseidon', 'Triton', 'Zeus', 'Aquarius', 'Aries', 'Cancer', 'Capricorn', 'Gemini', 'Libra', 'Leo', 'Pisces', + 'Sagittarius', 'Scorpio', 'Taurus', 'Virgo', 'Andromeda', 'Aquila', 'Cassiopeia', 'Cepheus', 'Cygnus', + 'Delphinus', 'Drako', 'Lyra', 'Orion', 'Perseus', 'Serpens', 'Triangulum', 'Anaconda', 'Boa', 'Cobra', + 'Copperhead', 'Cottonmouth', 'Garter', 'Kingsnake', 'Mamba', 'Python', 'Rattler', 'Sidewinder', 'Taipan', + 'Viper', 'Alligator', 'Barracuda', 'Crocodile', 'Gator', 'GreatWhite', 'Hammerhead', 'Jaws', 'Lionfish', + 'Mako', 'Moray', 'Orca', 'Piranha', 'Shark', 'Stingray', 'Axe', 'BattleAxe', 'Bayonet', 'Blade', 'Crossbowe', + 'Dagger', 'Excalibur', 'Halberd', 'Hatchet', 'Machete', 'Saber', 'Samurai', 'Scimitar', 'Scythe', 'Stiletto', + 'Spear', 'Sword', 'Aurora', 'Avalanche', 'Blizzard', 'Cyclone', 'Dewdrop', 'Downpour', 'Duststorm', 'Fogbank', + 'Freeze', 'Frost', 'GullyWasher', 'Gust', 'Hurricane', 'IceStorm', 'JetStream', 'Lightning', 'Mist', 'Monsoon', + 'Rainbow', 'Raindrop', 'SandStorm', 'Seabreeze', 'Snowflake', 'Stratosphere', 'Storm', 'Sunrise', 'Sunset', + 'Tornado', 'Thunder', 'Thunderbolt', 'Thunderstorm', 'TropicalStorm', 'Twister', 'Typhoon', 'Updraft', 'Vortex', + 'Waterspout', 'Whirlwind', 'WindChill', 'Archimedes', 'Aristotle', 'Confucius', 'Copernicus', 'Curie', + 'daVinci', 'Darwin', 'Descartes', 'Edison', 'Einstein', 'Epicurus', 'Freud', 'Galileo', 'Hawking', + 'Machiavelli', 'Marx', 'Newton', 'Pascal', 'Pasteur', 'Plato', 'Sagan', 'Socrates', 'Tesla', 'Voltaire', + 'Baccarat', 'Backgammon', 'Blackjack', 'Chess', 'Jenga', 'Jeopardy', 'Keno', 'Monopoly', 'Pictionary', 'Poker', + 'Scrabble', 'TrivialPursuit', 'Twister', 'Roulette', 'Stratego', 'Yahtzee', 'Aquaman', 'Batman', 'BlackPanther', + 'BlackWidow', 'CaptainAmerica', 'Catwoman', 'Daredevil', 'Dr.Strange', 'Flash', 'GreenArrow', 'GreenLantern', + 'Hulk', 'IronMan', 'Phantom', 'Thor', 'SilverSurfer', 'SpiderMan', 'Supergirl', 'Superman', 'WonderWoman', + 'Wolverine', 'Hypersonic', 'Lightspeed', 'Mach1,2,3,4,etc', 'Supersonic', 'WarpSpeed', 'Amiatina', 'Andalusian', + 'Appaloosa', 'Clydesdale', 'Colt', 'Falabella', 'Knabstrupper', 'Lipizzan', 'Lucitano', 'Maverick', 'Mustang', + 'Palomino', 'Pony', 'QuarterHorse', 'Stallion', 'Thoroughbred', 'Zebra', 'Antigua', 'Aruba', 'Azores', 'Baja', + 'Bali', 'Barbados', 'Bermuda', 'BoraBora', 'Borneo', 'Capri', 'Cayman', 'Corfu', 'Cozumel', 'Curacao', 'Fiji', + 'Galapagos', 'Hawaii', 'Ibiza', 'Jamaica', 'Kauai', 'Lanai', 'Majorca', 'Maldives', 'Maui', 'Mykonos', + 'Nantucket', 'Oahu', 'Tahiti', 'Tortuga', 'Roatan', 'Santorini', 'Seychelles', 'St.Johns', 'St.Lucia', + 'Albatross', 'BaldEagle', 'Blackhawk', 'BlueJay', 'Chukar', 'Condor', 'Crane', 'Dove', 'Eagle', 'Falcon', + 'Goose(GoldenGoose)', 'Grouse', 'Hawk', 'Heron', 'Hornbill', 'Hummingbird', 'Lark', 'Mallard', 'Oriole', + 'Osprey', 'Owl', 'Parrot', 'Penguin', 'Peregrine', 'Pelican', 'Pheasant', 'Quail', 'Raptor', 'Raven', 'Robin', + 'Sandpiper', 'Seagull', 'Sparrow', 'Stork', 'Thunderbird', 'Toucan', 'Vulture', 'Waterfowl', 'Woodpecker', + 'Wren', 'C-3PO', 'Chewbacca', 'Dagobah', 'DarthVader', 'DeathStar', 'Devaron', 'Droid', 'Endor', 'Ewok', 'Hoth', + 'Jakku', 'Jedi', 'Leia', 'Lightsaber', 'Lothal', 'Naboo', 'Padawan', 'R2-D2', 'Scarif', 'Sith', 'Skywalker', + 'Stormtrooper', 'Tatooine', 'Wookie', 'Yoda', 'Zanbar', 'Canoe', 'Catamaran', 'Cruiser', 'Cutter', 'Ferry', + 'Galleon', 'Gondola', 'Hovercraft', 'Hydrofoil', 'Jetski', 'Kayak', 'Longboat', 'Motorboat', 'Outrigger', + 'PirateShip', 'Riverboat', 'Sailboat', 'Skipjack', 'Schooner', 'Skiff', 'Sloop', 'Steamboat', 'Tanker', + 'Trimaran', 'Trawler', 'Tugboat', 'U-boat', 'Yacht', 'Yawl', 'Lancer', 'Volunteer', 'Searchlight', 'Passkey', + 'Deacon', 'Rawhide', 'Timberwolf', 'Eagle', 'Tumbler', 'Renegade', 'Mogul' + ] + + this_name = all_names[random.randint(0, len(all_names) - 1)] + return this_name + + +def random_no_phrase(): + phrases = [ + 'uhh...no', + 'lol no', + 'nope', + ] + + +async def send_to_bothole(ctx, content, embed): + await discord.utils.get(ctx.guild.text_channels, name='pd-bot-hole') \ + .send(content=content, embed=embed) + + +async def send_to_news(ctx, content, embed): + await discord.utils.get(ctx.guild.text_channels, name='pd-news-ticker') \ + .send(content=content, embed=embed) + + +async def typing_pause(ctx, seconds=1): + async with ctx.typing(): + await asyncio.sleep(seconds) + + +async def pause_then_type(ctx, message): + async with ctx.typing(): + await asyncio.sleep(len(message) / 100) + await ctx.send(message) + + +async def check_if_pdhole(ctx): + if ctx.message.channel.name != 'pd-bot-hole': + await ctx.send('Slide on down to my bot-hole for running commands.') + await ctx.message.add_reaction('❌') + return False + return True + + +def get_roster_sheet_legacy(team): + return f'https://docs.google.com/spreadsheets/d/{team.gsheet}/edit' + + +def get_special_embed(special): + embed = discord.Embed(title=f'{special.name} - Special #{special.get_id()}', + color=discord.Color.random(), + description=f'{special.short_desc}') + embed.add_field(name='Description', value=f'{special.long_desc}', inline=False) + # embed.add_field(name='To Redeem', value=f'Run .redeem {special.get_id()}', inline=False) + if special.thumbnail.lower() != 'none': + embed.set_thumbnail(url=f'{special.thumbnail}') + if special.url.lower() != 'none': + embed.set_image(url=f'{special.url}') + + return embed + + +def get_random_embed(title, thumb=None): + embed = discord.Embed(title=title, color=discord.Color.random()) + if thumb: + embed.set_thumbnail(url=thumb) + + return embed + + +async def get_player_photo(player): + search_term = player['bbref_id'] if player['bbref_id'] else player['p_name'] + req_url = f'https://www.thesportsdb.com/api/v1/json/1/searchplayers.php?p={search_term}' + + try: + resp = requests.get(req_url, timeout=.5) + except Exception as e: + return None + if resp.status_code == 200 and resp.json()['player']: + if resp.json()['player'][0]['strSport'] == 'Baseball': + db_patch('players', object_id=player['player_id'], + params=[('headshot', resp.json()['player'][0]['strThumb'])]) + return resp.json()['player'][0]['strThumb'] + return None + + +async def get_player_headshot(player): + search_term = player['bbref_id'] if player['bbref_id'] else player['p_name'] + req_url = f'https://www.baseball-reference.com/search/search.fcgi?search={search_term}' + + try: + resp = requests.get(req_url, timeout=2).text + soup = BeautifulSoup(resp, 'html.parser') + for item in soup.find_all('img'): + if 'headshot' in item['src']: + db_patch('players', object_id=player['player_id'], params=[('headshot', item['src'])]) + return item['src'] + except: + pass + return await get_player_photo(player) + + +def fuzzy_search(name, master_list): + if name.lower() in master_list: + return name.lower() + + great_matches = get_close_matches(name, master_list, cutoff=0.8) + if len(great_matches) == 1: + return great_matches[0] + elif len(great_matches) > 0: + matches = great_matches + else: + matches = get_close_matches(name, master_list, n=6) + if len(matches) == 1: + return matches[0] + + if not matches: + raise ValueError(f'{name.title()} was not found') + + return matches[0] + + +async def fuzzy_player_search(ctx, channel, bot, name, master_list): + """ + Takes a name to search and returns the name of the best match + + :param ctx: discord context + :param channel: discord channel + :param bot: discord.py bot object + :param name: string + :param master_list: list of names + :return: + """ + matches = fuzzy_search(name, master_list) + + embed = discord.Embed( + title="Did You Mean...", + description='Enter the number of the card you would like to see.', + color=0x7FC600 + ) + count = 1 + for x in matches: + embed.add_field(name=f'{count}', value=x, inline=False) + count += 1 + embed.set_footer(text='These are the closest matches. Spell better if they\'re not who you want.') + this_q = Question(bot, channel, None, 'int', 45, embed=embed) + resp = await this_q.ask([ctx.author]) + + if not resp: + return None + if resp < count: + return matches[resp - 1] + else: + raise ValueError(f'{resp} is not a valid response.') + + +async def create_channel_old( + ctx, channel_name: str, category_name: str, everyone_send=False, everyone_read=True, allowed_members=None, + allowed_roles=None): + this_category = discord.utils.get(ctx.guild.categories, name=category_name) + if not this_category: + raise ValueError(f'I couldn\'t find a category named **{category_name}**') + + overwrites = { + ctx.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), + ctx.guild.default_role: discord.PermissionOverwrite(read_messages=everyone_read, send_messages=everyone_send) + } + if allowed_members: + if isinstance(allowed_members, list): + for member in allowed_members: + overwrites[member] = discord.PermissionOverwrite(read_messages=True, send_messages=True) + if allowed_roles: + if isinstance(allowed_roles, list): + for role in allowed_roles: + overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) + + this_channel = await ctx.guild.create_text_channel( + channel_name, + overwrites=overwrites, + category=this_category + ) + + logging.info(f'Creating channel ({channel_name}) in ({category_name})') + + return this_channel + + +async def react_and_reply(ctx, reaction, message): + await ctx.message.add_reaction(reaction) + await ctx.send(message) + + +async def send_to_channel(bot, channel_name, content=None, embed=None): + guild = bot.get_guild(int(os.environ.get('GUILD_ID'))) + if not guild: + logging.error('Cannot send to channel - bot not logged in') + return + + this_channel = discord.utils.get(guild.text_channels, name=channel_name) + + if not this_channel: + this_channel = discord.utils.get(guild.text_channels, id=channel_name) + if not this_channel: + raise NameError(f'**{channel_name}** channel not found') + + return await this_channel.send(content=content, embed=embed) + + +async def get_or_create_role(ctx, role_name, mentionable=True): + this_role = discord.utils.get(ctx.guild.roles, name=role_name) + # logging.info(f'this_role: {this_role} / role_name: {role_name} (POST SEARCH)') + + if not this_role: + this_role = await ctx.guild.create_role(name=role_name, mentionable=mentionable) + # logging.info(f'this_role: {this_role} / role_name: {role_name} (PRE RETURN)') + + return this_role + + +""" +NEW FOR SEASON 4 +""" + + +def get_team_embed(title, team=None, thumbnail: bool = True): + if team: + embed = discord.Embed( + title=title, + color=int(team["color"], 16) if team["color"] else int(SBA_COLOR, 16) + ) + embed.set_footer(text=f'Paper Dynasty Season {team["season"]}', icon_url=IMAGES['logo']) + if thumbnail: + embed.set_thumbnail(url=team["logo"] if team["logo"] else IMAGES['logo']) + else: + embed = discord.Embed( + title=title, + color=int(SBA_COLOR, 16) + ) + embed.set_footer(text=f'Paper Dynasty Season {PD_SEASON}', icon_url=IMAGES['logo']) + if thumbnail: + embed.set_thumbnail(url=IMAGES['logo']) + + return embed + + +def get_team_by_owner(owner_id: int): + team = db_get('teams', params=[('gm_id', owner_id)]) + + if not team['count']: + return None + + return team['teams'][0] + + +async def team_role(ctx, team): + return await get_or_create_role(ctx, f'{team["abbrev"]} - {team["lname"]}') + + +def get_cal_user(ctx): + return ctx.guild.get_member(258104532423147520) + + +async def get_emoji(ctx, name, return_empty=True): + try: + emoji = await commands.converter.EmojiConverter().convert(ctx, name) + except: + if return_empty: + emoji = '' + else: + return name + return emoji + + +def get_all_pos(player): + all_pos = [] + + for x in range(1, 8): + if player[f'pos_{x}'] and player[f'pos_{x}'] != 'CP': + all_pos.append(player[f'pos_{x}']) + + return all_pos + + +async def create_channel( + ctx, channel_name: str, category_name: str, everyone_send=False, everyone_read=True, + read_send_members: list = None, read_send_roles: list = None, read_only_roles: list = None): + this_category = discord.utils.get(ctx.guild.categories, name=category_name) + if not this_category: + raise ValueError(f'I couldn\'t find a category named **{category_name}**') + + overwrites = { + ctx.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True), + ctx.guild.default_role: discord.PermissionOverwrite(read_messages=everyone_read, send_messages=everyone_send) + } + if read_send_members: + for member in read_send_members: + overwrites[member] = discord.PermissionOverwrite(read_messages=True, send_messages=True) + if read_send_roles: + for role in read_send_roles: + overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) + if read_only_roles: + for role in read_only_roles: + overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=False) + + this_channel = await ctx.guild.create_text_channel( + channel_name, + overwrites=overwrites, + category=this_category + ) + + logging.info(f'Creating channel ({channel_name}) in ({category_name})') + + return this_channel + + +async def share_channel(channel, user, read_only=False): + await channel.set_permissions(user, read_messages=True, send_messages=not read_only) + + +async def get_card_embeds(card) -> list: + embed = discord.Embed( + title=f'{card["player"]["p_name"]}', + color=int(card['player']['rarity']['color'], 16) + ) + # embed.description = card['team']['lname'] + embed.description = f'{card["player"]["cardset"]["name"]} / {card["player"]["mlbclub"]}' + embed.set_author(name=card['team']['lname'], url=IMAGES['logo'], icon_url=card['team']['logo']) + embed.set_footer(text=f'Paper Dynasty Season {card["team"]["season"]}', icon_url=IMAGES['logo']) + + # embed.add_field( + # name='Cardset', + # value=f'{card["player"]["cardset"]["name"]} ' + # f'({card["player"]["set_num"]}/{card["player"]["cardset"]["total_cards"]})', + # inline=False + # ) + embed.add_field(name='Cost', value=f'{card["player"]["cost"]}₼') + embed.add_field(name='Rarity', value=f'{card["player"]["rarity"]["name"]}') + + player_pages = f'[BBRef]({get_player_url(card["player"], "bbref")})' + embed.add_field(name='Player Page', value=f'{player_pages}') + + # all_dex = card['player']['paperdex'] + all_dex = db_get('paperdex', params=[("player_id", card["player"]["player_id"]), ('flat', True)]) + count = 0 if not all_dex else all_dex['count'] + if card['team']['lname'] != 'Paper Dynasty': + bool_list = [True for elem in all_dex['paperdex'] if elem['team'] == card['team']['id']] + if any(bool_list): + if count == 1: + coll_string = f'Only you' + else: + coll_string = f'You and {count - 1} other{"s" if count - 1 != 1 else ""}' + else: + coll_string = f'{count} other team{"s" if count != 1 else ""}' + embed.add_field(name='Collected By', value=coll_string) + else: + embed.add_field(name='Collected By', value=f'{count} team{"s" if count != 1 else ""}') + + # TODO: check for dupes with the included paperdex data + if card['team']['lname'] != 'Paper Dynasty': + team_dex = db_get('cards', params=[("player_id", card["player"]["player_id"]), ('team_id', card['team']['id'])]) + count = 1 if not team_dex['count'] else team_dex['count'] + embed.add_field(name='# Dupes', value=f'{count - 1} dupe{"s" if count - 1 != 1 else ""}') + + # embed.add_field(name='Team', value=f'{card["player"]["mlbclub"]}') + pos_string = ", ".join(get_all_pos(card['player'])) + embed.add_field(name='Positions', value=pos_string) + embed.set_image(url=card["player"]["image"]) + + headshot = card['player']['headshot'] if card['player']['headshot'] else await get_player_headshot(card['player']) + if headshot: + embed.set_thumbnail(url=headshot) + else: + embed.set_thumbnail(url=IMAGES['logo']) + + if not card['player']['image2']: + return [embed] + + card_two = discord.Embed(color=int(card['player']['rarity']['color'], 16)) + card_two.set_footer(text=f'Paper Dynasty Season {card["team"]["season"]}', icon_url=IMAGES['logo']) + card_two.set_image(url=card['player']['image2']) + + return [embed, card_two] + + +def image_embed(image_url: str, title: str = None, color: str = None, desc: str = None, author_name: str = None, + author_icon: str = None): + embed_color = int(SBA_COLOR, 16) + if color is not None: + embed_color = int(color, 16) + + embed = discord.Embed(color=embed_color) + + if title is not None: + embed.title = title + if desc is not None: + embed.description = desc + if author_name is not None: + icon = author_icon if author_icon is not None else IMAGES['logo'] + embed.set_author(name=author_name, icon_url=icon) + embed.set_footer(text=f'Paper Dynasty Season {PD_SEASON}', icon_url=IMAGES['logo']) + embed.set_image(url=image_url) + return embed + + +def is_shiny(card): + if card['player']['rarity']['value'] >= 5: + return True + return False + + +async def display_cards( + cards: list, team: dict, channel, user, bot, pack_cover: str = None, cust_message: str = None, + add_roster: bool = True, pack_name: str = None) -> bool: + cards.sort(key=lambda x: x['player']['rarity']['value']) + card_embeds = [await get_card_embeds(x) for x in cards] + page_num = 0 if pack_cover is None else -1 + seen_shiny = False + + view = Pagination([user], timeout=10) + l_emoji = await get_emoji(channel.guild, 'arrow_left') + r_emoji = await get_emoji(channel.guild, 'arrow_right') + view.left_button.disabled = True + view.left_button.label = f'{l_emoji}Prev: -/{len(card_embeds)}' + view.cancel_button.label = f'Close Pack' + view.right_button.label = f'Next: {page_num + 2}/{len(card_embeds)}{r_emoji}' + if len(cards) == 1: + view.right_button.disabled = True + + if pack_cover: + msg = await channel.send( + content=None, + embed=image_embed(pack_cover, title=f'{team["lname"]}', desc=pack_name), + view=view + ) + else: + msg = await channel.send(content=None, embeds=card_embeds[page_num], view=view) + + if cust_message: + follow_up = await channel.send(cust_message) + else: + follow_up = await channel.send(f'{user.mention} you\'ve got {len(cards)} cards here') + + while True: + await view.wait() + + if view.value: + if view.value == 'cancel': + await msg.edit(view=None) + if add_roster: + await follow_up.edit(content=f'Here is your roster sheet: {get_roster_sheet(team)}') + return True + if view.value == 'left': + page_num -= 1 if page_num > 0 else 0 + if view.value == 'right': + page_num += 1 if page_num <= len(card_embeds) else len(card_embeds) + else: + if page_num == len(card_embeds) - 1: + await msg.edit(view=None) + if add_roster: + await follow_up.edit(content=f'Here is your roster sheet: {get_roster_sheet(team)}') + return True + else: + page_num += 1 + + view.value = None + + if is_shiny(cards[page_num]) and not seen_shiny: + seen_shiny = True + view = Pagination([user], timeout=300) + view.cancel_button.style = discord.ButtonStyle.success + view.cancel_button.label = 'Flip!' + view.left_button.label = '-' + view.right_button.label = '-' + view.left_button.disabled = True + view.right_button.disabled = True + + await msg.edit( + embed=image_embed( + IMAGES['mvp'][cards[page_num]["player"]["franchise"]], + color='56f1fa', + author_name=team['lname'], + author_icon=team['logo'] + ), + view=view) + tmp_msg = await channel.send(content=f'@here we\'ve got an MVP!') + await follow_up.edit(content=f'@here we\'ve got an MVP!') + await tmp_msg.delete() + await view.wait() + + view = Pagination([user], timeout=10) + view.right_button.label = f'Next: {page_num + 2}/{len(card_embeds)}{r_emoji}' + view.cancel_button.label = f'Close Pack' + view.left_button.label = f'{l_emoji}Prev: {page_num}/{len(card_embeds)}' + if page_num == 0: + view.left_button.label = f'{l_emoji}Prev: -/{len(card_embeds)}' + view.left_button.disabled = True + elif page_num == len(card_embeds) - 1: + view.timeout = 600.0 + view.right_button.label = f'Next: -/{len(card_embeds)}{r_emoji}' + view.right_button.disabled = True + + await msg.edit(content=None, embeds=card_embeds[page_num], view=view) + + +async def embed_pagination( + all_embeds: list, channel: commands.Context.channel, user: discord.Member, custom_message: str = None, + timeout: int = 10, start_page: int = 0): + if start_page > len(all_embeds) - 1 or start_page < 0: + page_num = 0 + else: + page_num = start_page + + view = Pagination([user], timeout=timeout) + l_emoji = await get_emoji(channel.guild, 'arrow_left') + r_emoji = await get_emoji(channel.guild, 'arrow_right') + view.right_button.label = f'Next: {page_num + 2}/{len(all_embeds)}{r_emoji}' + view.cancel_button.label = f'Cancel' + view.left_button.label = f'{l_emoji}Prev: {page_num}/{len(all_embeds)}' + if page_num == 0: + view.left_button.label = f'{l_emoji}Prev: -/{len(all_embeds)}' + view.left_button.disabled = True + elif page_num == len(all_embeds) - 1: + view.right_button.label = f'Next: -/{len(all_embeds)}{r_emoji}' + view.right_button.disabled = True + + msg = await channel.send(content=custom_message, embed=all_embeds[page_num], view=view) + + while True: + await view.wait() + + if view.value: + if view.value == 'cancel': + await msg.edit(view=None) + return True + if view.value == 'left': + page_num -= 1 if page_num > 0 else 0 + if view.value == 'right': + page_num += 1 if page_num <= len(all_embeds) else len(all_embeds) + else: + if page_num == len(all_embeds) - 1: + await msg.edit(view=None) + return True + else: + page_num += 1 + + view.value = None + + view = Pagination([user], timeout=timeout) + view.right_button.label = f'Next: {page_num + 2}/{len(all_embeds)}{r_emoji}' + view.cancel_button.label = f'Cancel' + view.left_button.label = f'{l_emoji}Prev: {page_num}/{len(all_embeds)}' + if page_num == 0: + view.left_button.label = f'{l_emoji}Prev: -/{len(all_embeds)}' + view.left_button.disabled = True + elif page_num == len(all_embeds) - 1: + view.timeout = 600.0 + view.right_button.label = f'Next: -/{len(all_embeds)}{r_emoji}' + view.right_button.disabled = True + + await msg.edit(content=None, embed=all_embeds[page_num], view=view) + + +def get_roster_sheet(team, allow_embed: bool = False): + return f'{"" if allow_embed else "<"}' \ + f'https://docs.google.com/spreadsheets/d/{team["gsheet"]}/edit' \ + f'{"" if allow_embed else ">"}' + + +def get_player_url(player, which="sba"): + if which == 'bbref': + return f'https://www.baseball-reference.com/search/search.fcgi?search={player["bbref_id"]}' + else: + stub_name = player["p_name"].replace(" ", "%20") + return f'https://sombaseball.ddns.net/players?name={stub_name}' + + +async def bad_channel(ctx): + bad_channels = ['paper-dynasty-chat', 'pd-news-ticker'] + if ctx.message.channel.name in bad_channels: + await ctx.message.add_reaction('❌') + bot_hole = discord.utils.get( + ctx.guild.text_channels, + name=f'pd-bot-hole' + ) + await ctx.send(f'Slide on down to the {bot_hole.mention} ;)') + return True + else: + return False + + +def get_channel(ctx, name): + channel = discord.utils.get( + ctx.guild.text_channels, + name=name + ) + if channel: + return channel + + return None + + +async def get_test_pack(ctx, team): + pull_notifs = [] + this_pack = db_post('packs/one', payload={ + 'team_id': team['id'], 'pack_type_id': 1, + 'open_time': int(datetime.datetime.timestamp(datetime.datetime.now())*1000) + }) + first_three = db_get('players/random', params=[('max_rarity', 1), ('limit', 3)])['players'] + fourth = db_get('players/random', params=[('min_rarity', 1), ('max_rarity', 3), ('limit', 1)])['players'] + fifth = db_get('players/random', params=[('min_rarity', 5), ('max_rarity', 5), ('limit', 1)])['players'] + all_cards = [*first_three, *fourth, *fifth] + + success = db_post('cards', timeout=10, payload={'cards': [{ + 'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': this_pack['id']} for x in all_cards] + }) + if not success: + await ctx.send(f'I was not able to create these cards {get_emoji(ctx, "slight_frown")}') + return + + for x in all_cards: + if x['rarity']['value'] >= 3: + pull_notifs.append(x) + + for pull in pull_notifs: + db_post('notifs', payload={ + 'created': int(datetime.datetime.timestamp(datetime.datetime.now())*1000), + 'title': 'Rare Pull', + 'field_name': f'{pull["description"]} ({pull["rarity"]["name"]})', + 'message': f'Pulled by {team["abbrev"]}', + 'about': f'Player-{pull["player_id"]}' + }) + + return [{'player': x, 'team': team} for x in all_cards] + + +async def roll_for_cards(all_packs: list, extra_val=None) -> list: + """ + Pack odds are calculated based on the pack type + + Parameters + ---------- + extra_val + all_packs + + Returns + ------- + + """ + all_players = [] + team = all_packs[0]['team'] + pack_ids = [] + for pack in all_packs: + counts = { + 'Rep': { + 'count': 0, + 'rarity': 0 + }, + 'Res': { + 'count': 0, + 'rarity': 1 + }, + 'Sta': { + 'count': 0, + 'rarity': 2 + }, + 'All': { + 'count': 0, + 'rarity': 3 + }, + 'MVP': { + 'count': 0, + 'rarity': 5 + }, + 'HoF': { + 'count': 0, + 'rarity': 8 + }, + } + this_pack_players = [] + if pack['pack_type']['name'] == 'Standard': + # Cards 1 - 2 + for x in range(2): + d_1000 = random.randint(1, 1000) + if d_1000 <= 450: + counts['Rep']['count'] += 1 + elif d_1000 <= 900: + counts['Res']['count'] += 1 + else: + counts['Sta']['count'] += 1 + + # Card 3 + d_1000 = random.randint(1, 1000) + if d_1000 <= 350: + counts['Rep']['count'] += 1 + elif d_1000 <= 700: + counts['Res']['count'] += 1 + elif d_1000 <= 950: + counts['Sta']['count'] += 1 + else: + counts['All']['count'] += 1 + + # Card 4 + d_1000 = random.randint(1, 1000) + if d_1000 <= 310: + counts['Rep']['count'] += 1 + elif d_1000 <= 620: + counts['Res']['count'] += 1 + elif d_1000 <= 940: + counts['Sta']['count'] += 1 + elif d_1000 <= 990: + counts['All']['count'] += 1 + else: + counts['MVP']['count'] += 1 + + # Card 5 + d_1000 = random.randint(1, 1000) + if d_1000 <= 215: + counts['Rep']['count'] += 1 + elif d_1000 <= 430: + counts['Res']['count'] += 1 + elif d_1000 <= 930: + counts['Sta']['count'] += 1 + elif d_1000 <= 980: + counts['All']['count'] += 1 + elif d_1000 <= 990: + counts['MVP']['count'] += 1 + else: + counts['HoF']['count'] += 1 + + elif pack['pack_type']['name'] == 'Premium': + # Card 1 + d_1000 = random.randint(1, 1000) + if d_1000 <= 400: + counts['Rep']['count'] += 1 + elif d_1000 <= 870: + counts['Res']['count'] += 1 + elif d_1000 <= 970: + counts['Sta']['count'] += 1 + elif d_1000 <= 990: + counts['All']['count'] += 1 + else: + counts['MVP']['count'] += 1 + + # Card 2 + d_1000 = random.randint(1, 1000) + if d_1000 <= 300: + counts['Rep']['count'] += 1 + elif d_1000 <= 770: + counts['Res']['count'] += 1 + elif d_1000 <= 970: + counts['Sta']['count'] += 1 + elif d_1000 <= 990: + counts['All']['count'] += 1 + else: + counts['MVP']['count'] += 1 + + # Card 3 + d_1000 = random.randint(1, 1000) + if d_1000 <= 200: + counts['Rep']['count'] += 1 + elif d_1000 <= 640: + counts['Res']['count'] += 1 + elif d_1000 <= 940: + counts['Sta']['count'] += 1 + elif d_1000 <= 990: + counts['All']['count'] += 1 + else: + counts['MVP']['count'] += 1 + + # Card 4 + d_1000 = random.randint(1, 1000) + if d_1000 <= 100: + counts['Rep']['count'] += 1 + if d_1000 <= 530: + counts['Res']['count'] += 1 + elif d_1000 <= 930: + counts['Sta']['count'] += 1 + elif d_1000 <= 980: + counts['All']['count'] += 1 + elif d_1000 <= 990: + counts['MVP']['count'] += 1 + else: + counts['HoF']['count'] += 1 + + # Card 5 + d_1000 = random.randint(1, 1000) + if d_1000 <= 380: + counts['Res']['count'] += 1 + elif d_1000 <= 880: + counts['Sta']['count'] += 1 + elif d_1000 <= 980: + counts['All']['count'] += 1 + elif d_1000 <= 990: + counts['MVP']['count'] += 1 + else: + counts['HoF']['count'] += 1 + + elif pack['pack_type']['name'] == 'Check-In Player': + logging.info(f'Building Check-In Pack // extra_val (type): {extra_val} {type(extra_val)}') + # Single Card + d_1000 = random.randint(1, 1000) + + if not extra_val or not isinstance(extra_val, int): + counts['Rep']['count'] += 1 + elif extra_val < 999: + if d_1000 <= 500: + counts['Rep']['count'] += 1 + else: + counts['Res']['count'] += 1 + + else: + raise TypeError(f'Pack type not recognized: {pack["pack_type"]["name"]}') + + pull_notifs = [] + for key in counts: + mvp_flag = None + + if counts[key]['count'] > 0: + pl = db_get('players/random', params=[ + ('min_rarity', counts[key]['rarity']), ('max_rarity', counts[key]['rarity']), + ('limit', counts[key]['count']) + ]) + + if pl['count'] != counts[key]['count']: + mvp_flag = counts[key]['count'] - pl['count'] + + for x in pl['players']: + this_pack_players.append(x) + all_players.append(x) + + if x['rarity']['value'] >= 3: + pull_notifs.append(x) + + if mvp_flag: + pl = db_get('players/random', params=[('min_rarity', 5), ('limit', mvp_flag)]) + + for x in pl['players']: + this_pack_players.append(x) + all_players.append(x) + + success = db_post( + 'cards', + payload={'cards': [{ + 'player_id': x['player_id'], 'team_id': pack['team']['id'], 'pack_id': pack['id']} for x in this_pack_players] + }, + timeout=10 + ) + if not success: + raise ConnectionError(f'Failed to create this pack of cards.') + + db_patch('packs', object_id=pack['id'], params=[ + ('open_time', int(datetime.datetime.timestamp(datetime.datetime.now())*1000)) + ]) + pack_ids.append(pack['id']) + + for pull in pull_notifs: + logging.info(f'good pull: {pull}') + db_post('notifs', payload={ + 'created': int(datetime.datetime.timestamp(datetime.datetime.now())*1000), + 'title': 'Rare Pull', + 'field_name': f'{pull["description"]} ({pull["rarity"]["name"]})', + 'message': f'Pulled by {team["abbrev"]}', + 'about': f'Player-{pull["player_id"]}' + }) + + return pack_ids + + +def give_packs(team: dict, num_packs: int, pack_type: dict = None) -> dict: + """ + Parameters + ---------- + pack_type + team + num_packs + + Returns + ------- + { 'count': int, 'packs': [ all team packs ] } + """ + pt_id = pack_type['id'] if pack_type is not None else 1 + db_post( + 'packs', + payload={'packs': [{'team_id': team['id'], 'pack_type_id': pt_id} for x in range(num_packs)]} + ) + total_packs = db_get('packs', params=[ + ('team_id', team['id']), ('opened', False) + ]) + + return total_packs + + +def get_sheets(bot): + try: + return bot.get_cog('Players').sheets + except Exception as e: + logging.error(f'Could not grab sheets auth: {e}') + raise ConnectionError(f'Bot has not authenticated with discord; please try again in 1 minute.') + + +def create_team_sheet(team, email: str, current, bot): + sheets = get_sheets(bot) + new_sheet = sheets.drive.copy_file( + f'{current["gsheet_template"]}', + f'{team["lname"]} Roster Sheet v{current["gsheet_version"]}', + '1539D0imTMjlUx2VF3NPMt7Sv85sb2XAJ' + ) + logging.info(f'new_sheet: {new_sheet}') + + this_sheet = sheets.open_by_key(new_sheet['id']) + this_sheet.share(email, role='writer') + team_data = this_sheet.worksheet_by_title('Team Data') + team_data.update_values( + crange='B1:B2', + values=[[f'{team["id"]}'], [f'\'{team_hash(team)}']] + ) + logging.debug(f'this_sheet: {this_sheet}') + return this_sheet + + +async def refresh_sheet(team, bot, sheets=None) -> None: + if not sheets: + sheets = get_sheets(bot) + + this_sheet = sheets.open_by_key(team['gsheet']) + my_cards = this_sheet.worksheet_by_title('My Cards') + all_cards = this_sheet.worksheet_by_title('All Cards') + + my_cards.update_value('A2', 'FALSE') + all_cards.update_value('A2', 'FALSE') + await asyncio.sleep(1) + + my_cards.update_value('A2', 'TRUE') + await asyncio.sleep(0.5) + all_cards.update_value('A2', 'TRUE') + + +def delete_sheet(team, bot): + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(team['gsheet']) + this_sheet.delete() + + +def share_sheet(team, email, bot) -> None: + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(team['gsheet']) + this_sheet.share(email, role='writer') + + +def int_timestamp(datetime_obj: datetime.datetime) -> int: + return int(datetime.datetime.timestamp(datetime_obj) * 1000) + + +def get_pos_abbrev(pos_name): + if pos_name == 'Catcher': + return 'C' + elif pos_name == 'First Base': + return '1B' + elif pos_name == 'Second Base': + return '2B' + elif pos_name == 'Third Base': + return '3B' + elif pos_name == 'Shortstop': + return 'SS' + elif pos_name == 'Left Field': + return 'LF' + elif pos_name == 'Center Field': + return 'CF' + elif pos_name == 'Right Field': + return 'RF' + elif pos_name == 'Pitcher': + return 'P' + elif pos_name == 'Designated Hitter': + return 'DH' + elif pos_name == 'Pinch Hitter': + return 'PH' + else: + raise KeyError(f'{pos_name} is not a recognized position name') + + +def cardset_search(cardset: str, cardset_list: list) -> Optional[dict]: + cardset_name = fuzzy_search(cardset, cardset_list) + if not cardset_name: + return None + + c_query = db_get('cardsets', params=[('name', cardset_name)]) + if c_query['count'] == 0: + return None + return c_query['cardsets'][0] + + +def get_blank_team_card(player): + return {'player': player, 'team': {'lname': 'Paper Dynasty', 'logo': IMAGES['logo'], 'season': PD_SEASON}} + + +def get_rosters(team, bot, roster_num: Optional[int] = None) -> list: + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(team['gsheet']) + r_sheet = this_sheet.worksheet_by_title(f'My Rosters') + logging.debug(f'this_sheet: {this_sheet} / r_sheet = {r_sheet}') + + all_rosters = [None, None, None] + + # Pull roster 1 + if not roster_num or roster_num == 1: + roster_1 = r_sheet.range('B3:B28') + roster_name = r_sheet.cell('F30').value + logging.info(f'roster_1: {roster_1}') + + if not roster_1[0][0].value == '': + all_rosters[0] = {'name': roster_name, 'roster_num': 1, 'team_id': team['id'], 'cards': None} + all_rosters[0]['cards'] = [int(x[0].value) for x in roster_1] + + # Pull roster 2 + if not roster_num or roster_num == 2: + roster_2 = r_sheet.range('B29:B54') + roster_name = r_sheet.cell('F31').value + logging.info(f'roster_2: {roster_2}') + + if not roster_2[0][0].value == '': + all_rosters[1] = {'name': roster_name, 'roster_num': 2, 'team_id': team['id'], 'cards': None} + all_rosters[1]['cards'] = [int(x[0].value) for x in roster_2] + + # Pull roster 3 + if not roster_num or roster_num == 3: + roster_3 = r_sheet.range('B55:B80') + roster_name = r_sheet.cell('F32').value + logging.info(f'roster_3: {roster_3}') + + if not roster_3[0][0].value == '': + all_rosters[2] = {'name': roster_name, 'roster_num': 3, 'team_id': team['id'], 'cards': None} + all_rosters[2]['cards'] = [int(x[0].value) for x in roster_3] + + return all_rosters + + +def get_roster_lineups(team, bot, roster_num, lineup_num) -> list: + sheets = get_sheets(bot) + logging.info(f'sheets: {sheets}') + this_sheet = sheets.open_by_key(team['gsheet']) + logging.info(f'this_sheet: {this_sheet}') + r_sheet = this_sheet.worksheet_by_title('My Rosters') + logging.info(f'r_sheet: {r_sheet}') + + if lineup_num == 1: + row_start = 9 + row_end = 17 + else: + row_start = 18 + row_end = 26 + + if roster_num == 1: + l_range = f'H{row_start}:I{row_end}' + elif roster_num == 2: + l_range = f'J{row_start}:K{row_end}' + else: + l_range = f'L{row_start}:M{row_end}' + + logging.debug(f'l_range: {l_range}') + raw_cells = r_sheet.range(l_range) + logging.debug(f'raw_cells: {raw_cells}') + + try: + lineup_cells = [(row[0].value, int(row[1].value)) for row in raw_cells] + except ValueError as e: + logging.error(f'Could not pull roster for {team["abbrev"]} due to a ValueError') + raise ValueError(f'Uh oh. Looks like your roster might not be saved. I am reading blanks when I try to ' + f'get the card IDs') + logging.debug(f'lineup_cells: {lineup_cells}') + + return lineup_cells + + +def post_ratings_guide(team, bot, this_sheet=None): + if not this_sheet: + sheets = get_sheets(bot) + this_sheet = sheets.open_by_key(team['gsheet']) + p_guide = this_sheet.worksheet_by_title('Full Guide - Pitchers') + b_guide = this_sheet.worksheet_by_title('Full Guide - Batters') + + p_guide.update_value('A1', RATINGS_PITCHER_FORMULA) + b_guide.update_value('A1', RATINGS_BATTER_FORMULA) + + +async def legal_channel(ctx): + bad_channels = ['paper-dynasty-chat', 'pd-news-ticker', 'pd-network-news'] + + if isinstance(ctx, commands.Context): + if ctx.channel.name in bad_channels: + raise commands.CheckFailure(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') + else: + return True + + elif ctx.channel.name in bad_channels: + # await ctx.message.add_reaction('❌') + # await ctx.send(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') + # logging.warning(f'{ctx.author.name} posted in illegal channel.') + # return False + raise discord.app_commands.AppCommandError(f'Slide on down to the {get_channel(ctx, "pd-bot-hole").mention} ;)') + else: + return True + + +def owner_only(interaction: discord.Interaction): + if interaction.user.id == 258104532423147520: + return True + else: + return False + + +def get_role(ctx, role_name): + return discord.utils.get(ctx.guild.roles, name=role_name) + + +def team_summary_embed(team, ctx, include_roster: bool = True): + embed = get_team_embed(f'{team["lname"]} Overview', team) + + embed.add_field(name='General Manager', value=team['gmname'], inline=False) + embed.add_field(name='Wallet', value=f'{team["wallet"]}₼') + # embed.add_field(name='Collection Value', value=team['collection_value']) + + p_query = db_get('packs', params=[('team_id', team['id']), ('opened', False)]) + if p_query['count'] > 0: + all_packs = {} + for x in p_query['packs']: + if x['pack_type']['name'] not in all_packs: + all_packs[x['pack_type']['name']] = 1 + else: + all_packs[x['pack_type']['name']] += 1 + + pack_string = '' + for pack_type in all_packs: + pack_string += f'{pack_type.title()}: {all_packs[pack_type]}\n' + else: + pack_string = 'None' + embed.add_field(name='Unopened Packs', value=pack_string) + embed.add_field(name='Team Rating', value=f'{team["ranking"]}') + + r_query = db_get(f'results/team/{team["id"]}') + if r_query: + embed.add_field( + name='Record', + value=f'Ranked: {r_query["ranked_wins"]}-{r_query["ranked_losses"]}\n' + f'Unlimited: {r_query["casual_wins"]}-{r_query["casual_losses"]}' + ) + + try: + r_query = db_get('rosters', params=[('team_id', team['id'])]) + if r_query['count']: + embed.add_field(name=f'Rosters', value=f'** **', inline=False) + for roster in r_query['rosters']: + roster_string = '' + for i in range(1, 27): + card = roster[f'card_{i}'] + roster_string += f'{card["player"]["description"]} ({card["player"]["pos_1"]})\n' + embed.add_field( + name=f'{roster["name"]} Roster', + value=roster_string if len(roster_string) else "Unknown" + ) + else: + embed.add_field( + name='Rosters', + value='You can set up to three rosters for quick switching from your team sheet.', + inline=False + ) + except Exception as e: + logging.error(f'Could not pull rosters for {team["abbrev"]}') + embed.add_field( + name='Rosters', + value='Unable to pull current rosters. `/pullroster` to sync.', + inline=False + ) + + if include_roster: + embed.add_field(name='Team Sheet', value=get_roster_sheet(team, allow_embed=True), inline=False) + + embed.add_field( + name='For Help', + value=f'`/help-pd` has FAQs; feel free to post questions in ' + f'{get_channel(ctx, "paper-dynasty-chat").mention}.', + inline=False + ) + + return embed + + +def give_cards_to_team(team, players: list = None, player_ids: list = None, pack_id=None): + if not pack_id: + pack_id = db_post( + 'packs/one', + payload={ + 'team_id': team['id'], + 'pack_type_id': 4, + 'open_time': datetime.datetime.timestamp(datetime.datetime.now()) * 1000} + )['id'] + + if not players and not player_ids: + raise ValueError('One of players or player_ids must be provided to distribute cards') + + if players: + db_post('cards', payload={'cards': [ + {'player_id': x['player_id'], 'team_id': team['id'], 'pack_id': pack_id} for x in players + ]}, timeout=10) + elif player_ids: + db_post('cards', payload={'cards': [ + {'player_id': x, 'team_id': team['id'], 'pack_id': pack_id} for x in player_ids + ]}, timeout=10) + + +def get_ratings_guide(sheets): + this_sheet = sheets.open_by_key(RATINGS_SHEET_KEY) + b_sheet = this_sheet.worksheet_by_title('ratings_Batters') + p_sheet = this_sheet.worksheet_by_title('ratings_Pitchers') + + b_data = b_sheet.range('A2:N') + p_data = p_sheet.range('A2:N') + + try: + batters = [ + { + 'player_id': int(x[0].value), + 'p_name': x[1].value, + 'rating': int(x[2].value), + 'contact-r': int(x[3].value), + 'contact-l': int(x[4].value), + 'power-r': int(x[5].value), + 'power-l': int(x[6].value), + 'vision': int(x[7].value), + 'speed': int(x[8].value), + 'stealing': int(x[9].value), + 'reaction': int(x[10].value), + 'arm': int(x[11].value), + 'fielding': int(x[12].value), + 'hand': int(x[13].value), + } for x in b_data + ] + pitchers = [ + { + 'player_id': int(x[0].value), + 'p_name': x[1].value, + 'rating': int(x[2].value), + 'control-r': int(x[3].value), + 'control-l': int(x[4].value), + 'stuff-r': int(x[5].value), + 'stuff-l': int(x[6].value), + 'stamina': int(x[7].value), + 'fielding': int(x[8].value), + 'hit-9': int(x[9].value), + 'k-9': int(x[10].value), + 'bb-9': int(x[11].value), + 'hr-9': int(x[12].value), + 'hand': int(x[13].value), + } for x in p_data + ] + except Exception as e: + return {'valid': False} + + return { + 'valid': True, + 'batter_ratings': batters, + 'pitcher_ratings': pitchers + } + diff --git a/paperdynasty.py b/paperdynasty.py index 6c57504..fbae075 100644 --- a/paperdynasty.py +++ b/paperdynasty.py @@ -1,36 +1,68 @@ +import discord +import datetime +import logging +import asyncio +import os + from discord.ext import commands +raw_log_level = os.getenv('LOG_LEVEL') +if raw_log_level == 'DEBUG': + log_level = logging.DEBUG +elif raw_log_level == 'INFO': + log_level = logging.INFO +elif raw_log_level == 'WARN': + log_level = logging.WARNING +else: + log_level = logging.ERROR + +date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}' +logging.basicConfig( + filename=f'logs/{date}.log', + format='%(asctime)s - %(levelname)s - %(message)s', + level=log_level +) + COGS = [ 'cogs.owner', - 'cogs.helpers', 'cogs.admins', 'cogs.economy', 'cogs.players', + 'cogs.gameplay', ] +intents = discord.Intents.default() +intents.members = True +intents.message_content = True bot = commands.Bot(command_prefix='.', + intents=intents, + # help_command=None, description='The Paper Dynasty Bot\nIf you have questions, feel free to contact Cal.', case_insensitive=True, - owner_id=000) + owner_id=258104532423147520) @bot.event async def on_ready(): - print('Logged in as:') - print(bot.user.name) - print(bot.user.id) - print('------') + logging.info('Logged in as:') + logging.info(bot.user.name) + logging.info(bot.user.id) -for c in COGS: - try: - bot.load_extension(c) - print(f'Loaded cog: {c}') - except Exception as e: - print(f'******\nFailed to load cog: {c}') - print(f'{type(e).__name__} - {e}') - print('******') - print('------') +# @bot.tree.error +# async def on_error(interaction, error): +# await interaction.channel.send(f'{error}') -bot.run("TOKEN") +async def main(): + for c in COGS: + try: + await bot.load_extension(c) + logging.info(f'Loaded cog: {c}') + except Exception as e: + logging.error(f'Failed to load cog: {c}') + logging.error(f'{e}') + async with bot: + await bot.start(os.environ.get('BOT_TOKEN')) + +asyncio.run(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c8143d6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +discord.py @ git+https://github.com/Rapptz/discord.py +peewee +pygsheets +pydantic +gsheets +bs4 \ No newline at end of file