sim16/weather.py
2022-09-18 12:02:34 -04:00

725 lines
29 KiB
Python

import random, math, roman
from gametext import appearance_outcomes, game_strings_base, base_string
from discord.app_commands import Choice
class Weather:
name = "Sunny"
emoji = "🌞"
duration_range = [3,5]
out_extension = False
def __init__(self, game):
pass
def __str__(self):
return f"{self.emoji} {self.name}"
def set_duration(self):
pass
def modify_atbat_stats(self, player_rolls):
# Activates before batting
pass
def modify_steal_stats(self, roll):
pass
def modify_atbat_roll(self, outcome, roll, defender):
# activates after batter roll
pass
def activate(self, game, result):
# activates after the batter calculation. modify result, or just return another thing
pass
def steal_activate(self, game, result):
pass
def steal_post_activate(self, game, result):
pass
def post_activate(self, game, result):
pass
def on_choose_next_batter(self, game):
pass
def on_flip_inning(self, game):
pass
def modify_top_of_inning_message(self, game, state):
pass
def modify_atbat_message(self, game, state):
pass
def modify_gamestate(self, game, state):
pass
def modify_game_end_message(self, game, state):
pass
def weather_report(self, game, state):
game.weather = random.choice(list(safe_weathers().values()))(game)
state["update_emoji"] = "🚌"
state["update_text"] += f" Weather report: {game.weather.name} {game.weather.emoji}"
class Supernova(Weather):
name = "Supernova"
emoji = "🌟"
duration_range = [1,2]
def modify_atbat_stats(self, roll):
roll["pitch_stat"] *= 0.8
class Midnight(Weather):
name = "Midnight"
emoji = "🕶"
duration_range = [1,1]
def modify_steal_stats(self, roll):
roll["run_stars"] *= 2
class SlightTailwind(Weather):
name = "Slight Tailwind"
emoji = "🏌️‍♀️"
duration_range = [1,2]
def activate(self, game, result):
if "mulligan" not in game.last_update[0].keys() and not result["ishit"] and result["outcome"] != appearance_outcomes.walk:
mulligan_roll_target = -((((game.get_batter().stlats["batting_stars"])-5)/6)**2)+1
if random.random() > mulligan_roll_target and game.get_batter().stlats["batting_stars"] <= 5:
result.clear()
result.update({
"text": f"{game.get_batter()} would have gone out, but they took a mulligan!",
"mulligan": True,
"text_only": True,
"weather_message": True,
})
class Starlight(Weather):
name = "Starlight"
emoji = "🌃"
duration_range = [2,2]
def activate(self, game, result):
if (result["outcome"] == appearance_outcomes.homerun or result["outcome"] == appearance_outcomes.grandslam):
result["weather_message"] = True
dinger_roll = random.random()
if "dragon" in game.get_batter().name.lower():
result["dragin_the_park"] = True
elif dinger_roll < 0.941:
result.clear()
result.update({
"text": f"{game.get_batter()} hits a dinger, but the stars do not approve! The ball pulls foul.",
"text_only": True,
"weather_message": True
})
else:
result["in_the_park"] = True
def modify_atbat_message(self, game, state):
result = game.last_update[0]
if "in_the_park" in result.keys():
state["update_text"] = f"The stars are pleased with {result['batter']}, and allow a dinger! {game.last_update[1]} runs scored!"
elif "dragin_the_park" in result.keys():
state["update_text"] = f"The stars enjoy watching dragons play baseball, and allow {result['batter']} to hit a dinger! {game.last_update[1]} runs scored!"
class Blizzard(Weather):
name = "Blizzard"
emoji = ""
duration_range = [2,3]
def __init__(self, game):
self.counter_away = random.randint(0,len(game.teams['away'].lineup)-1)
self.counter_home = random.randint(0,len(game.teams['home'].lineup)-1)
self.swapped_batter_data = None
def activate(self, game, result):
if self.swapped_batter_data:
original, sub = self.swapped_batter_data
self.swapped_batter_data = None
result.clear()
result.update({
"snow_atbat": True,
"text": f"{original.name}'s hands are too cold! {sub.name} is forced to bat!",
"text_only": True,
"weather_message": True,
})
def on_flip_inning(self, game):
if game.top_of_inning and self.counter_away < game.teams["away"].lineup_position:
self.counter_away = self.pitcher_insert_index(game.teams["away"])
if not game.top_of_inning and self.counter_home < game.teams["home"].lineup_position:
self.counter_home = self.pitcher_insert_index(game.teams["home"])
def pitcher_insert_index(self, this_team):
rounds = math.ceil(this_team.lineup_position / len(this_team.lineup))
position = random.randint(0, len(this_team.lineup)-1)
return rounds * len(this_team.lineup) + position
def on_choose_next_batter(self, game):
if game.top_of_inning:
bat_team = game.teams["away"]
counter = self.counter_away
else:
bat_team = game.teams["home"]
counter = self.counter_home
if bat_team.lineup_position == counter:
self.swapped_batter_data = (game.current_batter, bat_team.pitcher) # store this to generate the message during activate()
game.current_batter = bat_team.pitcher
class Twilight(Weather):
name = "Twilight"
emoji = "👻"
duration_range = [2,3]
def modify_atbat_roll(self, outcome, roll, defender):
error_line = - (math.log(defender.stlats["defense_stars"] + 1)/50) + 1
error_roll = random.random()
if error_roll > error_line:
outcome["error"] = True
outcome["weather_message"] = True
outcome["defender"] = defender
roll["pb_system_stat"] = 0.1
def modify_atbat_message(self, this_game, state):
result = this_game.last_update[0]
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!"
class ThinnedVeil(Weather):
name = "Thinned Veil"
emoji = "🌌"
duration_range = [1,3]
def activate(self, game, result):
if result["ishit"]:
if result["outcome"] == appearance_outcomes.homerun or result["outcome"] == appearance_outcomes.grandslam:
result["veil"] = True
def modify_atbat_message(self, game, state):
if "veil" in game.last_update[0].keys():
state["update_emoji"] = self.emoji
state["update_text"] += f" {game.last_update[0]['batter']}'s will manifests on {base_string(game.last_update[1])} base."
class HeatWave(Weather):
name = "Heat Wave"
emoji = "🌄"
duration_range = [2,3]
def __init__(self,game):
self.counter_away = random.randint(2,4)
self.counter_home = random.randint(2,4)
self.swapped_pitcher_data = None
def on_flip_inning(self, game):
original_pitcher = game.get_pitcher()
if game.top_of_inning:
bat_team = game.teams["home"]
counter = self.counter_home
else:
bat_team = game.teams["away"]
counter = self.counter_away
if game.inning == counter:
if game.top_of_inning:
self.counter_home = self.counter_home - (self.counter_home % 5) + 5 + random.randint(1,4) #rounds down to last 5, adds up to next 5. then adds a random number 2<=x<=5 to determine next pitcher
else:
self.counter_away = self.counter_away - (self.counter_away % 5) + 5 + random.randint(1,4)
#swap, accounting for teams where where someone's both batter and pitcher
tries = 0
while game.get_pitcher() == original_pitcher and tries < 3:
bat_team.set_pitcher(use_lineup = True)
tries += 1
if game.get_pitcher() != original_pitcher:
self.swapped_pitcher_data = (original_pitcher, game.get_pitcher())
def modify_top_of_inning_message(self, game, state):
if self.swapped_pitcher_data:
original, sub = self.swapped_pitcher_data
self.swapped_pitcher_data = None
state["update_emoji"] = self.emoji
state["update_text"] += f' {original} is exhausted from the heat. {sub} is forced to pitch!'
class Drizzle(Weather):
name = "Drizzle"
emoji = "🌧"
duration_range = [2,3]
def on_flip_inning(self, game):
if game.top_of_inning:
next_team = "away"
else:
next_team = "home"
lineup = game.teams[next_team].lineup
game.bases[2] = lineup[(game.teams[next_team].lineup_position-1) % len(lineup)]
def modify_top_of_inning_message(self, game, state):
if game.top_of_inning:
next_team = "away"
else:
next_team = "home"
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.'
class Breezy(Weather):
name = "Breezy"
emoji = "🎐"
duration_range = [1,3]
def __init__(self, game):
self.activation_chance = 0.08
def activate(self, game, result):
if random.random() < self.activation_chance:
teamtype = random.choice(["away","home"])
team = game.teams[teamtype]
player = random.choice(team.lineup)
player.stlats["batting_stars"] = player.stlats["pitching_stars"]
player.stlats["pitching_stars"] = player.stlats["baserunning_stars"]
old_player_name = player.name
if not hasattr(player, "stat_name"):
player.stat_name = old_player_name
if ' ' in player.name:
names = player.name.split(" ")
try:
first_first_letter = names[0][0]
last_first_letter = names[-1][0]
names[0] = last_first_letter + names[0][1:]
names[-1] = first_first_letter + names[-1][1:]
player.name = ' '.join(names)
except:
first_letter = player.name[0]
last_letter = player.name[-1]
player.name = last_letter + player.name[1:-1] + first_letter
else:
#name is one word, so turn 'bartholemew' into 'martholemeb'
first_letter = player.name[0]
last_letter = player.name[-1]
player.name = last_letter + player.name[1:-1] + first_letter
book_adjectives = ["action-packed", "historical", "mystery", "thriller", "horror", "sci-fi", "fantasy", "spooky","romantic"]
book_types = ["novel", "novella", "poem", "anthology", "fan fiction", "autobiography"]
book = "{} {}".format(random.choice(book_adjectives),random.choice(book_types))
result.clear()
result.update({
"text": "{} stopped to enjoy a {} in the nice breeze! {} is now {}!".format(old_player_name, book, old_player_name, player.name),
"text_only": True,
"weather_message": True
})
class MeteorShower(Weather):
name = "Meteor Shower"
emoji = "🌠"
duration_range = [1,3]
def __init__(self, game):
self.activation_chance = 0.13
def activate(self, game, result):
if random.random() < self.activation_chance and game.occupied_bases() != {}:
base, runner = random.choice(list(game.occupied_bases().items()))
runner = game.bases[base]
game.bases[base] = None
if game.top_of_inning:
bat_team = game.teams["away"]
else:
bat_team = game.teams["home"]
bat_team.score += 1
result.clear()
result.update({
"text": f"{runner.name} wished upon one of the shooting stars, and was warped to None base!! 1 runs scored!",
"text_only": True,
"weather_message": True
})
class Hurricane(Weather):
name = "Hurricane"
emoji = "🌀"
duration_range = [1,1]
def __init__(self, game):
self.swaplength = random.randint(2,4)
self.swapped = False
def on_flip_inning(self, game):
if game.top_of_inning and (game.inning % self.swaplength) == 0:
self.swaplength = random.randint(2,4)
self.swapped = True
def modify_top_of_inning_message(self, game, state):
if self.swapped:
game.teams["home"].score, game.teams["away"].score = (game.teams["away"].score, game.teams["home"].score) #swap scores
state["away_score"], state["home_score"] = (game.teams["away"].score, game.teams["home"].score)
state["update_emoji"] = self.emoji
state["update_text"] += " The hurricane rages on, flipping the scoreboard!"
self.swapped = False
class Tornado(Weather):
name = "Tornado"
emoji = "🌪"
duration_range = [1,2]
def __init__(self, game):
self.activation_chance = 0.33
self.counter = 0
def activate(self, game, result):
if self.counter == 0 and random.random() < self.activation_chance and game.occupied_bases() != {}:
runners = list(game.bases.values())
current_runners = runners.copy()
self.counter = 5
while runners == current_runners and self.counter > 0:
random.shuffle(runners)
self.counter -= 1
for index in range(1,4):
game.bases[index] = runners[index-1]
result.clear()
result.update({
"text": f"The tornado sweeps across the field and pushes {'the runners' if len(game.occupied_bases().values())>1 else list(game.occupied_bases().values())[0].name} to a different base!",
"text_only": True,
"weather_message": True
})
self.counter = 2
elif self.counter > 0:
self.counter -= 1
class Downpour(Weather):
name = "Torrential Downpour"
emoji = ''
duration_range = [1,1]
def __init__(self, game):
self.target = game.max_innings
self.name = f"Torrential Downpour: {roman.roman_convert(str(self.target))}"
self.emoji = ''
def on_flip_inning(self, game):
high_score = game.teams["home"].score if game.teams["home"].score > game.teams["away"].score else game.teams["away"].score
if high_score >= self.target and game.teams["home"].score != game.teams["away"].score:
game.max_innings = game.inning
else:
game.max_innings = game.inning + 1
def modify_gamestate(self, game, state):
state["max_innings"] = ""
def modify_top_of_inning_message(self, game, state):
state["update_emoji"] = self.emoji
if game.teams["away"].score >= self.target: #if the away team has met the target
if game.teams["home"].score == game.teams["away"].score: #if the teams are tied
state["update_text"] = "The gods demand a victor. Play on."
else:
state["update_text"] = f"The gods are pleased, but demand more from {game.teams['home'].name}. Take the field."
else:
state["update_text"] = "The gods are not yet pleased. Play continues through the storm."
def modify_game_end_message(self, game, state):
state["update_emoji"] = self.emoji
state["update_text"] = f"{self.target} runs are reached, pleasing the gods. The storm clears."
class SummerMist(Weather):
name = "Summer Mist"
emoji = "🌁"
duration_range = [1,3]
substances = ["yellow mustard", "cat fur", "dread", "caramel", "nacho cheese", "mud", "dirt", "justice", "a green goo", "water, probably", "antimatter", "something not of this world", "live ferrets", "snow", "leaves",
"yarn", "seaweed", "sawdust", "stardust", "code fragments", "milk", "lizards", "a large tarp", "feathers"]
def __init__(self, game):
self.missing_players = {game.teams["home"].name: None, game.teams["away"].name: None}
self.text = ""
def activate(self, game, result):
if result["outcome"] in [appearance_outcomes.flyout, appearance_outcomes.groundout, appearance_outcomes.sacrifice]:
roll = random.random()
if roll < .4: #get lost
result["mist"] = True
self.text = f" {result['batter'].name} gets lost in the mist on the way back to the dugout."
if self.missing_players[result["offense_team"].name] is not None:
self.text += f" {self.missing_players[result['offense_team'].name].name} wanders back, covered in {random.choice(self.substances)}!"
result["offense_team"].lineup[result["offense_team"].lineup_position % len(result["offense_team"].lineup)] = self.missing_players[result["offense_team"].name]
else:
result["offense_team"].lineup.pop(result["offense_team"].lineup_position % len(result["offense_team"].lineup))
self.missing_players[result["offense_team"].name] = result["batter"]
def modify_atbat_message(self, game, state):
if "mist" in game.last_update[0]:
state["update_emoji"] = self.emoji
state["update_text"] += self.text
self.text = ""
class LeafEddies(Weather):
name = "Leaf Eddies"
emoji = "🍂"
duration_range = [1,2]
leaves = ["orange", "brown", "yellow", "red", "fake", "real", "green", "magenta", "violet", "black", "infrared", "cosmic", "microscopic", "celestial", "spiritual", "ghostly", "transparent"]
eddy_types = [" cloud", " small tornado", "n orb", " sheet", "n eddy", " smattering", " large number", " pair"]
out_counter = 0
sent = False
first = True
def __init__(self, game):
self.name = f"Leaf Eddies: {roman.roman_convert(str(game.max_innings*3))}"
self.original_innings = game.max_innings
game.max_innings = 1
self.inning_text = "The umpires have remembered their jobs. They shoo the defenders off the field!"
def activate(self, game, result):
if game.inning == 1:
if self.out_counter % 3 == 0 and not self.out_counter == 0 and not self.sent:
if self.first:
self.first = False
updatetext = "The leaves have distracted the umpires, and they've been unable to keep track of outs!"
else:
leaf = random.sample(self.leaves, 2)
eddy = random.choice(self.eddy_types)
updatetext = f"A{eddy} of {leaf[0]} and {leaf[1]} leaves blows through, and the umpires remain distracted!"
self.sent = True
result.clear()
result.update({
"text": updatetext,
"text_only": True,
"weather_message": True
})
else:
game.outs = 2
def steal_post_activate(self, game, result):
self.post_activate(game, result)
def post_activate(self, game, result):
if game.inning == 1:
if game.outs > 0:
self.out_counter += game.outs
game.outs = 0
self.sent = False
if self.out_counter < (self.original_innings * 3):
self.name = f"Leaf Eddies: {roman.roman_convert(str(self.original_innings*3-self.out_counter))}"
else:
self.name = "Leaf Eddies"
self.out_counter = 0
game.outs = 3
elif game.teams["home"].score != game.teams["away"].score:
game.outs = 3
if game.top_of_inning:
game.inning += 1
game.top_of_inning = False
def modify_top_of_inning_message(self, game, state):
state["update_emoji"] = self.emoji
if game.inning == 1:
self.name = f"Leaf Eddies: {roman.roman_convert(str(self.original_innings*3-self.out_counter))}"
else:
self.name = "Leaf Eddies: Golden Run"
state["update_emoji"] = ""
self.inning_text = "SUDDEN DEATH ⚠"
state["update_text"] = self.inning_text
state["weather_text"] = self.name
def modify_atbat_message(self, game, state):
if game.inning == 1:
state["weather_text"] = self.name
class Smog(Weather):
name = "Smog"
emoji = "🚌"
duration_range = [1,2]
def __init__(self, game):
game.random_weather_flag = True
setattr(game, "weather", random.choice(list(safe_weathers().values()))(game))
class Dusk(Weather):
name = "Dusk"
emoji = "🌆"
duration_range = [2,3]
def __init__(self, game):
for team in game.teams.values():
random.shuffle(team.lineup)
def activate(self, game, result):
if result["outcome"] in [appearance_outcomes.strikeoutlooking, appearance_outcomes.strikeoutswinging, appearance_outcomes.groundout, appearance_outcomes.flyout, appearance_outcomes.fielderschoice, appearance_outcomes.doubleplay, appearance_outcomes.sacrifice]:
result["offense_team"].lineup_position -= 1
result["weather_message"] = True
if game.outs >= 2 or (game.outs >= 1 and result["outcome"] == appearance_outcomes.doubleplay):
result["displaytext"] += random.choice([" A shade returns to the dugout with them, waiting.",
" They return to the dugout alongside a shade.",
" A shade waits patiently."])
else:
if random.random() < 0.01:
result["displaytext"] += " But it refused."
else:
result["displaytext"] += random.choice([" They leave a shade behind!",
" A shade of the self remains.",
" They leave a shade in the light of the setting sun.",
" They return to the dugout, but their shade remains.",
" They leave a shade at the plate for another plate appearance.",
" Their shade refuses to leave the plate, and shoulders the bat."])
class Runoff(Weather):
name = "Runoff"
emoji = "🏔️"
duration_range = [2,4]
def __init__(self, game):
self.overflow_out = False
self.out_extension = True
def post_activate(self, game, result):
if game.outs >= 4:
self.overflow_out = True
def on_flip_inning(self, game):
if self.overflow_out:
game.outs += 1
def modify_top_of_inning_message(self, game, state):
if self.overflow_out:
state["update_text"] += " The extra out from last inning carries over in the runoff!"
state["update_emoji"] = self.emoji
self.overflow_out = False
def all_weathers():
weathers_dic = {
"Supernova" : Supernova,
"Midnight": Midnight,
"Slight Tailwind": SlightTailwind,
"Blizzard": Blizzard,
"Twilight" : Twilight,
"Thinned Veil" : ThinnedVeil,
"Heat Wave" : HeatWave,
"Drizzle" : Drizzle,
"Breezy": Breezy,
"Starlight" : Starlight,
"Meteor Shower" : MeteorShower,
"Hurricane" : Hurricane,
"Tornado" : Tornado,
"Torrential Downpour" : Downpour,
"Summer Mist" : SummerMist,
"Leaf Eddies" : LeafEddies,
"Smog" : Smog,
"Dusk" : Dusk,
"Runoff" : Runoff
}
return weathers_dic
def weather_choices():
lst = []
for key, value in list(all_weathers().items()):
lst.append(Choice(name=key, value=key))
return lst
def safe_weathers():
"""weathers safe to swap in mid-game"""
weathers_dic = {
"Supernova" : Supernova,
"Midnight": Midnight,
"Slight Tailwind": SlightTailwind,
"Twilight" : Twilight,
"Thinned Veil" : ThinnedVeil,
"Drizzle" : Drizzle,
"Breezy": Breezy,
"Starlight" : Starlight,
"Meteor Shower" : MeteorShower,
"Hurricane" : Hurricane,
"Tornado" : Tornado,
"Summer Mist" : SummerMist,
"Dusk" : Dusk
}
return weathers_dic
class WeatherChains():
light = [SlightTailwind, Twilight, Breezy, Drizzle, SummerMist, LeafEddies, Runoff] #basic starting points for weather, good comfortable spots to return to
magic = [Twilight, ThinnedVeil, MeteorShower, Starlight, Dusk] #weathers involving breaking the fabric of spacetime
sudden = [Tornado, Hurricane, Twilight, Starlight, Midnight, Downpour, Smog] #weathers that always happen and leave over 1-3 games
disaster = [Hurricane, Tornado, Downpour, Blizzard] #storms
aftermath = [Midnight, Starlight, MeteorShower, SummerMist, Dusk, Runoff] #calm epilogues
dictionary = {
#Supernova : (magic + sudden + disaster, None), supernova happens leaguewide and shouldn't need a chain, but here just in case
Midnight : ([SlightTailwind, Breezy, Drizzle, Starlight, MeteorShower, HeatWave, SummerMist],[2,2,2,4,4,1,2]),
SlightTailwind : ([Breezy, Drizzle, LeafEddies, Smog, Tornado], [3,3,3,3,1]),
Blizzard : ([Midnight, Starlight, MeteorShower, Twilight, Downpour, Dusk], [2,2,2,2,4,2]),
Twilight : ([ThinnedVeil, Midnight, MeteorShower, SlightTailwind, SummerMist], [2,4,2,1,2]),
ThinnedVeil : (light, None),
HeatWave : ([Tornado, Hurricane, SlightTailwind, Breezy, SummerMist, Dusk],[4,4,1,1,2,1]),
Drizzle : ([Hurricane, Downpour, Blizzard],[2,2,1]),
Breezy : ([Drizzle, HeatWave, Blizzard, Smog, Tornado], [3,3,1,3,1]),
Starlight : ([SlightTailwind, Twilight, Breezy, Drizzle, ThinnedVeil, HeatWave], None),
MeteorShower : ([Starlight, ThinnedVeil, HeatWave, Smog], None),
Hurricane : ([LeafEddies, Midnight, Starlight, MeteorShower, Twilight, Downpour], [3,2,2,2,2,4]),
Tornado : ([LeafEddies, Midnight, Starlight, MeteorShower, Twilight, Downpour],[3,2,2,2,2,4]),
SummerMist : ([Drizzle, Breezy, Hurricane, Downpour, Dusk],[2, 1, 1, 1,4]),
LeafEddies : ([Breezy, Tornado, SummerMist, ThinnedVeil, Smog], None),
Downpour : (aftermath, None),
Smog : (disaster + [Drizzle], None),
Dusk : ([ThinnedVeil, Midnight, MeteorShower, Starlight], [4,2,2,3]),
Runoff : (magic, None)
}
chains = [
[Hurricane, Drizzle, Hurricane]
]
def chain_weather(weather_instance):
#weather_type = type(weather_instance)
weather_type = weather_instance
options, weight = WeatherChains.dictionary[weather_type]
return random.choices(options, weights = weight)[0]
def parent_weathers(weather_type):
parents = []
for this_weather, (children, _) in WeatherChains.dictionary.items():
if weather_type in children:
parents.append(this_weather)
return parents
def starting_weather():
return random.choice(WeatherChains.light + WeatherChains.magic)
def debug_weathers():
names = ["a.txt", "b.txt", "c.txt"]
for name in names:
current = random.choice(list(all_weathers().values()))
out = ""
for i in range(0,50):
out += f"{current.name} {current.emoji}\n"
current = WeatherChains.chain_weather(current)
with open("data/"+name, "w", encoding='utf-8') as file:
file.write(out)