436 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			436 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from typing import Dict, Union, List
 | |
| from BaseClasses import MultiWorld
 | |
| from Options import Toggle, DefaultOnToggle, DeathLink, Choice, Range, Option, OptionDict, OptionList
 | |
| 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(Toggle):
 | |
|     "Shuffles the positions of all bosses."
 | |
|     display_name = "Boss Randomization"
 | |
| 
 | |
| 
 | |
| class BossScaling(DefaultOnToggle):
 | |
|     "When Boss Rando is enabled, scales the bosses' HP, XP, and ATK to the stats of the location they replace (Recommended)"
 | |
|     display_name = "Scale Random Boss Stats"
 | |
| 
 | |
| 
 | |
| 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 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"""
 | |
|     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")
 | |
|     })
 | |
|     display_name = "Rising Tides Overrides"
 | |
|     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 },
 | |
|     }
 | |
| 
 | |
| 
 | |
| 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" }
 | |
|     default = [ "Meteor Sparrow Trap", "Poison Trap", "Chaos Trap", "Neurotoxin Trap", "Bee Trap" ]
 | |
| 
 | |
| 
 | |
| # Some options that are available in the timespinner randomizer arent currently implemented
 | |
| timespinner_options: Dict[str, Option] = {
 | |
|     "StartWithJewelryBox": StartWithJewelryBox,
 | |
|     "DownloadableItems": DownloadableItems,
 | |
|     "EyeSpy": EyeSpy,
 | |
|     "StartWithMeyef": StartWithMeyef,
 | |
|     "QuickSeed": QuickSeed,
 | |
|     "SpecificKeycards": SpecificKeycards,
 | |
|     "Inverted": Inverted,
 | |
|     "GyreArchives": GyreArchives,
 | |
|     "Cantoran": Cantoran,
 | |
|     "LoreChecks": LoreChecks,
 | |
|     "BossRando": BossRando,
 | |
|     "BossScaling": BossScaling,
 | |
|     "DamageRando": DamageRando,
 | |
|     "DamageRandoOverrides": DamageRandoOverrides,
 | |
|     "HpCap": HpCap,
 | |
|     "LevelCap": LevelCap,
 | |
|     "ExtraEarringsXP": ExtraEarringsXP,
 | |
|     "BossHealing": BossHealing,
 | |
|     "ShopFill": ShopFill,
 | |
|     "ShopWarpShards": ShopWarpShards,
 | |
|     "ShopMultiplier": ShopMultiplier,
 | |
|     "LootPool": LootPool,
 | |
|     "DropRateCategory": DropRateCategory,
 | |
|     "FixedDropRate": FixedDropRate,
 | |
|     "LootTierDistro": LootTierDistro,
 | |
|     "ShowBestiary": ShowBestiary,
 | |
|     "ShowDrops": ShowDrops,
 | |
|     "EnterSandman": EnterSandman,
 | |
|     "DadPercent": DadPercent,
 | |
|     "RisingTides": RisingTides,
 | |
|     "RisingTidesOverrides": RisingTidesOverrides,
 | |
|     "UnchainedKeys": UnchainedKeys,
 | |
|     "TrapChance": TrapChance,
 | |
|     "Traps": Traps,
 | |
|     "DeathLink": DeathLink,
 | |
| }
 | |
| 
 | |
| 
 | |
| def is_option_enabled(world: MultiWorld, player: int, name: str) -> bool:
 | |
|     return get_option_value(world, player, name) > 0
 | |
| 
 | |
| 
 | |
| def get_option_value(world: MultiWorld, player: int, name: str) -> Union[int, Dict, List]:
 | |
|     option = getattr(world, name, None)
 | |
|     if option == None:
 | |
|         return 0
 | |
| 
 | |
|     return option[player].value
 | 
