* Timespinner: Add "no hell spiders" enemy rando option that is present in upstream settings * Timespinner: Prism Break support tweaks (including tracker support) * Timespinner: Add support for upstream Lock Key Amadeus flag * Timespinner: Add support for upstream Risky Warps flag * Timespinner: Add support for upstream Pyramid Start flag * Timespinner: fix error in lab connectivity logic * Timespinner: use has_all to simplify one check Per PR suggestion. Co-authored-by: Scipio Wright <scipiowright@gmail.com> * Timespinner: fix apparent logic error inherited from in-rando logic * Timespinner: adjust "Origins" location logic slightly further to account for a Risky Warps case * Timespinner: remove the backward compat options for the recent flag additions * Timespinner: add newly added Gate Keep option from rando * Timespinner: adjust the laser access colours in the tracker * Timespinner: fix an item description in the tracker * Timespinner: based on testing feedback, put Laser Access items in their own category * Timespinner: add support for new upstream flag Royal Roadblock * Timespinner: also ensure the new flag gets put in slot data * Timespinner: fix bug in universal tracker support indicating castle basement is accessible at the lower Rising Tides flooding level * Timespinner: exclude Talaria Attachment and Timespinner Wheel from pyramid start starter progression items * Timespinner: fix region logic for the left pyramid warp * Timespinner: fix main Gyre access logic when Risky Warps warps you behind the lasers * Timespinner: apply suggested spacing fix Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com> --------- Co-authored-by: sgrunt <sgrunt1987@gmail.com> Co-authored-by: Scipio Wright <scipiowright@gmail.com>
		
			
				
	
	
		
			661 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			661 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from dataclasses import dataclass
 | 
						|
from typing import Type, Any
 | 
						|
from typing import Dict
 | 
						|
from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, OptionDict, OptionList, Visibility, Option
 | 
						|
from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions
 | 
						|
from schema import Schema, And, Optional, Or
 | 
						|
 | 
						|
class StartWithJewelryBox(Toggle):
 | 
						|
    "Start with Jewelry Box unlocked"
 | 
						|
    display_name = "Start with Jewelry Box"
 | 
						|
 | 
						|
class DownloadableItems(DefaultOnToggle):
 | 
						|
    "With the tablet you will be able to download items at terminals"
 | 
						|
    display_name = "Downloadable items"
 | 
						|
 | 
						|
class EyeSpy(Toggle):
 | 
						|
    "Requires Oculus Ring in inventory to be able to break hidden walls."
 | 
						|
    display_name = "Eye Spy"
 | 
						|
 | 
						|
class StartWithMeyef(Toggle):
 | 
						|
    "Start with Meyef, ideal for when you want to play multiplayer."
 | 
						|
    display_name = "Start with Meyef"
 | 
						|
 | 
						|
class QuickSeed(Toggle):
 | 
						|
    "Start with Talaria Attachment, Nyoom!"
 | 
						|
    display_name = "Quick seed"
 | 
						|
 | 
						|
class SpecificKeycards(Toggle):
 | 
						|
    "Keycards can only open corresponding doors"
 | 
						|
    display_name = "Specific Keycards"
 | 
						|
 | 
						|
class Inverted(Toggle):
 | 
						|
    "Start in the past"
 | 
						|
    display_name = "Inverted"
 | 
						|
 | 
						|
class GyreArchives(Toggle):
 | 
						|
    "Gyre locations are in logic. New warps are gated by Merchant Crow and Kobo"
 | 
						|
    display_name = "Gyre Archives"
 | 
						|
 | 
						|
class Cantoran(Toggle):
 | 
						|
    "Cantoran's fight and check are available upon revisiting his room"
 | 
						|
    display_name = "Cantoran"
 | 
						|
 | 
						|
class LoreChecks(Toggle):
 | 
						|
    "Memories and journal entries contain items."
 | 
						|
    display_name = "Lore Checks"
 | 
						|
 | 
						|
class BossRando(Choice):
 | 
						|
    "Wheter all boss locations are shuffled, and if their damage/hp should be scaled."
 | 
						|
    display_name = "Boss Randomization"
 | 
						|
    option_off = 0
 | 
						|
    option_scaled = 1
 | 
						|
    option_unscaled = 2
 | 
						|
    alias_true = 1
 | 
						|
 | 
						|
class EnemyRando(Choice):
 | 
						|
    "Wheter enemies will be randomized, and if their damage/hp should be scaled."
 | 
						|
    display_name = "Enemy Randomization"
 | 
						|
    option_off = 0
 | 
						|
    option_scaled = 1
 | 
						|
    option_unscaled = 2
 | 
						|
    option_ryshia = 3
 | 
						|
    option_no_hell_spiders = 4
 | 
						|
    alias_true = 1
 | 
						|
 | 
						|
class DamageRando(Choice):
 | 
						|
    "Randomly nerfs and buffs some orbs and their associated spells as well as some associated rings."
 | 
						|
    display_name = "Damage Rando"
 | 
						|
    option_off = 0
 | 
						|
    option_allnerfs = 1
 | 
						|
    option_mostlynerfs = 2
 | 
						|
    option_balanced = 3
 | 
						|
    option_mostlybuffs = 4
 | 
						|
    option_allbuffs = 5
 | 
						|
    option_manual = 6
 | 
						|
    alias_true = 2
 | 
						|
 | 
						|
class DamageRandoOverrides(OptionDict):
 | 
						|
    """Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that
 | 
						|
    you don't specify will roll with 1/1/1 as odds"""
 | 
						|
    schema = Schema({
 | 
						|
        Optional("Blue"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Blade"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Fire"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Plasma"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Iron"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Ice"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Wind"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Gun"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Umbra"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Empire"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Eye"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Blood"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("ForbiddenTome"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Shattered"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Nether"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
        Optional("Radiant"): { 
 | 
						|
            "MinusOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "NormalOdds": And(int, lambda n: n >= 0), 
 | 
						|
            "PlusOdds": And(int, lambda n: n >= 0) 
 | 
						|
        },
 | 
						|
    })
 | 
						|
    display_name = "Damage Rando Overrides"
 | 
						|
    default = {
 | 
						|
        "Blue": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Blade": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Fire": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Plasma": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Iron": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Ice": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Wind": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Gun": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Umbra": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Empire": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Eye": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Blood": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "ForbiddenTome": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Shattered": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Nether": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
        "Radiant": { "MinusOdds": 1, "NormalOdds": 1, "PlusOdds": 1 },
 | 
						|
    }
 | 
						|
 | 
						|
class HpCap(Range):
 | 
						|
    """Sets the number that Lunais's HP maxes out at."""
 | 
						|
    display_name = "HP Cap"
 | 
						|
    range_start = 1
 | 
						|
    range_end = 999
 | 
						|
    default = 999
 | 
						|
 | 
						|
class AuraCap(Range):
 | 
						|
    """Sets the maximum Aura Lunais is allowed to have. Level 1 is 80. Djinn Inferno costs 45."""
 | 
						|
    display_name = "Aura Cap"
 | 
						|
    range_start = 45
 | 
						|
    range_end = 999
 | 
						|
    default = 999
 | 
						|
 | 
						|
class LevelCap(Range):
 | 
						|
    """Sets the max level Lunais can achieve."""
 | 
						|
    display_name = "Level Cap"
 | 
						|
    range_start = 1
 | 
						|
    range_end = 99
 | 
						|
    default = 99
 | 
						|
 | 
						|
class ExtraEarringsXP(Range):
 | 
						|
    """Adds additional XP granted by Galaxy Earrings."""
 | 
						|
    display_name = "Extra Earrings XP"
 | 
						|
    range_start = 0
 | 
						|
    range_end = 24
 | 
						|
    default = 0
 | 
						|
 | 
						|
class BossHealing(DefaultOnToggle):
 | 
						|
    "Enables/disables healing after boss fights. NOTE: Currently only applicable when Boss Rando is enabled."
 | 
						|
    display_name = "Heal After Bosses"
 | 
						|
 | 
						|
class ShopFill(Choice):
 | 
						|
    """Sets the items for sale in Merchant Crow's shops.
 | 
						|
    Default: No sunglasses or trendy jacket, but sand vials for sale.
 | 
						|
    Randomized: Up to 4 random items in each shop.
 | 
						|
    Vanilla: Keep shops the same as the base game.
 | 
						|
    Empty: Sell no items at the shop."""
 | 
						|
    display_name = "Shop Inventory"
 | 
						|
    option_default = 0
 | 
						|
    option_randomized = 1
 | 
						|
    option_vanilla = 2
 | 
						|
    option_empty = 3
 | 
						|
 | 
						|
class ShopWarpShards(DefaultOnToggle):
 | 
						|
    "Shops always sell warp shards (when keys possessed), ignoring inventory setting."
 | 
						|
    display_name = "Always Sell Warp Shards"
 | 
						|
 | 
						|
class ShopMultiplier(Range):
 | 
						|
    "Multiplier for the cost of items in the shop. Set to 0 for free shops."
 | 
						|
    display_name = "Shop Price Multiplier"
 | 
						|
    range_start = 0
 | 
						|
    range_end = 10
 | 
						|
    default = 1
 | 
						|
 | 
						|
class LootPool(Choice):
 | 
						|
    """Sets the items that drop from enemies (does not apply to boss reward checks)
 | 
						|
    Vanilla: Drops are the same as the base game
 | 
						|
    Randomized: Each slot of every enemy's drop table is given a random use item or piece of equipment.
 | 
						|
    Empty: Enemies drop nothing."""
 | 
						|
    display_name = "Loot Pool"
 | 
						|
    option_vanilla = 0
 | 
						|
    option_randomized = 1
 | 
						|
    option_empty = 2
 | 
						|
 | 
						|
class DropRateCategory(Choice):
 | 
						|
    """Sets the drop rate when 'Loot Pool' is set to 'Random'
 | 
						|
    Tiered: Based on item rarity/value
 | 
						|
    Vanilla: Based on bestiary slot the item is placed into
 | 
						|
    Random: Assigned a random tier/drop rate
 | 
						|
    Fixed: Set by the 'Fixed Drop Rate' setting
 | 
						|
    """
 | 
						|
    display_name = "Drop Rate Category"
 | 
						|
    option_tiered = 0
 | 
						|
    option_vanilla = 1
 | 
						|
    option_randomized = 2
 | 
						|
    option_fixed = 3
 | 
						|
 | 
						|
class FixedDropRate(Range):
 | 
						|
    "Base drop rate percentage when 'Drop Rate Category' is set to 'Fixed'"
 | 
						|
    display_name = "Fixed Drop Rate"
 | 
						|
    range_start = 0
 | 
						|
    range_end = 100
 | 
						|
    default = 5
 | 
						|
 | 
						|
class LootTierDistro(Choice):
 | 
						|
    """Sets how often items of each rarity tier are placed when 'Loot Pool' is set to 'Random'
 | 
						|
    Default Weight: Rarer items will be assigned to enemy drop slots less frequently than common items
 | 
						|
    Full Random: Any item has an equal chance of being placed in an enemy's drop slot
 | 
						|
    Inverted Weight: Rarest items show up the most frequently, while common items are the rarest
 | 
						|
    """
 | 
						|
    display_name = "Loot Tier Distrubution"
 | 
						|
    option_default_weight = 0
 | 
						|
    option_full_random = 1
 | 
						|
    option_inverted_weight = 2
 | 
						|
 | 
						|
class ShowBestiary(Toggle):
 | 
						|
    "All entries in the bestiary are visible, without needing to kill one of a given enemy first"
 | 
						|
    display_name = "Show Bestiary Entries"
 | 
						|
 | 
						|
class ShowDrops(Toggle):
 | 
						|
    "All item drops in the bestiary are visible, without needing an enemy to drop one of a given item first"
 | 
						|
    display_name = "Show Bestiary Item Drops"
 | 
						|
 | 
						|
class EnterSandman(Toggle):
 | 
						|
    "The Ancient Pyramid is unlocked by the Twin Pyramid Keys, but the final boss door opens if you have all 5 Timespinner pieces"
 | 
						|
    display_name = "Enter Sandman"
 | 
						|
 | 
						|
class DadPercent(Toggle):
 | 
						|
    """The win condition is beating the boss of Emperor's Tower"""
 | 
						|
    display_name = "Dad Percent"
 | 
						|
 | 
						|
class RisingTides(Toggle):
 | 
						|
    """Random areas are flooded or drained, can be further specified with RisingTidesOverrides"""
 | 
						|
    display_name = "Rising Tides"
 | 
						|
 | 
						|
def rising_tide_option(location: str, with_save_point_option: bool = False) -> Dict[Optional, Or]:
 | 
						|
    if with_save_point_option:
 | 
						|
        return {
 | 
						|
            Optional(location): Or(
 | 
						|
                And({
 | 
						|
                    Optional("Dry"): And(int, lambda n: n >= 0),
 | 
						|
                    Optional("Flooded"): And(int, lambda n: n >= 0),
 | 
						|
                    Optional("FloodedWithSavePointAvailable"): And(int, lambda n: n >= 0)
 | 
						|
                }, lambda d: any(v > 0 for v in d.values())),
 | 
						|
                "Dry",
 | 
						|
                "Flooded",
 | 
						|
                "FloodedWithSavePointAvailable")
 | 
						|
        }
 | 
						|
    else:
 | 
						|
        return {
 | 
						|
            Optional(location): Or(
 | 
						|
                And({
 | 
						|
                    Optional("Dry"): And(int, lambda n: n >= 0),
 | 
						|
                    Optional("Flooded"): And(int, lambda n: n >= 0)
 | 
						|
                }, lambda d: any(v > 0 for v in d.values())),
 | 
						|
                "Dry",
 | 
						|
                "Flooded")
 | 
						|
        }
 | 
						|
 | 
						|
class RisingTidesOverrides(OptionDict):
 | 
						|
    """Odds for specific areas to be flooded or drained, only has effect when RisingTides is on.
 | 
						|
    Areas that are not specified will roll with the default 33% chance of getting flooded or drained"""
 | 
						|
    display_name = "Rising Tides Overrides"
 | 
						|
    schema = Schema({
 | 
						|
        **rising_tide_option("Xarion"),
 | 
						|
        **rising_tide_option("Maw"),
 | 
						|
        **rising_tide_option("AncientPyramidShaft"),
 | 
						|
        **rising_tide_option("Sandman"),
 | 
						|
        **rising_tide_option("CastleMoat"),
 | 
						|
        **rising_tide_option("CastleBasement", with_save_point_option=True),
 | 
						|
        **rising_tide_option("CastleCourtyard"),
 | 
						|
        **rising_tide_option("LakeDesolation"),
 | 
						|
        **rising_tide_option("LakeSerene"),
 | 
						|
        **rising_tide_option("LakeSereneBridge"),
 | 
						|
        **rising_tide_option("Lab"),
 | 
						|
    })
 | 
						|
    default = {
 | 
						|
        "Xarion": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "Maw": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "AncientPyramidShaft": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "Sandman": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "CastleMoat": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "CastleBasement": { "Dry": 66, "Flooded": 17, "FloodedWithSavePointAvailable": 17 },
 | 
						|
        "CastleCourtyard": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "LakeDesolation": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "LakeSerene": { "Dry": 33, "Flooded": 67 },
 | 
						|
        "LakeSereneBridge": { "Dry": 67, "Flooded": 33 },
 | 
						|
        "Lab": { "Dry": 67, "Flooded": 33 },
 | 
						|
    }
 | 
						|
 | 
						|
class UnchainedKeys(Toggle):
 | 
						|
    """Start with Twin Pyramid Key, which does not give free warp;
 | 
						|
    warp items for Past, Present, (and ??? with Enter Sandman) can be found."""
 | 
						|
    display_name = "Unchained Keys"
 | 
						|
 | 
						|
class TrapChance(Range):
 | 
						|
    """Chance of traps in the item pool.
 | 
						|
    Traps will only replace filler items such as potions, vials and antidotes"""
 | 
						|
    display_name = "Trap Chance"
 | 
						|
    range_start = 0
 | 
						|
    range_end = 100
 | 
						|
    default = 10
 | 
						|
 | 
						|
class Traps(OptionList):
 | 
						|
    """List of traps that may be in the item pool to find"""
 | 
						|
    display_name = "Traps Types"
 | 
						|
    valid_keys = { "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap" }
 | 
						|
    default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap", "Throw Stun Trap" ]
 | 
						|
 | 
						|
class PresentAccessWithWheelAndSpindle(Toggle):
 | 
						|
    """When inverted, allows using the refugee camp warp when both the Timespinner Wheel and Spindle is acquired."""
 | 
						|
    display_name = "Back to the future"
 | 
						|
 | 
						|
class PrismBreak(Toggle):
 | 
						|
    """Adds 3 Laser Access items to the item pool to remove the lasers blocking the military hangar area
 | 
						|
    instead of needing to beat the Golden Idol, Aelana, and The Maw."""
 | 
						|
    display_name = "Prism Break"
 | 
						|
    
 | 
						|
class LockKeyAmadeus(Toggle):
 | 
						|
    """Lasers in Amadeus' Laboratory are disabled via items, rather than by de-powering the lab. Experiments will spawn in the lab."""
 | 
						|
    display_name = "Lock Key Amadeus"
 | 
						|
    
 | 
						|
class RiskyWarps(Toggle):
 | 
						|
    """Expanded free-warp eligible locations, including Azure Queen, Xarion, Amadeus' Laboratory, and Emperor's Tower."""
 | 
						|
    display_name = "Risky Warps"
 | 
						|
 | 
						|
class PyramidStart(Toggle):
 | 
						|
    """Start in ???. Takes priority over Inverted. Additional chests in Dark Forest and Pyramid. Sandman door behaves as it does in Enter Sandman."""
 | 
						|
    display_name = "Pyramid Start"
 | 
						|
 | 
						|
class GateKeep(Toggle):
 | 
						|
    """The castle drawbridge starts raised, and can be lowered via item."""
 | 
						|
    display_name = "Gate Keep"
 | 
						|
 | 
						|
class RoyalRoadblock(Toggle):
 | 
						|
    """The Royal Towers entrance door requires a royal orb (Plasma Orb, Plasma Geyser, or Royal Ring) to enter."""
 | 
						|
    display_name = "Royal Roadblock"
 | 
						|
 | 
						|
@dataclass
 | 
						|
class TimespinnerOptions(PerGameCommonOptions, DeathLinkMixin):
 | 
						|
    start_with_jewelry_box: StartWithJewelryBox
 | 
						|
    downloadable_items: DownloadableItems
 | 
						|
    eye_spy: EyeSpy
 | 
						|
    start_with_meyef: StartWithMeyef
 | 
						|
    quick_seed: QuickSeed
 | 
						|
    specific_keycards: SpecificKeycards
 | 
						|
    inverted: Inverted
 | 
						|
    gyre_archives: GyreArchives
 | 
						|
    cantoran: Cantoran
 | 
						|
    lore_checks: LoreChecks
 | 
						|
    boss_rando: BossRando
 | 
						|
    enemy_rando: EnemyRando
 | 
						|
    damage_rando: DamageRando
 | 
						|
    damage_rando_overrides: DamageRandoOverrides
 | 
						|
    hp_cap: HpCap
 | 
						|
    aura_cap: AuraCap
 | 
						|
    level_cap: LevelCap
 | 
						|
    extra_earrings_xp: ExtraEarringsXP
 | 
						|
    boss_healing: BossHealing
 | 
						|
    shop_fill: ShopFill
 | 
						|
    shop_warp_shards: ShopWarpShards
 | 
						|
    shop_multiplier: ShopMultiplier
 | 
						|
    loot_pool: LootPool
 | 
						|
    drop_rate_category: DropRateCategory
 | 
						|
    fixed_drop_rate: FixedDropRate
 | 
						|
    loot_tier_distro: LootTierDistro
 | 
						|
    show_bestiary: ShowBestiary
 | 
						|
    show_drops: ShowDrops
 | 
						|
    enter_sandman: EnterSandman
 | 
						|
    dad_percent: DadPercent
 | 
						|
    rising_tides: RisingTides
 | 
						|
    rising_tides_overrides: RisingTidesOverrides
 | 
						|
    unchained_keys: UnchainedKeys
 | 
						|
    back_to_the_future: PresentAccessWithWheelAndSpindle
 | 
						|
    prism_break: PrismBreak
 | 
						|
    lock_key_amadeus: LockKeyAmadeus
 | 
						|
    risky_warps: RiskyWarps
 | 
						|
    pyramid_start: PyramidStart
 | 
						|
    gate_keep: GateKeep
 | 
						|
    royal_roadblock: RoyalRoadblock
 | 
						|
    trap_chance: TrapChance
 | 
						|
    traps: Traps
 | 
						|
 | 
						|
class HiddenDamageRandoOverrides(DamageRandoOverrides): 
 | 
						|
    """Manual +/-/normal odds for an orb. Put 0 if you don't want a certain nerf or buff to be a possibility. Orbs that
 | 
						|
    you don't specify will roll with 1/1/1 as odds"""
 | 
						|
    visibility = Visibility.none
 | 
						|
 | 
						|
class HiddenRisingTidesOverrides(RisingTidesOverrides):
 | 
						|
    """Odds for specific areas to be flooded or drained, only has effect when RisingTides is on.
 | 
						|
    Areas that are not specified will roll with the default 33% chance of getting flooded or drained"""
 | 
						|
    visibility = Visibility.none
 | 
						|
 | 
						|
class HiddenTraps(Traps):
 | 
						|
    """List of traps that may be in the item pool to find"""
 | 
						|
    visibility = Visibility.none
 | 
						|
 | 
						|
class HiddenDeathLink(DeathLink):
 | 
						|
    """When you die, everyone who enabled death link dies. Of course, the reverse is true too."""
 | 
						|
    visibility = Visibility.none
 | 
						|
 | 
						|
def hidden(option: Type[Option[Any]]) -> Type[Option]:
 | 
						|
    new_option = AssembleOptions(f"{option.__name__}Hidden", option.__bases__, vars(option).copy())
 | 
						|
    new_option.visibility = Visibility.none
 | 
						|
    new_option.__doc__ = option.__doc__
 | 
						|
    globals()[f"{option.__name__}Hidden"] = new_option
 | 
						|
    return new_option
 | 
						|
    
 | 
						|
class HasReplacedCamelCase(Toggle):
 | 
						|
    """For internal use will display a warning message if true"""
 | 
						|
    visibility = Visibility.none
 | 
						|
 | 
						|
@dataclass
 | 
						|
class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions):
 | 
						|
    StartWithJewelryBox: hidden(StartWithJewelryBox) # type: ignore
 | 
						|
    DownloadableItems: hidden(DownloadableItems) # type: ignore
 | 
						|
    EyeSpy: hidden(EyeSpy) # type: ignore
 | 
						|
    StartWithMeyef: hidden(StartWithMeyef) # type: ignore
 | 
						|
    QuickSeed: hidden(QuickSeed) # type: ignore
 | 
						|
    SpecificKeycards: hidden(SpecificKeycards) # type: ignore
 | 
						|
    Inverted: hidden(Inverted) # type: ignore
 | 
						|
    GyreArchives: hidden(GyreArchives) # type: ignore
 | 
						|
    Cantoran: hidden(Cantoran) # type: ignore
 | 
						|
    LoreChecks: hidden(LoreChecks) # type: ignore
 | 
						|
    BossRando: hidden(BossRando) # type: ignore
 | 
						|
    EnemyRando: hidden(EnemyRando) # type: ignore
 | 
						|
    DamageRando: hidden(DamageRando) # type: ignore
 | 
						|
    DamageRandoOverrides: HiddenDamageRandoOverrides
 | 
						|
    HpCap: hidden(HpCap) # type: ignore
 | 
						|
    LevelCap: hidden(LevelCap) # type: ignore
 | 
						|
    ExtraEarringsXP: hidden(ExtraEarringsXP) # type: ignore
 | 
						|
    BossHealing: hidden(BossHealing) # type: ignore
 | 
						|
    ShopFill: hidden(ShopFill) # type: ignore
 | 
						|
    ShopWarpShards: hidden(ShopWarpShards) # type: ignore
 | 
						|
    ShopMultiplier: hidden(ShopMultiplier) # type: ignore
 | 
						|
    LootPool: hidden(LootPool) # type: ignore
 | 
						|
    DropRateCategory: hidden(DropRateCategory) # type: ignore
 | 
						|
    FixedDropRate: hidden(FixedDropRate) # type: ignore
 | 
						|
    LootTierDistro: hidden(LootTierDistro) # type: ignore
 | 
						|
    ShowBestiary: hidden(ShowBestiary) # type: ignore
 | 
						|
    ShowDrops: hidden(ShowDrops) # type: ignore
 | 
						|
    EnterSandman: hidden(EnterSandman) # type: ignore
 | 
						|
    DadPercent: hidden(DadPercent) # type: ignore
 | 
						|
    RisingTides: hidden(RisingTides) # type: ignore
 | 
						|
    RisingTidesOverrides: HiddenRisingTidesOverrides
 | 
						|
    UnchainedKeys: hidden(UnchainedKeys) # type: ignore
 | 
						|
    PresentAccessWithWheelAndSpindle: hidden(PresentAccessWithWheelAndSpindle) # type: ignore
 | 
						|
    TrapChance: hidden(TrapChance) # type: ignore
 | 
						|
    Traps: HiddenTraps # type: ignore
 | 
						|
    DeathLink: HiddenDeathLink # type: ignore
 | 
						|
    has_replaced_options: HasReplacedCamelCase
 | 
						|
 | 
						|
    def handle_backward_compatibility(self) -> None:
 | 
						|
        if self.StartWithJewelryBox != StartWithJewelryBox.default and \
 | 
						|
            self.start_with_jewelry_box == StartWithJewelryBox.default:
 | 
						|
            self.start_with_jewelry_box.value = self.StartWithJewelryBox.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.DownloadableItems != DownloadableItems.default and \
 | 
						|
            self.downloadable_items == DownloadableItems.default:
 | 
						|
            self.downloadable_items.value = self.DownloadableItems.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.EyeSpy != EyeSpy.default and \
 | 
						|
            self.eye_spy == EyeSpy.default:
 | 
						|
            self.eye_spy.value = self.EyeSpy.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.StartWithMeyef != StartWithMeyef.default and \
 | 
						|
            self.start_with_meyef == StartWithMeyef.default:
 | 
						|
            self.start_with_meyef.value = self.StartWithMeyef.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.QuickSeed != QuickSeed.default and \
 | 
						|
            self.quick_seed == QuickSeed.default:
 | 
						|
            self.quick_seed.value = self.QuickSeed.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.SpecificKeycards != SpecificKeycards.default and \
 | 
						|
            self.specific_keycards == SpecificKeycards.default:
 | 
						|
            self.specific_keycards.value = self.SpecificKeycards.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.Inverted != Inverted.default and \
 | 
						|
            self.inverted == Inverted.default:
 | 
						|
            self.inverted.value = self.Inverted.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.GyreArchives != GyreArchives.default and \
 | 
						|
            self.gyre_archives == GyreArchives.default:
 | 
						|
            self.gyre_archives.value = self.GyreArchives.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.Cantoran != Cantoran.default and \
 | 
						|
            self.cantoran == Cantoran.default:
 | 
						|
            self.cantoran.value = self.Cantoran.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.LoreChecks != LoreChecks.default and \
 | 
						|
            self.lore_checks == LoreChecks.default:
 | 
						|
            self.lore_checks.value = self.LoreChecks.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.BossRando != BossRando.default and \
 | 
						|
            self.boss_rando == BossRando.default:
 | 
						|
            self.boss_rando.value = self.BossRando.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.EnemyRando != EnemyRando.default and \
 | 
						|
            self.enemy_rando == EnemyRando.default:
 | 
						|
            self.enemy_rando.value = self.EnemyRando.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.DamageRando != DamageRando.default and \
 | 
						|
            self.damage_rando == DamageRando.default:
 | 
						|
            self.damage_rando.value = self.DamageRando.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.DamageRandoOverrides != DamageRandoOverrides.default and \
 | 
						|
            self.damage_rando_overrides == DamageRandoOverrides.default:
 | 
						|
            self.damage_rando_overrides.value = self.DamageRandoOverrides.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.HpCap != HpCap.default and \
 | 
						|
            self.hp_cap == HpCap.default:
 | 
						|
            self.hp_cap.value = self.HpCap.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.LevelCap != LevelCap.default and \
 | 
						|
            self.level_cap == LevelCap.default:
 | 
						|
            self.level_cap.value = self.LevelCap.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.ExtraEarringsXP != ExtraEarringsXP.default and \
 | 
						|
            self.extra_earrings_xp == ExtraEarringsXP.default:
 | 
						|
            self.extra_earrings_xp.value = self.ExtraEarringsXP.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.BossHealing != BossHealing.default and \
 | 
						|
            self.boss_healing == BossHealing.default:
 | 
						|
            self.boss_healing.value = self.BossHealing.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.ShopFill != ShopFill.default and \
 | 
						|
            self.shop_fill == ShopFill.default:
 | 
						|
            self.shop_fill.value = self.ShopFill.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.ShopWarpShards != ShopWarpShards.default and \
 | 
						|
            self.shop_warp_shards == ShopWarpShards.default:
 | 
						|
            self.shop_warp_shards.value = self.ShopWarpShards.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.ShopMultiplier != ShopMultiplier.default and \
 | 
						|
            self.shop_multiplier == ShopMultiplier.default:
 | 
						|
            self.shop_multiplier.value = self.ShopMultiplier.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.LootPool != LootPool.default and \
 | 
						|
            self.loot_pool == LootPool.default:
 | 
						|
            self.loot_pool.value = self.LootPool.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.DropRateCategory != DropRateCategory.default and \
 | 
						|
            self.drop_rate_category == DropRateCategory.default:
 | 
						|
            self.drop_rate_category.value = self.DropRateCategory.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.FixedDropRate != FixedDropRate.default and \
 | 
						|
            self.fixed_drop_rate == FixedDropRate.default:
 | 
						|
            self.fixed_drop_rate.value = self.FixedDropRate.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.LootTierDistro != LootTierDistro.default and \
 | 
						|
            self.loot_tier_distro == LootTierDistro.default:
 | 
						|
            self.loot_tier_distro.value = self.LootTierDistro.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.ShowBestiary != ShowBestiary.default and \
 | 
						|
            self.show_bestiary == ShowBestiary.default:
 | 
						|
            self.show_bestiary.value = self.ShowBestiary.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.ShowDrops != ShowDrops.default and \
 | 
						|
            self.show_drops == ShowDrops.default:
 | 
						|
            self.show_drops.value = self.ShowDrops.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.EnterSandman != EnterSandman.default and \
 | 
						|
            self.enter_sandman == EnterSandman.default:
 | 
						|
            self.enter_sandman.value = self.EnterSandman.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.DadPercent != DadPercent.default and \
 | 
						|
            self.dad_percent == DadPercent.default:
 | 
						|
            self.dad_percent.value = self.DadPercent.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.RisingTides != RisingTides.default and \
 | 
						|
            self.rising_tides == RisingTides.default:
 | 
						|
            self.rising_tides.value = self.RisingTides.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.RisingTidesOverrides != RisingTidesOverrides.default and \
 | 
						|
            self.rising_tides_overrides == RisingTidesOverrides.default:
 | 
						|
            self.rising_tides_overrides.value = self.RisingTidesOverrides.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.UnchainedKeys != UnchainedKeys.default and \
 | 
						|
            self.unchained_keys == UnchainedKeys.default:
 | 
						|
            self.unchained_keys.value = self.UnchainedKeys.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.PresentAccessWithWheelAndSpindle != PresentAccessWithWheelAndSpindle.default and \
 | 
						|
            self.back_to_the_future == PresentAccessWithWheelAndSpindle.default:
 | 
						|
            self.back_to_the_future.value = self.PresentAccessWithWheelAndSpindle.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.TrapChance != TrapChance.default and \
 | 
						|
            self.trap_chance == TrapChance.default:
 | 
						|
            self.trap_chance.value = self.TrapChance.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.Traps != Traps.default and \
 | 
						|
            self.traps == Traps.default:
 | 
						|
            self.traps.value = self.Traps.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 | 
						|
        if self.DeathLink != DeathLink.default and \
 | 
						|
            self.death_link == DeathLink.default:
 | 
						|
            self.death_link.value = self.DeathLink.value
 | 
						|
            self.has_replaced_options.value = Toggle.option_true
 |