* adding in boss rando type options for Timespinner * removing new options from the backwards compatible section * adding in boss rando type options for Timespinner * removing new options from the backwards compatible section * re-adding accidentally deleted line * better documenting the different boss rando types * adding missing options to the interpret_slot_data function * making boss override schema more strict and allow for weights * now actually rolling using the weights for boss rando overrides * adding boss rando overrides to the spoiler header * simplifying the schema for the manual boss mappings
196 lines
9.0 KiB
Python
196 lines
9.0 KiB
Python
from typing import Tuple, Dict, Union, List
|
|
from random import Random
|
|
from .Options import TimespinnerOptions
|
|
|
|
class PreCalculatedWeights:
|
|
pyramid_keys_unlock: str
|
|
present_key_unlock: str
|
|
past_key_unlock: str
|
|
time_key_unlock: str
|
|
|
|
flood_basement: bool
|
|
flood_basement_high: bool
|
|
flood_xarion: bool
|
|
flood_maw: bool
|
|
flood_pyramid_shaft: bool
|
|
flood_pyramid_back: bool
|
|
flood_moat: bool
|
|
flood_courtyard: bool
|
|
flood_lake_desolation: bool
|
|
flood_lake_serene: bool
|
|
flood_lake_serene_bridge: bool
|
|
flood_lab: bool
|
|
|
|
boss_rando_overrides: Dict[str, str]
|
|
|
|
def __init__(self, options: TimespinnerOptions, random: Random):
|
|
if options.rising_tides:
|
|
weights_overrrides: Dict[str, Union[str, Dict[str, int]]] = self.get_flood_weights_overrides(options)
|
|
|
|
self.flood_basement, self.flood_basement_high = \
|
|
self.roll_flood_setting(random, weights_overrrides, "CastleBasement")
|
|
self.flood_xarion, _ = self.roll_flood_setting(random, weights_overrrides, "Xarion")
|
|
self.flood_maw, _ = self.roll_flood_setting(random, weights_overrrides, "Maw")
|
|
self.flood_pyramid_shaft, _ = self.roll_flood_setting(random, weights_overrrides, "AncientPyramidShaft")
|
|
self.flood_pyramid_back, _ = self.roll_flood_setting(random, weights_overrrides, "Sandman")
|
|
self.flood_moat, _ = self.roll_flood_setting(random, weights_overrrides, "CastleMoat")
|
|
self.flood_courtyard, _ = self.roll_flood_setting(random, weights_overrrides, "CastleCourtyard")
|
|
self.flood_lake_desolation, _ = self.roll_flood_setting(random, weights_overrrides, "LakeDesolation")
|
|
self.flood_lake_serene, _ = self.roll_flood_setting(random, weights_overrrides, "LakeSerene")
|
|
self.flood_lake_serene_bridge, _ = self.roll_flood_setting(random, weights_overrrides, "LakeSereneBridge")
|
|
self.flood_lab, _ = self.roll_flood_setting(random, weights_overrrides, "Lab")
|
|
else:
|
|
self.flood_basement = False
|
|
self.flood_basement_high = False
|
|
self.flood_xarion = False
|
|
self.flood_maw = False
|
|
self.flood_pyramid_shaft = False
|
|
self.flood_pyramid_back = False
|
|
self.flood_moat = False
|
|
self.flood_courtyard = False
|
|
self.flood_lake_desolation = False
|
|
self.flood_lake_serene = True
|
|
self.flood_lake_serene_bridge = False
|
|
self.flood_lab = False
|
|
|
|
boss_rando_weights_overrides: Dict[str, Union[str, Dict[str, int]]] = self.get_boss_rando_weights_overrides(options)
|
|
self.boss_rando_overrides = {
|
|
"FelineSentry": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "FelineSentry"),
|
|
"Varndagroth": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Varndagroth"),
|
|
"AzureQueen": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "AzureQueen"),
|
|
"GoldenIdol": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "GoldenIdol"),
|
|
"Aelana": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Aelana"),
|
|
"Maw": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Maw"),
|
|
"Cantoran": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Cantoran"),
|
|
"Genza": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Genza"),
|
|
"Nuvius": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Nuvius"),
|
|
"Vol": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Vol"),
|
|
"Prince": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Prince"),
|
|
"Xarion": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Xarion"),
|
|
"Ravenlord": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Ravenlord"),
|
|
"Ifrit": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Ifrit"),
|
|
"Sandman": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Sandman"),
|
|
"Nightmare": self.roll_boss_rando_setting(random, boss_rando_weights_overrides, "Nightmare")
|
|
}
|
|
|
|
self.pyramid_keys_unlock, self.present_key_unlock, self.past_key_unlock, self.time_key_unlock = \
|
|
self.get_pyramid_keys_unlocks(options, random, self.flood_maw, self.flood_xarion, self.flood_lab)
|
|
|
|
@staticmethod
|
|
def get_pyramid_keys_unlocks(options: TimespinnerOptions, random: Random,
|
|
is_maw_flooded: bool, is_xarion_flooded: bool,
|
|
is_lab_flooded: bool) -> Tuple[str, str, str, str]:
|
|
|
|
present_teleportation_gates: List[str] = [
|
|
"GateKittyBoss",
|
|
"GateLeftLibrary",
|
|
"GateMilitaryGate",
|
|
"GateSealedCaves",
|
|
"GateSealedSirensCave",
|
|
"GateLakeDesolation"
|
|
]
|
|
|
|
past_teleportation_gates: List[str] = [
|
|
"GateLakeSereneRight",
|
|
"GateAccessToPast",
|
|
"GateCastleRamparts",
|
|
"GateCastleKeep",
|
|
"GateRoyalTowers",
|
|
"GateCavesOfBanishment"
|
|
]
|
|
|
|
ancient_pyramid_teleportation_gates: Tuple[str, ...] = (
|
|
"GateGyre",
|
|
"GateLeftPyramid",
|
|
"GateRightPyramid"
|
|
)
|
|
|
|
if not is_maw_flooded:
|
|
past_teleportation_gates.append("GateMaw")
|
|
|
|
if options.risky_warps:
|
|
past_teleportation_gates.append("GateLakeSereneLeft")
|
|
if not is_xarion_flooded:
|
|
present_teleportation_gates.append("GateXarion")
|
|
# Prevent going past the lazers without a way to the past
|
|
if options.unchained_keys or options.prism_break or not options.pyramid_start:
|
|
present_teleportation_gates.append("GateDadsTower")
|
|
if not is_lab_flooded:
|
|
present_teleportation_gates.append("GateLabEntrance")
|
|
|
|
# Prevent getting stuck in the past without a way back to the future
|
|
if options.inverted or (options.pyramid_start and not options.back_to_the_future):
|
|
all_gates: Tuple[str, ...] = present_teleportation_gates
|
|
else:
|
|
all_gates: Tuple[str, ...] = past_teleportation_gates + present_teleportation_gates
|
|
|
|
return (
|
|
random.choice(all_gates),
|
|
random.choice(present_teleportation_gates),
|
|
random.choice(past_teleportation_gates),
|
|
random.choice(ancient_pyramid_teleportation_gates)
|
|
)
|
|
|
|
@staticmethod
|
|
def get_flood_weights_overrides(options: TimespinnerOptions) -> Dict[str, Union[str, Dict[str, int]]]:
|
|
weights_overrides_option: Union[int, Dict[str, Union[str, Dict[str, int]]]] = \
|
|
options.rising_tides_overrides.value
|
|
|
|
default_weights: Dict[str, Dict[str, int]] = options.rising_tides_overrides.default
|
|
|
|
if not weights_overrides_option:
|
|
weights_overrides_option = default_weights
|
|
else:
|
|
for key, weights in default_weights.items():
|
|
if not key in weights_overrides_option:
|
|
weights_overrides_option[key] = weights
|
|
|
|
return weights_overrides_option
|
|
|
|
@staticmethod
|
|
def roll_flood_setting(random: Random, all_weights: Dict[str, Union[Dict[str, int], str]],
|
|
key: str) -> Tuple[bool, bool]:
|
|
|
|
weights: Union[Dict[str, int], str] = all_weights[key]
|
|
|
|
if isinstance(weights, dict):
|
|
result: str = random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
|
|
else:
|
|
result: str = weights
|
|
|
|
if result == "Dry":
|
|
return False, False
|
|
elif result == "Flooded":
|
|
return True, True
|
|
elif result == "FloodedWithSavePointAvailable":
|
|
return True, False
|
|
|
|
@staticmethod
|
|
def get_boss_rando_weights_overrides(options: TimespinnerOptions) -> Dict[str, Union[str, Dict[str, int]]]:
|
|
weights_overrides_option: Union[int, Dict[str, Union[str, Dict[str, int]]]] = \
|
|
options.boss_rando_overrides.value
|
|
|
|
default_weights: Dict[str, Dict[str, int]] = options.boss_rando_overrides.default
|
|
|
|
if not weights_overrides_option:
|
|
weights_overrides_option = default_weights
|
|
else:
|
|
for key, weights in default_weights.items():
|
|
if not key in weights_overrides_option:
|
|
weights_overrides_option[key] = weights
|
|
|
|
return weights_overrides_option
|
|
|
|
@staticmethod
|
|
def roll_boss_rando_setting(random: Random, all_weights: Dict[str, Union[Dict[str, int], str]],
|
|
key: str) -> str:
|
|
|
|
weights: Union[Dict[str, int], str] = all_weights[key]
|
|
|
|
if isinstance(weights, dict):
|
|
result: str = random.choices(list(weights.keys()), weights=list(map(int, weights.values())))[0]
|
|
else:
|
|
result: str = weights
|
|
|
|
return result
|