Timespinner: Add Boss Rando Type Options (#4466)
* 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
This commit is contained in:
@@ -53,6 +53,75 @@ class BossRando(Choice):
|
||||
option_unscaled = 2
|
||||
alias_true = 1
|
||||
|
||||
class BossRandoType(Choice):
|
||||
"""
|
||||
Sets what type of boss shuffling occurs.
|
||||
Shuffle: Bosses will be shuffled amongst each other
|
||||
Chaos: Bosses will be randomized with the chance of duplicate bosses
|
||||
Singularity: All bosses will be replaced with a single boss
|
||||
Manual: Bosses will be placed according to the Boss Rando Overrides setting
|
||||
"""
|
||||
display_name = "Boss Randomization Type"
|
||||
option_shuffle = 0
|
||||
option_chaos = 1
|
||||
option_singularity = 2
|
||||
option_manual = 3
|
||||
|
||||
class BossRandoOverrides(OptionDict):
|
||||
"""
|
||||
Manual mapping of bosses to the boss they will be replaced with.
|
||||
Bosses that you don't specify will be the vanilla boss.
|
||||
"""
|
||||
bosses = [
|
||||
"FelineSentry",
|
||||
"Varndagroth",
|
||||
"AzureQueen",
|
||||
"GoldenIdol",
|
||||
"Aelana",
|
||||
"Maw",
|
||||
"Cantoran",
|
||||
"Genza",
|
||||
"Nuvius",
|
||||
"Vol",
|
||||
"Prince",
|
||||
"Xarion",
|
||||
"Ravenlord",
|
||||
"Ifrit",
|
||||
"Sandman",
|
||||
"Nightmare",
|
||||
]
|
||||
|
||||
schema = Schema(
|
||||
{
|
||||
Optional(Or(*bosses)): Or(
|
||||
And(
|
||||
{Optional(boss): And(int, lambda n: n >= 0) for boss in bosses},
|
||||
lambda d: any(v > 0 for v in d.values()),
|
||||
),
|
||||
*bosses
|
||||
)
|
||||
}
|
||||
)
|
||||
display_name = "Boss Rando Overrides"
|
||||
default = {
|
||||
"FelineSentry": "FelineSentry",
|
||||
"Varndagroth": "Varndagroth",
|
||||
"AzureQueen": "AzureQueen",
|
||||
"GoldenIdol": "GoldenIdol",
|
||||
"Aelana": "Aelana",
|
||||
"Maw": "Maw",
|
||||
"Cantoran": "Cantoran",
|
||||
"Genza": "Genza",
|
||||
"Nuvius": "Nuvius",
|
||||
"Vol": "Vol",
|
||||
"Prince": "Prince",
|
||||
"Xarion": "Xarion",
|
||||
"Ravenlord": "Ravenlord",
|
||||
"Ifrit": "Ifrit",
|
||||
"Sandman": "Sandman",
|
||||
"Nightmare": "Nightmare"
|
||||
}
|
||||
|
||||
class EnemyRando(Choice):
|
||||
"Wheter enemies will be randomized, and if their damage/hp should be scaled."
|
||||
display_name = "Enemy Randomization"
|
||||
@@ -420,6 +489,8 @@ class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
|
||||
cantoran: Cantoran
|
||||
lore_checks: LoreChecks
|
||||
boss_rando: BossRando
|
||||
boss_rando_type: BossRandoType
|
||||
boss_rando_overrides: BossRandoOverrides
|
||||
enemy_rando: EnemyRando
|
||||
damage_rando: DamageRando
|
||||
damage_rando_overrides: DamageRandoOverrides
|
||||
|
||||
@@ -21,6 +21,8 @@ class PreCalculatedWeights:
|
||||
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)
|
||||
@@ -51,6 +53,26 @@ class PreCalculatedWeights:
|
||||
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)
|
||||
|
||||
@@ -142,3 +164,32 @@ class PreCalculatedWeights:
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@ from BaseClasses import Item, Tutorial, ItemClassification
|
||||
from .Items import get_item_names_per_category
|
||||
from .Items import item_table, starter_melee_weapons, starter_spells, filler_items, starter_progression_items, pyramid_start_starter_progression_items
|
||||
from .Locations import get_location_datas, EventId
|
||||
from .Options import BackwardsCompatiableTimespinnerOptions, Toggle
|
||||
from .Options import BackwardsCompatiableTimespinnerOptions, Toggle, BossRandoType
|
||||
from .PreCalculatedWeights import PreCalculatedWeights
|
||||
from .Regions import create_regions_and_locations
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
@@ -104,6 +104,8 @@ class TimespinnerWorld(World):
|
||||
"Cantoran": self.options.cantoran.value,
|
||||
"LoreChecks": self.options.lore_checks.value,
|
||||
"BossRando": self.options.boss_rando.value,
|
||||
"BossRandoType": self.options.boss_rando_type.value,
|
||||
"BossRandoOverrides": self.precalculated_weights.boss_rando_overrides,
|
||||
"EnemyRando": self.options.enemy_rando.value,
|
||||
"DamageRando": self.options.damage_rando.value,
|
||||
"DamageRandoOverrides": self.options.damage_rando_overrides.value,
|
||||
@@ -181,6 +183,8 @@ class TimespinnerWorld(World):
|
||||
self.options.cantoran.value = slot_data["Cantoran"]
|
||||
self.options.lore_checks.value = slot_data["LoreChecks"]
|
||||
self.options.boss_rando.value = slot_data["BossRando"]
|
||||
self.options.boss_rando_type.value = slot_data["BossRandoType"]
|
||||
self.precalculated_weights.boss_rando_overrides = slot_data["BossRandoOverrides"]
|
||||
self.options.damage_rando.value = slot_data["DamageRando"]
|
||||
self.options.damage_rando_overrides.value = slot_data["DamageRandoOverrides"]
|
||||
self.options.hp_cap.value = slot_data["HpCap"]
|
||||
@@ -201,6 +205,7 @@ class TimespinnerWorld(World):
|
||||
self.options.rising_tides.value = slot_data["RisingTides"]
|
||||
self.options.unchained_keys.value = slot_data["UnchainedKeys"]
|
||||
self.options.back_to_the_future.value = slot_data["PresentAccessWithWheelAndSpindle"]
|
||||
self.options.prism_break.value = slot_data["PrismBreak"]
|
||||
self.options.traps.value = slot_data["Traps"]
|
||||
self.options.death_link.value = slot_data["DeathLink"]
|
||||
# Readonly slot_data["StinkyMaw"]
|
||||
@@ -237,7 +242,10 @@ class TimespinnerWorld(World):
|
||||
spoiler_handle.write(f'Mysterious Warp Beacon unlock: {self.precalculated_weights.time_key_unlock}\n')
|
||||
else:
|
||||
spoiler_handle.write(f'Twin Pyramid Keys unlock: {self.precalculated_weights.pyramid_keys_unlock}\n')
|
||||
|
||||
|
||||
if self.options.boss_rando.value and self.options.boss_rando_type.value == BossRandoType.option_manual:
|
||||
spoiler_handle.write(f'Selected bosses: {self.precalculated_weights.boss_rando_overrides}\n')
|
||||
|
||||
if self.options.rising_tides:
|
||||
flooded_areas: List[str] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user