diff --git a/README.md b/README.md index a36951a..462524a 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ these folks are helping me a *ton* via patreon, and i cannot possibly thank them - Evie Diver - iliana etaoin - yooori +- Bend ## Attribution diff --git a/games.py b/games.py index 6493e94..9739055 100644 --- a/games.py +++ b/games.py @@ -30,7 +30,6 @@ def config(): return json.load(config_file) - class player(object): def __init__(self, json_string): self.stlats = json.loads(json_string) @@ -791,4 +790,3 @@ def search_team(search_term): teams.append(team_json) return teams - diff --git a/league_storage.py b/league_storage.py index 2b36a6f..0dcee16 100644 --- a/league_storage.py +++ b/league_storage.py @@ -34,11 +34,16 @@ def statements(): ROUND(total_bases*1.0 / (plate_appearances - (walks_taken + sacrifices)*1.0),3) as SLG, ROUND((walks_taken + hits)*1.0/plate_appearances*1.0,3) as OBP, ROUND((walks_taken + hits)*1.0/plate_appearances*1.0,3) + ROUND(total_bases*1.0 / (plate_appearances - (walks_taken + sacrifices)*1.0),3) as OPS -FROM stats WHERE plate_appearances > 8""", +FROM stats WHERE plate_appearances > """, + "bat_base_req": 3, "avg" : ["ORDER BY BA DESC;", "bat_base"], "slg" : ["ORDER BY SLG DESC;", "bat_base"], "obp" : ["ORDER BY OBP DESC;", "bat_base"], "ops" : ["ORDER BY OPS DESC;", "bat_base"], + "bat_count_base": "SELECT name, team_name,\n\tplate_appearances - (walks_taken + sacrifices) as ABs,\nwalks_taken as BB,\nhits as H,\nhome_runs as HR,\nrbis as RBIs,\nstrikeouts_taken as K,\nsacrifices\nFROM stats WHERE plate_appearances > 8", + "home runs": ["ORDER BY HR DESC;", "bat_count_base"], + "walks drawn": ["ORDER BY BB DESC;", "bat_count_base"], + "bat_count_base_req" : 3, "pitch_base" : """SELECT name, team_name, ROUND(((outs_pitched*1.0)/3.0),1) as IP, ROUND(runs_allowed*27.0/(outs_pitched*1.0),3) as ERA, @@ -46,8 +51,9 @@ FROM stats WHERE plate_appearances > 8""", ROUND(walks_allowed*27.0/(outs_pitched*1.0),3) as BBper9, ROUND(strikeouts_given*27.0/(outs_pitched*1.0),3) as Kper9, ROUND(strikeouts_given*1.0/walks_allowed*1.0,3) as KperBB -FROM stats WHERE outs_pitched > 20 +FROM stats WHERE outs_pitched > """, + "pitch_base_req": 2, "era" : ["ORDER BY ERA ASC;", "pitch_base"], "whip" : ["ORDER BY WHIP ASC;", "pitch_base"], "kper9" : ["ORDER BY Kper9 DESC;", "pitch_base"], @@ -85,6 +91,9 @@ def state(league_name): return json.load(state_file) def init_league_db(league): + if os.path.exists(os.path.join(data_dir, league_dir, league.name, f"{league.name}.db")): + os.remove(os.path.join(data_dir, league_dir, league.name, f"{league.name}.db")) + conn = create_connection(league.name) player_stats_table_check_string = """ CREATE TABLE IF NOT EXISTS stats ( @@ -169,7 +178,7 @@ def add_stats(league_name, player_game_stats_list): conn.commit() conn.close() -def get_stats(league_name, stat, is_batter=True): +def get_stats(league_name, stat, is_batter=True, day = 10): conn = create_connection(league_name) stats = None if conn is not None: @@ -177,7 +186,8 @@ def get_stats(league_name, stat, is_batter=True): c=conn.cursor() if stat in statements().keys(): - c.execute(statements()[statements()[stat][1]]+"\n"+statements()[stat][0]) + req_number = str(day * int(statements()[statements()[stat][1]+"_req"])) + c.execute(statements()[statements()[stat][1]]+req_number+"\n"+statements()[stat][0]) stats = c.fetchall() conn.close() return stats diff --git a/leagues.py b/leagues.py index 7970c0d..f4dd135 100644 --- a/leagues.py +++ b/leagues.py @@ -89,11 +89,13 @@ class league_structure(object): tournaments.append(tourney) return tournaments - def find_team(self, team_name): + def find_team(self, team_search): for subleague in iter(self.league.keys()): for division in iter(self.league[subleague].keys()): - if team_name in self.league[subleague][division]: - return (subleague, division) + for team in self.league[subleague][division]: + if team.name == team_search.name: + return (subleague, division) + return (None, None) def teams_in_league(self): teams = [] @@ -417,7 +419,7 @@ class league_structure(object): def stat_embed(self, stat_name): this_embed = Embed(color=Color.purple(), title=f"{self.name} Season {self.season} {stat_name} Leaders") - stats = league_db.get_stats(self.name, stat_name.lower()) + stats = league_db.get_stats(self.name, stat_name.lower(), day = self.day) if stats is None: return None else: @@ -548,6 +550,13 @@ def save_league(this_league): json.dump(league_json_string, league_file, indent=4) league_db.save_league(this_league) +def save_league_as_new(this_league): + league_db.init_league_db(this_league) + with open(os.path.join(data_dir, league_dir, this_league.name, f"{this_league.name}.league"), "w") as league_file: + league_json_string = jsonpickle.encode(this_league.league, keys=True) + json.dump(league_json_string, league_file, indent=4) + league_db.save_league(this_league) + def load_league_file(league_name): if league_db.league_exists(league_name): state = league_db.state(league_name) diff --git a/the_prestige.py b/the_prestige.py index cdf95b8..4787f60 100644 --- a/the_prestige.py +++ b/the_prestige.py @@ -146,6 +146,12 @@ class StartGameCommand(Command): await msg.channel.send("Make sure you put an integer after the -d flag.") return + weather_name = None + if "-w " in command.split("\n")[0]: + weather_name = command.split("\n")[0].split("-w ")[1].split("-")[0].strip() + elif "--weather " in command.split("\n")[0]: + weather_name = command.split("\n")[0].split("--weather ")[1].split("-")[0].strip() + innings = None try: team_name1 = command.split("\n")[1].strip() @@ -183,8 +189,11 @@ class StartGameCommand(Command): game.teams['away'].set_pitcher(rotation_slot = day) game.teams['home'].set_pitcher(rotation_slot = day) channel = msg.channel - await msg.delete() + if weather_name is not None and weather_name in weather.all_weathers().keys(): + game.weather = weather.all_weathers()[weather_name](game) + + game_task = asyncio.create_task(watch_game(channel, game, user=msg.author, league=league)) await game_task else: @@ -202,7 +211,6 @@ class StartRandomGameCommand(Command): return channel = msg.channel - await msg.delete() await channel.send("Rolling the bones... This might take a while.") teamslist = games.get_all_teams() @@ -1116,8 +1124,114 @@ class LeagueForceStopCommand(Command): await msg.channel.send("League halted, boss. We hope you did that on purpose.") return await msg.channel.send("That league either doesn't exist or isn't in the active list. So, huzzah?") - - + +class LeagueSwapTeamCommand(Command): + name = "leagueswapteam" + template = "m;leagueswapteam [league name]\n[team to remove]\n[team to add]" + description = "Adds a team to a league, removing the old one in the process. Can only be executed by a league owner, and only before the start of a new season." + + async def execute(self, msg, command): + league_name = command.split("\n")[0].strip() + if league_exists(league_name): + league = leagues.load_league_file(league_name) + if league.day != 1: + await msg.channel.send("That league hasn't finished its current season yet, chief. Either reset it, or be patient.") + return + if (league.owner is not None and msg.author.id in league.owner) or (league.owner is not None and msg.author.id in config()["owners"]): + try: + team_del = get_team_fuzzy_search(command.split("\n")[1].strip()) + team_add = get_team_fuzzy_search(command.split("\n")[2].strip()) + except IndexError: + await msg.channel.send("Three lines, boss. Make sure you give us the team to remove, then the team to add.") + return + if team_add.name == team_del.name: + await msg.channel.send("Quit being cheeky. The teams have to be different.") + return + + if team_del is None or team_add is None: + await msg.channel.send("We couldn't find one or both of those teams, boss. Try again.") + return + subleague, division = league.find_team(team_del) + + if subleague is None or division is None: + await msg.channel.send("That first team isn't in that league, chief. So, that's good, right?") + return + + if league.find_team(team_add)[0] is not None: + await msg.channel.send("That second team is already in that league, chief. No doubles.") + return + + for index in range(0, len(league.league[subleague][division])): + if league.league[subleague][division][index].name == team_del.name: + league.league[subleague][division].pop(index) + league.league[subleague][division].append(team_add) + league.schedule = {} + league.generate_schedule() + leagues.save_league_as_new(league) + await msg.channel.send(embed=league.standings_embed()) + await msg.channel.send("Paperwork signed, stamped, copied, and faxed up to the goddess. Xie's pretty quick with this stuff.") + else: + await msg.channel.send("That league isn't yours, chief.") + else: + await msg.channel.send("We can't find that league.") + +class LeagueRenameCommand(Command): + name = "leaguerename" + template = "m;leaguerename [league name]\n[old subleague/division name]\n[new subleague/division name]" + description = "Changes the name of an existing subleague or division. Can only be executed by a league owner, and only before the start of a new season." + + async def execute(self, msg, command): + league_name = command.split("\n")[0].strip() + if league_exists(league_name): + league = leagues.load_league_file(league_name) + if league.day != 1: + await msg.channel.send("That league hasn't finished its current season yet, chief. Either reset it, or be patient.") + return + if (league.owner is not None and msg.author.id in league.owner) or (league.owner is not None and msg.author.id in config()["owners"]): + try: + old_name = command.split("\n")[1].strip() + new_name = command.split("\n")[2].strip() + except IndexError: + await msg.channel.send("Three lines, boss. Make sure you give us the old name, then the new name, on their own lines.") + return + + if old_name == new_name: + await msg.channel.send("Quit being cheeky. They have to be different names, clearly.") + + + found = False + for subleague in league.league.keys(): + if subleague == new_name: + found = True + break + for division in league.league[subleague]: + if division == new_name: + found = True + break + if found: + await msg.channel.send(f"{new_name} is already present in that league, chief. They have to be different.") + + found = False + for subleague in league.league.keys(): + if subleague == old_name: + league.league[new_name] = league.league.pop(old_name) + found = True + break + for division in league.league[subleague]: + if division == old_name: + league.league[subleague][new_name] = league.league[subleague].pop(old_name) + found = True + break + if not found: + await msg.channel.send(f"We couldn't find {old_name} anywhere in that league, boss.") + return + leagues.save_league_as_new(league) + await msg.channel.send(embed=league.standings_embed()) + await msg.channel.send("Paperwork signed, stamped, copied, and faxed up to the goddess. Xie's pretty quick with this stuff.") + else: + await msg.channel.send("That league isn't yours, chief.") + else: + await msg.channel.send("We can't find that league.") commands = [ @@ -1153,6 +1267,8 @@ commands = [ LeagueScheduleCommand(), LeagueTeamScheduleCommand(), LeagueRegenerateScheduleCommand(), + LeagueSwapTeamCommand(), + LeagueRenameCommand(), LeagueForceStopCommand(), CreditCommand(), RomanCommand(), @@ -1161,6 +1277,7 @@ commands = [ DraftPlayerCommand() ] +watching = False client = discord.Client() gamesarray = [] active_tournaments = [] @@ -1168,6 +1285,7 @@ active_leagues = [] active_standings = {} setupmessages = {} + thread1 = threading.Thread(target=main_controller.update_loop) thread1.start() @@ -1196,10 +1314,13 @@ def config(): @client.event async def on_ready(): + global watching db.initialcheck() print(f"logged in as {client.user} with token {config()['token']} to {len(client.guilds)} servers") - watch_task = asyncio.create_task(game_watcher()) - await watch_task + if not watching: + watching = True + watch_task = asyncio.create_task(game_watcher()) + await watch_task @client.event @@ -1396,7 +1517,7 @@ async def watch_game(channel, newgame, user = None, league = None): main_controller.master_games_dic[id] = (newgame, state_init, discrim_string) def prepare_game(newgame, league = None, weather_name = None): - if weather_name is None: + if weather_name is None and newgame.weather.name == "Sunny": weathers = weather.all_weathers() newgame.weather = weathers[random.choice(list(weathers.keys()))](newgame) @@ -1575,7 +1696,7 @@ async def tourney_round_watcher(channel, tourney, games_list, filter_url, finals if finals: #if this last round was finals embed = discord.Embed(color = discord.Color.dark_purple(), title = f"{winner_list[0]} win the {tourney.name} finals!") - if tourney.day > tourney.league.day: + if tourney.league is not None and tourney.day > tourney.league.day: tourney.league.day = tourney.day await channel.send(embed=embed) tourney.winner = get_team_fuzzy_search(winner_list[0]) @@ -2005,7 +2126,7 @@ async def league_day_watcher(channel, league, games_list, filter_url, last = Fal else: league.active = False - if league.autoplay == 0 or config()["game_freeze"]: #if number of series to autoplay has been reached + if league.autoplay <= 0 or config()["game_freeze"]: #if number of series to autoplay has been reached if league in active_standings.keys(): await active_standings[league].unpin() active_standings[league] = await channel.send(embed=league.standings_embed()) diff --git a/weather.py b/weather.py index 2bc18b9..1abc16d 100644 --- a/weather.py +++ b/weather.py @@ -5,7 +5,7 @@ from gametext import appearance_outcomes, base_string class Weather: def __init__(self, game): self.name = "Sunny" - self.emoji = "🌞" + "\uFE00" + self.emoji = "🌞" def __str__(self): return f"{self.emoji} {self.name}" @@ -38,18 +38,18 @@ class Weather: pass -class Supernova(Weather): # todo +class Supernova(Weather): def __init__(self, game): self.name = "Supernova" - self.emoji = "🌟" + "\uFE00" + self.emoji = "🌟" def modify_atbat_stats(self, roll): roll["pitch_stat"] *= 0.9 -class Midnight(Weather): # todo +class Midnight(Weather): def __init__(self, game): self.name = "Midnight" - self.emoji = "🕶" + "\uFE00" + self.emoji = "🕶" def modify_steal_stats(self, roll): roll["run_stars"] *= 2 @@ -57,7 +57,7 @@ class Midnight(Weather): # todo class SlightTailwind(Weather): def __init__(self, game): self.name = "Slight Tailwind" - self.emoji = "🏌️‍♀️" + "\uFE00" + self.emoji = "🏌️‍♀️" def activate(self, game, result): if game.top_of_inning: @@ -82,7 +82,7 @@ class SlightTailwind(Weather): class HeavySnow(Weather): def __init__(self, game): self.name = "Heavy Snow" - self.emoji = "❄" + "\uFE00" + self.emoji = "❄" self.counter_away = random.randint(0,len(game.teams['away'].lineup)-1) self.counter_home = random.randint(0,len(game.teams['home'].lineup)-1) @@ -127,7 +127,7 @@ class HeavySnow(Weather): class Twilight(Weather): def __init__(self,game): self.name = "Twilight" - self.emoji = "👻" + "\uFE00" + self.emoji = "👻" def modify_atbat_roll(self, outcome, roll, defender): error_line = - (math.log(defender.stlats["defense_stars"] + 1)/50) + 1 @@ -143,12 +143,12 @@ class Twilight(Weather): if "error" in result.keys(): state["update_text"] = f"{result['batter']}'s hit goes ethereal, and {result['defender']} can't catch it! {result['batter']} reaches base safely." if this_game.last_update[1] > 0: - state["update_text"] += f"{this_game.last_update[1]} runs scored!" + state["update_text"] += f" {this_game.last_update[1]} runs scored!" class ThinnedVeil(Weather): def __init__(self,game): self.name = "Thinned Veil" - self.emoji = "🌌" + "\uFE00" + self.emoji = "🌌" def activate(self, game, result): if result["ishit"]: @@ -163,7 +163,7 @@ class ThinnedVeil(Weather): class HeatWave(Weather): def __init__(self,game): self.name = "Heat Wave" - self.emoji = "🌄" + "\uFE00" + self.emoji = "🌄" self.counter_away = random.randint(2,4) self.counter_home = random.randint(2,4) @@ -225,7 +225,7 @@ class Drizzle(Weather): placed_player = game.teams[next_team].lineup[(game.teams[next_team].lineup_position-1) % len(game.teams[next_team].lineup)] state["update_emoji"] = self.emoji - state["update_text"] += f'Due to inclement weather, {placed_player.name} is placed on second base.' + state["update_text"] += f' Due to inclement weather, {placed_player.name} is placed on second base.' class Sun2(Weather): @@ -327,9 +327,9 @@ class Feedback(Weather): def all_weathers(): weathers_dic = { - #"Supernova" : Supernova, - #"Midnight": Midnight, - #"Slight Tailwind": SlightTailwind, + "Supernova" : Supernova, + "Midnight": Midnight, + "Slight Tailwind": SlightTailwind, "Heavy Snow": HeavySnow, "Twilight" : Twilight, "Thinned Veil" : ThinnedVeil,