mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-27 15:03:22 -07:00
Merge remote-tracking branch 'remotes/upstream/main'
This commit is contained in:
@@ -207,6 +207,7 @@ components: List[Component] = [
|
||||
]
|
||||
|
||||
|
||||
# if registering an icon from within an apworld, the format "ap:module.name/path/to/file.png" can be used
|
||||
icon_paths = {
|
||||
'icon': local_path('data', 'icon.png'),
|
||||
'mcicon': local_path('data', 'mcicon.png'),
|
||||
|
||||
@@ -47,8 +47,6 @@ class LocationData:
|
||||
self.local_item: int = None
|
||||
|
||||
def get_random_position(self, random):
|
||||
x: int = None
|
||||
y: int = None
|
||||
if self.world_positions is None or len(self.world_positions) == 0:
|
||||
if self.room_id is None:
|
||||
return None
|
||||
|
||||
@@ -76,10 +76,9 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player
|
||||
multiworld.regions.append(credits_room_far_side)
|
||||
|
||||
dragon_slay_check = options.dragon_slay_check.value
|
||||
priority_locations = determine_priority_locations(multiworld, dragon_slay_check)
|
||||
priority_locations = determine_priority_locations()
|
||||
|
||||
for name, location_data in location_table.items():
|
||||
require_sword = False
|
||||
if location_data.region == "Varies":
|
||||
if location_data.name == "Slay Yorgle":
|
||||
if not dragon_slay_check:
|
||||
@@ -154,6 +153,7 @@ def create_regions(options: PerGameCommonOptions, multiworld: MultiWorld, player
|
||||
|
||||
|
||||
# Placeholder for adding sets of priority locations at generation, possibly as an option in the future
|
||||
def determine_priority_locations(world: MultiWorld, dragon_slay_check: bool) -> {}:
|
||||
# def determine_priority_locations(multiworld: MultiWorld, dragon_slay_check: bool) -> {}:
|
||||
def determine_priority_locations() -> {}:
|
||||
priority_locations = {}
|
||||
return priority_locations
|
||||
|
||||
@@ -86,9 +86,7 @@ class AdventureDeltaPatch(APPatch, metaclass=AutoPatchRegister):
|
||||
|
||||
# locations: [], autocollect: [], seed_name: bytes,
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
patch_only = True
|
||||
if "autocollect" in kwargs:
|
||||
patch_only = False
|
||||
self.foreign_items: [AdventureForeignItemInfo] = [AdventureForeignItemInfo(loc.short_location_id, loc.room_id, loc.room_x, loc.room_y)
|
||||
for loc in kwargs["locations"]]
|
||||
|
||||
|
||||
@@ -446,7 +446,7 @@ class AdventureWorld(World):
|
||||
# end of ordered Main.py calls
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_data: ItemData = item_table.get(name)
|
||||
item_data: ItemData = item_table[name]
|
||||
return AdventureItem(name, item_data.classification, item_data.id, self.player)
|
||||
|
||||
def create_event(self, name: str, classification: ItemClassification) -> Item:
|
||||
|
||||
@@ -59,156 +59,316 @@ class ItemData:
|
||||
type: ItemType
|
||||
group: ItemGroup
|
||||
|
||||
def __init__(self, id: int, count: int, type: ItemType, group: ItemGroup):
|
||||
def __init__(self, aId: int, count: int, aType: ItemType, group: ItemGroup):
|
||||
"""
|
||||
Initialisation of the item data
|
||||
@param id: The item ID
|
||||
@param aId: The item ID
|
||||
@param count: the number of items in the pool
|
||||
@param type: the importance type of the item
|
||||
@param aType: the importance type of the item
|
||||
@param group: the usage of the item in the game
|
||||
"""
|
||||
self.id = id
|
||||
self.id = aId
|
||||
self.count = count
|
||||
self.type = type
|
||||
self.type = aType
|
||||
self.group = group
|
||||
|
||||
class ItemNames:
|
||||
"""
|
||||
Constants used to represent the mane of every items.
|
||||
"""
|
||||
# Normal items
|
||||
ANEMONE = "Anemone"
|
||||
ARNASSI_STATUE = "Arnassi Statue"
|
||||
BIG_SEED = "Big Seed"
|
||||
GLOWING_SEED = "Glowing Seed"
|
||||
BLACK_PEARL = "Black Pearl"
|
||||
BABY_BLASTER = "Baby Blaster"
|
||||
CRAB_ARMOR = "Crab Armor"
|
||||
BABY_DUMBO = "Baby Dumbo"
|
||||
TOOTH = "Tooth"
|
||||
ENERGY_STATUE = "Energy Statue"
|
||||
KROTITE_ARMOR = "Krotite Armor"
|
||||
GOLDEN_STARFISH = "Golden Starfish"
|
||||
GOLDEN_GEAR = "Golden Gear"
|
||||
JELLY_BEACON = "Jelly Beacon"
|
||||
JELLY_COSTUME = "Jelly Costume"
|
||||
JELLY_PLANT = "Jelly Plant"
|
||||
MITHALAS_DOLL = "Mithalas Doll"
|
||||
MITHALAN_DRESS = "Mithalan Dress"
|
||||
MITHALAS_BANNER = "Mithalas Banner"
|
||||
MITHALAS_POT = "Mithalas Pot"
|
||||
MUTANT_COSTUME = "Mutant Costume"
|
||||
BABY_NAUTILUS = "Baby Nautilus"
|
||||
BABY_PIRANHA = "Baby Piranha"
|
||||
ARNASSI_ARMOR = "Arnassi Armor"
|
||||
SEED_BAG = "Seed Bag"
|
||||
KING_S_SKULL = "King's Skull"
|
||||
SONG_PLANT_SPORE = "Song Plant Spore"
|
||||
STONE_HEAD = "Stone Head"
|
||||
SUN_KEY = "Sun Key"
|
||||
GIRL_COSTUME = "Girl Costume"
|
||||
ODD_CONTAINER = "Odd Container"
|
||||
TRIDENT = "Trident"
|
||||
TURTLE_EGG = "Turtle Egg"
|
||||
JELLY_EGG = "Jelly Egg"
|
||||
URCHIN_COSTUME = "Urchin Costume"
|
||||
BABY_WALKER = "Baby Walker"
|
||||
VEDHA_S_CURE_ALL = "Vedha's Cure-All"
|
||||
ZUUNA_S_PEROGI = "Zuuna's Perogi"
|
||||
ARCANE_POULTICE = "Arcane Poultice"
|
||||
BERRY_ICE_CREAM = "Berry Ice Cream"
|
||||
BUTTERY_SEA_LOAF = "Buttery Sea Loaf"
|
||||
COLD_BORSCHT = "Cold Borscht"
|
||||
COLD_SOUP = "Cold Soup"
|
||||
CRAB_CAKE = "Crab Cake"
|
||||
DIVINE_SOUP = "Divine Soup"
|
||||
DUMBO_ICE_CREAM = "Dumbo Ice Cream"
|
||||
FISH_OIL = "Fish Oil"
|
||||
GLOWING_EGG = "Glowing Egg"
|
||||
HAND_ROLL = "Hand Roll"
|
||||
HEALING_POULTICE = "Healing Poultice"
|
||||
HEARTY_SOUP = "Hearty Soup"
|
||||
HOT_BORSCHT = "Hot Borscht"
|
||||
HOT_SOUP = "Hot Soup"
|
||||
ICE_CREAM = "Ice Cream"
|
||||
LEADERSHIP_ROLL = "Leadership Roll"
|
||||
LEAF_POULTICE = "Leaf Poultice"
|
||||
LEECHING_POULTICE = "Leeching Poultice"
|
||||
LEGENDARY_CAKE = "Legendary Cake"
|
||||
LOAF_OF_LIFE = "Loaf of Life"
|
||||
LONG_LIFE_SOUP = "Long Life Soup"
|
||||
MAGIC_SOUP = "Magic Soup"
|
||||
MUSHROOM_X_2 = "Mushroom x 2"
|
||||
PEROGI = "Perogi"
|
||||
PLANT_LEAF = "Plant Leaf"
|
||||
PLUMP_PEROGI = "Plump Perogi"
|
||||
POISON_LOAF = "Poison Loaf"
|
||||
POISON_SOUP = "Poison Soup"
|
||||
RAINBOW_MUSHROOM = "Rainbow Mushroom"
|
||||
RAINBOW_SOUP = "Rainbow Soup"
|
||||
RED_BERRY = "Red Berry"
|
||||
RED_BULB_X_2 = "Red Bulb x 2"
|
||||
ROTTEN_CAKE = "Rotten Cake"
|
||||
ROTTEN_LOAF_X_8 = "Rotten Loaf x 8"
|
||||
ROTTEN_MEAT = "Rotten Meat"
|
||||
ROYAL_SOUP = "Royal Soup"
|
||||
SEA_CAKE = "Sea Cake"
|
||||
SEA_LOAF = "Sea Loaf"
|
||||
SHARK_FIN_SOUP = "Shark Fin Soup"
|
||||
SIGHT_POULTICE = "Sight Poultice"
|
||||
SMALL_BONE_X_2 = "Small Bone x 2"
|
||||
SMALL_EGG = "Small Egg"
|
||||
SMALL_TENTACLE_X_2 = "Small Tentacle x 2"
|
||||
SPECIAL_BULB = "Special Bulb"
|
||||
SPECIAL_CAKE = "Special Cake"
|
||||
SPICY_MEAT_X_2 = "Spicy Meat x 2"
|
||||
SPICY_ROLL = "Spicy Roll"
|
||||
SPICY_SOUP = "Spicy Soup"
|
||||
SPIDER_ROLL = "Spider Roll"
|
||||
SWAMP_CAKE = "Swamp Cake"
|
||||
TASTY_CAKE = "Tasty Cake"
|
||||
TASTY_ROLL = "Tasty Roll"
|
||||
TOUGH_CAKE = "Tough Cake"
|
||||
TURTLE_SOUP = "Turtle Soup"
|
||||
VEDHA_SEA_CRISP = "Vedha Sea Crisp"
|
||||
VEGGIE_CAKE = "Veggie Cake"
|
||||
VEGGIE_ICE_CREAM = "Veggie Ice Cream"
|
||||
VEGGIE_SOUP = "Veggie Soup"
|
||||
VOLCANO_ROLL = "Volcano Roll"
|
||||
HEALTH_UPGRADE = "Health Upgrade"
|
||||
WOK = "Wok"
|
||||
EEL_OIL_X_2 = "Eel Oil x 2"
|
||||
FISH_MEAT_X_2 = "Fish Meat x 2"
|
||||
FISH_OIL_X_3 = "Fish Oil x 3"
|
||||
GLOWING_EGG_X_2 = "Glowing Egg x 2"
|
||||
HEALING_POULTICE_X_2 = "Healing Poultice x 2"
|
||||
HOT_SOUP_X_2 = "Hot Soup x 2"
|
||||
LEADERSHIP_ROLL_X_2 = "Leadership Roll x 2"
|
||||
LEAF_POULTICE_X_3 = "Leaf Poultice x 3"
|
||||
PLANT_LEAF_X_2 = "Plant Leaf x 2"
|
||||
PLANT_LEAF_X_3 = "Plant Leaf x 3"
|
||||
ROTTEN_MEAT_X_2 = "Rotten Meat x 2"
|
||||
ROTTEN_MEAT_X_8 = "Rotten Meat x 8"
|
||||
SEA_LOAF_X_2 = "Sea Loaf x 2"
|
||||
SMALL_BONE_X_3 = "Small Bone x 3"
|
||||
SMALL_EGG_X_2 = "Small Egg x 2"
|
||||
LI_AND_LI_SONG = "Li and Li Song"
|
||||
SHIELD_SONG = "Shield Song"
|
||||
BEAST_FORM = "Beast Form"
|
||||
SUN_FORM = "Sun Form"
|
||||
NATURE_FORM = "Nature Form"
|
||||
ENERGY_FORM = "Energy Form"
|
||||
BIND_SONG = "Bind Song"
|
||||
FISH_FORM = "Fish Form"
|
||||
SPIRIT_FORM = "Spirit Form"
|
||||
DUAL_FORM = "Dual Form"
|
||||
TRANSTURTLE_VEIL_TOP_LEFT = "Transturtle Veil top left"
|
||||
TRANSTURTLE_VEIL_TOP_RIGHT = "Transturtle Veil top right"
|
||||
TRANSTURTLE_OPEN_WATERS = "Transturtle Open Waters top right"
|
||||
TRANSTURTLE_KELP_FOREST = "Transturtle Kelp Forest bottom left"
|
||||
TRANSTURTLE_HOME_WATERS = "Transturtle Home Waters"
|
||||
TRANSTURTLE_ABYSS = "Transturtle Abyss right"
|
||||
TRANSTURTLE_BODY = "Transturtle Final Boss"
|
||||
TRANSTURTLE_SIMON_SAYS = "Transturtle Simon Says"
|
||||
TRANSTURTLE_ARNASSI_RUINS = "Transturtle Arnassi Ruins"
|
||||
# Events name
|
||||
BODY_TONGUE_CLEARED = "Body Tongue cleared"
|
||||
HAS_SUN_CRYSTAL = "Has Sun Crystal"
|
||||
FALLEN_GOD_BEATED = "Fallen God beated"
|
||||
MITHALAN_GOD_BEATED = "Mithalan God beated"
|
||||
DRUNIAN_GOD_BEATED = "Drunian God beated"
|
||||
LUMEREAN_GOD_BEATED = "Lumerean God beated"
|
||||
THE_GOLEM_BEATED = "The Golem beated"
|
||||
NAUTILUS_PRIME_BEATED = "Nautilus Prime beated"
|
||||
BLASTER_PEG_PRIME_BEATED = "Blaster Peg Prime beated"
|
||||
MERGOG_BEATED = "Mergog beated"
|
||||
MITHALAN_PRIESTS_BEATED = "Mithalan priests beated"
|
||||
OCTOPUS_PRIME_BEATED = "Octopus Prime beated"
|
||||
CRABBIUS_MAXIMUS_BEATED = "Crabbius Maximus beated"
|
||||
MANTIS_SHRIMP_PRIME_BEATED = "Mantis Shrimp Prime beated"
|
||||
KING_JELLYFISH_GOD_PRIME_BEATED = "King Jellyfish God Prime beated"
|
||||
VICTORY = "Victory"
|
||||
FIRST_SECRET_OBTAINED = "First Secret obtained"
|
||||
SECOND_SECRET_OBTAINED = "Second Secret obtained"
|
||||
THIRD_SECRET_OBTAINED = "Third Secret obtained"
|
||||
|
||||
"""Information data for every (not event) item."""
|
||||
item_table = {
|
||||
# name: ID, Nb, Item Type, Item Group
|
||||
"Anemone": ItemData(698000, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_anemone
|
||||
"Arnassi Statue": ItemData(698001, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_arnassi_statue
|
||||
"Big Seed": ItemData(698002, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_big_seed
|
||||
"Glowing Seed": ItemData(698003, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_bio_seed
|
||||
"Black Pearl": ItemData(698004, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_blackpearl
|
||||
"Baby Blaster": ItemData(698005, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_blaster
|
||||
"Crab Armor": ItemData(698006, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_crab_costume
|
||||
"Baby Dumbo": ItemData(698007, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_dumbo
|
||||
"Tooth": ItemData(698008, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_boss
|
||||
"Energy Statue": ItemData(698009, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_statue
|
||||
"Krotite Armor": ItemData(698010, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_temple
|
||||
"Golden Starfish": ItemData(698011, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_gold_star
|
||||
"Golden Gear": ItemData(698012, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_golden_gear
|
||||
"Jelly Beacon": ItemData(698013, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_beacon
|
||||
"Jelly Costume": ItemData(698014, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_jelly_costume
|
||||
"Jelly Plant": ItemData(698015, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_plant
|
||||
"Mithalas Doll": ItemData(698016, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithala_doll
|
||||
"Mithalan Dress": ItemData(698017, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalan_costume
|
||||
"Mithalas Banner": ItemData(698018, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_banner
|
||||
"Mithalas Pot": ItemData(698019, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_pot
|
||||
"Mutant Costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume
|
||||
"Baby Nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus
|
||||
"Baby Piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha
|
||||
"Arnassi Armor": ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume
|
||||
"Seed Bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
|
||||
"King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull
|
||||
"Song Plant Spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed
|
||||
"Stone Head": ItemData(698027, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_stone_head
|
||||
"Sun Key": ItemData(698028, 1, ItemType.NORMAL, ItemGroup.COLLECTIBLE), # collectible_sun_key
|
||||
"Girl Costume": ItemData(698029, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_teen_costume
|
||||
"Odd Container": ItemData(698030, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_treasure_chest
|
||||
"Trident": ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head
|
||||
"Turtle Egg": ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg
|
||||
"Jelly Egg": ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
|
||||
"Urchin Costume": ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
|
||||
"Baby Walker": ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker
|
||||
"Vedha's Cure-All-All": ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All
|
||||
"Zuuna's perogi": ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi
|
||||
"Arcane poultice": ItemData(698038, 7, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_arcanepoultice
|
||||
"Berry ice cream": ItemData(698039, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_berryicecream
|
||||
"Buttery sea loaf": ItemData(698040, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_butterysealoaf
|
||||
"Cold borscht": ItemData(698041, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldborscht
|
||||
"Cold soup": ItemData(698042, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldsoup
|
||||
"Crab cake": ItemData(698043, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_crabcake
|
||||
"Divine soup": ItemData(698044, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_divinesoup
|
||||
"Dumbo ice cream": ItemData(698045, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_dumboicecream
|
||||
"Fish oil": ItemData(698046, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
|
||||
"Glowing egg": ItemData(698047, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
|
||||
"Hand roll": ItemData(698048, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_handroll
|
||||
"Healing poultice": ItemData(698049, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
|
||||
"Hearty soup": ItemData(698050, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_heartysoup
|
||||
"Hot borscht": ItemData(698051, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_hotborscht
|
||||
"Hot soup": ItemData(698052, 3, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
|
||||
"Ice cream": ItemData(698053, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_icecream
|
||||
"Leadership roll": ItemData(698054, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
|
||||
"Leaf poultice": ItemData(698055, 5, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice
|
||||
"Leeching poultice": ItemData(698056, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leechingpoultice
|
||||
"Legendary cake": ItemData(698057, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_legendarycake
|
||||
"Loaf of life": ItemData(698058, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_loafoflife
|
||||
"Long life soup": ItemData(698059, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_longlifesoup
|
||||
"Magic soup": ItemData(698060, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_magicsoup
|
||||
"Mushroom x 2": ItemData(698061, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_mushroom
|
||||
"Perogi": ItemData(698062, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_perogi
|
||||
"Plant leaf": ItemData(698063, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
"Plump perogi": ItemData(698064, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_plumpperogi
|
||||
"Poison loaf": ItemData(698065, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonloaf
|
||||
"Poison soup": ItemData(698066, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonsoup
|
||||
"Rainbow mushroom": ItemData(698067, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_rainbowmushroom
|
||||
"Rainbow soup": ItemData(698068, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_rainbowsoup
|
||||
"Red berry": ItemData(698069, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redberry
|
||||
"Red bulb x 2": ItemData(698070, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redbulb
|
||||
"Rotten cake": ItemData(698071, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottencake
|
||||
"Rotten loaf x 8": ItemData(698072, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottenloaf
|
||||
"Rotten meat": ItemData(698073, 5, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
"Royal soup": ItemData(698074, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_royalsoup
|
||||
"Sea cake": ItemData(698075, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_seacake
|
||||
"Sea loaf": ItemData(698076, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
|
||||
"Shark fin soup": ItemData(698077, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sharkfinsoup
|
||||
"Sight poultice": ItemData(698078, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sightpoultice
|
||||
"Small bone x 2": ItemData(698079, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
|
||||
"Small egg": ItemData(698080, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
|
||||
"Small tentacle x 2": ItemData(698081, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smalltentacle
|
||||
"Special bulb": ItemData(698082, 5, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_specialbulb
|
||||
"Special cake": ItemData(698083, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_specialcake
|
||||
"Spicy meat x 2": ItemData(698084, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_spicymeat
|
||||
"Spicy roll": ItemData(698085, 11, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicyroll
|
||||
"Spicy soup": ItemData(698086, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicysoup
|
||||
"Spider roll": ItemData(698087, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spiderroll
|
||||
"Swamp cake": ItemData(698088, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_swampcake
|
||||
"Tasty cake": ItemData(698089, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastycake
|
||||
"Tasty roll": ItemData(698090, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastyroll
|
||||
"Tough cake": ItemData(698091, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_toughcake
|
||||
"Turtle soup": ItemData(698092, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_turtlesoup
|
||||
"Vedha sea crisp": ItemData(698093, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_vedhaseacrisp
|
||||
"Veggie cake": ItemData(698094, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiecake
|
||||
"Veggie ice cream": ItemData(698095, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggieicecream
|
||||
"Veggie soup": ItemData(698096, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiesoup
|
||||
"Volcano roll": ItemData(698097, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_volcanoroll
|
||||
"Health upgrade": ItemData(698098, 5, ItemType.NORMAL, ItemGroup.HEALTH), # upgrade_health_?
|
||||
"Wok": ItemData(698099, 1, ItemType.NORMAL, ItemGroup.UTILITY), # upgrade_wok
|
||||
"Eel oil x 2": ItemData(698100, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_eeloil
|
||||
"Fish meat x 2": ItemData(698101, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishmeat
|
||||
"Fish oil x 3": ItemData(698102, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
|
||||
"Glowing egg x 2": ItemData(698103, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
|
||||
"Healing poultice x 2": ItemData(698104, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
|
||||
"Hot soup x 2": ItemData(698105, 1, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
|
||||
"Leadership roll x 2": ItemData(698106, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
|
||||
"Leaf poultice x 3": ItemData(698107, 2, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_leafpoultice
|
||||
"Plant leaf x 2": ItemData(698108, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
"Plant leaf x 3": ItemData(698109, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
"Rotten meat x 2": ItemData(698110, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
"Rotten meat x 8": ItemData(698111, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
"Sea loaf x 2": ItemData(698112, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
|
||||
"Small bone x 3": ItemData(698113, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
|
||||
"Small egg x 2": ItemData(698114, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
|
||||
"Li and Li song": ItemData(698115, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_li
|
||||
"Shield song": ItemData(698116, 1, ItemType.NORMAL, ItemGroup.SONG), # song_shield
|
||||
"Beast form": ItemData(698117, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_beast
|
||||
"Sun form": ItemData(698118, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_sun
|
||||
"Nature form": ItemData(698119, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_nature
|
||||
"Energy form": ItemData(698120, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_energy
|
||||
"Bind song": ItemData(698121, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_bind
|
||||
"Fish form": ItemData(698122, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_fish
|
||||
"Spirit form": ItemData(698123, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_spirit
|
||||
"Dual form": ItemData(698124, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_dual
|
||||
"Transturtle Veil top left": ItemData(698125, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil01
|
||||
"Transturtle Veil top right": ItemData(698126, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil02
|
||||
"Transturtle Open Water top right": ItemData(698127, 1, ItemType.PROGRESSION,
|
||||
ItemGroup.TURTLE), # transport_openwater03
|
||||
"Transturtle Forest bottom left": ItemData(698128, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest04
|
||||
"Transturtle Home Water": ItemData(698129, 1, ItemType.NORMAL, ItemGroup.TURTLE), # transport_mainarea
|
||||
"Transturtle Abyss right": ItemData(698130, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_abyss03
|
||||
"Transturtle Final Boss": ItemData(698131, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_finalboss
|
||||
"Transturtle Simon Says": ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
|
||||
"Transturtle Arnassi Ruins": ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
|
||||
ItemNames.ANEMONE: ItemData(698000, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_anemone
|
||||
ItemNames.ARNASSI_STATUE: ItemData(698001, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_arnassi_statue
|
||||
ItemNames.BIG_SEED: ItemData(698002, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_big_seed
|
||||
ItemNames.GLOWING_SEED: ItemData(698003, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_bio_seed
|
||||
ItemNames.BLACK_PEARL: ItemData(698004, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_blackpearl
|
||||
ItemNames.BABY_BLASTER: ItemData(698005, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_blaster
|
||||
ItemNames.CRAB_ARMOR: ItemData(698006, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_crab_costume
|
||||
ItemNames.BABY_DUMBO: ItemData(698007, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_dumbo
|
||||
ItemNames.TOOTH: ItemData(698008, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_boss
|
||||
ItemNames.ENERGY_STATUE: ItemData(698009, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_statue
|
||||
ItemNames.KROTITE_ARMOR: ItemData(698010, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_energy_temple
|
||||
ItemNames.GOLDEN_STARFISH: ItemData(698011, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_gold_star
|
||||
ItemNames.GOLDEN_GEAR: ItemData(698012, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_golden_gear
|
||||
ItemNames.JELLY_BEACON: ItemData(698013, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_beacon
|
||||
ItemNames.JELLY_COSTUME: ItemData(698014, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_jelly_costume
|
||||
ItemNames.JELLY_PLANT: ItemData(698015, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_jelly_plant
|
||||
ItemNames.MITHALAS_DOLL: ItemData(698016, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithala_doll
|
||||
ItemNames.MITHALAN_DRESS: ItemData(698017, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalan_costume
|
||||
ItemNames.MITHALAS_BANNER: ItemData(698018, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_banner
|
||||
ItemNames.MITHALAS_POT: ItemData(698019, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mithalas_pot
|
||||
ItemNames.MUTANT_COSTUME: ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume
|
||||
ItemNames.BABY_NAUTILUS: ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus
|
||||
ItemNames.BABY_PIRANHA: ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha
|
||||
ItemNames.ARNASSI_ARMOR: ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume
|
||||
ItemNames.SEED_BAG: ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
|
||||
ItemNames.KING_S_SKULL: ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull
|
||||
ItemNames.SONG_PLANT_SPORE: ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed
|
||||
ItemNames.STONE_HEAD: ItemData(698027, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_stone_head
|
||||
ItemNames.SUN_KEY: ItemData(698028, 1, ItemType.NORMAL, ItemGroup.COLLECTIBLE), # collectible_sun_key
|
||||
ItemNames.GIRL_COSTUME: ItemData(698029, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_teen_costume
|
||||
ItemNames.ODD_CONTAINER: ItemData(698030, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_treasure_chest
|
||||
ItemNames.TRIDENT: ItemData(698031, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_trident_head
|
||||
ItemNames.TURTLE_EGG: ItemData(698032, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_turtle_egg
|
||||
ItemNames.JELLY_EGG: ItemData(698033, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_upsidedown_seed
|
||||
ItemNames.URCHIN_COSTUME: ItemData(698034, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_urchin_costume
|
||||
ItemNames.BABY_WALKER: ItemData(698035, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_walker
|
||||
ItemNames.VEDHA_S_CURE_ALL: ItemData(698036, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Vedha'sCure-All
|
||||
ItemNames.ZUUNA_S_PEROGI: ItemData(698037, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_Zuuna'sperogi
|
||||
ItemNames.ARCANE_POULTICE: ItemData(698038, 7, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_arcanepoultice
|
||||
ItemNames.BERRY_ICE_CREAM: ItemData(698039, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_berryicecream
|
||||
ItemNames.BUTTERY_SEA_LOAF: ItemData(698040, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_butterysealoaf
|
||||
ItemNames.COLD_BORSCHT: ItemData(698041, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldborscht
|
||||
ItemNames.COLD_SOUP: ItemData(698042, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_coldsoup
|
||||
ItemNames.CRAB_CAKE: ItemData(698043, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_crabcake
|
||||
ItemNames.DIVINE_SOUP: ItemData(698044, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_divinesoup
|
||||
ItemNames.DUMBO_ICE_CREAM: ItemData(698045, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_dumboicecream
|
||||
ItemNames.FISH_OIL: ItemData(698046, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
|
||||
ItemNames.GLOWING_EGG: ItemData(698047, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
|
||||
ItemNames.HAND_ROLL: ItemData(698048, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_handroll
|
||||
ItemNames.HEALING_POULTICE: ItemData(698049, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
|
||||
ItemNames.HEARTY_SOUP: ItemData(698050, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_heartysoup
|
||||
ItemNames.HOT_BORSCHT: ItemData(698051, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_hotborscht
|
||||
ItemNames.HOT_SOUP: ItemData(698052, 3, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
|
||||
ItemNames.ICE_CREAM: ItemData(698053, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_icecream
|
||||
ItemNames.LEADERSHIP_ROLL: ItemData(698054, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
|
||||
ItemNames.LEAF_POULTICE: ItemData(698055, 5, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leafpoultice
|
||||
ItemNames.LEECHING_POULTICE: ItemData(698056, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leechingpoultice
|
||||
ItemNames.LEGENDARY_CAKE: ItemData(698057, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_legendarycake
|
||||
ItemNames.LOAF_OF_LIFE: ItemData(698058, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_loafoflife
|
||||
ItemNames.LONG_LIFE_SOUP: ItemData(698059, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_longlifesoup
|
||||
ItemNames.MAGIC_SOUP: ItemData(698060, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_magicsoup
|
||||
ItemNames.MUSHROOM_X_2: ItemData(698061, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_mushroom
|
||||
ItemNames.PEROGI: ItemData(698062, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_perogi
|
||||
ItemNames.PLANT_LEAF: ItemData(698063, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
ItemNames.PLUMP_PEROGI: ItemData(698064, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_plumpperogi
|
||||
ItemNames.POISON_LOAF: ItemData(698065, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonloaf
|
||||
ItemNames.POISON_SOUP: ItemData(698066, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_poisonsoup
|
||||
ItemNames.RAINBOW_MUSHROOM: ItemData(698067, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_rainbowmushroom
|
||||
ItemNames.RAINBOW_SOUP: ItemData(698068, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_rainbowsoup
|
||||
ItemNames.RED_BERRY: ItemData(698069, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redberry
|
||||
ItemNames.RED_BULB_X_2: ItemData(698070, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_redbulb
|
||||
ItemNames.ROTTEN_CAKE: ItemData(698071, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottencake
|
||||
ItemNames.ROTTEN_LOAF_X_8: ItemData(698072, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_rottenloaf
|
||||
ItemNames.ROTTEN_MEAT: ItemData(698073, 5, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
ItemNames.ROYAL_SOUP: ItemData(698074, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_royalsoup
|
||||
ItemNames.SEA_CAKE: ItemData(698075, 4, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_seacake
|
||||
ItemNames.SEA_LOAF: ItemData(698076, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
|
||||
ItemNames.SHARK_FIN_SOUP: ItemData(698077, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sharkfinsoup
|
||||
ItemNames.SIGHT_POULTICE: ItemData(698078, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_sightpoultice
|
||||
ItemNames.SMALL_BONE_X_2: ItemData(698079, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
|
||||
ItemNames.SMALL_EGG: ItemData(698080, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
|
||||
ItemNames.SMALL_TENTACLE_X_2: ItemData(698081, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smalltentacle
|
||||
ItemNames.SPECIAL_BULB: ItemData(698082, 5, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_specialbulb
|
||||
ItemNames.SPECIAL_CAKE: ItemData(698083, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_specialcake
|
||||
ItemNames.SPICY_MEAT_X_2: ItemData(698084, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_spicymeat
|
||||
ItemNames.SPICY_ROLL: ItemData(698085, 11, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicyroll
|
||||
ItemNames.SPICY_SOUP: ItemData(698086, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spicysoup
|
||||
ItemNames.SPIDER_ROLL: ItemData(698087, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_spiderroll
|
||||
ItemNames.SWAMP_CAKE: ItemData(698088, 3, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_swampcake
|
||||
ItemNames.TASTY_CAKE: ItemData(698089, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastycake
|
||||
ItemNames.TASTY_ROLL: ItemData(698090, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_tastyroll
|
||||
ItemNames.TOUGH_CAKE: ItemData(698091, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_toughcake
|
||||
ItemNames.TURTLE_SOUP: ItemData(698092, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_turtlesoup
|
||||
ItemNames.VEDHA_SEA_CRISP: ItemData(698093, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_vedhaseacrisp
|
||||
ItemNames.VEGGIE_CAKE: ItemData(698094, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiecake
|
||||
ItemNames.VEGGIE_ICE_CREAM: ItemData(698095, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggieicecream
|
||||
ItemNames.VEGGIE_SOUP: ItemData(698096, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_veggiesoup
|
||||
ItemNames.VOLCANO_ROLL: ItemData(698097, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_volcanoroll
|
||||
ItemNames.HEALTH_UPGRADE: ItemData(698098, 5, ItemType.NORMAL, ItemGroup.HEALTH), # upgrade_health_?
|
||||
ItemNames.WOK: ItemData(698099, 1, ItemType.NORMAL, ItemGroup.UTILITY), # upgrade_wok
|
||||
ItemNames.EEL_OIL_X_2: ItemData(698100, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_eeloil
|
||||
ItemNames.FISH_MEAT_X_2: ItemData(698101, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishmeat
|
||||
ItemNames.FISH_OIL_X_3: ItemData(698102, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_fishoil
|
||||
ItemNames.GLOWING_EGG_X_2: ItemData(698103, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_glowingegg
|
||||
ItemNames.HEALING_POULTICE_X_2: ItemData(698104, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_healingpoultice
|
||||
ItemNames.HOT_SOUP_X_2: ItemData(698105, 1, ItemType.PROGRESSION, ItemGroup.RECIPE), # ingredient_hotsoup
|
||||
ItemNames.LEADERSHIP_ROLL_X_2: ItemData(698106, 1, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leadershiproll
|
||||
ItemNames.LEAF_POULTICE_X_3: ItemData(698107, 2, ItemType.NORMAL, ItemGroup.RECIPE), # ingredient_leafpoultice
|
||||
ItemNames.PLANT_LEAF_X_2: ItemData(698108, 2, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
ItemNames.PLANT_LEAF_X_3: ItemData(698109, 4, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_plantleaf
|
||||
ItemNames.ROTTEN_MEAT_X_2: ItemData(698110, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
ItemNames.ROTTEN_MEAT_X_8: ItemData(698111, 1, ItemType.JUNK, ItemGroup.INGREDIENT), # ingredient_rottenmeat
|
||||
ItemNames.SEA_LOAF_X_2: ItemData(698112, 1, ItemType.JUNK, ItemGroup.RECIPE), # ingredient_sealoaf
|
||||
ItemNames.SMALL_BONE_X_3: ItemData(698113, 3, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallbone
|
||||
ItemNames.SMALL_EGG_X_2: ItemData(698114, 1, ItemType.NORMAL, ItemGroup.INGREDIENT), # ingredient_smallegg
|
||||
ItemNames.LI_AND_LI_SONG: ItemData(698115, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_li
|
||||
ItemNames.SHIELD_SONG: ItemData(698116, 1, ItemType.NORMAL, ItemGroup.SONG), # song_shield
|
||||
ItemNames.BEAST_FORM: ItemData(698117, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_beast
|
||||
ItemNames.SUN_FORM: ItemData(698118, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_sun
|
||||
ItemNames.NATURE_FORM: ItemData(698119, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_nature
|
||||
ItemNames.ENERGY_FORM: ItemData(698120, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_energy
|
||||
ItemNames.BIND_SONG: ItemData(698121, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_bind
|
||||
ItemNames.FISH_FORM: ItemData(698122, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_fish
|
||||
ItemNames.SPIRIT_FORM: ItemData(698123, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_spirit
|
||||
ItemNames.DUAL_FORM: ItemData(698124, 1, ItemType.PROGRESSION, ItemGroup.SONG), # song_dual
|
||||
ItemNames.TRANSTURTLE_VEIL_TOP_LEFT: ItemData(698125, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil01
|
||||
ItemNames.TRANSTURTLE_VEIL_TOP_RIGHT: ItemData(698126, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_veil02
|
||||
ItemNames.TRANSTURTLE_OPEN_WATERS: ItemData(698127, 1, ItemType.PROGRESSION,
|
||||
ItemGroup.TURTLE), # transport_openwater03
|
||||
ItemNames.TRANSTURTLE_KELP_FOREST: ItemData(698128, 1, ItemType.PROGRESSION, ItemGroup.TURTLE),
|
||||
# transport_forest04
|
||||
ItemNames.TRANSTURTLE_HOME_WATERS: ItemData(698129, 1, ItemType.NORMAL, ItemGroup.TURTLE), # transport_mainarea
|
||||
ItemNames.TRANSTURTLE_ABYSS: ItemData(698130, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_abyss03
|
||||
ItemNames.TRANSTURTLE_BODY: ItemData(698131, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_finalboss
|
||||
ItemNames.TRANSTURTLE_SIMON_SAYS: ItemData(698132, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_forest05
|
||||
ItemNames.TRANSTURTLE_ARNASSI_RUINS: ItemData(698133, 1, ItemType.PROGRESSION, ItemGroup.TURTLE), # transport_seahorse
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,10 @@ class IngredientRandomizer(Choice):
|
||||
"""
|
||||
display_name = "Randomize Ingredients"
|
||||
option_off = 0
|
||||
alias_false = 0
|
||||
option_common_ingredients = 1
|
||||
alias_on = 1
|
||||
alias_true = 1
|
||||
option_all_ingredients = 2
|
||||
default = 0
|
||||
|
||||
@@ -29,14 +32,43 @@ class TurtleRandomizer(Choice):
|
||||
"""Randomize the transportation turtle."""
|
||||
display_name = "Turtle Randomizer"
|
||||
option_none = 0
|
||||
alias_off = 0
|
||||
alias_false = 0
|
||||
option_all = 1
|
||||
option_all_except_final = 2
|
||||
alias_on = 2
|
||||
alias_true = 2
|
||||
default = 2
|
||||
|
||||
|
||||
class EarlyEnergyForm(DefaultOnToggle):
|
||||
""" Force the Energy Form to be in a location early in the game """
|
||||
display_name = "Early Energy Form"
|
||||
class EarlyBindSong(Choice):
|
||||
"""
|
||||
Force the Bind song to be in a location early in the multiworld (or directly in your world if Early and Local is
|
||||
selected).
|
||||
"""
|
||||
display_name = "Early Bind song"
|
||||
option_off = 0
|
||||
alias_false = 0
|
||||
option_early = 1
|
||||
alias_on = 1
|
||||
alias_true = 1
|
||||
option_early_and_local = 2
|
||||
default = 1
|
||||
|
||||
|
||||
class EarlyEnergyForm(Choice):
|
||||
"""
|
||||
Force the Energy form to be in a location early in the multiworld (or directly in your world if Early and Local is
|
||||
selected).
|
||||
"""
|
||||
display_name = "Early Energy form"
|
||||
option_off = 0
|
||||
alias_false = 0
|
||||
option_early = 1
|
||||
alias_on = 1
|
||||
alias_true = 1
|
||||
option_early_and_local = 2
|
||||
default = 1
|
||||
|
||||
|
||||
class AquarianTranslation(Toggle):
|
||||
@@ -47,7 +79,7 @@ class AquarianTranslation(Toggle):
|
||||
class BigBossesToBeat(Range):
|
||||
"""
|
||||
The number of big bosses to beat before having access to the creator (the final boss). The big bosses are
|
||||
"Fallen God", "Mithalan God", "Drunian God", "Sun God" and "The Golem".
|
||||
"Fallen God", "Mithalan God", "Drunian God", "Lumerean God" and "The Golem".
|
||||
"""
|
||||
display_name = "Big bosses to beat"
|
||||
range_start = 0
|
||||
@@ -104,7 +136,7 @@ class LightNeededToGetToDarkPlaces(DefaultOnToggle):
|
||||
display_name = "Light needed to get to dark places"
|
||||
|
||||
|
||||
class BindSongNeededToGetUnderRockBulb(Toggle):
|
||||
class BindSongNeededToGetUnderRockBulb(DefaultOnToggle):
|
||||
"""
|
||||
Make sure that the bind song can be acquired before having to obtain sing bulbs under rocks.
|
||||
"""
|
||||
@@ -121,13 +153,18 @@ class BlindGoal(Toggle):
|
||||
|
||||
class UnconfineHomeWater(Choice):
|
||||
"""
|
||||
Open the way out of the Home Water area so that Naija can go to open water and beyond without the bind song.
|
||||
Open the way out of the Home Waters area so that Naija can go to open water and beyond without the bind song.
|
||||
Note that if you turn this option off, it is recommended to turn on the Early Energy form and Early Bind Song
|
||||
options.
|
||||
"""
|
||||
display_name = "Unconfine Home Water Area"
|
||||
display_name = "Unconfine Home Waters Area"
|
||||
option_off = 0
|
||||
alias_false = 0
|
||||
option_via_energy_door = 1
|
||||
option_via_transturtle = 2
|
||||
option_via_both = 3
|
||||
alias_on = 3
|
||||
alias_true = 3
|
||||
default = 0
|
||||
|
||||
|
||||
@@ -142,6 +179,7 @@ class AquariaOptions(PerGameCommonOptions):
|
||||
big_bosses_to_beat: BigBossesToBeat
|
||||
turtle_randomizer: TurtleRandomizer
|
||||
early_energy_form: EarlyEnergyForm
|
||||
early_bind_song: EarlyBindSong
|
||||
light_needed_to_get_to_dark_places: LightNeededToGetToDarkPlaces
|
||||
bind_song_needed_to_get_under_rock_bulb: BindSongNeededToGetUnderRockBulb
|
||||
unconfine_home_water: UnconfineHomeWater
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,10 @@ Description: Main module for Aquaria game multiworld randomizer
|
||||
from typing import List, Dict, ClassVar, Any
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from BaseClasses import Tutorial, MultiWorld, ItemClassification
|
||||
from .Items import item_table, AquariaItem, ItemType, ItemGroup
|
||||
from .Locations import location_table
|
||||
from .Options import AquariaOptions
|
||||
from .Items import item_table, AquariaItem, ItemType, ItemGroup, ItemNames
|
||||
from .Locations import location_table, AquariaLocationNames
|
||||
from .Options import (AquariaOptions, IngredientRandomizer, TurtleRandomizer, EarlyBindSong, EarlyEnergyForm,
|
||||
UnconfineHomeWater, Objective)
|
||||
from .Regions import AquariaRegions
|
||||
|
||||
|
||||
@@ -65,15 +66,15 @@ class AquariaWorld(World):
|
||||
web: WebWorld = AquariaWeb()
|
||||
"The web page generation informations"
|
||||
|
||||
item_name_to_id: ClassVar[Dict[str, int]] =\
|
||||
item_name_to_id: ClassVar[Dict[str, int]] = \
|
||||
{name: data.id for name, data in item_table.items()}
|
||||
"The name and associated ID of each item of the world"
|
||||
|
||||
item_name_groups = {
|
||||
"Damage": {"Energy form", "Nature form", "Beast form",
|
||||
"Li and Li song", "Baby Nautilus", "Baby Piranha",
|
||||
"Baby Blaster"},
|
||||
"Light": {"Sun form", "Baby Dumbo"}
|
||||
"Damage": {ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
|
||||
ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
|
||||
ItemNames.BABY_BLASTER},
|
||||
"Light": {ItemNames.SUN_FORM, ItemNames.BABY_DUMBO}
|
||||
}
|
||||
"""Grouping item make it easier to find them"""
|
||||
|
||||
@@ -148,23 +149,32 @@ class AquariaWorld(World):
|
||||
def create_items(self) -> None:
|
||||
"""Create every item in the world"""
|
||||
precollected = [item.name for item in self.multiworld.precollected_items[self.player]]
|
||||
if self.options.turtle_randomizer.value > 0:
|
||||
if self.options.turtle_randomizer.value == 2:
|
||||
self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected)
|
||||
if self.options.turtle_randomizer.value != TurtleRandomizer.option_none:
|
||||
if self.options.turtle_randomizer.value == TurtleRandomizer.option_all_except_final:
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_BODY, AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
|
||||
precollected)
|
||||
else:
|
||||
self.__pre_fill_item("Transturtle Veil top left", "The Veil top left area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Veil top right", "The Veil top right area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Open Water top right", "Open Water top right area, Transturtle",
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_VEIL_TOP_LEFT,
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_TRANSTURTLE, precollected)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_VEIL_TOP_RIGHT,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_TRANSTURTLE, precollected)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_OPEN_WATERS,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_TRANSTURTLE,
|
||||
precollected)
|
||||
self.__pre_fill_item("Transturtle Forest bottom left", "Kelp Forest bottom left area, Transturtle",
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_KELP_FOREST,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_TRANSTURTLE,
|
||||
precollected)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_HOME_WATERS, AquariaLocationNames.HOME_WATERS_TRANSTURTLE,
|
||||
precollected)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_ABYSS, AquariaLocationNames.ABYSS_RIGHT_AREA_TRANSTURTLE,
|
||||
precollected)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_BODY, AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
|
||||
precollected)
|
||||
self.__pre_fill_item("Transturtle Home Water", "Home Water, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected)
|
||||
self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected)
|
||||
# The last two are inverted because in the original game, they are special turtle that communicate directly
|
||||
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected,
|
||||
ItemClassification.progression)
|
||||
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_SIMON_SAYS, AquariaLocationNames.ARNASSI_RUINS_TRANSTURTLE,
|
||||
precollected, ItemClassification.progression)
|
||||
self.__pre_fill_item(ItemNames.TRANSTURTLE_ARNASSI_RUINS, AquariaLocationNames.SIMON_SAYS_AREA_TRANSTURTLE,
|
||||
precollected)
|
||||
for name, data in item_table.items():
|
||||
if name not in self.exclude:
|
||||
for i in range(data.count):
|
||||
@@ -175,10 +185,17 @@ class AquariaWorld(World):
|
||||
"""
|
||||
Launched when the Multiworld generator is ready to generate rules
|
||||
"""
|
||||
|
||||
if self.options.early_energy_form == EarlyEnergyForm.option_early:
|
||||
self.multiworld.early_items[self.player][ItemNames.ENERGY_FORM] = 1
|
||||
elif self.options.early_energy_form == EarlyEnergyForm.option_early_and_local:
|
||||
self.multiworld.local_early_items[self.player][ItemNames.ENERGY_FORM] = 1
|
||||
if self.options.early_bind_song == EarlyBindSong.option_early:
|
||||
self.multiworld.early_items[self.player][ItemNames.BIND_SONG] = 1
|
||||
elif self.options.early_bind_song == EarlyBindSong.option_early_and_local:
|
||||
self.multiworld.local_early_items[self.player][ItemNames.BIND_SONG] = 1
|
||||
self.regions.adjusting_rules(self.options)
|
||||
self.multiworld.completion_condition[self.player] = lambda \
|
||||
state: state.has("Victory", self.player)
|
||||
state: state.has(ItemNames.VICTORY, self.player)
|
||||
|
||||
def generate_basic(self) -> None:
|
||||
"""
|
||||
@@ -186,13 +203,13 @@ class AquariaWorld(World):
|
||||
Used to fill then `ingredients_substitution` list
|
||||
"""
|
||||
simple_ingredients_substitution = [i for i in range(27)]
|
||||
if self.options.ingredient_randomizer.value > 0:
|
||||
if self.options.ingredient_randomizer.value == 1:
|
||||
if self.options.ingredient_randomizer.value > IngredientRandomizer.option_off:
|
||||
if self.options.ingredient_randomizer.value == IngredientRandomizer.option_common_ingredients:
|
||||
simple_ingredients_substitution.pop(-1)
|
||||
simple_ingredients_substitution.pop(-1)
|
||||
simple_ingredients_substitution.pop(-1)
|
||||
self.random.shuffle(simple_ingredients_substitution)
|
||||
if self.options.ingredient_randomizer.value == 1:
|
||||
if self.options.ingredient_randomizer.value == IngredientRandomizer.option_common_ingredients:
|
||||
simple_ingredients_substitution.extend([24, 25, 26])
|
||||
dishes_substitution = [i for i in range(27, 76)]
|
||||
if self.options.dish_randomizer:
|
||||
@@ -205,14 +222,19 @@ class AquariaWorld(World):
|
||||
return {"ingredientReplacement": self.ingredients_substitution,
|
||||
"aquarian_translate": bool(self.options.aquarian_translation.value),
|
||||
"blind_goal": bool(self.options.blind_goal.value),
|
||||
"secret_needed": self.options.objective.value > 0,
|
||||
"secret_needed":
|
||||
self.options.objective.value == Objective.option_obtain_secrets_and_kill_the_creator,
|
||||
"minibosses_to_kill": self.options.mini_bosses_to_beat.value,
|
||||
"bigbosses_to_kill": self.options.big_bosses_to_beat.value,
|
||||
"skip_first_vision": bool(self.options.skip_first_vision.value),
|
||||
"unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3],
|
||||
"unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3],
|
||||
"unconfine_home_water_energy_door":
|
||||
self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_energy_door
|
||||
or self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_both,
|
||||
"unconfine_home_water_transturtle":
|
||||
self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_transturtle
|
||||
or self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_both,
|
||||
"bind_song_needed_to_get_under_rock_bulb": bool(self.options.bind_song_needed_to_get_under_rock_bulb),
|
||||
"no_progression_hard_or_hidden_locations": bool(self.options.no_progression_hard_or_hidden_locations),
|
||||
"light_needed_to_get_to_dark_places": bool(self.options.light_needed_to_get_to_dark_places),
|
||||
"turtle_randomizer": self.options.turtle_randomizer.value,
|
||||
"turtle_randomizer": self.options.turtle_randomizer.value
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ The locations in the randomizer are:
|
||||
* Beating Mithalan God boss
|
||||
* Fish Cave puzzle
|
||||
* Beating Drunian God boss
|
||||
* Beating Sun God boss
|
||||
* Beating Lumerean God boss
|
||||
* Breaking Li cage in the body
|
||||
|
||||
Note that, unlike the vanilla game, when opening sing bulbs, Mithalas urns and Sunken City crates,
|
||||
|
||||
@@ -6,211 +6,212 @@ Description: Base class for the Aquaria randomizer unit tests
|
||||
|
||||
|
||||
from test.bases import WorldTestBase
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
# Every location accessible after the home water.
|
||||
after_home_water_locations = [
|
||||
"Sun Crystal",
|
||||
"Home Water, Transturtle",
|
||||
"Open Water top left area, bulb under the rock in the right path",
|
||||
"Open Water top left area, bulb under the rock in the left path",
|
||||
"Open Water top left area, bulb to the right of the save crystal",
|
||||
"Open Water top right area, bulb in the small path before Mithalas",
|
||||
"Open Water top right area, bulb in the path from the left entrance",
|
||||
"Open Water top right area, bulb in the clearing close to the bottom exit",
|
||||
"Open Water top right area, bulb in the big clearing close to the save crystal",
|
||||
"Open Water top right area, bulb in the big clearing to the top exit",
|
||||
"Open Water top right area, first urn in the Mithalas exit",
|
||||
"Open Water top right area, second urn in the Mithalas exit",
|
||||
"Open Water top right area, third urn in the Mithalas exit",
|
||||
"Open Water top right area, bulb in the turtle room",
|
||||
"Open Water top right area, Transturtle",
|
||||
"Open Water bottom left area, bulb behind the chomper fish",
|
||||
"Open Water bottom left area, bulb inside the lowest fish pass",
|
||||
"Open Water skeleton path, bulb close to the right exit",
|
||||
"Open Water skeleton path, bulb behind the chomper fish",
|
||||
"Open Water skeleton path, King Skull",
|
||||
"Arnassi Ruins, bulb in the right part",
|
||||
"Arnassi Ruins, bulb in the left part",
|
||||
"Arnassi Ruins, bulb in the center part",
|
||||
"Arnassi Ruins, Song Plant Spore",
|
||||
"Arnassi Ruins, Arnassi Armor",
|
||||
"Arnassi Ruins, Arnassi Statue",
|
||||
"Arnassi Ruins, Transturtle",
|
||||
"Arnassi Ruins, Crab Armor",
|
||||
"Simon Says area, Transturtle",
|
||||
"Mithalas City, first bulb in the left city part",
|
||||
"Mithalas City, second bulb in the left city part",
|
||||
"Mithalas City, bulb in the right part",
|
||||
"Mithalas City, bulb at the top of the city",
|
||||
"Mithalas City, first bulb in a broken home",
|
||||
"Mithalas City, second bulb in a broken home",
|
||||
"Mithalas City, bulb in the bottom left part",
|
||||
"Mithalas City, first bulb in one of the homes",
|
||||
"Mithalas City, second bulb in one of the homes",
|
||||
"Mithalas City, first urn in one of the homes",
|
||||
"Mithalas City, second urn in one of the homes",
|
||||
"Mithalas City, first urn in the city reserve",
|
||||
"Mithalas City, second urn in the city reserve",
|
||||
"Mithalas City, third urn in the city reserve",
|
||||
"Mithalas City, first bulb at the end of the top path",
|
||||
"Mithalas City, second bulb at the end of the top path",
|
||||
"Mithalas City, bulb in the top path",
|
||||
"Mithalas City, Mithalas Pot",
|
||||
"Mithalas City, urn in the Castle flower tube entrance",
|
||||
"Mithalas City, Doll",
|
||||
"Mithalas City, urn inside a home fish pass",
|
||||
"Mithalas City Castle, bulb in the flesh hole",
|
||||
"Mithalas City Castle, Blue Banner",
|
||||
"Mithalas City Castle, urn in the bedroom",
|
||||
"Mithalas City Castle, first urn of the single lamp path",
|
||||
"Mithalas City Castle, second urn of the single lamp path",
|
||||
"Mithalas City Castle, urn in the bottom room",
|
||||
"Mithalas City Castle, first urn on the entrance path",
|
||||
"Mithalas City Castle, second urn on the entrance path",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Mithalas City Castle, Trident Head",
|
||||
"Mithalas Cathedral, first urn in the top right room",
|
||||
"Mithalas Cathedral, second urn in the top right room",
|
||||
"Mithalas Cathedral, third urn in the top right room",
|
||||
"Mithalas Cathedral, urn in the flesh room with fleas",
|
||||
"Mithalas Cathedral, first urn in the bottom right path",
|
||||
"Mithalas Cathedral, second urn in the bottom right path",
|
||||
"Mithalas Cathedral, urn behind the flesh vein",
|
||||
"Mithalas Cathedral, urn in the top left eyes boss room",
|
||||
"Mithalas Cathedral, first urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, second urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, third urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, fourth urn in the top right room",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Mithalas Cathedral, urn below the left entrance",
|
||||
"Cathedral Underground, bulb in the center part",
|
||||
"Cathedral Underground, first bulb in the top left part",
|
||||
"Cathedral Underground, second bulb in the top left part",
|
||||
"Cathedral Underground, third bulb in the top left part",
|
||||
"Cathedral Underground, bulb close to the save crystal",
|
||||
"Cathedral Underground, bulb in the bottom right path",
|
||||
"Mithalas boss area, beating Mithalan God",
|
||||
"Kelp Forest top left area, bulb in the bottom left clearing",
|
||||
"Kelp Forest top left area, bulb in the path down from the top left clearing",
|
||||
"Kelp Forest top left area, bulb in the top left clearing",
|
||||
"Kelp Forest top left area, Jelly Egg",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Kelp Forest top right area, bulb under the rock in the right path",
|
||||
"Kelp Forest top right area, bulb at the left of the center clearing",
|
||||
"Kelp Forest top right area, bulb in the left path's big room",
|
||||
"Kelp Forest top right area, bulb in the left path's small room",
|
||||
"Kelp Forest top right area, bulb at the top of the center clearing",
|
||||
"Kelp Forest top right area, Black Pearl",
|
||||
"Kelp Forest top right area, bulb in the top fish pass",
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
"Kelp Forest bottom left area, Walker Baby",
|
||||
"Kelp Forest bottom left area, Transturtle",
|
||||
"Kelp Forest bottom right area, Odd Container",
|
||||
"Kelp Forest boss area, beating Drunian God",
|
||||
"Kelp Forest boss room, bulb at the bottom of the area",
|
||||
"Kelp Forest bottom left area, Fish Cave puzzle",
|
||||
"Kelp Forest sprite cave, bulb inside the fish pass",
|
||||
"Kelp Forest sprite cave, bulb in the second room",
|
||||
"Kelp Forest sprite cave, Seed Bag",
|
||||
"Mermog cave, bulb in the left part of the cave",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"The Veil top left area, In Li's cave",
|
||||
"The Veil top left area, bulb under the rock in the top right path",
|
||||
"The Veil top left area, bulb hidden behind the blocking rock",
|
||||
"The Veil top left area, Transturtle",
|
||||
"The Veil top left area, bulb inside the fish pass",
|
||||
"Turtle cave, Turtle Egg",
|
||||
"Turtle cave, bulb in Bubble Cliff",
|
||||
"Turtle cave, Urchin Costume",
|
||||
"The Veil top right area, bulb in the middle of the wall jump cliff",
|
||||
"The Veil top right area, Golden Starfish",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"The Veil top right area, Transturtle",
|
||||
"The Veil bottom area, bulb in the left path",
|
||||
"The Veil bottom area, bulb in the spirit path",
|
||||
"The Veil bottom area, Verse Egg",
|
||||
"The Veil bottom area, Stone Head",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Octopus Cave, bulb in the path below the Octopus Cave path",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Sun Temple, bulb in the top left part",
|
||||
"Sun Temple, bulb in the top right part",
|
||||
"Sun Temple, bulb at the top of the high dark room",
|
||||
"Sun Temple, Golden Gear",
|
||||
"Sun Temple, first bulb of the temple",
|
||||
"Sun Temple, bulb on the right part",
|
||||
"Sun Temple, bulb in the hidden room of the right part",
|
||||
"Sun Temple, Sun Key",
|
||||
"Sun Worm path, first path bulb",
|
||||
"Sun Worm path, second path bulb",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"Sun Temple boss area, beating Sun God",
|
||||
"Abyss left area, bulb in hidden path room",
|
||||
"Abyss left area, bulb in the right part",
|
||||
"Abyss left area, Glowing Seed",
|
||||
"Abyss left area, Glowing Plant",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Abyss right area, bulb in the middle path",
|
||||
"Abyss right area, bulb behind the rock in the middle path",
|
||||
"Abyss right area, bulb in the left green room",
|
||||
"Abyss right area, Transturtle",
|
||||
"Ice Cave, bulb in the room to the right",
|
||||
"Ice Cave, first bulb in the top exit room",
|
||||
"Ice Cave, second bulb in the top exit room",
|
||||
"Ice Cave, third bulb in the top exit room",
|
||||
"Ice Cave, bulb in the left room",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"The Whale, Verse Egg",
|
||||
"Sunken City right area, crate close to the save crystal",
|
||||
"Sunken City right area, crate in the left bottom room",
|
||||
"Sunken City left area, crate in the little pipe room",
|
||||
"Sunken City left area, crate close to the save crystal",
|
||||
"Sunken City left area, crate before the bedroom",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"The Body center area, breaking Li's cage",
|
||||
"The Body center area, bulb on the main path blocking tube",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"The Body bottom area, bulb in the Jelly Zap room",
|
||||
"The Body bottom area, bulb in the nautilus room",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Final Boss area, first bulb in the turtle room",
|
||||
"Final Boss area, second bulb in the turtle room",
|
||||
"Final Boss area, third bulb in the turtle room",
|
||||
"Final Boss area, Transturtle",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Simon Says area, beating Simon Says",
|
||||
"Beating Fallen God",
|
||||
"Beating Mithalan God",
|
||||
"Beating Drunian God",
|
||||
"Beating Sun God",
|
||||
"Beating the Golem",
|
||||
"Beating Nautilus Prime",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Beating Mergog",
|
||||
"Beating Mithalan priests",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"First secret",
|
||||
"Second secret",
|
||||
"Third secret",
|
||||
"Sunken City cleared",
|
||||
"Objective complete",
|
||||
AquariaLocationNames.SUN_CRYSTAL,
|
||||
AquariaLocationNames.HOME_WATERS_TRANSTURTLE,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_LEFT_AREA_BULB_UNDER_THE_ROCK_IN_THE_RIGHT_PATH,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_LEFT_AREA_BULB_UNDER_THE_ROCK_IN_THE_LEFT_PATH,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_LEFT_AREA_BULB_TO_THE_RIGHT_OF_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_SMALL_PATH_BEFORE_MITHALAS,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_PATH_FROM_THE_LEFT_ENTRANCE,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_CLEARING_CLOSE_TO_THE_BOTTOM_EXIT,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_BIG_CLEARING_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_BIG_CLEARING_TO_THE_TOP_EXIT,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_FIRST_URN_IN_THE_MITHALAS_EXIT,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_SECOND_URN_IN_THE_MITHALAS_EXIT,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_THIRD_URN_IN_THE_MITHALAS_EXIT,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_BULB_IN_THE_TURTLE_ROOM,
|
||||
AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.OPEN_WATERS_BOTTOM_LEFT_AREA_BULB_BEHIND_THE_CHOMPER_FISH,
|
||||
AquariaLocationNames.OPEN_WATERS_BOTTOM_LEFT_AREA_BULB_INSIDE_THE_LOWEST_FISH_PASS,
|
||||
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_BULB_CLOSE_TO_THE_RIGHT_EXIT,
|
||||
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_BULB_BEHIND_THE_CHOMPER_FISH,
|
||||
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_KING_SKULL,
|
||||
AquariaLocationNames.ARNASSI_RUINS_BULB_IN_THE_RIGHT_PART,
|
||||
AquariaLocationNames.ARNASSI_RUINS_BULB_IN_THE_LEFT_PART,
|
||||
AquariaLocationNames.ARNASSI_RUINS_BULB_IN_THE_CENTER_PART,
|
||||
AquariaLocationNames.ARNASSI_RUINS_SONG_PLANT_SPORE,
|
||||
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_ARMOR,
|
||||
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_STATUE,
|
||||
AquariaLocationNames.ARNASSI_RUINS_TRANSTURTLE,
|
||||
AquariaLocationNames.ARNASSI_RUINS_CRAB_ARMOR,
|
||||
AquariaLocationNames.SIMON_SAYS_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_IN_THE_LEFT_CITY_PART,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_IN_THE_LEFT_CITY_PART,
|
||||
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_RIGHT_PART,
|
||||
AquariaLocationNames.MITHALAS_CITY_BULB_AT_THE_TOP_OF_THE_CITY,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_IN_A_BROKEN_HOME,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_IN_A_BROKEN_HOME,
|
||||
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_BOTTOM_LEFT_PART,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_IN_ONE_OF_THE_HOMES,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_IN_ONE_OF_THE_HOMES,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_URN_IN_ONE_OF_THE_HOMES,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_URN_IN_ONE_OF_THE_HOMES,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_URN_IN_THE_CITY_RESERVE,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_URN_IN_THE_CITY_RESERVE,
|
||||
AquariaLocationNames.MITHALAS_CITY_THIRD_URN_IN_THE_CITY_RESERVE,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_AT_THE_END_OF_THE_TOP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_AT_THE_END_OF_THE_TOP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_TOP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_MITHALAS_POT,
|
||||
AquariaLocationNames.MITHALAS_CITY_URN_IN_THE_CASTLE_FLOWER_TUBE_ENTRANCE,
|
||||
AquariaLocationNames.MITHALAS_CITY_DOLL,
|
||||
AquariaLocationNames.MITHALAS_CITY_URN_INSIDE_A_HOME_FISH_PASS,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BULB_IN_THE_FLESH_HOLE,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BLUE_BANNER,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_URN_IN_THE_BEDROOM,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_FIRST_URN_OF_THE_SINGLE_LAMP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_SECOND_URN_OF_THE_SINGLE_LAMP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_URN_IN_THE_BOTTOM_ROOM,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_FIRST_URN_ON_THE_ENTRANCE_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_SECOND_URN_ON_THE_ENTRANCE_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_TRIDENT_HEAD,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_FIRST_URN_IN_THE_TOP_RIGHT_ROOM,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_SECOND_URN_IN_THE_TOP_RIGHT_ROOM,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_THIRD_URN_IN_THE_TOP_RIGHT_ROOM,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_FIRST_URN_IN_THE_BOTTOM_RIGHT_PATH,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_SECOND_URN_IN_THE_BOTTOM_RIGHT_PATH,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_URN_BEHIND_THE_FLESH_VEIN,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_URN_IN_THE_TOP_LEFT_EYES_BOSS_ROOM,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_FIRST_URN_IN_THE_PATH_BEHIND_THE_FLESH_VEIN,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_SECOND_URN_IN_THE_PATH_BEHIND_THE_FLESH_VEIN,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_THIRD_URN_IN_THE_PATH_BEHIND_THE_FLESH_VEIN,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_FOURTH_URN_IN_THE_TOP_RIGHT_ROOM,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_MITHALAN_DRESS,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_URN_BELOW_THE_LEFT_ENTRANCE,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_BULB_IN_THE_FLESH_ROOM_WITH_FLEAS,
|
||||
AquariaLocationNames.CATHEDRAL_UNDERGROUND_BULB_IN_THE_CENTER_PART,
|
||||
AquariaLocationNames.CATHEDRAL_UNDERGROUND_FIRST_BULB_IN_THE_TOP_LEFT_PART,
|
||||
AquariaLocationNames.CATHEDRAL_UNDERGROUND_SECOND_BULB_IN_THE_TOP_LEFT_PART,
|
||||
AquariaLocationNames.CATHEDRAL_UNDERGROUND_THIRD_BULB_IN_THE_TOP_LEFT_PART,
|
||||
AquariaLocationNames.CATHEDRAL_UNDERGROUND_BULB_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.CATHEDRAL_UNDERGROUND_BULB_IN_THE_BOTTOM_RIGHT_PATH,
|
||||
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_IN_THE_BOTTOM_LEFT_CLEARING,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_IN_THE_PATH_DOWN_FROM_THE_TOP_LEFT_CLEARING,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_IN_THE_TOP_LEFT_CLEARING,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_JELLY_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_UNDER_THE_ROCK_IN_THE_RIGHT_PATH,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_AT_THE_LEFT_OF_THE_CENTER_CLEARING,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_LEFT_PATH_S_BIG_ROOM,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_LEFT_PATH_S_SMALL_ROOM,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_CENTER_CLEARING,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BLACK_PEARL,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_TOP_FISH_PASS,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_BULB_CLOSE_TO_THE_SPIRIT_CRYSTALS,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_RIGHT_AREA_ODD_CONTAINER,
|
||||
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
|
||||
AquariaLocationNames.KELP_FOREST_BOSS_ROOM_BULB_AT_THE_BOTTOM_OF_THE_AREA,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_FISH_CAVE_PUZZLE,
|
||||
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_BULB_INSIDE_THE_FISH_PASS,
|
||||
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_BULB_IN_THE_SECOND_ROOM,
|
||||
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_SEED_BAG,
|
||||
AquariaLocationNames.MERMOG_CAVE_BULB_IN_THE_LEFT_PART_OF_THE_CAVE,
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_IN_LI_S_CAVE,
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_UNDER_THE_ROCK_IN_THE_TOP_RIGHT_PATH,
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_HIDDEN_BEHIND_THE_BLOCKING_ROCK,
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_INSIDE_THE_FISH_PASS,
|
||||
AquariaLocationNames.TURTLE_CAVE_TURTLE_EGG,
|
||||
AquariaLocationNames.TURTLE_CAVE_BULB_IN_BUBBLE_CLIFF,
|
||||
AquariaLocationNames.TURTLE_CAVE_URCHIN_COSTUME,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_IN_THE_MIDDLE_OF_THE_WALL_JUMP_CLIFF,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_GOLDEN_STARFISH,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_BULB_IN_THE_LEFT_PATH,
|
||||
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_BULB_IN_THE_SPIRIT_PATH,
|
||||
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_VERSE_EGG,
|
||||
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_STONE_HEAD,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_BULB_IN_THE_PATH_BELOW_THE_OCTOPUS_CAVE_PATH,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_LEFT_PART,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_RIGHT_PART,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_AT_THE_TOP_OF_THE_HIGH_DARK_ROOM,
|
||||
AquariaLocationNames.SUN_TEMPLE_GOLDEN_GEAR,
|
||||
AquariaLocationNames.SUN_TEMPLE_FIRST_BULB_OF_THE_TEMPLE,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_ON_THE_RIGHT_PART,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_HIDDEN_ROOM_OF_THE_RIGHT_PART,
|
||||
AquariaLocationNames.SUN_TEMPLE_SUN_KEY,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_PATH_BULB,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_PATH_BULB,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_CLIFF_BULB,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_HIDDEN_PATH_ROOM,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_RIGHT_PART,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_SEED,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_PLANT,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_MIDDLE_PATH,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_MIDDLE_PATH,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_LEFT_GREEN_ROOM,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_ROOM_TO_THE_RIGHT,
|
||||
AquariaLocationNames.ICE_CAVERN_FIRST_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_SECOND_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_THIRD_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_LEFT_ROOM,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
|
||||
AquariaLocationNames.THE_WHALE_VERSE_EGG,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_FIRST_BULB_IN_THE_TURTLE_ROOM,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_SECOND_BULB_IN_THE_TURTLE_ROOM,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_THIRD_BULB_IN_THE_TURTLE_ROOM,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.SIMON_SAYS_AREA_BEATING_SIMON_SAYS,
|
||||
AquariaLocationNames.BEATING_FALLEN_GOD,
|
||||
AquariaLocationNames.BEATING_MITHALAN_GOD,
|
||||
AquariaLocationNames.BEATING_DRUNIAN_GOD,
|
||||
AquariaLocationNames.BEATING_LUMEREAN_GOD,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.BEATING_NAUTILUS_PRIME,
|
||||
AquariaLocationNames.BEATING_BLASTER_PEG_PRIME,
|
||||
AquariaLocationNames.BEATING_MERGOG,
|
||||
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.BEATING_CRABBIUS_MAXIMUS,
|
||||
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
|
||||
AquariaLocationNames.BEATING_KING_JELLYFISH_GOD_PRIME,
|
||||
AquariaLocationNames.FIRST_SECRET,
|
||||
AquariaLocationNames.SECOND_SECRET,
|
||||
AquariaLocationNames.THIRD_SECRET,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE,
|
||||
]
|
||||
|
||||
class AquariaTestBase(WorldTestBase):
|
||||
|
||||
@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class BeastFormAccessTest(AquariaTestBase):
|
||||
@@ -13,16 +15,16 @@ class BeastFormAccessTest(AquariaTestBase):
|
||||
def test_beast_form_location(self) -> None:
|
||||
"""Test locations that require beast form"""
|
||||
locations = [
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Kelp Forest top left area, Jelly Egg",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Beating the Golem",
|
||||
"Beating Mergog",
|
||||
"Beating Octopus Prime",
|
||||
"Sunken City cleared",
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_JELLY_EGG,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_MITHALAN_DRESS,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.BEATING_MERGOG,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
]
|
||||
items = [["Beast form"]]
|
||||
items = [[ItemNames.BEAST_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class BeastForArnassiArmormAccessTest(AquariaTestBase):
|
||||
@@ -13,27 +15,27 @@ class BeastForArnassiArmormAccessTest(AquariaTestBase):
|
||||
def test_beast_form_arnassi_armor_location(self) -> None:
|
||||
"""Test locations that require beast form or arnassi armor"""
|
||||
locations = [
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Arnassi Ruins, Crab Armor",
|
||||
"Arnassi Ruins, Song Plant Spore",
|
||||
"Mithalas City, first bulb at the end of the top path",
|
||||
"Mithalas City, second bulb at the end of the top path",
|
||||
"Mithalas City, bulb in the top path",
|
||||
"Mithalas City, Mithalas Pot",
|
||||
"Mithalas City, urn in the Castle flower tube entrance",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Kelp Forest top left area, Jelly Egg",
|
||||
"The Veil top right area, bulb in the middle of the wall jump cliff",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Beating the Golem",
|
||||
"Beating Mergog",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Mithalan priests",
|
||||
"Sunken City cleared"
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
|
||||
AquariaLocationNames.ARNASSI_RUINS_CRAB_ARMOR,
|
||||
AquariaLocationNames.ARNASSI_RUINS_SONG_PLANT_SPORE,
|
||||
AquariaLocationNames.MITHALAS_CITY_FIRST_BULB_AT_THE_END_OF_THE_TOP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_SECOND_BULB_AT_THE_END_OF_THE_TOP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_BULB_IN_THE_TOP_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_MITHALAS_POT,
|
||||
AquariaLocationNames.MITHALAS_CITY_URN_IN_THE_CASTLE_FLOWER_TUBE_ENTRANCE,
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.MITHALAS_CATHEDRAL_MITHALAN_DRESS,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_JELLY_EGG,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_IN_THE_MIDDLE_OF_THE_WALL_JUMP_CLIFF,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.BEATING_MERGOG,
|
||||
AquariaLocationNames.BEATING_CRABBIUS_MAXIMUS,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED
|
||||
]
|
||||
items = [["Beast form", "Arnassi Armor"]]
|
||||
items = [[ItemNames.BEAST_FORM, ItemNames.ARNASSI_ARMOR]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -6,31 +6,36 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase, after_home_water_locations
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import UnconfineHomeWater, EarlyBindSong
|
||||
|
||||
|
||||
class BindSongAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the bind song"""
|
||||
options = {
|
||||
"bind_song_needed_to_get_under_rock_bulb": False,
|
||||
"unconfine_home_water": UnconfineHomeWater.option_off,
|
||||
"early_bind_song": EarlyBindSong.option_off
|
||||
}
|
||||
|
||||
def test_bind_song_location(self) -> None:
|
||||
"""Test locations that require Bind song"""
|
||||
locations = [
|
||||
"Verse Cave right area, Big Seed",
|
||||
"Home Water, bulb in the path below Nautilus Prime",
|
||||
"Home Water, bulb in the bottom left room",
|
||||
"Home Water, Nautilus Egg",
|
||||
"Song Cave, Verse Egg",
|
||||
"Energy Temple first area, beating the Energy Statue",
|
||||
"Energy Temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy Temple first area, Energy Idol",
|
||||
"Energy Temple second area, bulb under the rock",
|
||||
"Energy Temple bottom entrance, Krotite Armor",
|
||||
"Energy Temple third area, bulb in the bottom path",
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
AquariaLocationNames.VERSE_CAVE_RIGHT_AREA_BIG_SEED,
|
||||
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_PATH_BELOW_NAUTILUS_PRIME,
|
||||
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_BOTTOM_LEFT_ROOM,
|
||||
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
|
||||
AquariaLocationNames.SONG_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BEATING_THE_ENERGY_STATUE,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BULB_IN_THE_BOTTOM_ROOM_BLOCKED_BY_A_ROCK,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_ENERGY_IDOL,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOTTOM_ENTRANCE_KROTITE_ARMOR,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
|
||||
*after_home_water_locations
|
||||
]
|
||||
items = [["Bind song"]]
|
||||
items = [[ItemNames.BIND_SONG]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -7,6 +7,8 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
|
||||
from . import AquariaTestBase
|
||||
from .test_bind_song_access import after_home_water_locations
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class BindSongOptionAccessTest(AquariaTestBase):
|
||||
@@ -18,25 +20,25 @@ class BindSongOptionAccessTest(AquariaTestBase):
|
||||
def test_bind_song_location(self) -> None:
|
||||
"""Test locations that require Bind song with the bind song needed option activated"""
|
||||
locations = [
|
||||
"Verse Cave right area, Big Seed",
|
||||
"Verse Cave left area, bulb under the rock at the end of the path",
|
||||
"Home Water, bulb under the rock in the left path from the Verse Cave",
|
||||
"Song Cave, bulb under the rock close to the song door",
|
||||
"Song Cave, bulb under the rock in the path to the singing statues",
|
||||
"Naija's Home, bulb under the rock at the right of the main path",
|
||||
"Home Water, bulb in the path below Nautilus Prime",
|
||||
"Home Water, bulb in the bottom left room",
|
||||
"Home Water, Nautilus Egg",
|
||||
"Song Cave, Verse Egg",
|
||||
"Energy Temple first area, beating the Energy Statue",
|
||||
"Energy Temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy Temple first area, Energy Idol",
|
||||
"Energy Temple second area, bulb under the rock",
|
||||
"Energy Temple bottom entrance, Krotite Armor",
|
||||
"Energy Temple third area, bulb in the bottom path",
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
AquariaLocationNames.VERSE_CAVE_RIGHT_AREA_BIG_SEED,
|
||||
AquariaLocationNames.VERSE_CAVE_LEFT_AREA_BULB_UNDER_THE_ROCK_AT_THE_END_OF_THE_PATH,
|
||||
AquariaLocationNames.HOME_WATERS_BULB_UNDER_THE_ROCK_IN_THE_LEFT_PATH_FROM_THE_VERSE_CAVE,
|
||||
AquariaLocationNames.SONG_CAVE_BULB_UNDER_THE_ROCK_CLOSE_TO_THE_SONG_DOOR,
|
||||
AquariaLocationNames.SONG_CAVE_BULB_UNDER_THE_ROCK_IN_THE_PATH_TO_THE_SINGING_STATUES,
|
||||
AquariaLocationNames.NAIJA_S_HOME_BULB_UNDER_THE_ROCK_AT_THE_RIGHT_OF_THE_MAIN_PATH,
|
||||
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_PATH_BELOW_NAUTILUS_PRIME,
|
||||
AquariaLocationNames.HOME_WATERS_BULB_IN_THE_BOTTOM_LEFT_ROOM,
|
||||
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
|
||||
AquariaLocationNames.SONG_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BEATING_THE_ENERGY_STATUE,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_FIRST_AREA_BULB_IN_THE_BOTTOM_ROOM_BLOCKED_BY_A_ROCK,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_ENERGY_IDOL,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOTTOM_ENTRANCE_KROTITE_ARMOR,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
|
||||
*after_home_water_locations
|
||||
]
|
||||
items = [["Bind song"]]
|
||||
items = [[ItemNames.BIND_SONG]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,16 +5,17 @@ Description: Unit test used to test accessibility of region with the home water
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Options import UnconfineHomeWater, EarlyEnergyForm
|
||||
|
||||
|
||||
class ConfinedHomeWaterAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option disabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 0,
|
||||
"early_energy_form": False
|
||||
"unconfine_home_water": UnconfineHomeWater.option_off,
|
||||
"early_energy_form": EarlyEnergyForm.option_off
|
||||
}
|
||||
|
||||
def test_confine_home_water_location(self) -> None:
|
||||
"""Test region accessible with confined home water"""
|
||||
self.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area")
|
||||
self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room")
|
||||
self.assertFalse(self.can_reach_region("Open Waters top left area"), "Can reach Open Waters top left area")
|
||||
self.assertFalse(self.can_reach_region("Home Waters, turtle room"), "Can reach Home Waters, turtle room")
|
||||
|
||||
@@ -5,22 +5,25 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import TurtleRandomizer
|
||||
|
||||
|
||||
class LiAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the dual song"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
"turtle_randomizer": TurtleRandomizer.option_all,
|
||||
}
|
||||
|
||||
def test_li_song_location(self) -> None:
|
||||
"""Test locations that require the dual song"""
|
||||
locations = [
|
||||
"The Body bottom area, bulb in the Jelly Zap room",
|
||||
"The Body bottom area, bulb in the nautilus room",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE
|
||||
]
|
||||
items = [["Dual form"]]
|
||||
items = [[ItemNames.DUAL_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -6,28 +6,31 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import EarlyEnergyForm
|
||||
|
||||
|
||||
class EnergyFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the energy form"""
|
||||
options = {
|
||||
"early_energy_form": False,
|
||||
"early_energy_form": EarlyEnergyForm.option_off
|
||||
}
|
||||
|
||||
def test_energy_form_location(self) -> None:
|
||||
"""Test locations that require Energy form"""
|
||||
locations = [
|
||||
"Energy Temple second area, bulb under the rock",
|
||||
"Energy Temple third area, bulb in the bottom path",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Objective complete",
|
||||
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE,
|
||||
]
|
||||
items = [["Energy form"]]
|
||||
items = [[ItemNames.ENERGY_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,88 +5,74 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import EarlyEnergyForm, TurtleRandomizer
|
||||
|
||||
|
||||
class EnergyFormDualFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the energy form and dual form (and Li)"""
|
||||
options = {
|
||||
"early_energy_form": False,
|
||||
"early_energy_form": EarlyEnergyForm.option_off,
|
||||
"turtle_randomizer": TurtleRandomizer.option_all
|
||||
}
|
||||
|
||||
def test_energy_form_or_dual_form_location(self) -> None:
|
||||
"""Test locations that require Energy form or dual form"""
|
||||
locations = [
|
||||
"Naija's Home, bulb after the energy door",
|
||||
"Home Water, Nautilus Egg",
|
||||
"Energy Temple second area, bulb under the rock",
|
||||
"Energy Temple bottom entrance, Krotite Armor",
|
||||
"Energy Temple third area, bulb in the bottom path",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Mithalas boss area, beating Mithalan God",
|
||||
"Mithalas Cathedral, first urn in the top right room",
|
||||
"Mithalas Cathedral, second urn in the top right room",
|
||||
"Mithalas Cathedral, third urn in the top right room",
|
||||
"Mithalas Cathedral, urn in the flesh room with fleas",
|
||||
"Mithalas Cathedral, first urn in the bottom right path",
|
||||
"Mithalas Cathedral, second urn in the bottom right path",
|
||||
"Mithalas Cathedral, urn behind the flesh vein",
|
||||
"Mithalas Cathedral, urn in the top left eyes boss room",
|
||||
"Mithalas Cathedral, first urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, second urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, third urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, fourth urn in the top right room",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Mithalas Cathedral, urn below the left entrance",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Kelp Forest boss area, beating Drunian God",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Sun Temple boss area, beating Sun God",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"Sunken City right area, crate close to the save crystal",
|
||||
"Sunken City right area, crate in the left bottom room",
|
||||
"Sunken City left area, crate in the little pipe room",
|
||||
"Sunken City left area, crate close to the save crystal",
|
||||
"Sunken City left area, crate before the bedroom",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"The Body center area, breaking Li's cage",
|
||||
"The Body center area, bulb on the main path blocking tube",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"The Body bottom area, bulb in the Jelly Zap room",
|
||||
"The Body bottom area, bulb in the nautilus room",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Final Boss area, first bulb in the turtle room",
|
||||
"Final Boss area, second bulb in the turtle room",
|
||||
"Final Boss area, third bulb in the turtle room",
|
||||
"Final Boss area, Transturtle",
|
||||
"Beating Fallen God",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Beating Mithalan God",
|
||||
"Beating Drunian God",
|
||||
"Beating Sun God",
|
||||
"Beating the Golem",
|
||||
"Beating Nautilus Prime",
|
||||
"Beating Mergog",
|
||||
"Beating Mithalan priests",
|
||||
"Beating Octopus Prime",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"First secret",
|
||||
"Objective complete"
|
||||
AquariaLocationNames.NAIJA_S_HOME_BULB_AFTER_THE_ENERGY_DOOR,
|
||||
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_SECOND_AREA_BULB_UNDER_THE_ROCK,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOTTOM_ENTRANCE_KROTITE_ARMOR,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_THIRD_AREA_BULB_IN_THE_BOTTOM_PATH,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
|
||||
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.BEATING_FALLEN_GOD,
|
||||
AquariaLocationNames.BEATING_BLASTER_PEG_PRIME,
|
||||
AquariaLocationNames.BEATING_MITHALAN_GOD,
|
||||
AquariaLocationNames.BEATING_DRUNIAN_GOD,
|
||||
AquariaLocationNames.BEATING_LUMEREAN_GOD,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.BEATING_NAUTILUS_PRIME,
|
||||
AquariaLocationNames.BEATING_MERGOG,
|
||||
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.BEATING_KING_JELLYFISH_GOD_PRIME,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
AquariaLocationNames.FIRST_SECRET,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE
|
||||
]
|
||||
items = [["Energy form", "Dual form", "Li and Li song", "Body tongue cleared"]]
|
||||
items = [[ItemNames.ENERGY_FORM, ItemNames.DUAL_FORM, ItemNames.LI_AND_LI_SONG, ItemNames.BODY_TONGUE_CLEARED]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,33 +5,36 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import TurtleRandomizer
|
||||
|
||||
|
||||
class FishFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the fish form"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
"turtle_randomizer": TurtleRandomizer.option_all,
|
||||
}
|
||||
|
||||
def test_fish_form_location(self) -> None:
|
||||
"""Test locations that require fish form"""
|
||||
locations = [
|
||||
"The Veil top left area, bulb inside the fish pass",
|
||||
"Energy Temple first area, Energy Idol",
|
||||
"Mithalas City, Doll",
|
||||
"Mithalas City, urn inside a home fish pass",
|
||||
"Kelp Forest top right area, bulb in the top fish pass",
|
||||
"The Veil bottom area, Verse Egg",
|
||||
"Open Water bottom left area, bulb inside the lowest fish pass",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Mermog cave, bulb in the left part of the cave",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Beating Mergog",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Octopus Cave, bulb in the path below the Octopus Cave path",
|
||||
"Beating Octopus Prime",
|
||||
"Abyss left area, bulb in the bottom fish pass"
|
||||
AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_BULB_INSIDE_THE_FISH_PASS,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_ENERGY_IDOL,
|
||||
AquariaLocationNames.MITHALAS_CITY_DOLL,
|
||||
AquariaLocationNames.MITHALAS_CITY_URN_INSIDE_A_HOME_FISH_PASS,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BULB_IN_THE_TOP_FISH_PASS,
|
||||
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_VERSE_EGG,
|
||||
AquariaLocationNames.OPEN_WATERS_BOTTOM_LEFT_AREA_BULB_INSIDE_THE_LOWEST_FISH_PASS,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
|
||||
AquariaLocationNames.MERMOG_CAVE_BULB_IN_THE_LEFT_PART_OF_THE_CAVE,
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.BEATING_MERGOG,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_BULB_IN_THE_PATH_BELOW_THE_OCTOPUS_CAVE_PATH,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS
|
||||
]
|
||||
items = [["Fish form"]]
|
||||
items = [[ItemNames.FISH_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,41 +5,44 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import TurtleRandomizer
|
||||
|
||||
|
||||
class LiAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without Li"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
"turtle_randomizer": TurtleRandomizer.option_all,
|
||||
}
|
||||
|
||||
def test_li_song_location(self) -> None:
|
||||
"""Test locations that require Li"""
|
||||
locations = [
|
||||
"Sunken City right area, crate close to the save crystal",
|
||||
"Sunken City right area, crate in the left bottom room",
|
||||
"Sunken City left area, crate in the little pipe room",
|
||||
"Sunken City left area, crate close to the save crystal",
|
||||
"Sunken City left area, crate before the bedroom",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"The Body center area, breaking Li's cage",
|
||||
"The Body center area, bulb on the main path blocking tube",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"The Body bottom area, bulb in the Jelly Zap room",
|
||||
"The Body bottom area, bulb in the nautilus room",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"Objective complete"
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE
|
||||
]
|
||||
items = [["Li and Li song", "Body tongue cleared"]]
|
||||
items = [[ItemNames.LI_AND_LI_SONG, ItemNames.BODY_TONGUE_CLEARED]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,12 +5,15 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import TurtleRandomizer
|
||||
|
||||
|
||||
class LightAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without light"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
"turtle_randomizer": TurtleRandomizer.option_all,
|
||||
"light_needed_to_get_to_dark_places": True,
|
||||
}
|
||||
|
||||
@@ -19,52 +22,52 @@ class LightAccessTest(AquariaTestBase):
|
||||
locations = [
|
||||
# Since the `assertAccessDependency` sweep for events even if I tell it not to, those location cannot be
|
||||
# tested.
|
||||
# "Third secret",
|
||||
# "Sun Temple, bulb in the top left part",
|
||||
# "Sun Temple, bulb in the top right part",
|
||||
# "Sun Temple, bulb at the top of the high dark room",
|
||||
# "Sun Temple, Golden Gear",
|
||||
# "Sun Worm path, first path bulb",
|
||||
# "Sun Worm path, second path bulb",
|
||||
# "Sun Worm path, first cliff bulb",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Kelp Forest bottom right area, Odd Container",
|
||||
"Kelp Forest top right area, Black Pearl",
|
||||
"Abyss left area, bulb in hidden path room",
|
||||
"Abyss left area, bulb in the right part",
|
||||
"Abyss left area, Glowing Seed",
|
||||
"Abyss left area, Glowing Plant",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Abyss right area, bulb in the middle path",
|
||||
"Abyss right area, bulb behind the rock in the middle path",
|
||||
"Abyss right area, bulb in the left green room",
|
||||
"Ice Cave, bulb in the room to the right",
|
||||
"Ice Cave, first bulb in the top exit room",
|
||||
"Ice Cave, second bulb in the top exit room",
|
||||
"Ice Cave, third bulb in the top exit room",
|
||||
"Ice Cave, bulb in the left room",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"The Whale, Verse Egg",
|
||||
"First secret",
|
||||
"Sunken City right area, crate close to the save crystal",
|
||||
"Sunken City right area, crate in the left bottom room",
|
||||
"Sunken City left area, crate in the little pipe room",
|
||||
"Sunken City left area, crate close to the save crystal",
|
||||
"Sunken City left area, crate before the bedroom",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Sunken City cleared",
|
||||
"Beating the Golem",
|
||||
"Beating Octopus Prime",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Objective complete",
|
||||
# AquariaLocationNames.THIRD_SECRET,
|
||||
# AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_LEFT_PART,
|
||||
# AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_TOP_RIGHT_PART,
|
||||
# AquariaLocationNames.SUN_TEMPLE_BULB_AT_THE_TOP_OF_THE_HIGH_DARK_ROOM,
|
||||
# AquariaLocationNames.SUN_TEMPLE_GOLDEN_GEAR,
|
||||
# AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_PATH_BULB,
|
||||
# AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_PATH_BULB,
|
||||
# AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_RIGHT_AREA_ODD_CONTAINER,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_RIGHT_AREA_BLACK_PEARL,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_HIDDEN_PATH_ROOM,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_RIGHT_PART,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_SEED,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_GLOWING_PLANT,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_MIDDLE_PATH,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_MIDDLE_PATH,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_IN_THE_LEFT_GREEN_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_ROOM_TO_THE_RIGHT,
|
||||
AquariaLocationNames.ICE_CAVERN_FIRST_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_SECOND_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_THIRD_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_LEFT_ROOM,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
|
||||
AquariaLocationNames.BEATING_KING_JELLYFISH_GOD_PRIME,
|
||||
AquariaLocationNames.THE_WHALE_VERSE_EGG,
|
||||
AquariaLocationNames.FIRST_SECRET,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE,
|
||||
]
|
||||
items = [["Sun form", "Baby Dumbo", "Has sun crystal"]]
|
||||
items = [[ItemNames.SUN_FORM, ItemNames.BABY_DUMBO, ItemNames.HAS_SUN_CRYSTAL]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,53 +5,56 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
from ..Options import TurtleRandomizer
|
||||
|
||||
|
||||
class NatureFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the nature form"""
|
||||
options = {
|
||||
"turtle_randomizer": 1,
|
||||
"turtle_randomizer": TurtleRandomizer.option_all,
|
||||
}
|
||||
|
||||
def test_nature_form_location(self) -> None:
|
||||
"""Test locations that require nature form"""
|
||||
locations = [
|
||||
"Song Cave, Anemone Seed",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Kelp Forest sprite cave, bulb in the second room",
|
||||
"Kelp Forest sprite cave, Seed Bag",
|
||||
"Beating Mithalan priests",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"Sunken City right area, crate close to the save crystal",
|
||||
"Sunken City right area, crate in the left bottom room",
|
||||
"Sunken City left area, crate in the little pipe room",
|
||||
"Sunken City left area, crate close to the save crystal",
|
||||
"Sunken City left area, crate before the bedroom",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"The Body center area, breaking Li's cage",
|
||||
"The Body center area, bulb on the main path blocking tube",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"The Body bottom area, bulb in the Jelly Zap room",
|
||||
"The Body bottom area, bulb in the nautilus room",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
AquariaLocationNames.SONG_CAVE_ANEMONE_SEED,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
|
||||
AquariaLocationNames.BEATING_BLASTER_PEG_PRIME,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_TOP_LEFT_AREA_BULB_CLOSE_TO_THE_VERSE_EGG,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
|
||||
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_BULB_IN_THE_SECOND_ROOM,
|
||||
AquariaLocationNames.KELP_FOREST_SPRITE_CAVE_SEED_BAG,
|
||||
AquariaLocationNames.BEATING_MITHALAN_PRIESTS,
|
||||
AquariaLocationNames.ABYSS_LEFT_AREA_BULB_IN_THE_BOTTOM_FISH_PASS,
|
||||
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_RIGHT_AREA_CRATE_IN_THE_LEFT_BOTTOM_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_IN_THE_LITTLE_PIPE_ROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_CLOSE_TO_THE_SAVE_CRYSTAL,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_CRATE_BEFORE_THE_BEDROOM,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BREAKING_LI_S_CAGE,
|
||||
AquariaLocationNames.THE_BODY_CENTER_AREA_BULB_ON_THE_MAIN_PATH_BLOCKING_TUBE,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_FIRST_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_SECOND_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_BELOW_THE_WATER_STREAM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_LEFT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_TOP_PATH_TO_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_RIGHT_AREA_BULB_IN_THE_BOTTOM_FACE_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_JELLY_ZAP_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_BULB_IN_THE_NAUTILUS_ROOM,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE
|
||||
]
|
||||
items = [["Nature form"]]
|
||||
items = [[ItemNames.NATURE_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -6,6 +6,7 @@ Description: Unit test used to test that no progression items can be put in hard
|
||||
|
||||
from . import AquariaTestBase
|
||||
from BaseClasses import ItemClassification
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
@@ -15,31 +16,31 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
}
|
||||
|
||||
unfillable_locations = [
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Mithalas boss area, beating Mithalan God",
|
||||
"Kelp Forest boss area, beating Drunian God",
|
||||
"Sun Temple boss area, beating Sun God",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Home Water, Nautilus Egg",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
"Kelp Forest bottom left area, Walker Baby",
|
||||
"Sun Temple, Sun Key",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Sun Temple, bulb in the hidden room of the right part",
|
||||
"Arnassi Ruins, Arnassi Armor",
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
|
||||
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
|
||||
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_CLIFF_BULB,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_BULB_CLOSE_TO_THE_SPIRIT_CRYSTALS,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
|
||||
AquariaLocationNames.SUN_TEMPLE_SUN_KEY,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_HIDDEN_ROOM_OF_THE_RIGHT_PART,
|
||||
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_ARMOR,
|
||||
]
|
||||
|
||||
def test_unconfine_home_water_both_location_fillable(self) -> None:
|
||||
|
||||
@@ -5,6 +5,7 @@ Description: Unit test used to test that progression items can be put in hard or
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
@@ -14,31 +15,31 @@ class UNoProgressionHardHiddenTest(AquariaTestBase):
|
||||
}
|
||||
|
||||
unfillable_locations = [
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Mithalas boss area, beating Mithalan God",
|
||||
"Kelp Forest boss area, beating Drunian God",
|
||||
"Sun Temple boss area, beating Sun God",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Home Water, Nautilus Egg",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
"Kelp Forest bottom left area, Walker Baby",
|
||||
"Sun Temple, Sun Key",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Sun Temple, bulb in the hidden room of the right part",
|
||||
"Arnassi Ruins, Arnassi Armor",
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BOSS_AREA_FALLEN_GOD_TOOTH,
|
||||
AquariaLocationNames.MITHALAS_BOSS_AREA_BEATING_MITHALAN_GOD,
|
||||
AquariaLocationNames.KELP_FOREST_BOSS_AREA_BEATING_DRUNIAN_GOD,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_AREA_BEATING_LUMEREAN_GOD,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.HOME_WATERS_NAUTILUS_EGG,
|
||||
AquariaLocationNames.ENERGY_TEMPLE_BLASTER_ROOM_BLASTER_EGG,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_BEATING_THE_PRIESTS,
|
||||
AquariaLocationNames.MERMOG_CAVE_PIRANHA_EGG,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_BULB_IN_THE_RIGHT_PATH_FROM_KING_JELLY,
|
||||
AquariaLocationNames.KING_JELLYFISH_CAVE_JELLYFISH_COSTUME,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_FIRST_CLIFF_BULB,
|
||||
AquariaLocationNames.SUN_TEMPLE_BOSS_PATH_SECOND_CLIFF_BULB,
|
||||
AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_BULB_AT_THE_TOP_OF_THE_WATERFALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_BULB_CLOSE_TO_THE_SPIRIT_CRYSTALS,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
|
||||
AquariaLocationNames.SUN_TEMPLE_SUN_KEY,
|
||||
AquariaLocationNames.THE_BODY_BOTTOM_AREA_MUTANT_COSTUME,
|
||||
AquariaLocationNames.SUN_TEMPLE_BULB_IN_THE_HIDDEN_ROOM_OF_THE_RIGHT_PART,
|
||||
AquariaLocationNames.ARNASSI_RUINS_ARNASSI_ARMOR,
|
||||
]
|
||||
|
||||
def test_unconfine_home_water_both_location_fillable(self) -> None:
|
||||
|
||||
@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class SpiritFormAccessTest(AquariaTestBase):
|
||||
@@ -13,23 +15,23 @@ class SpiritFormAccessTest(AquariaTestBase):
|
||||
def test_spirit_form_location(self) -> None:
|
||||
"""Test locations that require spirit form"""
|
||||
locations = [
|
||||
"The Veil bottom area, bulb in the spirit path",
|
||||
"Mithalas City Castle, Trident Head",
|
||||
"Open Water skeleton path, King Skull",
|
||||
"Kelp Forest bottom left area, Walker Baby",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"The Whale, Verse Egg",
|
||||
"Ice Cave, bulb in the room to the right",
|
||||
"Ice Cave, first bulb in the top exit room",
|
||||
"Ice Cave, second bulb in the top exit room",
|
||||
"Ice Cave, third bulb in the top exit room",
|
||||
"Ice Cave, bulb in the left room",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"First secret",
|
||||
AquariaLocationNames.THE_VEIL_BOTTOM_AREA_BULB_IN_THE_SPIRIT_PATH,
|
||||
AquariaLocationNames.MITHALAS_CITY_CASTLE_TRIDENT_HEAD,
|
||||
AquariaLocationNames.OPEN_WATERS_SKELETON_PATH_KING_SKULL,
|
||||
AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_WALKER_BABY,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
|
||||
AquariaLocationNames.THE_WHALE_VERSE_EGG,
|
||||
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_ROOM_TO_THE_RIGHT,
|
||||
AquariaLocationNames.ICE_CAVERN_FIRST_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_SECOND_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_THIRD_BULB_IN_THE_TOP_EXIT_ROOM,
|
||||
AquariaLocationNames.ICE_CAVERN_BULB_IN_THE_LEFT_ROOM,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_LEFT_CAVE_WALL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_BULB_IN_THE_RIGHT_CAVE_WALL_BEHIND_THE_ICE_CRYSTAL,
|
||||
AquariaLocationNames.BUBBLE_CAVE_VERSE_EGG,
|
||||
AquariaLocationNames.SUNKEN_CITY_LEFT_AREA_GIRL_COSTUME,
|
||||
AquariaLocationNames.BEATING_MANTIS_SHRIMP_PRIME,
|
||||
AquariaLocationNames.FIRST_SECRET,
|
||||
]
|
||||
items = [["Spirit form"]]
|
||||
items = [[ItemNames.SPIRIT_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -5,6 +5,8 @@ Description: Unit test used to test accessibility of locations with and without
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Items import ItemNames
|
||||
from ..Locations import AquariaLocationNames
|
||||
|
||||
|
||||
class SunFormAccessTest(AquariaTestBase):
|
||||
@@ -13,16 +15,16 @@ class SunFormAccessTest(AquariaTestBase):
|
||||
def test_sun_form_location(self) -> None:
|
||||
"""Test locations that require sun form"""
|
||||
locations = [
|
||||
"First secret",
|
||||
"The Whale, Verse Egg",
|
||||
"Abyss right area, bulb behind the rock in the whale room",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Beating Octopus Prime",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Objective complete"
|
||||
AquariaLocationNames.FIRST_SECRET,
|
||||
AquariaLocationNames.THE_WHALE_VERSE_EGG,
|
||||
AquariaLocationNames.ABYSS_RIGHT_AREA_BULB_BEHIND_THE_ROCK_IN_THE_WHALE_ROOM,
|
||||
AquariaLocationNames.OCTOPUS_CAVE_DUMBO_EGG,
|
||||
AquariaLocationNames.BEATING_OCTOPUS_PRIME,
|
||||
AquariaLocationNames.SUNKEN_CITY_BULB_ON_TOP_OF_THE_BOSS_AREA,
|
||||
AquariaLocationNames.BEATING_THE_GOLEM,
|
||||
AquariaLocationNames.SUNKEN_CITY_CLEARED,
|
||||
AquariaLocationNames.FINAL_BOSS_AREA_BULB_IN_THE_BOSS_THIRD_FORM_ROOM,
|
||||
AquariaLocationNames.OBJECTIVE_COMPLETE
|
||||
]
|
||||
items = [["Sun form"]]
|
||||
items = [[ItemNames.SUN_FORM]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -6,16 +6,17 @@ Description: Unit test used to test accessibility of region with the unconfined
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Options import UnconfineHomeWater, EarlyEnergyForm
|
||||
|
||||
|
||||
class UnconfineHomeWaterBothAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 3,
|
||||
"early_energy_form": False
|
||||
"unconfine_home_water": UnconfineHomeWater.option_via_both,
|
||||
"early_energy_form": EarlyEnergyForm.option_off
|
||||
}
|
||||
|
||||
def test_unconfine_home_water_both_location(self) -> None:
|
||||
"""Test locations accessible with unconfined home water via energy door and transportation turtle"""
|
||||
self.assertTrue(self.can_reach_region("Open Water top left area"), "Cannot reach Open Water top left area")
|
||||
self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room")
|
||||
self.assertTrue(self.can_reach_region("Open Waters top left area"), "Cannot reach Open Waters top left area")
|
||||
self.assertTrue(self.can_reach_region("Home Waters, turtle room"), "Cannot reach Home Waters, turtle room")
|
||||
|
||||
@@ -5,16 +5,17 @@ Description: Unit test used to test accessibility of region with the unconfined
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Options import UnconfineHomeWater, EarlyEnergyForm
|
||||
|
||||
|
||||
class UnconfineHomeWaterEnergyDoorAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 1,
|
||||
"early_energy_form": False
|
||||
"unconfine_home_water": UnconfineHomeWater.option_via_energy_door,
|
||||
"early_energy_form": EarlyEnergyForm.option_off
|
||||
}
|
||||
|
||||
def test_unconfine_home_water_energy_door_location(self) -> None:
|
||||
"""Test locations accessible with unconfined home water via energy door"""
|
||||
self.assertTrue(self.can_reach_region("Open Water top left area"), "Cannot reach Open Water top left area")
|
||||
self.assertFalse(self.can_reach_region("Home Water, turtle room"), "Can reach Home Water, turtle room")
|
||||
self.assertTrue(self.can_reach_region("Open Waters top left area"), "Cannot reach Open Waters top left area")
|
||||
self.assertFalse(self.can_reach_region("Home Waters, turtle room"), "Can reach Home Waters, turtle room")
|
||||
|
||||
@@ -5,16 +5,17 @@ Description: Unit test used to test accessibility of region with the unconfined
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
from ..Options import UnconfineHomeWater, EarlyEnergyForm
|
||||
|
||||
|
||||
class UnconfineHomeWaterTransturtleAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of region with the unconfine home water option enabled"""
|
||||
options = {
|
||||
"unconfine_home_water": 2,
|
||||
"early_energy_form": False
|
||||
"unconfine_home_water": UnconfineHomeWater.option_via_transturtle,
|
||||
"early_energy_form": EarlyEnergyForm.option_off
|
||||
}
|
||||
|
||||
def test_unconfine_home_water_transturtle_location(self) -> None:
|
||||
"""Test locations accessible with unconfined home water via transportation turtle"""
|
||||
self.assertTrue(self.can_reach_region("Home Water, turtle room"), "Cannot reach Home Water, turtle room")
|
||||
self.assertFalse(self.can_reach_region("Open Water top left area"), "Can reach Open Water top left area")
|
||||
self.assertTrue(self.can_reach_region("Home Waters, turtle room"), "Cannot reach Home Waters, turtle room")
|
||||
self.assertFalse(self.can_reach_region("Open Waters top left area"), "Can reach Open Waters top left area")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions, OptionGroup
|
||||
from Options import Choice, Toggle, DefaultOnToggle, DeathLink, PerGameCommonOptions, StartInventoryPool, OptionGroup
|
||||
import random
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ class BlasphemousDeathLink(DeathLink):
|
||||
|
||||
@dataclass
|
||||
class BlasphemousOptions(PerGameCommonOptions):
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
prie_dieu_warp: PrieDieuWarp
|
||||
skip_cutscenes: SkipCutscenes
|
||||
corpse_hints: CorpseHints
|
||||
|
||||
@@ -137,12 +137,6 @@ class BlasphemousWorld(World):
|
||||
]
|
||||
|
||||
skipped_items = []
|
||||
junk: int = 0
|
||||
|
||||
for item, count in self.options.start_inventory.value.items():
|
||||
for _ in range(count):
|
||||
skipped_items.append(item)
|
||||
junk += 1
|
||||
|
||||
skipped_items.extend(unrandomized_dict.values())
|
||||
|
||||
@@ -194,9 +188,6 @@ class BlasphemousWorld(World):
|
||||
for _ in range(count):
|
||||
pool.append(self.create_item(item["name"]))
|
||||
|
||||
for _ in range(junk):
|
||||
pool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
self.multiworld.itempool += pool
|
||||
|
||||
self.place_items_from_dict(unrandomized_dict)
|
||||
|
||||
@@ -253,10 +253,10 @@ all_bosses = [
|
||||
}),
|
||||
DS3BossInfo("Lords of Cinder", 4100800, locations = {
|
||||
"KFF: Soul of the Lords",
|
||||
"FS: Billed Mask - Yuria after killing KFF boss",
|
||||
"FS: Black Dress - Yuria after killing KFF boss",
|
||||
"FS: Black Gauntlets - Yuria after killing KFF boss",
|
||||
"FS: Black Leggings - Yuria after killing KFF boss"
|
||||
"FS: Billed Mask - shop after killing Yuria",
|
||||
"FS: Black Dress - shop after killing Yuria",
|
||||
"FS: Black Gauntlets - shop after killing Yuria",
|
||||
"FS: Black Leggings - shop after killing Yuria"
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
@@ -764,29 +764,29 @@ location_tables: Dict[str, List[DS3LocationData]] = {
|
||||
DS3LocationData("US -> RS", None),
|
||||
|
||||
# Yoel/Yuria of Londor
|
||||
DS3LocationData("FS: Soul Arrow - Yoel/Yuria", "Soul Arrow",
|
||||
DS3LocationData("FS: Soul Arrow - Yoel/Yuria shop", "Soul Arrow",
|
||||
static='99,0:-1:50000,110000,70000116:', missable=True, npc=True,
|
||||
shop=True),
|
||||
DS3LocationData("FS: Heavy Soul Arrow - Yoel/Yuria", "Heavy Soul Arrow",
|
||||
DS3LocationData("FS: Heavy Soul Arrow - Yoel/Yuria shop", "Heavy Soul Arrow",
|
||||
static='99,0:-1:50000,110000,70000116:',
|
||||
missable=True, npc=True, shop=True),
|
||||
DS3LocationData("FS: Magic Weapon - Yoel/Yuria", "Magic Weapon",
|
||||
DS3LocationData("FS: Magic Weapon - Yoel/Yuria shop", "Magic Weapon",
|
||||
static='99,0:-1:50000,110000,70000116:', missable=True, npc=True,
|
||||
shop=True),
|
||||
DS3LocationData("FS: Magic Shield - Yoel/Yuria", "Magic Shield",
|
||||
DS3LocationData("FS: Magic Shield - Yoel/Yuria shop", "Magic Shield",
|
||||
static='99,0:-1:50000,110000,70000116:', missable=True, npc=True,
|
||||
shop=True),
|
||||
DS3LocationData("FS: Soul Greatsword - Yoel/Yuria", "Soul Greatsword",
|
||||
DS3LocationData("FS: Soul Greatsword - Yoel/Yuria shop", "Soul Greatsword",
|
||||
static='99,0:-1:50000,110000,70000450,70000475:', missable=True,
|
||||
npc=True, shop=True),
|
||||
DS3LocationData("FS: Dark Hand - Yoel/Yuria", "Dark Hand", missable=True, npc=True),
|
||||
DS3LocationData("FS: Untrue White Ring - Yoel/Yuria", "Untrue White Ring", missable=True,
|
||||
DS3LocationData("FS: Dark Hand - Yuria shop", "Dark Hand", missable=True, npc=True),
|
||||
DS3LocationData("FS: Untrue White Ring - Yuria shop", "Untrue White Ring", missable=True,
|
||||
npc=True),
|
||||
DS3LocationData("FS: Untrue Dark Ring - Yoel/Yuria", "Untrue Dark Ring", missable=True,
|
||||
DS3LocationData("FS: Untrue Dark Ring - Yuria shop", "Untrue Dark Ring", missable=True,
|
||||
npc=True),
|
||||
DS3LocationData("FS: Londor Braille Divine Tome - Yoel/Yuria", "Londor Braille Divine Tome",
|
||||
DS3LocationData("FS: Londor Braille Divine Tome - Yuria shop", "Londor Braille Divine Tome",
|
||||
static='99,0:-1:40000,110000,70000116:', missable=True, npc=True),
|
||||
DS3LocationData("FS: Darkdrift - Yoel/Yuria", "Darkdrift", missable=True, drop=True,
|
||||
DS3LocationData("FS: Darkdrift - kill Yuria", "Darkdrift", missable=True, drop=True,
|
||||
npc=True), # kill her or kill Soul of Cinder
|
||||
|
||||
# Cornyx of the Great Swamp
|
||||
@@ -2476,13 +2476,13 @@ location_tables: Dict[str, List[DS3LocationData]] = {
|
||||
"Firelink Leggings", boss=True, shop=True),
|
||||
|
||||
# Yuria (quest, after Soul of Cinder)
|
||||
DS3LocationData("FS: Billed Mask - Yuria after killing KFF boss", "Billed Mask",
|
||||
DS3LocationData("FS: Billed Mask - shop after killing Yuria", "Billed Mask",
|
||||
missable=True, npc=True),
|
||||
DS3LocationData("FS: Black Dress - Yuria after killing KFF boss", "Black Dress",
|
||||
DS3LocationData("FS: Black Dress - shop after killing Yuria", "Black Dress",
|
||||
missable=True, npc=True),
|
||||
DS3LocationData("FS: Black Gauntlets - Yuria after killing KFF boss", "Black Gauntlets",
|
||||
DS3LocationData("FS: Black Gauntlets - shop after killing Yuria", "Black Gauntlets",
|
||||
missable=True, npc=True),
|
||||
DS3LocationData("FS: Black Leggings - Yuria after killing KFF boss", "Black Leggings",
|
||||
DS3LocationData("FS: Black Leggings - shop after killing Yuria", "Black Leggings",
|
||||
missable=True, npc=True),
|
||||
],
|
||||
|
||||
|
||||
@@ -84,7 +84,11 @@ if __name__ == '__main__':
|
||||
table += f"<tr><td>{html.escape(name)}</td><td>{html.escape(description)}</td></tr>\n"
|
||||
table += "</table>\n"
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'docs/locations_en.md'), 'r+') as f:
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), 'docs/locations_en.md'),
|
||||
'r+',
|
||||
encoding='utf-8'
|
||||
) as f:
|
||||
original = f.read()
|
||||
start_flag = "<!-- begin location table -->\n"
|
||||
start = original.index(start_flag) + len(start_flag)
|
||||
|
||||
@@ -1020,7 +1020,7 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>CKG: Drakeblood Helm - tomb, after killing AP mausoleum NPC</td><td>On the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner</td></tr>
|
||||
<tr><td>CKG: Drakeblood Leggings - tomb, after killing AP mausoleum NPC</td><td>On the Drakeblood Knight after Oceiros fight, after defeating the Drakeblood Knight from the Serpent-Man Summoner</td></tr>
|
||||
<tr><td>CKG: Estus Shard - balcony</td><td>From the middle level of the first Consumed King's Gardens elevator, out the balcony and to the right</td></tr>
|
||||
<tr><td>CKG: Human Pine Resin - by lone stairway bottom</td><td>On the right side of the garden, following the wall past the entrance to the shortcut elevator building, in a toxic pool</td></tr>
|
||||
<tr><td>CKG: Human Pine Resin - pool by lift</td><td>On the right side of the garden, following the wall past the entrance to the shortcut elevator building, in a toxic pool</td></tr>
|
||||
<tr><td>CKG: Human Pine Resin - toxic pool, past rotunda</td><td>In between two platforms near the middle of the garden, by a tree in a toxic pool</td></tr>
|
||||
<tr><td>CKG: Magic Stoneplate Ring - mob drop before boss</td><td>Dropped by the Cathedral Knight closest to the Oceiros fog gate</td></tr>
|
||||
<tr><td>CKG: Ring of Sacrifice - under balcony</td><td>Along the right wall of the garden, next to the first elevator building</td></tr>
|
||||
@@ -1181,16 +1181,18 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Alluring Skull - Mortician's Ashes</td><td>Sold by Handmaid after giving Mortician's Ashes</td></tr>
|
||||
<tr><td>FS: Arstor's Spear - Ludleth for Greatwood</td><td>Boss weapon for Curse-Rotted Greatwood</td></tr>
|
||||
<tr><td>FS: Aural Decoy - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Billed Mask - Yuria after killing KFF boss</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Dress - Yuria after killing KFF boss</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Billed Mask - shop after killing Yuria</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Dress - shop after killing Yuria</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Fire Orb - Karla for Grave Warden Tome</td><td>Sold by Karla after giving her the Grave Warden Pyromancy Tome</td></tr>
|
||||
<tr><td>FS: Black Flame - Karla for Grave Warden Tome</td><td>Sold by Karla after giving her the Grave Warden Pyromancy Tome</td></tr>
|
||||
<tr><td>FS: Black Gauntlets - Yuria after killing KFF boss</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Gauntlets - shop after killing Yuria</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Hand Armor - shop after killing GA NPC</td><td>Sold by Handmaid after killing Black Hand Kumai</td></tr>
|
||||
<tr><td>FS: Black Hand Hat - shop after killing GA NPC</td><td>Sold by Handmaid after killing Black Hand Kumai</td></tr>
|
||||
<tr><td>FS: Black Iron Armor - shop after killing Tsorig</td><td>Sold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake</td></tr>
|
||||
<tr><td>FS: Black Iron Gauntlets - shop after killing Tsorig</td><td>Sold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake</td></tr>
|
||||
<tr><td>FS: Black Iron Helm - shop after killing Tsorig</td><td>Sold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake</td></tr>
|
||||
<tr><td>FS: Black Iron Leggings - shop after killing Tsorig</td><td>Sold by Handmaid after killing Knight Slayer Tsorig in Smouldering Lake</td></tr>
|
||||
<tr><td>FS: Black Leggings - Yuria after killing KFF boss</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Leggings - shop after killing Yuria</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Black Serpent - Ludleth for Wolnir</td><td>Boss weapon for High Lord Wolnir</td></tr>
|
||||
<tr><td>FS: Blessed Weapon - Irina for Tome of Lothric</td><td>Sold by Irina after giving her the Braille Divine Tome of Lothric</td></tr>
|
||||
<tr><td>FS: Blue Tearstone Ring - Greirat</td><td>Given by Greirat upon rescuing him from the High Wall cell</td></tr>
|
||||
@@ -1220,8 +1222,8 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Dancer's Leggings - shop after killing LC entry boss</td><td>Sold by Handmaid after defeating Dancer of the Boreal Valley</td></tr>
|
||||
<tr><td>FS: Dark Blade - Karla for Londor Tome</td><td>Sold by Irina or Karla after giving one the Londor Braille Divine Tome</td></tr>
|
||||
<tr><td>FS: Dark Edge - Karla</td><td>Sold by Karla after recruiting her, or in her ashes</td></tr>
|
||||
<tr><td>FS: Dark Hand - Yoel/Yuria</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Darkdrift - Yoel/Yuria</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Dark Hand - Yuria shop</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Darkdrift - kill Yuria</td><td>Dropped by Yuria upon death or quest completion.</td></tr>
|
||||
<tr><td>FS: Darkmoon Longbow - Ludleth for Aldrich</td><td>Boss weapon for Aldrich</td></tr>
|
||||
<tr><td>FS: Dead Again - Karla for Londor Tome</td><td>Sold by Irina or Karla after giving one the Londor Braille Divine Tome</td></tr>
|
||||
<tr><td>FS: Deep Protection - Karla for Deep Braille Tome</td><td>Sold by Irina or Karla after giving one the Deep Braille Divine Tome</td></tr>
|
||||
@@ -1264,6 +1266,9 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Exile Gauntlets - shop after killing NPCs in RS</td><td>Sold by Handmaid after killing the exiles just before Farron Keep</td></tr>
|
||||
<tr><td>FS: Exile Leggings - shop after killing NPCs in RS</td><td>Sold by Handmaid after killing the exiles just before Farron Keep</td></tr>
|
||||
<tr><td>FS: Exile Mask - shop after killing NPCs in RS</td><td>Sold by Handmaid after killing the exiles just before Farron Keep</td></tr>
|
||||
<tr><td>FS: Faraam Armor - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>FS: Faraam Boots - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>FS: Faraam Gauntlets - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>FS: Faraam Helm - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>FS: Farron Dart - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Farron Dart - shop</td><td>Sold by Handmaid</td></tr>
|
||||
@@ -1308,7 +1313,7 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Heal - Irina</td><td>Sold by Irina after recruiting her, or in her ashes</td></tr>
|
||||
<tr><td>FS: Heal Aid - shop</td><td>Sold by Handmaid</td></tr>
|
||||
<tr><td>FS: Heavy Soul Arrow - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Heavy Soul Arrow - Yoel/Yuria</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Heavy Soul Arrow - Yoel/Yuria shop</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Helm of Favor - shop after killing water reserve minibosses</td><td>Sold by Handmaid after killing Sulyvahn's Beasts in Water Reserve</td></tr>
|
||||
<tr><td>FS: Hidden Blessing - Dreamchaser's Ashes</td><td>Sold by Greirat after pillaging Irithyll</td></tr>
|
||||
<tr><td>FS: Hidden Blessing - Greirat from IBV</td><td>Sold by Greirat after pillaging Irithyll</td></tr>
|
||||
@@ -1338,7 +1343,7 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Lift Chamber Key - Leonhard</td><td>Given by Ringfinger Leonhard after acquiring a Pale Tongue.</td></tr>
|
||||
<tr><td>FS: Lightning Storm - Ludleth for Nameless</td><td>Boss weapon for Nameless King</td></tr>
|
||||
<tr><td>FS: Lloyd's Shield Ring - Paladin's Ashes</td><td>Sold by Handmaid after giving Paladin's Ashes</td></tr>
|
||||
<tr><td>FS: Londor Braille Divine Tome - Yoel/Yuria</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Londor Braille Divine Tome - Yuria shop</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Lorian's Armor - shop after killing GA boss</td><td>Sold by Handmaid after defeating Lothric, Younger Prince</td></tr>
|
||||
<tr><td>FS: Lorian's Gauntlets - shop after killing GA boss</td><td>Sold by Handmaid after defeating Lothric, Younger Prince</td></tr>
|
||||
<tr><td>FS: Lorian's Greatsword - Ludleth for Princes</td><td>Boss weapon for Twin Princes</td></tr>
|
||||
@@ -1347,9 +1352,9 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Lothric's Holy Sword - Ludleth for Princes</td><td>Boss weapon for Twin Princes</td></tr>
|
||||
<tr><td>FS: Magic Barrier - Irina for Tome of Lothric</td><td>Sold by Irina after giving her the Braille Divine Tome of Lothric</td></tr>
|
||||
<tr><td>FS: Magic Shield - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Magic Shield - Yoel/Yuria</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Magic Shield - Yoel/Yuria shop</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Magic Weapon - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Magic Weapon - Yoel/Yuria</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Magic Weapon - Yoel/Yuria shop</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Mail Breaker - Sirris for killing Creighton</td><td>Given by Sirris talking to her in Firelink Shrine after invading and vanquishing Creighton.</td></tr>
|
||||
<tr><td>FS: Master's Attire - NPC drop</td><td>Dropped by Sword Master</td></tr>
|
||||
<tr><td>FS: Master's Gloves - NPC drop</td><td>Dropped by Sword Master</td></tr>
|
||||
@@ -1401,10 +1406,10 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Sneering Mask - Yoel's room, kill Londor Pale Shade twice</td><td>In Yoel/Yuria's area after defeating both Londor Pale Shade invasions</td></tr>
|
||||
<tr><td>FS: Soothing Sunlight - Ludleth for Dancer</td><td>Boss weapon for Dancer of the Boreal Valley</td></tr>
|
||||
<tr><td>FS: Soul Arrow - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Soul Arrow - Yoel/Yuria</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Soul Arrow - Yoel/Yuria shop</td><td>Sold by Yoel/Yuria</td></tr>
|
||||
<tr><td>FS: Soul Arrow - shop</td><td>Sold by Handmaid</td></tr>
|
||||
<tr><td>FS: Soul Greatsword - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
<tr><td>FS: Soul Greatsword - Yoel/Yuria</td><td>Sold by Yoel/Yuria after using Draw Out True Strength</td></tr>
|
||||
<tr><td>FS: Soul Greatsword - Yoel/Yuria shop</td><td>Sold by Yoel/Yuria after using Draw Out True Strength</td></tr>
|
||||
<tr><td>FS: Soul Spear - Orbeck for Logan's Scroll</td><td>Sold by Orbeck after giving him Logan's Scroll</td></tr>
|
||||
<tr><td>FS: Soul of a Deserted Corpse - bell tower door</td><td>Next to the door requiring the Tower Key</td></tr>
|
||||
<tr><td>FS: Spook - Orbeck</td><td>Sold by Orbeck</td></tr>
|
||||
@@ -1427,8 +1432,8 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FS: Undead Legion Gauntlet - shop after killing FK boss</td><td>Sold by Handmaid after defeating Abyss Watchers</td></tr>
|
||||
<tr><td>FS: Undead Legion Helm - shop after killing FK boss</td><td>Sold by Handmaid after defeating Abyss Watchers</td></tr>
|
||||
<tr><td>FS: Undead Legion Leggings - shop after killing FK boss</td><td>Sold by Handmaid after defeating Abyss Watchers</td></tr>
|
||||
<tr><td>FS: Untrue Dark Ring - Yoel/Yuria</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Untrue White Ring - Yoel/Yuria</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Untrue Dark Ring - Yuria shop</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Untrue White Ring - Yuria shop</td><td>Sold by Yuria</td></tr>
|
||||
<tr><td>FS: Vordt's Great Hammer - Ludleth for Vordt</td><td>Boss weapon for Vordt of the Boreal Valley</td></tr>
|
||||
<tr><td>FS: Vow of Silence - Karla for Londor Tome</td><td>Sold by Irina or Karla after giving one the Londor Braille Divine Tome</td></tr>
|
||||
<tr><td>FS: Washing Pole - Easterner's Ashes</td><td>Sold by Handmaid after giving Easterner's Ashes</td></tr>
|
||||
@@ -1477,8 +1482,6 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>FSBT: Twinkling Titanite - lizard behind Firelink</td><td>Dropped by the Crystal Lizard behind Firelink Shrine. Can be accessed with tree jump by going all the way around the roof, left of the entrance to the rafters, or alternatively dropping down from the Bell Tower.</td></tr>
|
||||
<tr><td>FSBT: Very good! Carving - crow for Divine Blessing</td><td>Trade Divine Blessing with crow</td></tr>
|
||||
<tr><td>GA: Avelyn - 1F, drop from 3F onto bookshelves</td><td>On top of a bookshelf on the Archive first floor, accessible by going halfway up the stairs to the third floor, dropping down past the Grand Archives Scholar, and then dropping down again</td></tr>
|
||||
<tr><td>GA: Black Hand Armor - shop after killing GA NPC</td><td>Sold by Handmaid after killing Black Hand Kumai</td></tr>
|
||||
<tr><td>GA: Black Hand Hat - shop after killing GA NPC</td><td>Sold by Handmaid after killing Black Hand Kumai</td></tr>
|
||||
<tr><td>GA: Blessed Gem - rafters</td><td>On the rafters high above the Archives, can be accessed by dropping down from the Winged Knight roof area</td></tr>
|
||||
<tr><td>GA: Chaos Gem - dark room, lizard</td><td>Dropped by a Crystal Lizard on the Archives first floor in the dark room past the large wax pool</td></tr>
|
||||
<tr><td>GA: Cinders of a Lord - Lothric Prince</td><td>Dropped by Twin Princes</td></tr>
|
||||
@@ -1489,9 +1492,6 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>GA: Divine Pillars of Light - cage above rafters</td><td>In a cage above the rafters high above the Archives, can be accessed by dropping down from the Winged Knight roof area</td></tr>
|
||||
<tr><td>GA: Ember - 5F, by entrance</td><td>On a balcony high in the Archives overlooking the area with the Grand Archives Scholars with a shortcut ladder, on the opposite side from the wax pool</td></tr>
|
||||
<tr><td>GA: Estus Shard - dome, far balcony</td><td>On the Archives roof near the three Winged Knights, in a side area overlooking the ocean.</td></tr>
|
||||
<tr><td>GA: Faraam Armor - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>GA: Faraam Boots - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>GA: Faraam Gauntlets - shop after killing GA NPC</td><td>Sold by Handmaid after killing Lion Knight Albert</td></tr>
|
||||
<tr><td>GA: Fleshbite Ring - up stairs from 4F</td><td>From the first shortcut elevator with the movable bookshelf, past the Scholars right before going outside onto the roof, in an alcove to the right with many Clawed Curse bookshelves</td></tr>
|
||||
<tr><td>GA: Golden Wing Crest Shield - outside 5F, NPC drop</td><td>Dropped by Lion Knight Albert before the stairs leading up to Twin Princes</td></tr>
|
||||
<tr><td>GA: Heavy Gem - rooftops, lizard</td><td>Dropped by one of the pair of Crystal Lizards, on the right side, found going up a slope past the gargoyle on the Archives roof</td></tr>
|
||||
@@ -1525,15 +1525,15 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>GA: Titanite Chunk - 2F, by wax pool</td><td>Up the stairs from the Archives second floor on the right side from the entrance, in a corner near the small wax pool</td></tr>
|
||||
<tr><td>GA: Titanite Chunk - 2F, right after dark room</td><td>Exiting from the dark room with the Crystal Lizards on the first floor onto the second floor main room, then taking an immediate right</td></tr>
|
||||
<tr><td>GA: Titanite Chunk - 5F, far balcony</td><td>On a balcony outside where Lothric Knight stands on the top floor of the Archives, accessing by going right from the final wax pool or by dropping down from the gargoyle area</td></tr>
|
||||
<tr><td>GA: Titanite Chunk - rooftops, balcony</td><td>Going onto the roof and down the first ladder, all the way down the ledge facing the ocean to the right</td></tr>
|
||||
<tr><td>GA: Titanite Chunk - rooftops lower, ledge by buttress</td><td>Going onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, on a roof ledge to the right</td></tr>
|
||||
<tr><td>GA: Titanite Chunk - rooftops, balcony</td><td>Going onto the roof and down the first ladder, all the way down the ledge facing the ocean to the right</td></tr>
|
||||
<tr><td>GA: Titanite Chunk - rooftops, just before 5F</td><td>On the Archives roof, after a short dropdown, in the small area where the two Gargoyles attack you</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 1F, drop from 2F late onto bookshelves, lizard</td><td>Dropped by a Crystal Lizard on first floor bookshelves. Can be accessed by dropping down to the left at the end of the bridge which is the Crystal Sage's final location</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 1F, up stairs on bookshelf</td><td>On the Archives first floor, up a movable set of stairs near the large wax pool, on top of a bookshelf</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 2F, titanite scale atop bookshelf</td><td>On top of a bookshelf on the Archive second floor, accessible by going halfway up the stairs to the third floor and dropping down near a Grand Archives Scholar</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 3F, by ladder to 2F late</td><td>Going from the Crystal Sage's location on the third floor to its location on the bridge, on the left side of the ladder you descend, behind a table</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 3F, corner up stairs</td><td>From the Grand Archives third floor up past the thralls, in a corner with bookshelves to the left</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 5F, chest by exit</td><td>In a chest after the first elevator shortcut with the movable bookshelf, in the area with the Grand Archives Scholars, to the left of the stairwell leading up to the roof</td></tr>
|
||||
<tr><td>GA: Titanite Scale - 4F, chest by exit</td><td>In a chest after the first elevator shortcut with the movable bookshelf, in the area with the Grand Archives Scholars, to the left of the stairwell leading up to the roof</td></tr>
|
||||
<tr><td>GA: Titanite Scale - dark room, upstairs</td><td>Right after going up the stairs to the Archives second floor, on the left guarded by a Grand Archives Scholar and a sequence of Clawed Curse bookshelves</td></tr>
|
||||
<tr><td>GA: Titanite Scale - rooftops lower, path to 2F</td><td>Going onto the roof and down the first ladder, dropping down on either side from the ledge facing the ocean, then going past the corvians all the way to the left and making a jump</td></tr>
|
||||
<tr><td>GA: Titanite Slab - 1F, after pulling 2F switch</td><td>In a chest on the Archives first floor, behind a bookshelf moved by pulling a lever in the middle of the second floor between two cursed bookshelves</td></tr>
|
||||
@@ -1633,7 +1633,7 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>IBV: Large Soul of a Nameless Soldier - central, by bonfire</td><td>By the Central Irithyll bonfire</td></tr>
|
||||
<tr><td>IBV: Large Soul of a Nameless Soldier - central, by second fountain</td><td>Next to the fountain up the stairs from the Central Irithyll bonfire</td></tr>
|
||||
<tr><td>IBV: Large Soul of a Nameless Soldier - lake island</td><td>On an island in the lake leading to the Distant Manor bonfire</td></tr>
|
||||
<tr><td>IBV: Large Soul of a Nameless Soldier - stairs to plaza</td><td>On the path from Central Irithyll bonfire, before making the left toward Church of Yorshka</td></tr>
|
||||
<tr><td>IBV: Large Soul of a Nameless Soldier - path to plaza</td><td>On the path from Central Irithyll bonfire, before making the left toward Church of Yorshka</td></tr>
|
||||
<tr><td>IBV: Large Titanite Shard - Distant Manor, under overhang</td><td>Under overhang next to second set of stairs leading from Distant Manor bonfire</td></tr>
|
||||
<tr><td>IBV: Large Titanite Shard - ascent, by elevator door</td><td>On the path from the sewer leading up to Pontiff's cathedral, to the right of the statue surrounded by dogs</td></tr>
|
||||
<tr><td>IBV: Large Titanite Shard - ascent, down ladder in last building</td><td>Outside the final building before Pontiff's cathedral, coming from the sewer, dropping down to the left before the entrance</td></tr>
|
||||
@@ -1701,7 +1701,7 @@ static _Dark Souls III_ randomizer].
|
||||
<tr><td>ID: Large Titanite Shard - B1 far, rightmost cell</td><td>In a cell on the far end of the top corridor opposite to the bonfire in Irithyll Dungeon, nearby the Jailer</td></tr>
|
||||
<tr><td>ID: Large Titanite Shard - B1 near, by door</td><td>At the end of the top corridor on the bonfire side in Irithyll Dungeon, before the Jailbreaker's Key door</td></tr>
|
||||
<tr><td>ID: Large Titanite Shard - B3 near, right corner</td><td>In the main Jailer cell block, to the left of the hallway leading to the Path of the Dragon area</td></tr>
|
||||
<tr><td>ID: Large Titanite Shard - after bonfire, second cell on right</td><td>In the second cell on the right after Irithyll Dungeon bonfire</td></tr>
|
||||
<tr><td>ID: Large Titanite Shard - after bonfire, second cell on left</td><td>In the second cell on the right after Irithyll Dungeon bonfire</td></tr>
|
||||
<tr><td>ID: Large Titanite Shard - pit #1</td><td>On the floor where the Giant Slave is standing</td></tr>
|
||||
<tr><td>ID: Large Titanite Shard - pit #2</td><td>On the floor where the Giant Slave is standing</td></tr>
|
||||
<tr><td>ID: Lightning Blade - B3 lift, middle platform</td><td>On the middle platform riding the elevator up from the Path of the Dragon area</td></tr>
|
||||
|
||||
@@ -16,9 +16,9 @@ class Goal(Choice):
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""
|
||||
Choose the difficulty option. Those match DOOM's difficulty options.
|
||||
baby (I'm too young to die.) double ammos, half damage, less monsters or strength.
|
||||
easy (Hey, not too rough.) less monsters or strength.
|
||||
Choose the game difficulty. These options match DOOM's skill levels.
|
||||
baby (I'm too young to die.) Same as easy, with double ammo pickups and half damage taken.
|
||||
easy (Hey, not too rough.) Less monsters or strength.
|
||||
medium (Hurt me plenty.) Default.
|
||||
hard (Ultra-Violence.) More monsters or strength.
|
||||
nightmare (Nightmare!) Monsters attack more rapidly and respawn.
|
||||
@@ -29,6 +29,11 @@ class Difficulty(Choice):
|
||||
option_medium = 2
|
||||
option_hard = 3
|
||||
option_nightmare = 4
|
||||
alias_itytd = 0
|
||||
alias_hntr = 1
|
||||
alias_hmp = 2
|
||||
alias_uv = 3
|
||||
alias_nm = 4
|
||||
default = 2
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
You can find the folder in steam by finding the game in your library,
|
||||
right-clicking it and choosing **Manage -> Browse Local Files**. The WAD file is in the `/base/` folder.
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
## Joining a MultiWorld Game (via Launcher)
|
||||
|
||||
1. Launch apdoom-launcher.exe
|
||||
2. Select `Ultimate DOOM` from the drop-down
|
||||
@@ -28,6 +28,24 @@
|
||||
To continue a game, follow the same connection steps.
|
||||
Connecting with a different seed won't erase your progress in other seeds.
|
||||
|
||||
## Joining a MultiWorld Game (via command line)
|
||||
|
||||
1. In your command line, navigate to the directory where APDOOM is installed.
|
||||
2. Run `crispy-apdoom -game doom -apserver <server> -applayer <slot name>`, where:
|
||||
- `<server>` is the Archipelago server address, e.g. "`archipelago.gg:38281`"
|
||||
- `<slot name>` is your slot name; if it contains spaces, surround it with double quotes
|
||||
- If the server has a password, add `-password`, followed by the server password
|
||||
3. Enjoy!
|
||||
|
||||
Optionally, you can override some randomization settings from the command line:
|
||||
- `-apmonsterrando 0` will disable monster rando.
|
||||
- `-apitemrando 0` will disable item rando.
|
||||
- `-apmusicrando 0` will disable music rando.
|
||||
- `-apfliplevels 0` will disable flipping levels.
|
||||
- `-apresetlevelondeath 0` will disable resetting the level on death.
|
||||
- `-apdeathlinkoff` will force DeathLink off if it's enabled.
|
||||
- `-skill <1-5>` changes the game difficulty, from 1 (I'm too young to die) to 5 (Nightmare!)
|
||||
|
||||
## Archipelago Text Client
|
||||
|
||||
We recommend having Archipelago's Text Client open on the side to keep track of what items you receive and send.
|
||||
|
||||
@@ -6,9 +6,9 @@ from dataclasses import dataclass
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""
|
||||
Choose the difficulty option. Those match DOOM's difficulty options.
|
||||
baby (I'm too young to die.) double ammos, half damage, less monsters or strength.
|
||||
easy (Hey, not too rough.) less monsters or strength.
|
||||
Choose the game difficulty. These options match DOOM's skill levels.
|
||||
baby (I'm too young to die.) Same as easy, with double ammo pickups and half damage taken.
|
||||
easy (Hey, not too rough.) Less monsters or strength.
|
||||
medium (Hurt me plenty.) Default.
|
||||
hard (Ultra-Violence.) More monsters or strength.
|
||||
nightmare (Nightmare!) Monsters attack more rapidly and respawn.
|
||||
@@ -19,6 +19,11 @@ class Difficulty(Choice):
|
||||
option_medium = 2
|
||||
option_hard = 3
|
||||
option_nightmare = 4
|
||||
alias_itytd = 0
|
||||
alias_hntr = 1
|
||||
alias_hmp = 2
|
||||
alias_uv = 3
|
||||
alias_nm = 4
|
||||
default = 2
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
You can find the folder in steam by finding the game in your library,
|
||||
right clicking it and choosing *Manage→Browse Local Files*. The WAD file is in the `/base/` folder.
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
## Joining a MultiWorld Game (via Launcher)
|
||||
|
||||
1. Launch apdoom-launcher.exe
|
||||
2. Select `DOOM II` from the drop-down
|
||||
@@ -26,6 +26,24 @@
|
||||
To continue a game, follow the same connection steps.
|
||||
Connecting with a different seed won't erase your progress in other seeds.
|
||||
|
||||
## Joining a MultiWorld Game (via command line)
|
||||
|
||||
1. In your command line, navigate to the directory where APDOOM is installed.
|
||||
2. Run `crispy-apdoom -game doom2 -apserver <server> -applayer <slot name>`, where:
|
||||
- `<server>` is the Archipelago server address, e.g. "`archipelago.gg:38281`"
|
||||
- `<slot name>` is your slot name; if it contains spaces, surround it with double quotes
|
||||
- If the server has a password, add `-password`, followed by the server password
|
||||
3. Enjoy!
|
||||
|
||||
Optionally, you can override some randomization settings from the command line:
|
||||
- `-apmonsterrando 0` will disable monster rando.
|
||||
- `-apitemrando 0` will disable item rando.
|
||||
- `-apmusicrando 0` will disable music rando.
|
||||
- `-apfliplevels 0` will disable flipping levels.
|
||||
- `-apresetlevelondeath 0` will disable resetting the level on death.
|
||||
- `-apdeathlinkoff` will force DeathLink off if it's enabled.
|
||||
- `-skill <1-5>` changes the game difficulty, from 1 (I'm too young to die) to 5 (Nightmare!)
|
||||
|
||||
## Archipelago Text Client
|
||||
|
||||
We recommend having Archipelago's Text Client open on the side to keep track of what items you receive and send.
|
||||
|
||||
@@ -6,7 +6,7 @@ import typing
|
||||
from schema import Schema, Optional, And, Or
|
||||
|
||||
from Options import Choice, OptionDict, OptionSet, DefaultOnToggle, Range, DeathLink, Toggle, \
|
||||
StartInventoryPool, PerGameCommonOptions
|
||||
StartInventoryPool, PerGameCommonOptions, OptionGroup
|
||||
|
||||
# schema helpers
|
||||
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
|
||||
@@ -272,6 +272,12 @@ class AtomicRocketTrapCount(TrapCount):
|
||||
display_name = "Atomic Rocket Traps"
|
||||
|
||||
|
||||
class AtomicCliffRemoverTrapCount(TrapCount):
|
||||
"""Trap items that when received trigger an atomic rocket explosion on a random cliff.
|
||||
Warning: there is no warning. The launch is instantaneous."""
|
||||
display_name = "Atomic Cliff Remover Traps"
|
||||
|
||||
|
||||
class EvolutionTrapCount(TrapCount):
|
||||
"""Trap items that when received increase the enemy evolution."""
|
||||
display_name = "Evolution Traps"
|
||||
@@ -293,7 +299,7 @@ class FactorioWorldGen(OptionDict):
|
||||
with in-depth documentation at https://lua-api.factorio.com/latest/Concepts.html#MapGenSettings"""
|
||||
display_name = "World Generation"
|
||||
# FIXME: do we want default be a rando-optimized default or in-game DS?
|
||||
value: typing.Dict[str, typing.Dict[str, typing.Any]]
|
||||
value: dict[str, dict[str, typing.Any]]
|
||||
default = {
|
||||
"autoplace_controls": {
|
||||
# terrain
|
||||
@@ -402,7 +408,7 @@ class FactorioWorldGen(OptionDict):
|
||||
}
|
||||
})
|
||||
|
||||
def __init__(self, value: typing.Dict[str, typing.Any]):
|
||||
def __init__(self, value: dict[str, typing.Any]):
|
||||
advanced = {"pollution", "enemy_evolution", "enemy_expansion"}
|
||||
self.value = {
|
||||
"basic": {k: v for k, v in value.items() if k not in advanced},
|
||||
@@ -421,7 +427,7 @@ class FactorioWorldGen(OptionDict):
|
||||
optional_min_lte_max(enemy_expansion, "min_expansion_cooldown", "max_expansion_cooldown")
|
||||
|
||||
@classmethod
|
||||
def from_any(cls, data: typing.Dict[str, typing.Any]) -> FactorioWorldGen:
|
||||
def from_any(cls, data: dict[str, typing.Any]) -> FactorioWorldGen:
|
||||
if type(data) == dict:
|
||||
return cls(data)
|
||||
else:
|
||||
@@ -435,7 +441,7 @@ class ImportedBlueprint(DefaultOnToggle):
|
||||
|
||||
class EnergyLink(Toggle):
|
||||
"""Allow sending energy to other worlds. 25% of the energy is lost in the transfer."""
|
||||
display_name = "EnergyLink"
|
||||
display_name = "Energy Link"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -467,9 +473,42 @@ class FactorioOptions(PerGameCommonOptions):
|
||||
cluster_grenade_traps: ClusterGrenadeTrapCount
|
||||
artillery_traps: ArtilleryTrapCount
|
||||
atomic_rocket_traps: AtomicRocketTrapCount
|
||||
atomic_cliff_remover_traps: AtomicCliffRemoverTrapCount
|
||||
attack_traps: AttackTrapCount
|
||||
evolution_traps: EvolutionTrapCount
|
||||
evolution_trap_increase: EvolutionTrapIncrease
|
||||
death_link: DeathLink
|
||||
energy_link: EnergyLink
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
|
||||
|
||||
option_groups: list[OptionGroup] = [
|
||||
OptionGroup(
|
||||
"Technologies",
|
||||
[
|
||||
TechTreeLayout,
|
||||
Progressive,
|
||||
MinTechCost,
|
||||
MaxTechCost,
|
||||
TechCostDistribution,
|
||||
TechCostMix,
|
||||
RampingTechCosts,
|
||||
TechTreeInformation,
|
||||
]
|
||||
),
|
||||
OptionGroup(
|
||||
"Traps",
|
||||
[
|
||||
AttackTrapCount,
|
||||
EvolutionTrapCount,
|
||||
EvolutionTrapIncrease,
|
||||
TeleportTrapCount,
|
||||
GrenadeTrapCount,
|
||||
ClusterGrenadeTrapCount,
|
||||
ArtilleryTrapCount,
|
||||
AtomicRocketTrapCount,
|
||||
AtomicCliffRemoverTrapCount,
|
||||
],
|
||||
start_collapsed=True
|
||||
),
|
||||
]
|
||||
|
||||
@@ -12,7 +12,8 @@ from worlds.LauncherComponents import Component, components, Type, launch_subpro
|
||||
from worlds.generic import Rules
|
||||
from .Locations import location_pools, location_table
|
||||
from .Mod import generate_mod
|
||||
from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
|
||||
from .Options import (FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal,
|
||||
TechCostDistribution, option_groups)
|
||||
from .Shapes import get_shapes
|
||||
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
|
||||
all_product_sources, required_technologies, get_rocket_requirements, \
|
||||
@@ -61,6 +62,7 @@ class FactorioWeb(WebWorld):
|
||||
"setup/en",
|
||||
["Berserker, Farrak Kilhn"]
|
||||
)]
|
||||
option_groups = option_groups
|
||||
|
||||
|
||||
class FactorioItem(Item):
|
||||
@@ -75,6 +77,7 @@ all_items["Grenade Trap"] = factorio_base_id - 4
|
||||
all_items["Cluster Grenade Trap"] = factorio_base_id - 5
|
||||
all_items["Artillery Trap"] = factorio_base_id - 6
|
||||
all_items["Atomic Rocket Trap"] = factorio_base_id - 7
|
||||
all_items["Atomic Cliff Remover Trap"] = factorio_base_id - 8
|
||||
|
||||
|
||||
class Factorio(World):
|
||||
@@ -140,6 +143,7 @@ class Factorio(World):
|
||||
self.options.grenade_traps + \
|
||||
self.options.cluster_grenade_traps + \
|
||||
self.options.atomic_rocket_traps + \
|
||||
self.options.atomic_cliff_remover_traps + \
|
||||
self.options.artillery_traps
|
||||
|
||||
location_pool = []
|
||||
@@ -192,7 +196,8 @@ class Factorio(World):
|
||||
def create_items(self) -> None:
|
||||
self.custom_technologies = self.set_custom_technologies()
|
||||
self.set_custom_recipes()
|
||||
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket")
|
||||
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket",
|
||||
"Atomic Cliff Remover")
|
||||
for trap_name in traps:
|
||||
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
|
||||
range(getattr(self.options,
|
||||
|
||||
@@ -28,12 +28,23 @@ function random_offset_position(position, offset)
|
||||
end
|
||||
|
||||
function fire_entity_at_players(entity_name, speed)
|
||||
local entities = {}
|
||||
for _, player in ipairs(game.forces["player"].players) do
|
||||
current_character = player.character
|
||||
if current_character ~= nil then
|
||||
current_character.surface.create_entity{name=entity_name,
|
||||
position=random_offset_position(current_character.position, 128),
|
||||
target=current_character, speed=speed}
|
||||
if player.character ~= nil then
|
||||
table.insert(entities, player.character)
|
||||
end
|
||||
end
|
||||
return fire_entity_at_entities(entity_name, entities, speed)
|
||||
end
|
||||
|
||||
function fire_entity_at_entities(entity_name, entities, speed)
|
||||
for _, current_entity in ipairs(entities) do
|
||||
local target = current_entity
|
||||
if target.health == nil then
|
||||
target = target.position
|
||||
end
|
||||
current_entity.surface.create_entity{name=entity_name,
|
||||
position=random_offset_position(current_entity.position, 128),
|
||||
target=target, speed=speed}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -737,6 +737,13 @@ end,
|
||||
["Atomic Rocket Trap"] = function ()
|
||||
fire_entity_at_players("atomic-rocket", 0.1)
|
||||
end,
|
||||
["Atomic Cliff Remover Trap"] = function ()
|
||||
local cliffs = game.surfaces["nauvis"].find_entities_filtered{type = "cliff"}
|
||||
|
||||
if #cliffs > 0 then
|
||||
fire_entity_at_entities("atomic-rocket", {cliffs[math.random(#cliffs)]}, 0.1)
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
commands.add_command("ap-get-technology", "Grant a technology, used by the Archipelago Client.", function(call)
|
||||
|
||||
@@ -211,9 +211,12 @@ def stage_set_rules(multiworld):
|
||||
# If there's no enemies, there's no repeatable income sources
|
||||
no_enemies_players = [player for player in multiworld.get_game_players("Final Fantasy Mystic Quest")
|
||||
if multiworld.worlds[player].options.enemies_density == "none"]
|
||||
if (len([item for item in multiworld.itempool if item.classification in (ItemClassification.filler,
|
||||
ItemClassification.trap)]) > len([player for player in no_enemies_players if
|
||||
multiworld.worlds[player].options.accessibility == "minimal"]) * 3):
|
||||
if (
|
||||
len([item for item in multiworld.itempool if item.excludable]) >
|
||||
len([player
|
||||
for player in no_enemies_players
|
||||
if multiworld.worlds[player].options.accessibility != "minimal"]) * 3
|
||||
):
|
||||
for player in no_enemies_players:
|
||||
for location in vendor_locations:
|
||||
if multiworld.worlds[player].options.accessibility == "full":
|
||||
@@ -221,11 +224,8 @@ def stage_set_rules(multiworld):
|
||||
else:
|
||||
multiworld.get_location(location, player).access_rule = lambda state: False
|
||||
else:
|
||||
# There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing
|
||||
# advancement items so that useful items can be placed.
|
||||
for player in no_enemies_players:
|
||||
for location in vendor_locations:
|
||||
multiworld.get_location(location, player).item_rule = lambda item: not item.advancement
|
||||
raise Exception(f"Not enough filler/trap items for FFMQ players with full and items accessibility. "
|
||||
f"Add more items or change the 'Enemies Density' option to something besides 'none'")
|
||||
|
||||
|
||||
class FFMQLocation(Location):
|
||||
|
||||
@@ -16,14 +16,8 @@ class Goal(Choice):
|
||||
|
||||
class Difficulty(Choice):
|
||||
"""
|
||||
Choose the difficulty option. Those match DOOM's difficulty options.
|
||||
baby (I'm too young to die.) double ammos, half damage, less monsters or strength.
|
||||
easy (Hey, not too rough.) less monsters or strength.
|
||||
medium (Hurt me plenty.) Default.
|
||||
hard (Ultra-Violence.) More monsters or strength.
|
||||
nightmare (Nightmare!) Monsters attack more rapidly and respawn.
|
||||
|
||||
wet nurse (hou needeth a wet-nurse) - Fewer monsters and more items than medium. Damage taken is halved, and ammo pickups carry twice as much ammo. Any Quartz Flasks and Mystic Urns are automatically used when the player nears death.
|
||||
Choose the game difficulty. These options match Heretic's skill levels.
|
||||
wet nurse (Thou needeth a wet-nurse) - Fewer monsters and more items than medium. Damage taken is halved, and ammo pickups carry twice as much ammo. Any Quartz Flasks and Mystic Urns are automatically used when the player nears death.
|
||||
easy (Yellowbellies-r-us) - Fewer monsters and more items than medium.
|
||||
medium (Bringest them oneth) - Completely balanced, this is the standard difficulty level.
|
||||
hard (Thou art a smite-meister) - More monsters and fewer items than medium.
|
||||
@@ -35,6 +29,11 @@ class Difficulty(Choice):
|
||||
option_medium = 2
|
||||
option_hard = 3
|
||||
option_black_plague = 4
|
||||
alias_wn = 0
|
||||
alias_yru = 1
|
||||
alias_bto = 2
|
||||
alias_sm = 3
|
||||
alias_bp = 4
|
||||
default = 2
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
You can find the folder in steam by finding the game in your library,
|
||||
right clicking it and choosing *Manage→Browse Local Files*. The WAD file is in the `/base/` folder.
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
## Joining a MultiWorld Game (via Launcher)
|
||||
|
||||
1. Launch apdoom-launcher.exe
|
||||
2. Choose Heretic in the dropdown
|
||||
@@ -26,6 +26,23 @@
|
||||
To continue a game, follow the same connection steps.
|
||||
Connecting with a different seed won't erase your progress in other seeds.
|
||||
|
||||
## Joining a MultiWorld Game (via command line)
|
||||
|
||||
1. In your command line, navigate to the directory where APDOOM is installed.
|
||||
2. Run `crispy-apheretic -apserver <server> -applayer <slot name>`, where:
|
||||
- `<server>` is the Archipelago server address, e.g. "`archipelago.gg:38281`"
|
||||
- `<slot name>` is your slot name; if it contains spaces, surround it with double quotes
|
||||
- If the server has a password, add `-password`, followed by the server password
|
||||
3. Enjoy!
|
||||
|
||||
Optionally, you can override some randomization settings from the command line:
|
||||
- `-apmonsterrando 0` will disable monster rando.
|
||||
- `-apitemrando 0` will disable item rando.
|
||||
- `-apmusicrando 0` will disable music rando.
|
||||
- `-apresetlevelondeath 0` will disable resetting the level on death.
|
||||
- `-apdeathlinkoff` will force DeathLink off if it's enabled.
|
||||
- `-skill <1-5>` changes the game difficulty, from 1 (thou needeth a wet-nurse) to 5 (black plague possesses thee)
|
||||
|
||||
## Archipelago Text Client
|
||||
|
||||
We recommend having Archipelago's Text Client open on the side to keep track of what items you receive and send.
|
||||
|
||||
@@ -61,6 +61,7 @@ item_name_groups = ({
|
||||
"VesselFragments": lookup_type_to_names["Vessel"],
|
||||
"WhisperingRoots": lookup_type_to_names["Root"],
|
||||
"WhiteFragments": {"Queen_Fragment", "King_Fragment", "Void_Heart"},
|
||||
"DreamNails": {"Dream_Nail", "Dream_Gate", "Awoken_Dream_Nail"},
|
||||
})
|
||||
item_name_groups['Horizontal'] = item_name_groups['Cloak'] | item_name_groups['CDash']
|
||||
item_name_groups['Vertical'] = item_name_groups['Claw'] | {'Monarch_Wings'}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import typing
|
||||
import re
|
||||
from dataclasses import dataclass, make_dataclass
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
from .ExtractedData import logic_options, starts, pool_options
|
||||
from .Rules import cost_terms
|
||||
|
||||
@@ -340,7 +340,7 @@ class HKWorld(World):
|
||||
|
||||
for shop, locations in self.created_multi_locations.items():
|
||||
for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value):
|
||||
loc = self.create_location(shop)
|
||||
self.create_location(shop)
|
||||
unfilled_locations += 1
|
||||
|
||||
# Balance the pool
|
||||
@@ -356,7 +356,7 @@ class HKWorld(World):
|
||||
if shops:
|
||||
for _ in range(additional_shop_items):
|
||||
shop = self.random.choice(shops)
|
||||
loc = self.create_location(shop)
|
||||
self.create_location(shop)
|
||||
unfilled_locations += 1
|
||||
if len(self.created_multi_locations[shop]) >= 16:
|
||||
shops.remove(shop)
|
||||
|
||||
@@ -57,7 +57,7 @@ def generate_valid_level(world: "KDL3World", level: int, stage: int,
|
||||
|
||||
def generate_rooms(world: "KDL3World", level_regions: Dict[int, Region]) -> None:
|
||||
level_names = {location_name.level_names[level]: level for level in location_name.level_names}
|
||||
room_data = orjson.loads(get_data(__name__, os.path.join("data", "Rooms.json")))
|
||||
room_data = orjson.loads(get_data(__name__, "data/Rooms.json"))
|
||||
rooms: Dict[str, KDL3Room] = dict()
|
||||
for room_entry in room_data:
|
||||
room = KDL3Room(room_entry["name"], world.player, world.multiworld, None, room_entry["level"],
|
||||
|
||||
@@ -313,7 +313,7 @@ def handle_level_sprites(stages: List[Tuple[int, ...]], sprites: List[bytearray]
|
||||
def write_heart_star_sprites(rom: RomData) -> None:
|
||||
compressed = rom.read_bytes(heart_star_address, heart_star_size)
|
||||
decompressed = hal_decompress(compressed)
|
||||
patch = get_data(__name__, os.path.join("data", "APHeartStar.bsdiff4"))
|
||||
patch = get_data(__name__, "data/APHeartStar.bsdiff4")
|
||||
patched = bytearray(bsdiff4.patch(decompressed, patch))
|
||||
rom.write_bytes(0x1AF7DF, patched)
|
||||
patched[0:0] = [0xE3, 0xFF]
|
||||
@@ -327,10 +327,10 @@ def write_consumable_sprites(rom: RomData, consumables: bool, stars: bool) -> No
|
||||
decompressed = hal_decompress(compressed)
|
||||
patched = bytearray(decompressed)
|
||||
if consumables:
|
||||
patch = get_data(__name__, os.path.join("data", "APConsumable.bsdiff4"))
|
||||
patch = get_data(__name__, "data/APConsumable.bsdiff4")
|
||||
patched = bytearray(bsdiff4.patch(bytes(patched), patch))
|
||||
if stars:
|
||||
patch = get_data(__name__, os.path.join("data", "APStars.bsdiff4"))
|
||||
patch = get_data(__name__, "data/APStars.bsdiff4")
|
||||
patched = bytearray(bsdiff4.patch(bytes(patched), patch))
|
||||
patched[0:0] = [0xE3, 0xFF]
|
||||
patched.append(0xFF)
|
||||
@@ -380,7 +380,7 @@ class KDL3ProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
|
||||
def patch_rom(world: "KDL3World", patch: KDL3ProcedurePatch) -> None:
|
||||
patch.write_file("kdl3_basepatch.bsdiff4",
|
||||
get_data(__name__, os.path.join("data", "kdl3_basepatch.bsdiff4")))
|
||||
get_data(__name__, "data/kdl3_basepatch.bsdiff4"))
|
||||
|
||||
# Write open world patch
|
||||
if world.options.open_world:
|
||||
|
||||
@@ -355,6 +355,16 @@ class KH2FormRules(KH2Rules):
|
||||
RegionName.Master: lambda state: self.multi_form_region_access(),
|
||||
RegionName.Final: lambda state: self.final_form_region_access(state)
|
||||
}
|
||||
# Accessing Final requires being able to reach one of the locations in final_leveling_access, but reaching a
|
||||
# location requires being able to reach the region the location is in, so an indirect condition is required.
|
||||
# The access rules of each of the locations in final_leveling_access do not check for being able to reach other
|
||||
# locations or other regions, so it is only the parent region of each location that needs to be added as an
|
||||
# indirect condition.
|
||||
self.form_region_indirect_condition_regions = {
|
||||
RegionName.Final: {
|
||||
self.world.get_location(location).parent_region for location in final_leveling_access
|
||||
}
|
||||
}
|
||||
|
||||
def final_form_region_access(self, state: CollectionState) -> bool:
|
||||
"""
|
||||
@@ -388,12 +398,15 @@ class KH2FormRules(KH2Rules):
|
||||
for region_name in drive_form_list:
|
||||
if region_name == RegionName.Summon and not self.world.options.SummonLevelLocationToggle:
|
||||
continue
|
||||
indirect_condition_regions = self.form_region_indirect_condition_regions.get(region_name, ())
|
||||
# could get the location of each of these, but I feel like that would be less optimal
|
||||
region = self.multiworld.get_region(region_name, self.player)
|
||||
# if region_name in form_region_rules
|
||||
if region_name != RegionName.Summon:
|
||||
for entrance in region.entrances:
|
||||
entrance.access_rule = self.form_region_rules[region_name]
|
||||
for indirect_condition_region in indirect_condition_regions:
|
||||
self.multiworld.register_indirect_condition(indirect_condition_region, entrance)
|
||||
for loc in region.locations:
|
||||
loc.access_rule = self.form_rules[loc.name]
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class DungeonItemData(ItemData):
|
||||
@property
|
||||
def dungeon_index(self):
|
||||
return int(self.ladxr_id[-1])
|
||||
|
||||
|
||||
@property
|
||||
def dungeon_item_type(self):
|
||||
s = self.ladxr_id[:-1]
|
||||
@@ -69,7 +69,6 @@ class ItemName:
|
||||
BOMB = "Bomb"
|
||||
SWORD = "Progressive Sword"
|
||||
FLIPPERS = "Flippers"
|
||||
MAGNIFYING_LENS = "Magnifying Lens"
|
||||
MEDICINE = "Medicine"
|
||||
TAIL_KEY = "Tail Key"
|
||||
ANGLER_KEY = "Angler Key"
|
||||
@@ -175,7 +174,7 @@ class ItemName:
|
||||
TRADING_ITEM_SCALE = "Scale"
|
||||
TRADING_ITEM_MAGNIFYING_GLASS = "Magnifying Glass"
|
||||
|
||||
trade_item_prog = ItemClassification.progression
|
||||
trade_item_prog = ItemClassification.progression
|
||||
|
||||
links_awakening_items = [
|
||||
ItemData(ItemName.POWER_BRACELET, "POWER_BRACELET", ItemClassification.progression),
|
||||
@@ -191,7 +190,6 @@ links_awakening_items = [
|
||||
ItemData(ItemName.BOMB, "BOMB", ItemClassification.progression),
|
||||
ItemData(ItemName.SWORD, "SWORD", ItemClassification.progression),
|
||||
ItemData(ItemName.FLIPPERS, "FLIPPERS", ItemClassification.progression),
|
||||
ItemData(ItemName.MAGNIFYING_LENS, "MAGNIFYING_LENS", ItemClassification.progression),
|
||||
ItemData(ItemName.MEDICINE, "MEDICINE", ItemClassification.useful),
|
||||
ItemData(ItemName.TAIL_KEY, "TAIL_KEY", ItemClassification.progression),
|
||||
ItemData(ItemName.ANGLER_KEY, "ANGLER_KEY", ItemClassification.progression),
|
||||
@@ -305,3 +303,135 @@ ladxr_item_to_la_item_name = {
|
||||
links_awakening_items_by_name = {
|
||||
item.item_name : item for item in links_awakening_items
|
||||
}
|
||||
|
||||
links_awakening_item_name_groups: typing.Dict[str, typing.Set[str]] = {
|
||||
"Instruments": {
|
||||
"Full Moon Cello",
|
||||
"Conch Horn",
|
||||
"Sea Lily's Bell",
|
||||
"Surf Harp",
|
||||
"Wind Marimba",
|
||||
"Coral Triangle",
|
||||
"Organ of Evening Calm",
|
||||
"Thunder Drum",
|
||||
},
|
||||
"Entrance Keys": {
|
||||
"Tail Key",
|
||||
"Angler Key",
|
||||
"Face Key",
|
||||
"Bird Key",
|
||||
"Slime Key",
|
||||
},
|
||||
"Nightmare Keys": {
|
||||
"Nightmare Key (Angler's Tunnel)",
|
||||
"Nightmare Key (Bottle Grotto)",
|
||||
"Nightmare Key (Catfish's Maw)",
|
||||
"Nightmare Key (Color Dungeon)",
|
||||
"Nightmare Key (Eagle's Tower)",
|
||||
"Nightmare Key (Face Shrine)",
|
||||
"Nightmare Key (Key Cavern)",
|
||||
"Nightmare Key (Tail Cave)",
|
||||
"Nightmare Key (Turtle Rock)",
|
||||
},
|
||||
"Small Keys": {
|
||||
"Small Key (Angler's Tunnel)",
|
||||
"Small Key (Bottle Grotto)",
|
||||
"Small Key (Catfish's Maw)",
|
||||
"Small Key (Color Dungeon)",
|
||||
"Small Key (Eagle's Tower)",
|
||||
"Small Key (Face Shrine)",
|
||||
"Small Key (Key Cavern)",
|
||||
"Small Key (Tail Cave)",
|
||||
"Small Key (Turtle Rock)",
|
||||
},
|
||||
"Compasses": {
|
||||
"Compass (Angler's Tunnel)",
|
||||
"Compass (Bottle Grotto)",
|
||||
"Compass (Catfish's Maw)",
|
||||
"Compass (Color Dungeon)",
|
||||
"Compass (Eagle's Tower)",
|
||||
"Compass (Face Shrine)",
|
||||
"Compass (Key Cavern)",
|
||||
"Compass (Tail Cave)",
|
||||
"Compass (Turtle Rock)",
|
||||
},
|
||||
"Maps": {
|
||||
"Dungeon Map (Angler's Tunnel)",
|
||||
"Dungeon Map (Bottle Grotto)",
|
||||
"Dungeon Map (Catfish's Maw)",
|
||||
"Dungeon Map (Color Dungeon)",
|
||||
"Dungeon Map (Eagle's Tower)",
|
||||
"Dungeon Map (Face Shrine)",
|
||||
"Dungeon Map (Key Cavern)",
|
||||
"Dungeon Map (Tail Cave)",
|
||||
"Dungeon Map (Turtle Rock)",
|
||||
},
|
||||
"Stone Beaks": {
|
||||
"Stone Beak (Angler's Tunnel)",
|
||||
"Stone Beak (Bottle Grotto)",
|
||||
"Stone Beak (Catfish's Maw)",
|
||||
"Stone Beak (Color Dungeon)",
|
||||
"Stone Beak (Eagle's Tower)",
|
||||
"Stone Beak (Face Shrine)",
|
||||
"Stone Beak (Key Cavern)",
|
||||
"Stone Beak (Tail Cave)",
|
||||
"Stone Beak (Turtle Rock)",
|
||||
},
|
||||
"Trading Items": {
|
||||
"Yoshi Doll",
|
||||
"Ribbon",
|
||||
"Dog Food",
|
||||
"Bananas",
|
||||
"Stick",
|
||||
"Honeycomb",
|
||||
"Pineapple",
|
||||
"Hibiscus",
|
||||
"Letter",
|
||||
"Broom",
|
||||
"Fishing Hook",
|
||||
"Necklace",
|
||||
"Scale",
|
||||
"Magnifying Glass",
|
||||
},
|
||||
"Rupees": {
|
||||
"20 Rupees",
|
||||
"50 Rupees",
|
||||
"100 Rupees",
|
||||
"200 Rupees",
|
||||
"500 Rupees",
|
||||
},
|
||||
"Upgrades": {
|
||||
"Max Powder Upgrade",
|
||||
"Max Bombs Upgrade",
|
||||
"Max Arrows Upgrade",
|
||||
},
|
||||
"Songs": {
|
||||
"Ballad of the Wind Fish",
|
||||
"Manbo's Mambo",
|
||||
"Frog's Song of Soul",
|
||||
},
|
||||
"Tunics": {
|
||||
"Red Tunic",
|
||||
"Blue Tunic",
|
||||
},
|
||||
"Bush Breakers": {
|
||||
"Progressive Power Bracelet",
|
||||
"Magic Rod",
|
||||
"Magic Powder",
|
||||
"Bomb",
|
||||
"Progressive Sword",
|
||||
"Boomerang",
|
||||
},
|
||||
"Sword": {
|
||||
"Progressive Sword",
|
||||
},
|
||||
"Shield": {
|
||||
"Progressive Shield",
|
||||
},
|
||||
"Power Bracelet": {
|
||||
"Progressive Power Bracelet",
|
||||
},
|
||||
"Bracelet": {
|
||||
"Progressive Power Bracelet",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ from . import hints
|
||||
|
||||
from .patches import bank34
|
||||
from .utils import formatText
|
||||
from ..Options import TrendyGame, Palette
|
||||
from ..Options import TrendyGame, Palette, Warps
|
||||
from .roomEditor import RoomEditor, Object
|
||||
from .patches.aesthetics import rgb_to_bin, bin_to_rgb
|
||||
|
||||
@@ -153,7 +153,9 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
if world.ladxr_settings.witch:
|
||||
patches.witch.updateWitch(rom)
|
||||
patches.softlock.fixAll(rom)
|
||||
patches.maptweaks.tweakMap(rom)
|
||||
if not world.ladxr_settings.rooster:
|
||||
patches.maptweaks.tweakMap(rom)
|
||||
patches.maptweaks.tweakBirdKeyRoom(rom)
|
||||
patches.chest.fixChests(rom)
|
||||
patches.shop.fixShop(rom)
|
||||
patches.rooster.patchRooster(rom)
|
||||
@@ -176,11 +178,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
patches.songs.upgradeMarin(rom)
|
||||
patches.songs.upgradeManbo(rom)
|
||||
patches.songs.upgradeMamu(rom)
|
||||
if world.ladxr_settings.tradequest:
|
||||
patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings.boomerang)
|
||||
else:
|
||||
# Monkey bridge patch, always have the bridge there.
|
||||
rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True)
|
||||
patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings)
|
||||
patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal')
|
||||
if world.ladxr_settings.bowwow != 'normal':
|
||||
patches.bowwow.bowwowMapPatches(rom)
|
||||
@@ -268,6 +266,8 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
|
||||
|
||||
def gen_hint():
|
||||
if not world.options.in_game_hints:
|
||||
return 'Hints are disabled!'
|
||||
chance = world.random.uniform(0, 1)
|
||||
if chance < JUNK_HINT:
|
||||
return None
|
||||
@@ -288,7 +288,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
else:
|
||||
location_name = location.name
|
||||
|
||||
hint = f"{name} {location.item} is at {location_name}"
|
||||
hint = f"{name} {location.item.name} is at {location_name}"
|
||||
if location.player != world.player:
|
||||
# filter out { and } since they cause issues with string.format later on
|
||||
player_name = world.multiworld.player_name[location.player].replace("{", "").replace("}", "")
|
||||
@@ -342,11 +342,53 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
patches.enemies.doubleTrouble(rom)
|
||||
|
||||
if world.options.text_shuffle:
|
||||
excluded_ids = [
|
||||
# Overworld owl statues
|
||||
0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D,
|
||||
|
||||
# Dungeon owls
|
||||
0x288, 0x280, # D1
|
||||
0x28A, 0x289, 0x281, # D2
|
||||
0x282, 0x28C, 0x28B, # D3
|
||||
0x283, # D4
|
||||
0x28D, 0x284, # D5
|
||||
0x285, 0x28F, 0x28E, # D6
|
||||
0x291, 0x290, 0x286, # D7
|
||||
0x293, 0x287, 0x292, # D8
|
||||
0x263, # D0
|
||||
|
||||
# Hint books
|
||||
0x267, # color dungeon
|
||||
0x200, 0x201,
|
||||
0x202, 0x203,
|
||||
0x204, 0x205,
|
||||
0x206, 0x207,
|
||||
0x208, 0x209,
|
||||
0x20A, 0x20B,
|
||||
0x20C,
|
||||
0x20D, 0x20E,
|
||||
0x217, 0x218, 0x219, 0x21A,
|
||||
|
||||
# Goal sign
|
||||
0x1A3,
|
||||
|
||||
# Signpost maze
|
||||
0x1A9, 0x1AA, 0x1AB, 0x1AC, 0x1AD,
|
||||
|
||||
# Prices
|
||||
0x02C, 0x02D, 0x030, 0x031, 0x032, 0x033, # Shop items
|
||||
0x03B, # Trendy Game
|
||||
0x045, # Fisherman
|
||||
0x018, 0x019, # Crazy Tracy
|
||||
0x0DC, # Mamu
|
||||
0x0F0, # Raft ride
|
||||
]
|
||||
excluded_texts = [ rom.texts[excluded_id] for excluded_id in excluded_ids]
|
||||
buckets = defaultdict(list)
|
||||
# For each ROM bank, shuffle text within the bank
|
||||
for n, data in enumerate(rom.texts._PointerTable__data):
|
||||
# Don't muck up which text boxes are questions and which are statements
|
||||
if type(data) != int and data and data != b'\xFF':
|
||||
if type(data) != int and data and data != b'\xFF' and data not in excluded_texts:
|
||||
buckets[(rom.texts._PointerTable__banks[n], data[len(data) - 1] == 0xfe)].append((n, data))
|
||||
for bucket in buckets.values():
|
||||
# For each bucket, make a copy and shuffle
|
||||
@@ -418,8 +460,8 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
||||
for channel in range(3):
|
||||
color[channel] = color[channel] * 31 // 0xbc
|
||||
|
||||
if world.options.warp_improvements:
|
||||
patches.core.addWarpImprovements(rom, world.options.additional_warp_points)
|
||||
if world.options.warps != Warps.option_vanilla:
|
||||
patches.core.addWarpImprovements(rom, world.options.warps == Warps.option_improved_additional)
|
||||
|
||||
palette = world.options.palette
|
||||
if palette != Palette.option_normal:
|
||||
|
||||
@@ -1,23 +1,6 @@
|
||||
from .droppedKey import DroppedKey
|
||||
from ..roomEditor import RoomEditor
|
||||
from ..assembler import ASM
|
||||
|
||||
|
||||
class BirdKey(DroppedKey):
|
||||
def __init__(self):
|
||||
super().__init__(0x27A)
|
||||
|
||||
def patch(self, rom, option, *, multiworld=None):
|
||||
super().patch(rom, option, multiworld=multiworld)
|
||||
|
||||
re = RoomEditor(rom, self.room)
|
||||
|
||||
# Make the bird key accessible without the rooster
|
||||
re.removeObject(1, 6)
|
||||
re.removeObject(2, 6)
|
||||
re.removeObject(3, 5)
|
||||
re.removeObject(3, 6)
|
||||
re.moveObject(1, 5, 2, 6)
|
||||
re.moveObject(2, 5, 3, 6)
|
||||
re.addEntity(3, 5, 0x9D)
|
||||
re.store(rom)
|
||||
|
||||
@@ -24,11 +24,6 @@ class BoomerangGuy(ItemInfo):
|
||||
# But SHIELD, BOMB and MAGIC_POWDER would most likely break things.
|
||||
# SWORD and POWER_BRACELET would most likely introduce the lv0 shield/bracelet issue
|
||||
def patch(self, rom, option, *, multiworld=None):
|
||||
# Always have the boomerang trade guy enabled (normally you need the magnifier)
|
||||
rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E"), ASM("ld a, $0E\ncp $0E"), fill_nop=True) # show the guy
|
||||
rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E"), ASM("ld a, $0E\ncp $0E"), fill_nop=True) # load the proper room layout
|
||||
rom.patch(0x19, 0x05F4, ASM("ld a, [wTradeSequenceItem2]\nand a"), ASM("xor a"), fill_nop=True)
|
||||
|
||||
if self.setting == 'trade':
|
||||
inv = INVENTORY_MAP[option]
|
||||
# Patch the check if you traded back the boomerang (so traded twice)
|
||||
|
||||
@@ -25,7 +25,7 @@ CHEST_ITEMS = {
|
||||
PEGASUS_BOOTS: 0x05,
|
||||
OCARINA: 0x06,
|
||||
FEATHER: 0x07, SHOVEL: 0x08, MAGIC_POWDER: 0x09, BOMB: 0x0A, SWORD: 0x0B, FLIPPERS: 0x0C,
|
||||
MAGNIFYING_LENS: 0x0D, MEDICINE: 0x10,
|
||||
MEDICINE: 0x10,
|
||||
TAIL_KEY: 0x11, ANGLER_KEY: 0x12, FACE_KEY: 0x13, BIRD_KEY: 0x14, GOLD_LEAF: 0x15,
|
||||
RUPEES_50: 0x1B, RUPEES_20: 0x1C, RUPEES_100: 0x1D, RUPEES_200: 0x1E, RUPEES_500: 0x1F,
|
||||
SEASHELL: 0x20, MESSAGE: 0x21, GEL: 0x22,
|
||||
|
||||
@@ -11,7 +11,6 @@ MAGIC_POWDER = "MAGIC_POWDER"
|
||||
BOMB = "BOMB"
|
||||
SWORD = "SWORD"
|
||||
FLIPPERS = "FLIPPERS"
|
||||
MAGNIFYING_LENS = "MAGNIFYING_LENS"
|
||||
MEDICINE = "MEDICINE"
|
||||
TAIL_KEY = "TAIL_KEY"
|
||||
ANGLER_KEY = "ANGLER_KEY"
|
||||
|
||||
@@ -9,7 +9,7 @@ class Dungeon1:
|
||||
entrance.add(DungeonChest(0x113), DungeonChest(0x115), DungeonChest(0x10E))
|
||||
Location(dungeon=1).add(DroppedKey(0x116)).connect(entrance, OR(BOMB, r.push_hardhat)) # hardhat beetles (can kill with bomb)
|
||||
Location(dungeon=1).add(DungeonChest(0x10D)).connect(entrance, OR(r.attack_hookshot_powder, SHIELD)) # moldorm spawn chest
|
||||
stalfos_keese_room = Location(dungeon=1).add(DungeonChest(0x114)).connect(entrance, r.attack_hookshot) # 2 stalfos 2 keese room
|
||||
stalfos_keese_room = Location(dungeon=1).add(DungeonChest(0x114)).connect(entrance, AND(OR(r.attack_skeleton, SHIELD),r.attack_hookshot_powder)) # 2 stalfos 2 keese room
|
||||
Location(dungeon=1).add(DungeonChest(0x10C)).connect(entrance, BOMB) # hidden seashell room
|
||||
dungeon1_upper_left = Location(dungeon=1).connect(entrance, AND(KEY1, FOUND(KEY1, 3)))
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
@@ -19,21 +19,22 @@ class Dungeon1:
|
||||
dungeon1_right_side = Location(dungeon=1).connect(entrance, AND(KEY1, FOUND(KEY1, 3)))
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=1).add(OwlStatue(0x10A)).connect(dungeon1_right_side, STONE_BEAK1)
|
||||
Location(dungeon=1).add(DungeonChest(0x10A)).connect(dungeon1_right_side, OR(r.attack_hookshot, SHIELD)) # three of a kind, shield stops the suit from changing
|
||||
dungeon1_3_of_a_kind = Location(dungeon=1).add(DungeonChest(0x10A)).connect(dungeon1_right_side, OR(r.attack_hookshot_no_bomb, SHIELD)) # three of a kind, shield stops the suit from changing
|
||||
dungeon1_miniboss = Location(dungeon=1).connect(dungeon1_right_side, AND(r.miniboss_requirements[world_setup.miniboss_mapping[0]], FEATHER))
|
||||
dungeon1_boss = Location(dungeon=1).connect(dungeon1_miniboss, NIGHTMARE_KEY1)
|
||||
Location(dungeon=1).add(HeartContainer(0x106), Instrument(0x102)).connect(dungeon1_boss, r.boss_requirements[world_setup.boss_mapping[0]])
|
||||
boss = Location(dungeon=1).add(HeartContainer(0x106), Instrument(0x102)).connect(dungeon1_boss, r.boss_requirements[world_setup.boss_mapping[0]])
|
||||
|
||||
if options.logic not in ('normal', 'casual'):
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
stalfos_keese_room.connect(entrance, r.attack_hookshot_powder) # stalfos jump away when you press a button.
|
||||
|
||||
dungeon1_3_of_a_kind.connect(dungeon1_right_side, BOMB) # use timed bombs to match the 3 of a kinds
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
boss_key.connect(entrance, FEATHER) # super jump
|
||||
boss_key.connect(entrance, r.super_jump_feather) # super jump
|
||||
dungeon1_miniboss.connect(dungeon1_right_side, r.miniboss_requirements[world_setup.miniboss_mapping[0]]) # damage boost or buffer pause over the pit to cross or mushroom
|
||||
|
||||
if options.logic == 'hell':
|
||||
feather_chest.connect(dungeon1_upper_left, SWORD) # keep slashing the spiked beetles until they keep moving 1 pixel close towards you and the pit, to get them to fall
|
||||
boss_key.connect(entrance, FOUND(KEY1,3)) # damage boost off the hardhat to cross the pit
|
||||
boss_key.connect(entrance, AND(r.damage_boost, FOUND(KEY1,3))) # damage boost off the hardhat to cross the pit
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class Dungeon2:
|
||||
Location(dungeon=2).add(DungeonChest(0x137)).connect(dungeon2_r2, AND(KEY2, FOUND(KEY2, 5), OR(r.rear_attack, r.rear_attack_range))) # compass chest
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=2).add(OwlStatue(0x133)).connect(dungeon2_r2, STONE_BEAK2)
|
||||
dungeon2_r3 = Location(dungeon=2).add(DungeonChest(0x138)).connect(dungeon2_r2, r.attack_hookshot) # first chest with key, can hookshot the switch in previous room
|
||||
dungeon2_r3 = Location(dungeon=2).add(DungeonChest(0x138)).connect(dungeon2_r2, r.hit_switch) # first chest with key, can hookshot the switch in previous room
|
||||
dungeon2_r4 = Location(dungeon=2).add(DungeonChest(0x139)).connect(dungeon2_r3, FEATHER) # button spawn chest
|
||||
if options.logic == "casual":
|
||||
shyguy_key_drop = Location(dungeon=2).add(DroppedKey(0x134)).connect(dungeon2_r3, AND(FEATHER, OR(r.rear_attack, r.rear_attack_range))) # shyguy drop key
|
||||
@@ -39,16 +39,16 @@ class Dungeon2:
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
dungeon2_ghosts_chest.connect(dungeon2_ghosts_room, SWORD) # use sword to spawn ghosts on other side of the room so they run away (logically irrelevant because of torches at start)
|
||||
dungeon2_r6.connect(miniboss, FEATHER) # superjump to staircase next to hinox.
|
||||
dungeon2_r6.connect(miniboss, r.super_jump_feather) # superjump to staircase next to hinox.
|
||||
|
||||
if options.logic == 'hell':
|
||||
dungeon2_map_chest.connect(dungeon2_l2, AND(r.attack_hookshot_powder, PEGASUS_BOOTS)) # use boots to jump over the pits
|
||||
dungeon2_r4.connect(dungeon2_r3, OR(PEGASUS_BOOTS, HOOKSHOT)) # can use both pegasus boots bonks or hookshot spam to cross the pit room
|
||||
dungeon2_map_chest.connect(dungeon2_l2, AND(r.attack_hookshot_powder, r.boots_bonk_pit)) # use boots to jump over the pits
|
||||
dungeon2_r4.connect(dungeon2_r3, OR(r.boots_bonk_pit, r.hookshot_spam_pit)) # can use both pegasus boots bonks or hookshot spam to cross the pit room
|
||||
dungeon2_r4.connect(shyguy_key_drop, r.rear_attack_range, one_way=True) # adjust for alternate requirements for dungeon2_r4
|
||||
miniboss.connect(dungeon2_r5, AND(PEGASUS_BOOTS, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # use boots to dash over the spikes in the 2d section
|
||||
miniboss.connect(dungeon2_r5, AND(r.boots_dash_2d, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # use boots to dash over the spikes in the 2d section
|
||||
dungeon2_pre_stairs_boss.connect(dungeon2_r6, AND(HOOKSHOT, OR(BOW, BOMB, MAGIC_ROD, AND(OCARINA, SONG1)), FOUND(KEY2, 5))) # hookshot clip through the pot using both pol's voice
|
||||
dungeon2_post_stairs_boss.connect(dungeon2_pre_stairs_boss, OR(BOMB, AND(PEGASUS_BOOTS, FEATHER))) # use a bomb to lower the last platform, or boots + feather to cross over top (only relevant in hell logic)
|
||||
dungeon2_pre_boss.connect(dungeon2_post_stairs_boss, AND(PEGASUS_BOOTS, HOOKSHOT)) # boots bonk off bottom wall + hookshot spam across the two 1 tile pits vertically
|
||||
dungeon2_post_stairs_boss.connect(dungeon2_pre_stairs_boss, OR(BOMB, r.boots_jump)) # use a bomb to lower the last platform, or boots + feather to cross over top (only relevant in hell logic)
|
||||
dungeon2_pre_boss.connect(dungeon2_post_stairs_boss, AND(r.boots_bonk_pit, r.hookshot_spam_pit)) # boots bonk off bottom wall + hookshot spam across the two 1 tile pits vertically
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ class Dungeon3:
|
||||
Location(dungeon=3).add(OwlStatue(0x154)).connect(area_up, STONE_BEAK3)
|
||||
dungeon3_raised_blocks_north = Location(dungeon=3).add(DungeonChest(0x14C)) # chest locked behind raised blocks near staircase
|
||||
dungeon3_raised_blocks_east = Location(dungeon=3).add(DungeonChest(0x150)) # chest locked behind raised blocks next to slime chest
|
||||
area_up.connect(dungeon3_raised_blocks_north, r.attack_hookshot, one_way=True) # hit switch to reach north chest
|
||||
area_up.connect(dungeon3_raised_blocks_east, r.attack_hookshot, one_way=True) # hit switch to reach east chest
|
||||
area_up.connect(dungeon3_raised_blocks_north, r.hit_switch, one_way=True) # hit switch to reach north chest
|
||||
area_up.connect(dungeon3_raised_blocks_east, r.hit_switch, one_way=True) # hit switch to reach east chest
|
||||
|
||||
area_left = Location(dungeon=3).connect(area3, AND(KEY3, FOUND(KEY3, 8)))
|
||||
area_left_key_drop = Location(dungeon=3).add(DroppedKey(0x155)).connect(area_left, r.attack_hookshot) # west key drop (no longer requires feather to get across hole), can use boomerang to knock owls into pit
|
||||
@@ -54,28 +54,30 @@ class Dungeon3:
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
dungeon3_3_bombite_room.connect(area_right, BOOMERANG) # 3 bombite room from the left side, grab item with boomerang
|
||||
dungeon3_reverse_eye.connect(entrance, HOOKSHOT) # hookshot the chest to get to the right side
|
||||
dungeon3_north_key_drop.connect(area_up, POWER_BRACELET) # use pots to kill the enemies
|
||||
dungeon3_south_key_drop.connect(area_down, POWER_BRACELET) # use pots to kill enemies
|
||||
dungeon3_reverse_eye.connect(entrance, r.hookshot_over_pit) # hookshot the chest to get to the right side
|
||||
dungeon3_north_key_drop.connect(area_up, r.throw_pot) # use pots to kill the enemies
|
||||
dungeon3_south_key_drop.connect(area_down, r.throw_pot) # use pots to kill enemies
|
||||
area_up.connect(dungeon3_raised_blocks_north, r.throw_pot, one_way=True) # use pots to hit the switch
|
||||
area_up.connect(dungeon3_raised_blocks_east, AND(r.throw_pot, r.attack_hookshot_powder), one_way=True) # use pots to hit the switch
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
area2.connect(dungeon3_raised_blocks_east, AND(r.attack_hookshot_powder, FEATHER), one_way=True) # use superjump to get over the bottom left block
|
||||
area3.connect(dungeon3_raised_blocks_north, AND(OR(PEGASUS_BOOTS, HOOKSHOT), FEATHER), one_way=True) # use shagjump (unclipped superjump next to movable block) from north wall to get on the blocks. Instead of boots can also get to that area with a hookshot clip past the movable block
|
||||
area3.connect(dungeon3_zol_stalfos, HOOKSHOT, one_way=True) # hookshot clip through the northern push block next to raised blocks chest to get to the zol
|
||||
dungeon3_nightmare_key_chest.connect(area_right, AND(FEATHER, BOMB)) # superjump to right side 3 gap via top wall and jump the 2 gap
|
||||
dungeon3_post_dodongo_chest.connect(area_right, AND(FEATHER, FOUND(KEY3, 6))) # superjump from keyblock path. use 2 keys to open enough blocks TODO: nag messages to skip a key
|
||||
area2.connect(dungeon3_raised_blocks_east, AND(r.attack_hookshot_powder, r.super_jump_feather), one_way=True) # use superjump to get over the bottom left block
|
||||
area3.connect(dungeon3_raised_blocks_north, AND(OR(PEGASUS_BOOTS, r.hookshot_clip_block), r.shaq_jump), one_way=True) # use shagjump (unclipped superjump next to movable block) from north wall to get on the blocks. Instead of boots can also get to that area with a hookshot clip past the movable block
|
||||
area3.connect(dungeon3_zol_stalfos, r.hookshot_clip_block, one_way=True) # hookshot clip through the northern push block next to raised blocks chest to get to the zol
|
||||
dungeon3_nightmare_key_chest.connect(area_right, AND(r.super_jump_feather, BOMB)) # superjump to right side 3 gap via top wall and jump the 2 gap
|
||||
dungeon3_post_dodongo_chest.connect(area_right, AND(r.super_jump_feather, FOUND(KEY3, 6))) # superjump from keyblock path. use 2 keys to open enough blocks TODO: nag messages to skip a key
|
||||
|
||||
if options.logic == 'hell':
|
||||
area2.connect(dungeon3_raised_blocks_east, AND(PEGASUS_BOOTS, OR(BOW, MAGIC_ROD)), one_way=True) # use boots superhop to get over the bottom left block
|
||||
area3.connect(dungeon3_raised_blocks_north, AND(PEGASUS_BOOTS, OR(BOW, MAGIC_ROD)), one_way=True) # use boots superhop off top wall or left wall to get on raised blocks
|
||||
area_up.connect(dungeon3_zol_stalfos, AND(FEATHER, OR(BOW, MAGIC_ROD, SWORD)), one_way=True) # use superjump near top blocks chest to get to zol without boots, keep wall clip on right wall to get a clip on left wall or use obstacles
|
||||
area_left_key_drop.connect(area_left, SHIELD) # knock everything into the pit including the teleporting owls
|
||||
dungeon3_south_key_drop.connect(area_down, SHIELD) # knock everything into the pit including the teleporting owls
|
||||
dungeon3_nightmare_key_chest.connect(area_right, AND(FEATHER, SHIELD)) # superjump into jumping stalfos and shield bump to right ledge
|
||||
dungeon3_nightmare_key_chest.connect(area_right, AND(BOMB, PEGASUS_BOOTS, HOOKSHOT)) # boots bonk across the pits with pit buffering and hookshot to the chest
|
||||
area2.connect(dungeon3_raised_blocks_east, r.boots_superhop, one_way=True) # use boots superhop to get over the bottom left block
|
||||
area3.connect(dungeon3_raised_blocks_north, r.boots_superhop, one_way=True) # use boots superhop off top wall or left wall to get on raised blocks
|
||||
area_up.connect(dungeon3_zol_stalfos, AND(r.super_jump_feather, r.attack_skeleton), one_way=True) # use superjump near top blocks chest to get to zol without boots, keep wall clip on right wall to get a clip on left wall or use obstacles
|
||||
area_left_key_drop.connect(area_left, r.shield_bump) # knock everything into the pit including the teleporting owls
|
||||
dungeon3_south_key_drop.connect(area_down, r.shield_bump) # knock everything into the pit including the teleporting owls
|
||||
dungeon3_nightmare_key_chest.connect(area_right, AND(r.super_jump_feather, r.shield_bump)) # superjump into jumping stalfos and shield bump to right ledge
|
||||
dungeon3_nightmare_key_chest.connect(area_right, AND(BOMB, r.pit_buffer_boots, HOOKSHOT)) # boots bonk across the pits with pit buffering and hookshot to the chest
|
||||
compass_chest.connect(dungeon3_3_bombite_room, OR(BOW, MAGIC_ROD, AND(OR(FEATHER, PEGASUS_BOOTS), OR(SWORD, MAGIC_POWDER))), one_way=True) # 3 bombite room from the left side, use a bombite to blow open the wall without bombs
|
||||
pre_boss.connect(towards_boss4, AND(r.attack_no_boomerang, FEATHER, POWER_BRACELET)) # use bracelet super bounce glitch to pass through first part underground section
|
||||
pre_boss.connect(towards_boss4, AND(r.attack_no_boomerang, PEGASUS_BOOTS, "MEDICINE2")) # use medicine invulnerability to pass through the 2d section with a boots bonk to reach the staircase
|
||||
pre_boss.connect(towards_boss4, AND(r.attack_no_boomerang, r.boots_bonk_2d_spikepit)) # use medicine invulnerability to pass through the 2d section with a boots bonk to reach the staircase
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -42,32 +42,36 @@ class Dungeon4:
|
||||
boss = Location(dungeon=4).add(HeartContainer(0x166), Instrument(0x162)).connect(before_boss, AND(NIGHTMARE_KEY4, r.boss_requirements[world_setup.boss_mapping[3]]))
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
sidescroller_key.connect(before_miniboss, AND(FEATHER, BOOMERANG)) # grab the key jumping over the water and boomerang downwards
|
||||
sidescroller_key.connect(before_miniboss, AND(POWER_BRACELET, FLIPPERS)) # kill the zols with the pots in the room to spawn the key
|
||||
rightside_crossroads.connect(entrance, FEATHER) # jump across the corners
|
||||
puddle_crack_block_chest.connect(rightside_crossroads, FEATHER) # jump around the bombable block
|
||||
north_crossroads.connect(entrance, FEATHER) # jump across the corners
|
||||
after_double_lock.connect(entrance, FEATHER) # jump across the corners
|
||||
dungeon4_puddle_before_crossroads.connect(after_double_lock, FEATHER) # With a tight jump feather is enough to cross the puddle without flippers
|
||||
center_puddle_chest.connect(before_miniboss, FEATHER) # With a tight jump feather is enough to cross the puddle without flippers
|
||||
sidescroller_key.connect(before_miniboss, BOOMERANG) # fall off the bridge and boomerang downwards before hitting the water to grab the item
|
||||
sidescroller_key.connect(before_miniboss, AND(r.throw_pot, FLIPPERS)) # kill the zols with the pots in the room to spawn the key
|
||||
rightside_crossroads.connect(entrance, r.tight_jump) # jump across the corners
|
||||
puddle_crack_block_chest.connect(rightside_crossroads, r.tight_jump) # jump around the bombable block
|
||||
north_crossroads.connect(entrance, r.tight_jump) # jump across the corners
|
||||
after_double_lock.connect(entrance, r.tight_jump) # jump across the corners
|
||||
dungeon4_puddle_before_crossroads.connect(after_double_lock, r.tight_jump) # With a tight jump feather is enough to cross the puddle without flippers
|
||||
center_puddle_chest.connect(before_miniboss, r.tight_jump) # With a tight jump feather is enough to cross the puddle without flippers
|
||||
miniboss = Location(dungeon=4).connect(terrace_zols_chest, None, one_way=True) # reach flippers chest through the miniboss room without pulling the lever
|
||||
to_the_nightmare_key.connect(left_water_area, FEATHER) # With a tight jump feather is enough to reach the top left switch without flippers, or use flippers for puzzle and boots to get through 2d section
|
||||
before_boss.connect(left_water_area, FEATHER) # jump to the bottom right corner of boss door room
|
||||
to_the_nightmare_key.connect(left_water_area, r.tight_jump) # With a tight jump feather is enough to reach the top left switch without flippers, or use flippers for puzzle and boots to get through 2d section
|
||||
before_boss.connect(left_water_area, r.tight_jump) # jump to the bottom right corner of boss door room
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
pushable_block_chest.connect(rightside_crossroads, FLIPPERS) # sideways block push to skip bombs
|
||||
sidescroller_key.connect(before_miniboss, AND(FEATHER, OR(r.attack_hookshot_powder, POWER_BRACELET))) # superjump into the hole to grab the key while falling into the water
|
||||
miniboss.connect(before_miniboss, FEATHER) # use jesus jump to transition over the water left of miniboss
|
||||
pushable_block_chest.connect(rightside_crossroads, AND(r.sideways_block_push, FLIPPERS)) # sideways block push to skip bombs
|
||||
sidescroller_key.connect(before_miniboss, AND(r.super_jump_feather, OR(r.attack_hookshot_powder, r.throw_pot))) # superjump into the hole to grab the key while falling into the water
|
||||
miniboss.connect(before_miniboss, r.jesus_jump) # use jesus jump to transition over the water left of miniboss
|
||||
|
||||
if options.logic == 'hell':
|
||||
rightside_crossroads.connect(entrance, AND(PEGASUS_BOOTS, HOOKSHOT)) # pit buffer into the wall of the first pit, then boots bonk across the center, hookshot to get to the rightmost pit to a second villa buffer on the rightmost pit
|
||||
pushable_block_chest.connect(rightside_crossroads, OR(PEGASUS_BOOTS, FEATHER)) # use feather to water clip into the top right corner of the bombable block, and sideways block push to gain access. Can boots bonk of top right wall, then water buffer to top of chest and boots bonk to water buffer next to chest
|
||||
after_double_lock.connect(double_locked_room, AND(FOUND(KEY4, 4), PEGASUS_BOOTS), one_way=True) # use boots bonks to cross the water gaps
|
||||
rightside_crossroads.connect(entrance, AND(r.pit_buffer_boots, r.hookshot_spam_pit)) # pit buffer into the wall of the first pit, then boots bonk across the center, hookshot to get to the rightmost pit to a second villa buffer on the rightmost pit
|
||||
rightside_crossroads.connect(after_double_lock, AND(OR(BOMB, BOW), r.hookshot_clip_block)) # split zols for more entities, and clip through the block against the right wall
|
||||
pushable_block_chest.connect(rightside_crossroads, AND(r.sideways_block_push, OR(r.jesus_buffer, r.jesus_jump))) # use feather to water clip into the top right corner of the bombable block, and sideways block push to gain access. Can boots bonk of top right wall, then water buffer to top of chest and boots bonk to water buffer next to chest
|
||||
after_double_lock.connect(double_locked_room, AND(FOUND(KEY4, 4), r.pit_buffer_boots), one_way=True) # use boots bonks to cross the water gaps
|
||||
after_double_lock.connect(entrance, r.pit_buffer_boots) # boots bonk + pit buffer to the bottom
|
||||
after_double_lock.connect(entrance, AND(r.pit_buffer, r.hookshot_spam_pit)) # hookshot spam over the first pit of crossroads, then buffer down
|
||||
dungeon4_puddle_before_crossroads.connect(after_double_lock, AND(r.pit_buffer_boots, HOOKSHOT)) # boots bonk across the water bottom wall to the bottom left corner, then hookshot up
|
||||
north_crossroads.connect(entrance, AND(PEGASUS_BOOTS, HOOKSHOT)) # pit buffer into wall of the first pit, then boots bonk towards the top and hookshot spam to get across (easier with Piece of Power)
|
||||
after_double_lock.connect(entrance, PEGASUS_BOOTS) # boots bonk + pit buffer to the bottom
|
||||
dungeon4_puddle_before_crossroads.connect(after_double_lock, AND(PEGASUS_BOOTS, HOOKSHOT)) # boots bonk across the water bottom wall to the bottom left corner, then hookshot up
|
||||
to_the_nightmare_key.connect(left_water_area, AND(FLIPPERS, PEGASUS_BOOTS)) # Use flippers for puzzle and boots bonk to get through 2d section
|
||||
before_boss.connect(left_water_area, PEGASUS_BOOTS) # boots bonk across bottom wall then boots bonk to the platform before boss door
|
||||
before_miniboss.connect(north_crossroads, AND(r.shaq_jump, r.hookshot_clip_block)) # push block left of keyblock up, then shaq jump off the left wall and pause buffer to land on keyblock.
|
||||
before_miniboss.connect(north_crossroads, AND(OR(BOMB, BOW), r.hookshot_clip_block)) # split zol for more entities, and clip through the block left of keyblock by hookshot spam
|
||||
to_the_nightmare_key.connect(left_water_area, AND(FLIPPERS, r.boots_bonk)) # use flippers for puzzle and boots bonk to get through 2d section
|
||||
before_boss.connect(left_water_area, r.pit_buffer_boots) # boots bonk across bottom wall then boots bonk to the platform before boss door
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -39,43 +39,44 @@ class Dungeon5:
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
blade_trap_chest.connect(area2, AND(FEATHER, r.attack_hookshot_powder)) # jump past the blade traps
|
||||
boss_key.connect(after_stalfos, AND(FLIPPERS, FEATHER, PEGASUS_BOOTS)) # boots jump across
|
||||
boss_key.connect(after_stalfos, AND(FLIPPERS, r.boots_jump)) # boots jump across
|
||||
after_stalfos.connect(after_keyblock_boss, AND(FEATHER, r.attack_hookshot_powder)) # circumvent stalfos by going past gohma and backwards from boss door
|
||||
if butterfly_owl:
|
||||
butterfly_owl.connect(after_stalfos, AND(PEGASUS_BOOTS, STONE_BEAK5)) # boots charge + bonk to cross 2d bridge
|
||||
after_stalfos.connect(staircase_before_boss, AND(PEGASUS_BOOTS, r.attack_hookshot_powder), one_way=True) # pathway from stalfos to staircase: boots charge + bonk to cross bridge, past butterfly room and push the block
|
||||
staircase_before_boss.connect(post_gohma, AND(PEGASUS_BOOTS, HOOKSHOT)) # boots bonk in 2d section to skip feather
|
||||
north_of_crossroads.connect(after_stalfos, HOOKSHOT) # hookshot to the right block to cross pits
|
||||
first_bridge_chest.connect(north_of_crossroads, FEATHER) # tight jump from bottom wall clipped to make it over the pits
|
||||
butterfly_owl.connect(after_stalfos, AND(r.boots_bonk, STONE_BEAK5)) # boots charge + bonk to cross 2d bridge
|
||||
after_stalfos.connect(staircase_before_boss, AND(r.boots_bonk, r.attack_hookshot_powder), one_way=True) # pathway from stalfos to staircase: boots charge + bonk to cross bridge, past butterfly room and push the block
|
||||
staircase_before_boss.connect(post_gohma, AND(r.boots_bonk, HOOKSHOT)) # boots bonk in 2d section to skip feather
|
||||
north_of_crossroads.connect(after_stalfos, r.hookshot_over_pit) # hookshot to the right block to cross pits
|
||||
first_bridge_chest.connect(north_of_crossroads, AND(r.wall_clip, r.tight_jump)) # tight jump from bottom wall clipped to make it over the pits
|
||||
after_keyblock_boss.connect(after_stalfos, AND(FEATHER, r.attack_hookshot_powder)) # jump from bottom left to top right, skipping the keyblock
|
||||
before_boss.connect(after_stalfos, AND(FEATHER, PEGASUS_BOOTS, r.attack_hookshot_powder)) # cross pits room from bottom left to top left with boots jump
|
||||
before_boss.connect(after_stalfos, AND(r.boots_jump, r.attack_hookshot_powder)) # cross pits room from bottom left to top left with boots jump
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
start_hookshot_chest.connect(entrance, FEATHER) # 1 pit buffer to clip bottom wall and jump across the pits
|
||||
start_hookshot_chest.connect(entrance, r.pit_buffer) # 1 pit buffer to clip bottom wall and jump across the pits
|
||||
post_gohma.connect(area2, HOOKSHOT) # glitch through the blocks/pots with hookshot. Zoomerang can be used but has no logical implications because of 2d section requiring hookshot
|
||||
north_bridge_chest.connect(north_of_crossroads, FEATHER) # 1 pit buffer to clip bottom wall and jump across the pits
|
||||
east_bridge_chest.connect(first_bridge_chest, FEATHER) # 1 pit buffer to clip bottom wall and jump across the pits
|
||||
#after_stalfos.connect(staircase_before_boss, AND(FEATHER, OR(SWORD, BOW, MAGIC_ROD))) # use the keyblock to get a wall clip in right wall to perform a superjump over the pushable block TODO: nagmessages
|
||||
after_stalfos.connect(staircase_before_boss, AND(PEGASUS_BOOTS, FEATHER, OR(SWORD, BOW, MAGIC_ROD))) # charge a boots dash in bottom right corner to the right, jump before hitting the wall and use weapon to the left side before hitting the wall
|
||||
north_bridge_chest.connect(north_of_crossroads, r.pit_buffer) # 1 pit buffer to clip bottom wall and jump across the pits
|
||||
east_bridge_chest.connect(first_bridge_chest, r.pit_buffer) # 1 pit buffer to clip bottom wall and jump across the pits
|
||||
#after_stalfos.connect(staircase_before_boss, AND(r.text_clip, r.super_jump)) # use the keyblock to get a wall clip in right wall to perform a superjump over the pushable block
|
||||
after_stalfos.connect(staircase_before_boss, r.super_jump_boots) # charge a boots dash in bottom right corner to the right, jump before hitting the wall and use weapon to the left side before hitting the wall
|
||||
|
||||
if options.logic == 'hell':
|
||||
start_hookshot_chest.connect(entrance, PEGASUS_BOOTS) # use pit buffer to clip into the bottom wall and boots bonk off the wall again
|
||||
fourth_stalfos_area.connect(compass, AND(PEGASUS_BOOTS, SWORD)) # do an incredibly hard boots bonk setup to get across the hanging platforms in the 2d section
|
||||
blade_trap_chest.connect(area2, AND(PEGASUS_BOOTS, r.attack_hookshot_powder)) # boots bonk + pit buffer past the blade traps
|
||||
start_hookshot_chest.connect(entrance, r.pit_buffer_boots) # use pit buffer to clip into the bottom wall and boots bonk off the wall again
|
||||
fourth_stalfos_area.connect(compass, AND(r.boots_bonk_2d_hell, SWORD)) # do an incredibly hard boots bonk setup to get across the hanging platforms in the 2d section
|
||||
blade_trap_chest.connect(area2, AND(r.pit_buffer_boots, r.attack_hookshot_powder)) # boots bonk + pit buffer past the blade traps
|
||||
post_gohma.connect(area2, AND(PEGASUS_BOOTS, FEATHER, POWER_BRACELET, r.attack_hookshot_powder)) # use boots jump in room with 2 zols + flying arrows to pit buffer above pot, then jump across. Sideways block push + pick up pots to reach post_gohma
|
||||
staircase_before_boss.connect(post_gohma, AND(PEGASUS_BOOTS, FEATHER)) # to pass 2d section, tight jump on left screen: hug left wall on little platform, then dash right off platform and jump while in midair to bonk against right wall
|
||||
after_stalfos.connect(staircase_before_boss, AND(FEATHER, SWORD)) # unclipped superjump in bottom right corner of staircase before boss room, jumping left over the pushable block. reverse is push block
|
||||
staircase_before_boss.connect(post_gohma, r.boots_jump) # to pass 2d section, tight jump on left screen: hug left wall on little platform, then dash right off platform and jump while in midair to bonk against right wall
|
||||
after_stalfos.connect(staircase_before_boss, r.super_jump_sword) # unclipped superjump in bottom right corner of staircase before boss room, jumping left over the pushable block. reverse is push block
|
||||
after_stalfos.connect(area2, SWORD) # knock master stalfos down 255 times (about 23 minutes)
|
||||
north_bridge_chest.connect(north_of_crossroads, PEGASUS_BOOTS) # boots bonk across the pits with pit buffering
|
||||
first_bridge_chest.connect(north_of_crossroads, PEGASUS_BOOTS) # get to first chest via the north chest with pit buffering
|
||||
east_bridge_chest.connect(first_bridge_chest, PEGASUS_BOOTS) # boots bonk across the pits with pit buffering
|
||||
after_stalfos.connect(staircase_before_boss, r.zoomerang) # use zoomerang dashing left to get an unclipped boots superjump off the right wall over the block. reverse is push block
|
||||
north_bridge_chest.connect(north_of_crossroads, r.boots_bonk_pit) # boots bonk across the pits with pit buffering
|
||||
first_bridge_chest.connect(north_of_crossroads, r.boots_bonk_pit) # get to first chest via the north chest with pit buffering
|
||||
east_bridge_chest.connect(first_bridge_chest, r.boots_bonk_pit) # boots bonk across the pits with pit buffering
|
||||
third_arena.connect(north_of_crossroads, SWORD) # can beat 3rd m.stalfos with 255 sword spins
|
||||
m_stalfos_drop.connect(third_arena, AND(FEATHER, SWORD)) # beat master stalfos by knocking it down 255 times x 4 (takes about 1.5h total)
|
||||
m_stalfos_drop.connect(third_arena, AND(PEGASUS_BOOTS, SWORD)) # can reach fourth arena from entrance with pegasus boots and sword
|
||||
boss_key.connect(after_stalfos, FLIPPERS) # pit buffer across
|
||||
m_stalfos_drop.connect(third_arena, AND(r.boots_bonk_2d_hell, SWORD)) # can reach fourth arena from entrance with pegasus boots and sword
|
||||
boss_key.connect(after_stalfos, AND(r.pit_buffer_itemless, FLIPPERS)) # pit buffer across
|
||||
if butterfly_owl:
|
||||
after_keyblock_boss.connect(butterfly_owl, STONE_BEAK5, one_way=True) # pit buffer from top right to bottom in right pits room
|
||||
before_boss.connect(after_stalfos, AND(FEATHER, SWORD)) # cross pits room from bottom left to top left by unclipped superjump on bottom wall on top of side wall, then jump across
|
||||
after_keyblock_boss.connect(butterfly_owl, AND(r.pit_buffer_itemless, STONE_BEAK5), one_way=True) # pit buffer from top right to bottom in right pits room
|
||||
before_boss.connect(after_stalfos, r.super_jump_sword) # cross pits room from bottom left to top left by unclipped superjump on bottom wall on top of side wall, then jump across
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ from ..locations.all import *
|
||||
class Dungeon6:
|
||||
def __init__(self, options, world_setup, r, *, raft_game_chest=True):
|
||||
entrance = Location(dungeon=6)
|
||||
Location(dungeon=6).add(DungeonChest(0x1CF)).connect(entrance, OR(BOMB, BOW, MAGIC_ROD, COUNT(POWER_BRACELET, 2))) # 50 rupees
|
||||
Location(dungeon=6).add(DungeonChest(0x1C9)).connect(entrance, COUNT(POWER_BRACELET, 2)) # 100 rupees start
|
||||
Location(dungeon=6).add(DungeonChest(0x1CF)).connect(entrance, OR(r.attack_wizrobe, COUNT(POWER_BRACELET, 2))) # 50 rupees
|
||||
elephants_heart_chest = Location(dungeon=6).add(DungeonChest(0x1C9)).connect(entrance, COUNT(POWER_BRACELET, 2)) # 100 rupees start
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=6).add(OwlStatue(0x1BB)).connect(entrance, STONE_BEAK6)
|
||||
|
||||
@@ -15,9 +15,9 @@ class Dungeon6:
|
||||
bracelet_chest = Location(dungeon=6).add(DungeonChest(0x1CE)).connect(entrance, AND(BOMB, FEATHER))
|
||||
|
||||
# left side
|
||||
Location(dungeon=6).add(DungeonChest(0x1C0)).connect(entrance, AND(POWER_BRACELET, OR(BOMB, BOW, MAGIC_ROD))) # 3 wizrobes raised blocks dont need to hit the switch
|
||||
Location(dungeon=6).add(DungeonChest(0x1C0)).connect(entrance, AND(POWER_BRACELET, r.attack_wizrobe)) # 3 wizrobes raised blocks don't need to hit the switch
|
||||
left_side = Location(dungeon=6).add(DungeonChest(0x1B9)).add(DungeonChest(0x1B3)).connect(entrance, AND(POWER_BRACELET, OR(BOMB, BOOMERANG)))
|
||||
Location(dungeon=6).add(DroppedKey(0x1B4)).connect(left_side, OR(BOMB, BOW, MAGIC_ROD)) # 2 wizrobe drop key
|
||||
Location(dungeon=6).add(DroppedKey(0x1B4)).connect(left_side, OR(r.attack_wizrobe, BOW)) # 2 wizrobe drop key, allow bow as only 2
|
||||
top_left = Location(dungeon=6).add(DungeonChest(0x1B0)).connect(left_side, COUNT(POWER_BRACELET, 2)) # top left chest horseheads
|
||||
if raft_game_chest:
|
||||
Location().add(Chest(0x06C)).connect(top_left, POWER_BRACELET) # seashell chest in raft game
|
||||
@@ -25,14 +25,15 @@ class Dungeon6:
|
||||
# right side
|
||||
to_miniboss = Location(dungeon=6).connect(entrance, KEY6)
|
||||
miniboss = Location(dungeon=6).connect(to_miniboss, AND(BOMB, r.miniboss_requirements[world_setup.miniboss_mapping[5]]))
|
||||
lower_right_side = Location(dungeon=6).add(DungeonChest(0x1BE)).connect(entrance, AND(OR(BOMB, BOW, MAGIC_ROD), COUNT(POWER_BRACELET, 2))) # waterway key
|
||||
lower_right_side = Location(dungeon=6).add(DungeonChest(0x1BE)).connect(entrance, AND(r.attack_wizrobe, COUNT(POWER_BRACELET, 2))) # waterway key
|
||||
medicine_chest = Location(dungeon=6).add(DungeonChest(0x1D1)).connect(lower_right_side, FEATHER) # ledge chest medicine
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
lower_right_owl = Location(dungeon=6).add(OwlStatue(0x1D7)).connect(lower_right_side, AND(POWER_BRACELET, STONE_BEAK6))
|
||||
|
||||
center_1 = Location(dungeon=6).add(DroppedKey(0x1C3)).connect(miniboss, AND(COUNT(POWER_BRACELET, 2), FEATHER)) # tile room key drop
|
||||
center_2_and_upper_right_side = Location(dungeon=6).add(DungeonChest(0x1B1)).connect(center_1, AND(KEY6, FOUND(KEY6, 2))) # top right chest horseheads
|
||||
center_2_and_upper_right_side = Location(dungeon=6).add(DungeonChest(0x1B1)).connect(center_1, AND(COUNT(POWER_BRACELET, 2), PEGASUS_BOOTS, r.attack_pols_voice, KEY6, FOUND(KEY6, 2))) # top right chest horseheads
|
||||
boss_key = Location(dungeon=6).add(DungeonChest(0x1B6)).connect(center_2_and_upper_right_side, AND(AND(KEY6, FOUND(KEY6, 3), HOOKSHOT)))
|
||||
center_2_and_upper_right_side.connect(boss_key, AND(HOOKSHOT, POWER_BRACELET, KEY6, FOUND(KEY6, 3)), one_way=True)
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=6).add(OwlStatue(0x1B6)).connect(boss_key, STONE_BEAK6)
|
||||
|
||||
@@ -40,19 +41,22 @@ class Dungeon6:
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
bracelet_chest.connect(entrance, BOMB) # get through 2d section by "fake" jumping to the ladders
|
||||
center_1.connect(miniboss, AND(COUNT(POWER_BRACELET, 2), PEGASUS_BOOTS)) # use a boots dash to get over the platforms
|
||||
|
||||
center_1.connect(miniboss, AND(COUNT(POWER_BRACELET, 2), r.boots_dash_2d)) # use a boots dash to get over the platforms
|
||||
center_2_and_upper_right_side.connect(center_1, AND(COUNT(POWER_BRACELET, 2), r.damage_boost, r.attack_pols_voice, FOUND(KEY6, 2))) # damage_boost past the mini_thwomps
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
entrance.connect(left_side, AND(POWER_BRACELET, FEATHER), one_way=True) # path from entrance to left_side: use superjumps to pass raised blocks
|
||||
lower_right_side.connect(center_2_and_upper_right_side, AND(FEATHER, OR(SWORD, BOW, MAGIC_ROD)), one_way=True) # path from lower_right_side to center_2: superjump from waterway towards dodongos. superjump next to corner block, so weapons added
|
||||
center_2_and_upper_right_side.connect(center_1, AND(POWER_BRACELET, FEATHER), one_way=True) # going backwards from dodongos, use a shaq jump to pass by keyblock at tile room
|
||||
boss_key.connect(lower_right_side, FEATHER) # superjump from waterway to the left. POWER_BRACELET is implied from lower_right_side
|
||||
elephants_heart_chest.connect(entrance, BOMB) # kill moldorm on screen above wizrobes, then bomb trigger on the right side to break elephant statue to get to the second chest
|
||||
entrance.connect(left_side, AND(POWER_BRACELET, r.super_jump_feather), one_way=True) # path from entrance to left_side: use superjumps to pass raised blocks
|
||||
lower_right_side.connect(center_2_and_upper_right_side, r.super_jump, one_way=True) # path from lower_right_side to center_2: superjump from waterway towards dodongos. superjump next to corner block, so weapons added
|
||||
center_1.connect(miniboss, AND(r.bomb_trigger, OR(r.boots_dash_2d, FEATHER))) # bomb trigger the elephant statue after the miniboss
|
||||
center_2_and_upper_right_side.connect(center_1, AND(POWER_BRACELET, r.shaq_jump), one_way=True) # going backwards from dodongos, use a shaq jump to pass by keyblock at tile room
|
||||
boss_key.connect(lower_right_side, AND(POWER_BRACELET, r.super_jump_feather)) # superjump from waterway to the left.
|
||||
|
||||
if options.logic == 'hell':
|
||||
entrance.connect(left_side, AND(POWER_BRACELET, PEGASUS_BOOTS, OR(BOW, MAGIC_ROD)), one_way=True) # can boots superhop off the top right corner in 3 wizrobe raised blocks room
|
||||
medicine_chest.connect(lower_right_side, AND(PEGASUS_BOOTS, OR(MAGIC_ROD, BOW))) # can boots superhop off the top wall with bow or magic rod
|
||||
center_1.connect(miniboss, AND(COUNT(POWER_BRACELET, 2))) # use a double damage boost from the sparks to get across (first one is free, second one needs to buffer while in midair for spark to get close enough)
|
||||
lower_right_side.connect(center_2_and_upper_right_side, FEATHER, one_way=True) # path from lower_right_side to center_2: superjump from waterway towards dodongos. superjump next to corner block is super tight to get enough horizontal distance
|
||||
entrance.connect(left_side, AND(POWER_BRACELET, r.boots_superhop), one_way=True) # can boots superhop off the top right corner in 3 wizrobe raised blocks room
|
||||
medicine_chest.connect(lower_right_side, r.boots_superhop) # can boots superhop off the top wall with bow or magic rod
|
||||
center_1.connect(miniboss, AND(r.damage_boost_special, OR(r.bomb_trigger, COUNT(POWER_BRACELET, 2)))) # use a double damage boost from the sparks to get across (first one is free, second one needs to buffer while in midair for spark to get close enough)
|
||||
lower_right_side.connect(center_2_and_upper_right_side, r.super_jump_feather, one_way=True) # path from lower_right_side to center_2: superjump from waterway towards dodongos. superjump next to corner block is super tight to get enough horizontal distance
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ class Dungeon7:
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=7).add(OwlStatue(0x204)).connect(topright_pillar_area, STONE_BEAK7)
|
||||
topright_pillar_area.add(DungeonChest(0x209)) # stone slab chest can be reached by dropping down a hole
|
||||
three_of_a_kind_north = Location(dungeon=7).add(DungeonChest(0x211)).connect(topright_pillar_area, OR(r.attack_hookshot, AND(FEATHER, SHIELD))) # compass chest; path without feather with hitting switch by falling on the raised blocks. No bracelet because ball does not reset
|
||||
bottomleftF2_area = Location(dungeon=7).connect(topright_pillar_area, r.attack_hookshot) # area with hinox, be able to hit a switch to reach that area
|
||||
three_of_a_kind_north = Location(dungeon=7).add(DungeonChest(0x211)).connect(topright_pillar_area, OR(AND(r.hit_switch, r.attack_hookshot_no_bomb), AND(OR(BOMB, FEATHER), SHIELD))) # compass chest; either hit the switch, or have feather to fall on top of raised blocks. No bracelet because ball does not reset
|
||||
bottomleftF2_area = Location(dungeon=7).connect(topright_pillar_area, r.hit_switch) # area with hinox, be able to hit a switch to reach that area
|
||||
topleftF1_chest = Location(dungeon=7).add(DungeonChest(0x201)) # top left chest on F1
|
||||
bottomleftF2_area.connect(topleftF1_chest, None, one_way = True) # drop down in left most holes of hinox room or tile room
|
||||
Location(dungeon=7).add(DroppedKey(0x21B)).connect(bottomleftF2_area, r.attack_hookshot) # hinox drop key
|
||||
@@ -23,9 +23,9 @@ class Dungeon7:
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
bottomleft_owl = Location(dungeon=7).add(OwlStatue(0x21C)).connect(bottomleftF2_area, AND(BOMB, STONE_BEAK7))
|
||||
nightmare_key = Location(dungeon=7).add(DungeonChest(0x224)).connect(bottomleftF2_area, r.miniboss_requirements[world_setup.miniboss_mapping[6]]) # nightmare key after the miniboss
|
||||
mirror_shield_chest = Location(dungeon=7).add(DungeonChest(0x21A)).connect(bottomleftF2_area, r.attack_hookshot) # mirror shield chest, need to be able to hit a switch to reach or
|
||||
mirror_shield_chest = Location(dungeon=7).add(DungeonChest(0x21A)).connect(bottomleftF2_area, r.hit_switch) # mirror shield chest, need to be able to hit a switch to reach or
|
||||
bottomleftF2_area.connect(mirror_shield_chest, AND(KEY7, FOUND(KEY7, 3)), one_way = True) # reach mirror shield chest from hinox area by opening keyblock
|
||||
toprightF1_chest = Location(dungeon=7).add(DungeonChest(0x204)).connect(bottomleftF2_area, r.attack_hookshot) # chest on the F1 right ledge. Added attack_hookshot since switch needs to be hit to get back up
|
||||
toprightF1_chest = Location(dungeon=7).add(DungeonChest(0x204)).connect(bottomleftF2_area, r.hit_switch) # chest on the F1 right ledge. Added attack_hookshot since switch needs to be hit to get back up
|
||||
final_pillar_area = Location(dungeon=7).add(DungeonChest(0x21C)).connect(bottomleftF2_area, AND(BOMB, HOOKSHOT)) # chest that needs to spawn to get to the last pillar
|
||||
final_pillar = Location(dungeon=7).connect(final_pillar_area, POWER_BRACELET) # decouple chest from pillar
|
||||
|
||||
@@ -33,25 +33,28 @@ class Dungeon7:
|
||||
beamos_horseheads = Location(dungeon=7).add(DungeonChest(0x220)).connect(beamos_horseheads_area, POWER_BRACELET) # 100 rupee chest / medicine chest (DX) behind boss door
|
||||
pre_boss = Location(dungeon=7).connect(beamos_horseheads_area, HOOKSHOT) # raised plateau before boss staircase
|
||||
boss = Location(dungeon=7).add(HeartContainer(0x223), Instrument(0x22c)).connect(pre_boss, r.boss_requirements[world_setup.boss_mapping[6]])
|
||||
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
three_of_a_kind_north.connect(topright_pillar_area, BOMB) # use timed bombs to match the 3 of a kinds (south 3 of a kind room is implicite as normal logic can not reach chest without hookshot)
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
topright_pillar_area.connect(entrance, AND(FEATHER, SWORD)) # superjump in the center to get on raised blocks, superjump in switch room to right side to walk down. center superjump has to be low so sword added
|
||||
toprightF1_chest.connect(topright_pillar_area, FEATHER) # superjump from F1 switch room
|
||||
topleftF2_area = Location(dungeon=7).connect(topright_pillar_area, FEATHER) # superjump in top left pillar room over the blocks from right to left, to reach tile room
|
||||
topright_pillar_area.connect(entrance, r.super_jump_sword) # superjump in the center to get on raised blocks, superjump in switch room to right side to walk down. center superjump has to be low so sword added
|
||||
toprightF1_chest.connect(topright_pillar_area, r.super_jump_feather) # superjump from F1 switch room
|
||||
topleftF2_area = Location(dungeon=7).connect(topright_pillar_area, r.super_jump_feather) # superjump in top left pillar room over the blocks from right to left, to reach tile room
|
||||
topleftF2_area.connect(topleftF1_chest, None, one_way = True) # fall down tile room holes on left side to reach top left chest on ground floor
|
||||
topleftF1_chest.connect(bottomleftF2_area, AND(PEGASUS_BOOTS, FEATHER), one_way = True) # without hitting the switch, jump on raised blocks at f1 pegs chest (0x209), and boots jump to stairs to reach hinox area
|
||||
final_pillar_area.connect(bottomleftF2_area, OR(r.attack_hookshot, POWER_BRACELET, AND(FEATHER, SHIELD))) # sideways block push to get to the chest and pillar, kill requirement for 3 of a kind enemies to access chest. Assumes you do not get ball stuck on raised pegs for bracelet path
|
||||
topleftF1_chest.connect(bottomleftF2_area, r.boots_jump, one_way = True) # without hitting the switch, jump on raised blocks at f1 pegs chest (0x209), and boots jump to stairs to reach hinox area
|
||||
final_pillar_area.connect(bottomleftF2_area, AND(r.sideways_block_push, OR(r.attack_hookshot, POWER_BRACELET, AND(FEATHER, SHIELD)))) # sideways block push to get to the chest and pillar, kill requirement for 3 of a kind enemies to access chest. Assumes you do not get ball stuck on raised pegs for bracelet path
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
bottomleft_owl.connect(bottomleftF2_area, STONE_BEAK7) # sideways block push to get to the owl statue
|
||||
bottomleft_owl.connect(bottomleftF2_area, AND(r.sideways_block_push, STONE_BEAK7)) # sideways block push to get to the owl statue
|
||||
final_pillar.connect(bottomleftF2_area, BOMB) # bomb trigger pillar
|
||||
pre_boss.connect(final_pillar, FEATHER) # superjump on top of goomba to extend superjump to boss door plateau
|
||||
pre_boss.connect(final_pillar, r.super_jump_feather) # superjump on top of goomba to extend superjump to boss door plateau
|
||||
pre_boss.connect(beamos_horseheads_area, None, one_way=True) # can drop down from raised plateau to beamos horseheads area
|
||||
|
||||
if options.logic == 'hell':
|
||||
topright_pillar_area.connect(entrance, FEATHER) # superjump in the center to get on raised blocks, has to be low
|
||||
topright_pillar_area.connect(entrance, AND(PEGASUS_BOOTS, OR(BOW, MAGIC_ROD))) # boots superhop in the center to get on raised blocks
|
||||
toprightF1_chest.connect(topright_pillar_area, AND(PEGASUS_BOOTS, OR(BOW, MAGIC_ROD))) # boots superhop from F1 switch room
|
||||
pre_boss.connect(final_pillar, AND(PEGASUS_BOOTS, OR(BOW, MAGIC_ROD))) # boots superhop on top of goomba to extend superhop to boss door plateau
|
||||
topright_pillar_area.connect(entrance, r.super_jump_feather) # superjump in the center to get on raised blocks, has to be low
|
||||
topright_pillar_area.connect(entrance, r.boots_superhop) # boots superhop in the center to get on raised blocks
|
||||
toprightF1_chest.connect(topright_pillar_area, r.boots_superhop) # boots superhop from F1 switch room
|
||||
pre_boss.connect(final_pillar, r.boots_superhop) # boots superhop on top of goomba to extend superhop to boss door plateau
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -11,7 +11,10 @@ class Dungeon8:
|
||||
|
||||
# left side
|
||||
entrance_left.add(DungeonChest(0x24D)) # zamboni room chest
|
||||
Location(dungeon=8).add(DungeonChest(0x25C)).connect(entrance_left, r.attack_hookshot) # eye magnet chest
|
||||
eye_magnet_chest = Location(dungeon=8).add(DungeonChest(0x25C)) # eye magnet chest bottom left below rolling bones
|
||||
eye_magnet_chest.connect(entrance_left, OR(BOW, MAGIC_ROD, BOOMERANG, AND(FEATHER, r.attack_hookshot))) # damageless roller should be default
|
||||
if options.hardmode != "ohko":
|
||||
eye_magnet_chest.connect(entrance_left, r.attack_hookshot) # can take a hit
|
||||
vire_drop_key = Location(dungeon=8).add(DroppedKey(0x24C)).connect(entrance_left, r.attack_hookshot_no_bomb) # vire drop key
|
||||
sparks_chest = Location(dungeon=8).add(DungeonChest(0x255)).connect(entrance_left, OR(HOOKSHOT, FEATHER)) # chest before lvl1 miniboss
|
||||
Location(dungeon=8).add(DungeonChest(0x246)).connect(entrance_left, MAGIC_ROD) # key chest that spawns after creating fire
|
||||
@@ -30,7 +33,7 @@ class Dungeon8:
|
||||
upper_center = Location(dungeon=8).connect(lower_center, AND(KEY8, FOUND(KEY8, 2)))
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=8).add(OwlStatue(0x245)).connect(upper_center, STONE_BEAK8)
|
||||
Location(dungeon=8).add(DroppedKey(0x23E)).connect(upper_center, r.attack_skeleton) # 2 gibdos cracked floor; technically possible to use pits to kill but dumb
|
||||
gibdos_drop_key = Location(dungeon=8).add(DroppedKey(0x23E)).connect(upper_center, r.attack_gibdos) # 2 gibdos cracked floor; technically possible to use pits to kill but dumb
|
||||
medicine_chest = Location(dungeon=8).add(DungeonChest(0x235)).connect(upper_center, AND(FEATHER, HOOKSHOT)) # medicine chest
|
||||
|
||||
middle_center_1 = Location(dungeon=8).connect(upper_center, BOMB)
|
||||
@@ -66,33 +69,36 @@ class Dungeon8:
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
entrance_left.connect(entrance, BOMB) # use bombs to kill vire and hinox
|
||||
vire_drop_key.connect(entrance_left, BOMB) # use bombs to kill rolling bones and vire
|
||||
bottom_right.connect(slime_chest, FEATHER) # diagonal jump over the pits to reach rolling rock / zamboni
|
||||
up_left.connect(vire_drop_key, BOMB, one_way=True) # use bombs to kill rolling bones and vire, do not allow pathway through hinox with just bombs, as not enough bombs are available
|
||||
bottom_right.connect(slime_chest, r.tight_jump) # diagonal jump over the pits to reach rolling rock / zamboni
|
||||
gibdos_drop_key.connect(upper_center, OR(HOOKSHOT, MAGIC_ROD)) # crack one of the floor tiles and hookshot the gibdos in, or burn the gibdos and make them jump into pit
|
||||
up_left.connect(lower_center, AND(BOMB, FEATHER)) # blow up hidden walls from peahat room -> dark room -> eye statue room
|
||||
slime_chest.connect(entrance, AND(r.attack_hookshot_powder, POWER_BRACELET)) # kill vire with powder or bombs
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
sparks_chest.connect(entrance_left, OR(r.attack_hookshot, FEATHER, PEGASUS_BOOTS)) # 1 pit buffer across the pit. Add requirements for all the options to get to this area
|
||||
lower_center.connect(entrance_up, None) # sideways block push in peahat room to get past keyblock
|
||||
miniboss_entrance.connect(lower_center, AND(BOMB, FEATHER, HOOKSHOT)) # blow up hidden wall for darkroom, use feather + hookshot to clip past keyblock in front of stairs
|
||||
miniboss_entrance.connect(lower_center, AND(BOMB, FEATHER, FOUND(KEY8, 7))) # same as above, but without clipping past the keyblock
|
||||
up_left.connect(lower_center, FEATHER) # use jesus jump in refill room left of peahats to clip bottom wall and push bottom block left, to get a place to super jump
|
||||
up_left.connect(upper_center, FEATHER) # from up left you can jesus jump / lava swim around the key door next to the boss.
|
||||
top_left_stairs.connect(up_left, AND(FEATHER, SWORD)) # superjump
|
||||
medicine_chest.connect(upper_center, FEATHER) # jesus super jump
|
||||
up_left.connect(bossdoor, FEATHER, one_way=True) # superjump off the bottom or right wall to jump over to the boss door
|
||||
sparks_chest.connect(entrance_left, r.pit_buffer_itemless) # 1 pit buffer across the pit.
|
||||
entrance_up.connect(bottomright_pot_chest, r.super_jump_boots, one_way = True) # underground section with fire balls jumping up out of lava. Use boots superjump off left wall to jump over the pot blocking the way
|
||||
lower_center.connect(entrance_up, r.sideways_block_push) # sideways block push in peahat room to get past keyblock
|
||||
miniboss_entrance.connect(lower_center, AND(BOMB, r.bookshot)) # blow up hidden wall for darkroom, use feather + hookshot to clip past keyblock in front of stairs
|
||||
miniboss_entrance.connect(lower_center, AND(BOMB, r.super_jump_feather, FOUND(KEY8, 7))) # same as above, but without clipping past the keyblock
|
||||
up_left.connect(lower_center, r.jesus_jump) # use jesus jump in refill room left of peahats to clip bottom wall and push bottom block left, to get a place to super jump
|
||||
up_left.connect(upper_center, r.jesus_jump) # from up left you can jesus jump / lava swim around the key door next to the boss.
|
||||
top_left_stairs.connect(up_left, r.super_jump_feather) # superjump
|
||||
medicine_chest.connect(upper_center, AND(r.super_jump_feather, r.jesus_jump)) # jesus super jump
|
||||
up_left.connect(bossdoor, r.super_jump_feather, one_way=True) # superjump off the bottom or right wall to jump over to the boss door
|
||||
|
||||
if options.logic == 'hell':
|
||||
if bottomright_owl:
|
||||
bottomright_owl.connect(entrance, AND(SWORD, POWER_BRACELET, PEGASUS_BOOTS, STONE_BEAK8)) # underground section past mimics, boots bonking across the gap to the ladder
|
||||
bottomright_pot_chest.connect(entrance, AND(SWORD, POWER_BRACELET, PEGASUS_BOOTS)) # underground section past mimics, boots bonking across the gap to the ladder
|
||||
entrance.connect(bottomright_pot_chest, AND(FEATHER, SWORD), one_way=True) # use NW zamboni staircase backwards, subpixel manip for superjump past the pots
|
||||
medicine_chest.connect(upper_center, AND(PEGASUS_BOOTS, HOOKSHOT)) # boots bonk + lava buffer to the bottom wall, then bonk onto the middle section
|
||||
miniboss.connect(miniboss_entrance, AND(PEGASUS_BOOTS, r.miniboss_requirements[world_setup.miniboss_mapping[7]])) # get through 2d section with boots bonks
|
||||
top_left_stairs.connect(map_chest, AND(PEGASUS_BOOTS, MAGIC_ROD)) # boots bonk + lava buffer from map chest to entrance_up, then boots bonk through 2d section
|
||||
nightmare_key.connect(top_left_stairs, AND(PEGASUS_BOOTS, SWORD, FOUND(KEY8, 7))) # use a boots bonk to cross the 2d section + the lava in cueball room
|
||||
bottom_right.connect(entrance_up, AND(POWER_BRACELET, PEGASUS_BOOTS), one_way=True) # take staircase to NW zamboni room, boots bonk onto the lava and water buffer all the way down to push the zamboni
|
||||
bossdoor.connect(entrance_up, AND(PEGASUS_BOOTS, MAGIC_ROD)) # boots bonk through 2d section
|
||||
bottomright_owl.connect(entrance, AND(SWORD, POWER_BRACELET, r.boots_bonk_2d_hell, STONE_BEAK8)) # underground section past mimics, boots bonking across the gap to the ladder
|
||||
bottomright_pot_chest.connect(entrance, AND(SWORD, POWER_BRACELET, r.boots_bonk_2d_hell)) # underground section past mimics, boots bonking across the gap to the ladder
|
||||
entrance.connect(bottomright_pot_chest, r.shaq_jump, one_way=True) # use NW zamboni staircase backwards, and get a naked shaq jump off the bottom wall in the bottom right corner to pass by the pot
|
||||
gibdos_drop_key.connect(upper_center, AND(FEATHER, SHIELD)) # lock gibdos into pits and crack the tile they stand on, then use shield to bump them into the pit
|
||||
medicine_chest.connect(upper_center, AND(r.pit_buffer_boots, HOOKSHOT)) # boots bonk + lava buffer to the bottom wall, then bonk onto the middle section
|
||||
miniboss.connect(miniboss_entrance, AND(r.boots_bonk_2d_hell, r.miniboss_requirements[world_setup.miniboss_mapping[7]])) # get through 2d section with boots bonks
|
||||
top_left_stairs.connect(map_chest, AND(r.jesus_buffer, r.boots_bonk_2d_hell, MAGIC_ROD)) # boots bonk + lava buffer from map chest to entrance_up, then boots bonk through 2d section
|
||||
nightmare_key.connect(top_left_stairs, AND(r.boots_bonk_pit, SWORD, FOUND(KEY8, 7))) # use a boots bonk to cross the 2d section + the lava in cueball room
|
||||
bottom_right.connect(entrance_up, AND(POWER_BRACELET, r.jesus_buffer), one_way=True) # take staircase to NW zamboni room, boots bonk onto the lava and water buffer all the way down to push the zamboni
|
||||
bossdoor.connect(entrance_up, AND(r.boots_bonk_2d_hell, MAGIC_ROD)) # boots bonk through 2d section
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class DungeonColor:
|
||||
room2.add(DungeonChest(0x314)) # key
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=9).add(OwlStatue(0x308), OwlStatue(0x30F)).connect(room2, STONE_BEAK9)
|
||||
room2_weapon = Location(dungeon=9).connect(room2, r.attack_hookshot)
|
||||
room2_weapon = Location(dungeon=9).connect(room2, AND(r.attack_hookshot, POWER_BRACELET))
|
||||
room2_weapon.add(DungeonChest(0x311)) # stone beak
|
||||
room2_lights = Location(dungeon=9).connect(room2, OR(r.attack_hookshot, SHIELD))
|
||||
room2_lights.add(DungeonChest(0x30F)) # compass chest
|
||||
@@ -20,22 +20,24 @@ class DungeonColor:
|
||||
room3 = Location(dungeon=9).connect(room2, AND(KEY9, FOUND(KEY9, 2), r.miniboss_requirements[world_setup.miniboss_mapping["c1"]])) # After the miniboss
|
||||
room4 = Location(dungeon=9).connect(room3, POWER_BRACELET) # need to lift a pot to reveal button
|
||||
room4.add(DungeonChest(0x306)) # map
|
||||
room4karakoro = Location(dungeon=9).add(DroppedKey(0x307)).connect(room4, r.attack_hookshot) # require item to knock Karakoro enemies into shell
|
||||
room4karakoro = Location(dungeon=9).add(DroppedKey(0x307)).connect(room4, AND(r.attack_hookshot, POWER_BRACELET)) # require item to knock Karakoro enemies into shell
|
||||
if options.owlstatues == "both" or options.owlstatues == "dungeon":
|
||||
Location(dungeon=9).add(OwlStatue(0x30A)).connect(room4, STONE_BEAK9)
|
||||
room5 = Location(dungeon=9).connect(room4, OR(r.attack_hookshot, SHIELD)) # lights room
|
||||
room6 = Location(dungeon=9).connect(room5, AND(KEY9, FOUND(KEY9, 3))) # room with switch and nightmare door
|
||||
pre_boss = Location(dungeon=9).connect(room6, OR(r.attack_hookshot, AND(PEGASUS_BOOTS, FEATHER))) # before the boss, require item to hit switch or jump past raised blocks
|
||||
pre_boss = Location(dungeon=9).connect(room6, OR(r.hit_switch, AND(PEGASUS_BOOTS, FEATHER))) # before the boss, require item to hit switch or jump past raised blocks
|
||||
boss = Location(dungeon=9).connect(pre_boss, AND(NIGHTMARE_KEY9, r.boss_requirements[world_setup.boss_mapping[8]]))
|
||||
boss.add(TunicFairy(0), TunicFairy(1))
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
room2.connect(entrance, POWER_BRACELET) # throw pots at enemies
|
||||
pre_boss.connect(room6, FEATHER) # before the boss, jump past raised blocks without boots
|
||||
room2.connect(entrance, r.throw_pot) # throw pots at enemies
|
||||
room2_weapon.connect(room2, r.attack_hookshot_no_bomb) # knock the karakoro into the pit without picking them up.
|
||||
pre_boss.connect(room6, r.tight_jump) # before the boss, jump past raised blocks without boots
|
||||
|
||||
if options.logic == 'hell':
|
||||
room2_weapon.connect(room2, SHIELD) # shield bump karakoro into the holes
|
||||
room4karakoro.connect(room4, SHIELD) # shield bump karakoro into the holes
|
||||
room2_weapon.connect(room2, r.attack_hookshot) # also have a bomb as option to knock the karakoro into the pit without bracelet
|
||||
room2_weapon.connect(room2, r.shield_bump) # shield bump karakoro into the holes
|
||||
room4karakoro.connect(room4, r.shield_bump) # shield bump karakoro into the holes
|
||||
|
||||
self.entrance = entrance
|
||||
|
||||
|
||||
@@ -19,10 +19,13 @@ class World:
|
||||
Location().add(DroppedKey(0x1E4)).connect(rooster_cave, AND(OCARINA, SONG3))
|
||||
|
||||
papahl_house = Location("Papahl House")
|
||||
papahl_house.connect(Location().add(TradeSequenceItem(0x2A6, TRADING_ITEM_RIBBON)), TRADING_ITEM_YOSHI_DOLL)
|
||||
mamasha_trade = Location().add(TradeSequenceItem(0x2A6, TRADING_ITEM_RIBBON))
|
||||
papahl_house.connect(mamasha_trade, TRADING_ITEM_YOSHI_DOLL)
|
||||
|
||||
trendy_shop = Location("Trendy Shop").add(TradeSequenceItem(0x2A0, TRADING_ITEM_YOSHI_DOLL))
|
||||
#trendy_shop.connect(Location())
|
||||
trendy_shop = Location("Trendy Shop")
|
||||
trendy_shop.connect(Location().add(TradeSequenceItem(0x2A0, TRADING_ITEM_YOSHI_DOLL)), FOUND("RUPEES", 50))
|
||||
outside_trendy = Location()
|
||||
outside_trendy.connect(mabe_village, r.bush)
|
||||
|
||||
self._addEntrance("papahl_house_left", mabe_village, papahl_house, None)
|
||||
self._addEntrance("papahl_house_right", mabe_village, papahl_house, None)
|
||||
@@ -61,9 +64,9 @@ class World:
|
||||
self._addEntrance("banana_seller", sword_beach, banana_seller, r.bush)
|
||||
boomerang_cave = Location("Boomerang Cave")
|
||||
if options.boomerang == 'trade':
|
||||
Location().add(BoomerangGuy()).connect(boomerang_cave, OR(BOOMERANG, HOOKSHOT, MAGIC_ROD, PEGASUS_BOOTS, FEATHER, SHOVEL))
|
||||
Location().add(BoomerangGuy()).connect(boomerang_cave, AND(r.shuffled_magnifier, OR(BOOMERANG, HOOKSHOT, MAGIC_ROD, PEGASUS_BOOTS, FEATHER, SHOVEL)))
|
||||
elif options.boomerang == 'gift':
|
||||
Location().add(BoomerangGuy()).connect(boomerang_cave, None)
|
||||
Location().add(BoomerangGuy()).connect(boomerang_cave, r.shuffled_magnifier)
|
||||
self._addEntrance("boomerang_cave", sword_beach, boomerang_cave, BOMB)
|
||||
self._addEntranceRequirementExit("boomerang_cave", None) # if exiting, you do not need bombs
|
||||
|
||||
@@ -84,7 +87,7 @@ class World:
|
||||
crazy_tracy_hut_inside = Location("Crazy Tracy's House")
|
||||
Location().add(KeyLocation("MEDICINE2")).connect(crazy_tracy_hut_inside, FOUND("RUPEES", 50))
|
||||
self._addEntrance("crazy_tracy", crazy_tracy_hut, crazy_tracy_hut_inside, None)
|
||||
start_house.connect(crazy_tracy_hut, SONG2, one_way=True) # Manbo's Mambo into the pond outside Tracy
|
||||
start_house.connect(crazy_tracy_hut, AND(OCARINA, SONG2), one_way=True) # Manbo's Mambo into the pond outside Tracy
|
||||
|
||||
forest_madbatter = Location("Forest Mad Batter")
|
||||
Location().add(MadBatter(0x1E1)).connect(forest_madbatter, MAGIC_POWDER)
|
||||
@@ -92,7 +95,7 @@ class World:
|
||||
self._addEntranceRequirementExit("forest_madbatter", None) # if exiting, you do not need bracelet
|
||||
|
||||
forest_cave = Location("Forest Cave")
|
||||
Location().add(Chest(0x2BD)).connect(forest_cave, SWORD) # chest in forest cave on route to mushroom
|
||||
forest_cave_crystal_chest = Location().add(Chest(0x2BD)).connect(forest_cave, SWORD) # chest in forest cave on route to mushroom
|
||||
log_cave_heartpiece = Location().add(HeartPiece(0x2AB)).connect(forest_cave, POWER_BRACELET) # piece of heart in the forest cave on route to the mushroom
|
||||
forest_toadstool = Location().add(Toadstool())
|
||||
self._addEntrance("toadstool_entrance", forest, forest_cave, None)
|
||||
@@ -130,6 +133,7 @@ class World:
|
||||
self._addEntranceRequirementExit("d0", None) # if exiting, you do not need bracelet
|
||||
ghost_grave = Location().connect(forest, POWER_BRACELET)
|
||||
Location().add(Seashell(0x074)).connect(ghost_grave, AND(r.bush, SHOVEL)) # next to grave cave, digging spot
|
||||
graveyard.connect(forest_heartpiece, OR(BOOMERANG, HOOKSHOT), one_way=True) # grab the heart piece surrounded by pits from the north
|
||||
|
||||
graveyard_cave_left = Location()
|
||||
graveyard_cave_right = Location().connect(graveyard_cave_left, OR(FEATHER, ROOSTER))
|
||||
@@ -167,7 +171,9 @@ class World:
|
||||
prairie_island_seashell = Location().add(Seashell(0x0A6)).connect(ukuku_prairie, AND(FLIPPERS, r.bush)) # next to lv3
|
||||
Location().add(Seashell(0x08B)).connect(ukuku_prairie, r.bush) # next to seashell house
|
||||
Location().add(Seashell(0x0A4)).connect(ukuku_prairie, PEGASUS_BOOTS) # smash into tree next to phonehouse
|
||||
self._addEntrance("castle_jump_cave", ukuku_prairie, Location().add(Chest(0x1FD)), OR(AND(FEATHER, PEGASUS_BOOTS), ROOSTER)) # left of the castle, 5 holes turned into 3
|
||||
self._addEntrance("castle_jump_cave", ukuku_prairie, Location().add(Chest(0x1FD)), ROOSTER)
|
||||
if not options.rooster:
|
||||
self._addEntranceRequirement("castle_jump_cave", AND(FEATHER, PEGASUS_BOOTS)) # left of the castle, 5 holes turned into 3
|
||||
Location().add(Seashell(0x0B9)).connect(ukuku_prairie, POWER_BRACELET) # under the rock
|
||||
|
||||
left_bay_area = Location()
|
||||
@@ -192,6 +198,7 @@ class World:
|
||||
bay_madbatter_connector_exit = Location().connect(bay_madbatter_connector_entrance, FLIPPERS)
|
||||
bay_madbatter_connector_outside = Location()
|
||||
bay_madbatter = Location().connect(Location().add(MadBatter(0x1E0)), MAGIC_POWDER)
|
||||
outside_bay_madbatter_entrance = Location()
|
||||
self._addEntrance("prairie_madbatter_connector_entrance", left_bay_area, bay_madbatter_connector_entrance, AND(OR(FEATHER, ROOSTER), OR(SWORD, MAGIC_ROD, BOOMERANG)))
|
||||
self._addEntranceRequirementExit("prairie_madbatter_connector_entrance", AND(OR(FEATHER, ROOSTER), r.bush)) # if exiting, you can pick up the bushes by normal means
|
||||
self._addEntrance("prairie_madbatter_connector_exit", bay_madbatter_connector_outside, bay_madbatter_connector_exit, None)
|
||||
@@ -237,7 +244,8 @@ class World:
|
||||
castle_courtyard = Location()
|
||||
castle_frontdoor = Location().connect(castle_courtyard, r.bush)
|
||||
castle_frontdoor.connect(ukuku_prairie, "CASTLE_BUTTON") # the button in the castle connector allows access to the castle grounds in ER
|
||||
self._addEntrance("castle_secret_entrance", next_to_castle, castle_secret_entrance_right, OR(BOMB, BOOMERANG, MAGIC_POWDER, MAGIC_ROD, SWORD))
|
||||
self._addEntrance("castle_secret_entrance", next_to_castle, castle_secret_entrance_right, r.pit_bush)
|
||||
self._addEntranceRequirementExit("castle_secret_entrance", None) # leaving doesn't require pit_bush
|
||||
self._addEntrance("castle_secret_exit", castle_courtyard, castle_secret_entrance_left, None)
|
||||
|
||||
Location().add(HeartPiece(0x078)).connect(bay_water, FLIPPERS) # in the moat of the castle
|
||||
@@ -245,7 +253,7 @@ class World:
|
||||
Location().add(KeyLocation("CASTLE_BUTTON")).connect(castle_inside, None)
|
||||
castle_top_outside = Location()
|
||||
castle_top_inside = Location()
|
||||
self._addEntrance("castle_main_entrance", castle_frontdoor, castle_inside, r.bush)
|
||||
self._addEntrance("castle_main_entrance", castle_frontdoor, castle_inside, None)
|
||||
self._addEntrance("castle_upper_left", castle_top_outside, castle_inside, None)
|
||||
self._addEntrance("castle_upper_right", castle_top_outside, castle_top_inside, None)
|
||||
Location().add(GoldLeaf(0x05A)).connect(castle_courtyard, OR(SWORD, BOW, MAGIC_ROD)) # mad bomber, enemy hiding in the 6 holes
|
||||
@@ -274,7 +282,8 @@ class World:
|
||||
animal_village.connect(ukuku_prairie, OR(HOOKSHOT, ROOSTER))
|
||||
animal_village_connector_left = Location()
|
||||
animal_village_connector_right = Location().connect(animal_village_connector_left, PEGASUS_BOOTS)
|
||||
self._addEntrance("prairie_to_animal_connector", ukuku_prairie, animal_village_connector_left, OR(BOMB, BOOMERANG, MAGIC_POWDER, MAGIC_ROD, SWORD)) # passage under river blocked by bush
|
||||
self._addEntrance("prairie_to_animal_connector", ukuku_prairie, animal_village_connector_left, r.pit_bush) # passage under river blocked by bush
|
||||
self._addEntranceRequirementExit("prairie_to_animal_connector", None) # leaving doesn't require pit_bush
|
||||
self._addEntrance("animal_to_prairie_connector", animal_village, animal_village_connector_right, None)
|
||||
if options.owlstatues == "both" or options.owlstatues == "overworld":
|
||||
animal_village.add(OwlStatue(0x0DA))
|
||||
@@ -282,7 +291,7 @@ class World:
|
||||
desert = Location().connect(animal_village, r.bush) # Note: We moved the walrus blocking the desert.
|
||||
if options.owlstatues == "both" or options.owlstatues == "overworld":
|
||||
desert.add(OwlStatue(0x0CF))
|
||||
desert_lanmola = Location().add(AnglerKey()).connect(desert, OR(BOW, SWORD, HOOKSHOT, MAGIC_ROD, BOOMERANG))
|
||||
desert_lanmola = Location().add(AnglerKey()).connect(desert, r.attack_hookshot_no_bomb)
|
||||
|
||||
animal_village_bombcave = Location()
|
||||
self._addEntrance("animal_cave", desert, animal_village_bombcave, BOMB)
|
||||
@@ -296,13 +305,15 @@ class World:
|
||||
Location().add(HeartPiece(0x1E8)).connect(desert_cave, BOMB) # above the quicksand cave
|
||||
Location().add(Seashell(0x0FF)).connect(desert, POWER_BRACELET) # bottom right corner of the map
|
||||
|
||||
armos_maze = Location().connect(animal_village, POWER_BRACELET)
|
||||
armos_temple = Location()
|
||||
armos_maze = Location("Armos Maze").connect(animal_village, POWER_BRACELET)
|
||||
armos_temple = Location("Southern Shrine")
|
||||
Location().add(FaceKey()).connect(armos_temple, r.miniboss_requirements[world_setup.miniboss_mapping["armos_temple"]])
|
||||
if options.owlstatues == "both" or options.owlstatues == "overworld":
|
||||
armos_maze.add(OwlStatue(0x08F))
|
||||
self._addEntrance("armos_maze_cave", armos_maze, Location().add(Chest(0x2FC)), None)
|
||||
self._addEntrance("armos_temple", armos_maze, armos_temple, None)
|
||||
outside_armos_cave = Location("Outside Armos Maze Cave").connect(armos_maze, OR(r.attack_hookshot, SHIELD))
|
||||
outside_armos_temple = Location("Outside Southern Shrine").connect(armos_maze, OR(r.attack_hookshot, SHIELD))
|
||||
self._addEntrance("armos_maze_cave", outside_armos_cave, Location().add(Chest(0x2FC)), None)
|
||||
self._addEntrance("armos_temple", outside_armos_temple, armos_temple, None)
|
||||
|
||||
armos_fairy_entrance = Location().connect(bay_water, FLIPPERS).connect(animal_village, POWER_BRACELET)
|
||||
self._addEntrance("armos_fairy", armos_fairy_entrance, None, BOMB)
|
||||
@@ -347,17 +358,21 @@ class World:
|
||||
lower_right_taltal.connect(below_right_taltal, FLIPPERS, one_way=True)
|
||||
|
||||
heartpiece_swim_cave = Location().connect(Location().add(HeartPiece(0x1F2)), FLIPPERS)
|
||||
outside_swim_cave = Location()
|
||||
below_right_taltal.connect(outside_swim_cave, FLIPPERS)
|
||||
self._addEntrance("heartpiece_swim_cave", below_right_taltal, heartpiece_swim_cave, FLIPPERS) # cave next to level 4
|
||||
d4_entrance = Location().connect(below_right_taltal, FLIPPERS)
|
||||
lower_right_taltal.connect(d4_entrance, AND(ANGLER_KEY, "ANGLER_KEYHOLE"), one_way=True)
|
||||
self._addEntrance("d4", d4_entrance, None, ANGLER_KEY)
|
||||
self._addEntranceRequirementExit("d4", FLIPPERS) # if exiting, you can leave with flippers without opening the dungeon
|
||||
outside_mambo = Location("Outside Manbo").connect(d4_entrance, FLIPPERS)
|
||||
inside_mambo = Location("Manbo's Cave")
|
||||
mambo = Location().connect(Location().add(Song(0x2FD)), AND(OCARINA, FLIPPERS)) # Manbo's Mambo
|
||||
self._addEntrance("mambo", d4_entrance, mambo, FLIPPERS)
|
||||
self._addEntrance("mambo", d4_entrance, mambo, FLIPPERS)
|
||||
|
||||
# Raft game.
|
||||
raft_house = Location("Raft House")
|
||||
Location().add(KeyLocation("RAFT")).connect(raft_house, COUNT("RUPEES", 100))
|
||||
Location().add(KeyLocation("RAFT")).connect(raft_house, AND(r.bush, COUNT("RUPEES", 100))) # add bush requirement for farming in case player has to try again
|
||||
raft_return_upper = Location()
|
||||
raft_return_lower = Location().connect(raft_return_upper, None, one_way=True)
|
||||
outside_raft_house = Location().connect(below_right_taltal, HOOKSHOT).connect(below_right_taltal, FLIPPERS, one_way=True)
|
||||
@@ -379,7 +394,9 @@ class World:
|
||||
self._addEntrance("rooster_house", outside_rooster_house, None, None)
|
||||
bird_cave = Location()
|
||||
bird_key = Location().add(BirdKey())
|
||||
bird_cave.connect(bird_key, OR(AND(FEATHER, COUNT(POWER_BRACELET, 2)), ROOSTER))
|
||||
bird_cave.connect(bird_key, ROOSTER)
|
||||
if not options.rooster:
|
||||
bird_cave.connect(bird_key, AND(FEATHER, COUNT(POWER_BRACELET, 2))) # elephant statue added
|
||||
if options.logic != "casual":
|
||||
bird_cave.connect(lower_right_taltal, None, one_way=True) # Drop in a hole at bird cave
|
||||
self._addEntrance("bird_cave", outside_rooster_house, bird_cave, None)
|
||||
@@ -387,10 +404,13 @@ class World:
|
||||
|
||||
multichest_cave = Location()
|
||||
multichest_cave_secret = Location().connect(multichest_cave, BOMB)
|
||||
multichest_cave.connect(multichest_cave_secret, BOMB, one_way=True)
|
||||
water_cave_hole = Location() # Location with the hole that drops you onto the hearth piece under water
|
||||
if options.logic != "casual":
|
||||
water_cave_hole.connect(heartpiece_swim_cave, FLIPPERS, one_way=True)
|
||||
outside_multichest_left = Location()
|
||||
multichest_outside = Location().add(Chest(0x01D)) # chest after multichest puzzle outside
|
||||
lower_right_taltal.connect(outside_multichest_left, OR(FLIPPERS, ROOSTER))
|
||||
self._addEntrance("multichest_left", lower_right_taltal, multichest_cave, OR(FLIPPERS, ROOSTER))
|
||||
self._addEntrance("multichest_right", water_cave_hole, multichest_cave, None)
|
||||
self._addEntrance("multichest_top", multichest_outside, multichest_cave_secret, None)
|
||||
@@ -428,7 +448,7 @@ class World:
|
||||
left_right_connector_cave_exit = Location()
|
||||
left_right_connector_cave_entrance.connect(left_right_connector_cave_exit, OR(HOOKSHOT, ROOSTER), one_way=True) # pass through the underground passage to left side
|
||||
taltal_boulder_zone = Location()
|
||||
self._addEntrance("left_to_right_taltalentrance", mountain_bridge_staircase, left_right_connector_cave_entrance, OR(BOMB, BOOMERANG, MAGIC_POWDER, MAGIC_ROD, SWORD))
|
||||
self._addEntrance("left_to_right_taltalentrance", mountain_bridge_staircase, left_right_connector_cave_entrance, r.pit_bush)
|
||||
self._addEntrance("left_taltal_entrance", taltal_boulder_zone, left_right_connector_cave_exit, None)
|
||||
mountain_heartpiece = Location().add(HeartPiece(0x2BA)) # heartpiece in connecting cave
|
||||
left_right_connector_cave_entrance.connect(mountain_heartpiece, BOMB, one_way=True) # in the connecting cave from right to left. one_way to prevent access to left_side_mountain via glitched logic
|
||||
@@ -460,130 +480,169 @@ class World:
|
||||
windfish = Location("Windfish").connect(nightmare, AND(MAGIC_POWDER, SWORD, OR(BOOMERANG, BOW)))
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
hookshot_cave.connect(hookshot_cave_chest, AND(FEATHER, PEGASUS_BOOTS)) # boots jump the gap to the chest
|
||||
graveyard_cave_left.connect(graveyard_cave_right, HOOKSHOT, one_way=True) # hookshot the block behind the stairs while over the pit
|
||||
swamp_chest.connect(swamp, None) # Clip past the flower
|
||||
hookshot_cave.connect(hookshot_cave_chest, r.boots_jump) # boots jump the gap to the chest
|
||||
graveyard_cave_left.connect(graveyard_cave_right, r.hookshot_over_pit, one_way=True) # hookshot the block behind the stairs while over the pit
|
||||
swamp_chest.connect(swamp, r.wall_clip) # Clip past the flower
|
||||
self._addEntranceRequirement("d2", POWER_BRACELET) # clip the top wall to walk between the goponga flower and the wall
|
||||
self._addEntranceRequirement("d2", COUNT(SWORD, 2)) # use l2 sword spin to kill goponga flowers
|
||||
swamp.connect(writes_hut_outside, HOOKSHOT, one_way=True) # hookshot the sign in front of writes hut
|
||||
self._addEntranceRequirementExit("d2", r.wall_clip) # Clip out at d2 entrance door
|
||||
swamp.connect(writes_hut_outside, r.hookshot_over_pit, one_way=True) # hookshot the sign in front of writes hut
|
||||
graveyard_heartpiece.connect(graveyard_cave_right, FEATHER) # jump to the bottom right tile around the blocks
|
||||
graveyard_heartpiece.connect(graveyard_cave_right, OR(HOOKSHOT, BOOMERANG)) # push bottom block, wall clip and hookshot/boomerang corner to grab item
|
||||
|
||||
self._addEntranceRequirement("mamu", AND(FEATHER, POWER_BRACELET)) # can clear the gaps at the start with just feather, can reach bottom left sign with a well timed jump while wall clipped
|
||||
graveyard_heartpiece.connect(graveyard_cave_right, AND(r.wall_clip, OR(HOOKSHOT, BOOMERANG))) # push bottom block, wall clip and hookshot/boomerang corner to grab item
|
||||
|
||||
self._addEntranceRequirement("mamu", AND(r.wall_clip, FEATHER, POWER_BRACELET)) # can clear the gaps at the start with just feather, can reach bottom left sign with a well timed jump while wall clipped
|
||||
self._addEntranceRequirement("prairie_madbatter_connector_entrance", AND(OR(FEATHER, ROOSTER), OR(MAGIC_POWDER, BOMB))) # use bombs or powder to get rid of a bush on the other side by jumping across and placing the bomb/powder before you fall into the pit
|
||||
fisher_under_bridge.connect(bay_water, AND(TRADING_ITEM_FISHING_HOOK, FLIPPERS)) # can talk to the fisherman from the water when the boat is low (requires swimming up out of the water a bit)
|
||||
crow_gold_leaf.connect(castle_courtyard, POWER_BRACELET) # bird on tree at left side kanalet, can use both rocks to kill the crow removing the kill requirement
|
||||
castle_inside.connect(kanalet_chain_trooper, BOOMERANG, one_way=True) # kill the ball and chain trooper from the left side, then use boomerang to grab the dropped item
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, AND(PEGASUS_BOOTS, FEATHER)) # jump across horizontal 4 gap to heart piece
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, r.boots_jump) # jump across horizontal 4 gap to heart piece
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, AND(BOMB, FEATHER, BOOMERANG)) # use jump + boomerang to grab the item from below the ledge
|
||||
desert_lanmola.connect(desert, BOMB) # use bombs to kill lanmola
|
||||
|
||||
|
||||
armos_maze.connect(outside_armos_cave, None) # dodge the armos statues by activating them and running
|
||||
armos_maze.connect(outside_armos_temple, None) # dodge the armos statues by activating them and running
|
||||
d6_connector_left.connect(d6_connector_right, AND(OR(FLIPPERS, PEGASUS_BOOTS), FEATHER)) # jump the gap in underground passage to d6 left side to skip hookshot
|
||||
bird_key.connect(bird_cave, COUNT(POWER_BRACELET, 2)) # corner walk past the one pit on the left side to get to the elephant statue
|
||||
fire_cave_bottom.connect(fire_cave_top, PEGASUS_BOOTS, one_way=True) # flame skip
|
||||
obstacle_cave_exit.connect(obstacle_cave_inside, AND(FEATHER, r.hookshot_over_pit), one_way=True) # one way from right exit to middle, jump past the obstacle, and use hookshot to pull past the double obstacle
|
||||
if not options.rooster:
|
||||
bird_key.connect(bird_cave, COUNT(POWER_BRACELET, 2)) # corner walk past the one pit on the left side to get to the elephant statue
|
||||
right_taltal_connector2.connect(right_taltal_connector3, ROOSTER, one_way=True) # jump off the ledge and grab rooster after landing on the pit
|
||||
fire_cave_bottom.connect(fire_cave_top, AND(r.damage_boost_special, PEGASUS_BOOTS), one_way=True) # flame skip
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
papahl_house.connect(mamasha_trade, r.bomb_trigger) # use a bomb trigger to trade with mamasha without having yoshi doll
|
||||
#self._addEntranceRequirement("dream_hut", FEATHER) # text clip TODO: require nag messages
|
||||
self._addEntranceRequirementEnter("dream_hut", HOOKSHOT) # clip past the rocks in front of dream hut
|
||||
dream_hut_right.connect(dream_hut_left, FEATHER) # super jump
|
||||
forest.connect(swamp, BOMB) # bomb trigger tarin
|
||||
self._addEntranceRequirementEnter("dream_hut", r.hookshot_clip) # clip past the rocks in front of dream hut
|
||||
dream_hut_right.connect(dream_hut_left, r.super_jump_feather) # super jump
|
||||
forest.connect(swamp, r.bomb_trigger) # bomb trigger tarin
|
||||
forest.connect(forest_heartpiece, BOMB, one_way=True) # bomb trigger heartpiece
|
||||
self._addEntranceRequirementEnter("hookshot_cave", HOOKSHOT) # clip past the rocks in front of hookshot cave
|
||||
swamp.connect(forest_toadstool, None, one_way=True) # villa buffer from top (swamp phonebooth area) to bottom (toadstool area)
|
||||
writes_hut_outside.connect(swamp, None, one_way=True) # villa buffer from top (writes hut) to bottom (swamp phonebooth area) or damage boost
|
||||
self._addEntranceRequirementEnter("hookshot_cave", r.hookshot_clip) # clip past the rocks in front of hookshot cave
|
||||
swamp.connect(forest_toadstool, r.pit_buffer_itemless, one_way=True) # villa buffer from top (swamp phonebooth area) to bottom (toadstool area)
|
||||
writes_hut_outside.connect(swamp, r.pit_buffer_itemless, one_way=True) # villa buffer from top (writes hut) to bottom (swamp phonebooth area) or damage boost
|
||||
graveyard.connect(forest_heartpiece, None, one_way=True) # villa buffer from top.
|
||||
log_cave_heartpiece.connect(forest_cave, FEATHER) # super jump
|
||||
log_cave_heartpiece.connect(forest_cave, BOMB) # bomb trigger
|
||||
graveyard_cave_left.connect(graveyard_heartpiece, BOMB, one_way=True) # bomb trigger the heartpiece from the left side
|
||||
graveyard_heartpiece.connect(graveyard_cave_right, None) # sideways block push from the right staircase.
|
||||
graveyard.connect(forest, None, one_way=True) # villa buffer from the top twice to get to the main forest area
|
||||
log_cave_heartpiece.connect(forest_cave, r.super_jump_feather) # super jump
|
||||
log_cave_heartpiece.connect(forest_cave, r.bomb_trigger) # bomb trigger
|
||||
graveyard_cave_left.connect(graveyard_heartpiece, r.bomb_trigger, one_way=True) # bomb trigger the heartpiece from the left side
|
||||
graveyard_heartpiece.connect(graveyard_cave_right, r.sideways_block_push) # sideways block push from the right staircase.
|
||||
|
||||
prairie_island_seashell.connect(ukuku_prairie, AND(FEATHER, r.bush)) # jesus jump from right side, screen transition on top of the water to reach the island
|
||||
self._addEntranceRequirement("castle_jump_cave", FEATHER) # 1 pit buffer to clip bottom wall and jump across.
|
||||
left_bay_area.connect(ghost_hut_outside, FEATHER) # 1 pit buffer to get across
|
||||
tiny_island.connect(left_bay_area, AND(FEATHER, r.bush)) # jesus jump around
|
||||
bay_madbatter_connector_exit.connect(bay_madbatter_connector_entrance, FEATHER, one_way=True) # jesus jump (3 screen) through the underground passage leading to martha's bay mad batter
|
||||
self._addEntranceRequirement("prairie_madbatter_connector_entrance", AND(FEATHER, POWER_BRACELET)) # villa buffer into the top side of the bush, then pick it up
|
||||
|
||||
ukuku_prairie.connect(richard_maze, OR(BOMB, BOOMERANG, MAGIC_POWDER, MAGIC_ROD, SWORD), one_way=True) # break bushes on north side of the maze, and 1 pit buffer into the maze
|
||||
fisher_under_bridge.connect(bay_water, AND(BOMB, FLIPPERS)) # can bomb trigger the item without having the hook
|
||||
animal_village.connect(ukuku_prairie, FEATHER) # jesus jump
|
||||
below_right_taltal.connect(next_to_castle, FEATHER) # jesus jump (north of kanalet castle phonebooth)
|
||||
animal_village_connector_right.connect(animal_village_connector_left, FEATHER) # text clip past the obstacles (can go both ways), feather to wall clip the obstacle without triggering text or shaq jump in bottom right corner if text is off
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, AND(BOMB, OR(HOOKSHOT, FEATHER, PEGASUS_BOOTS))) # bomb trigger from right side, corner walking top right pit is stupid so hookshot or boots added
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, FEATHER) # villa buffer across the pits
|
||||
prairie_island_seashell.connect(ukuku_prairie, AND(r.jesus_jump, r.bush)) # jesus jump from right side, screen transition on top of the water to reach the island
|
||||
self._addEntranceRequirement("castle_jump_cave", r.pit_buffer) # 1 pit buffer to clip bottom wall and jump across.
|
||||
left_bay_area.connect(ghost_hut_outside, r.pit_buffer) # 1 pit buffer to get across
|
||||
tiny_island.connect(left_bay_area, AND(r.jesus_jump, r.bush)) # jesus jump around
|
||||
bay_madbatter_connector_exit.connect(bay_madbatter_connector_entrance, r.jesus_jump, one_way=True) # jesus jump (3 screen) through the underground passage leading to martha's bay mad batter
|
||||
left_bay_area.connect(outside_bay_madbatter_entrance, AND(r.pit_buffer, POWER_BRACELET)) # villa buffer into the top side of the bush, then pick it up
|
||||
|
||||
d6_entrance.connect(ukuku_prairie, FEATHER, one_way=True) # jesus jump (2 screen) from d6 entrance bottom ledge to ukuku prairie
|
||||
d6_entrance.connect(armos_fairy_entrance, FEATHER, one_way=True) # jesus jump (2 screen) from d6 entrance top ledge to armos fairy entrance
|
||||
armos_fairy_entrance.connect(d6_armos_island, FEATHER, one_way=True) # jesus jump from top (fairy bomb cave) to armos island
|
||||
armos_fairy_entrance.connect(raft_exit, FEATHER) # jesus jump (2-ish screen) from fairy cave to lower raft connector
|
||||
self._addEntranceRequirementEnter("obstacle_cave_entrance", HOOKSHOT) # clip past the rocks in front of obstacle cave entrance
|
||||
obstacle_cave_inside_chest.connect(obstacle_cave_inside, FEATHER) # jump to the rightmost pits + 1 pit buffer to jump across
|
||||
obstacle_cave_exit.connect(obstacle_cave_inside, FEATHER) # 1 pit buffer above boots crystals to get past
|
||||
lower_right_taltal.connect(hibiscus_item, AND(TRADING_ITEM_PINEAPPLE, BOMB), one_way=True) # bomb trigger papahl from below ledge, requires pineapple
|
||||
|
||||
self._addEntranceRequirement("heartpiece_swim_cave", FEATHER) # jesus jump into the cave entrance after jumping down the ledge, can jesus jump back to the ladder 1 screen below
|
||||
self._addEntranceRequirement("mambo", FEATHER) # jesus jump from (unlocked) d4 entrance to mambo's cave entrance
|
||||
outside_raft_house.connect(below_right_taltal, FEATHER, one_way=True) # jesus jump from the ledge at raft to the staircase 1 screen south
|
||||
ukuku_prairie.connect(richard_maze, AND(r.pit_buffer_itemless, OR(AND(MAGIC_POWDER, MAX_POWDER_UPGRADE), BOMB, BOOMERANG, MAGIC_ROD, SWORD)), one_way=True) # break bushes on north side of the maze, and 1 pit buffer into the maze
|
||||
richard_maze.connect(ukuku_prairie, AND(r.pit_buffer_itemless, OR(MAGIC_POWDER, BOMB, BOOMERANG, MAGIC_ROD, SWORD)), one_way=True) # same as above (without powder upgrade) in one of the two northern screens of the maze to escape
|
||||
fisher_under_bridge.connect(bay_water, AND(r.bomb_trigger, AND(FEATHER, FLIPPERS))) # up-most left wall is a pit: bomb trigger with it. If photographer is there, clear that first which is why feather is required logically
|
||||
animal_village.connect(ukuku_prairie, r.jesus_jump) # jesus jump
|
||||
below_right_taltal.connect(next_to_castle, r.jesus_jump) # jesus jump (north of kanalet castle phonebooth)
|
||||
#animal_village_connector_right.connect(animal_village_connector_left, AND(r.text_clip, FEATHER)) # text clip past the obstacles (can go both ways), feather to wall clip the obstacle without triggering text
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, AND(r.bomb_trigger, OR(HOOKSHOT, FEATHER, r.boots_bonk_pit))) # bomb trigger from right side, corner walking top right pit is stupid so hookshot or boots added
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, r.pit_buffer) # villa buffer across the pits
|
||||
|
||||
self._addEntranceRequirement("multichest_left", FEATHER) # jesus jump past staircase leading up the mountain
|
||||
outside_rooster_house.connect(lower_right_taltal, FEATHER) # jesus jump (1 or 2 screen depending if angler key is used) to staircase leading up the mountain
|
||||
d6_entrance.connect(ukuku_prairie, r.jesus_jump, one_way=True) # jesus jump (2 screen) from d6 entrance bottom ledge to ukuku prairie
|
||||
d6_entrance.connect(armos_fairy_entrance, r.jesus_jump, one_way=True) # jesus jump (2 screen) from d6 entrance top ledge to armos fairy entrance
|
||||
d6_connector_left.connect(d6_connector_right, r.jesus_jump) # jesus jump over water; left side is jumpable, or villa buffer if it's easier for you
|
||||
armos_fairy_entrance.connect(d6_armos_island, r.jesus_jump, one_way=True) # jesus jump from top (fairy bomb cave) to armos island
|
||||
armos_fairy_entrance.connect(raft_exit, r.jesus_jump) # jesus jump (2-ish screen) from fairy cave to lower raft connector
|
||||
self._addEntranceRequirementEnter("obstacle_cave_entrance", r.hookshot_clip) # clip past the rocks in front of obstacle cave entrance
|
||||
obstacle_cave_inside_chest.connect(obstacle_cave_inside, r.pit_buffer) # jump to the rightmost pits + 1 pit buffer to jump across
|
||||
obstacle_cave_exit.connect(obstacle_cave_inside, r.pit_buffer) # 1 pit buffer above boots crystals to get past
|
||||
lower_right_taltal.connect(hibiscus_item, AND(TRADING_ITEM_PINEAPPLE, r.bomb_trigger), one_way=True) # bomb trigger papahl from below ledge, requires pineapple
|
||||
|
||||
self._addEntranceRequirement("heartpiece_swim_cave", r.jesus_jump) # jesus jump into the cave entrance after jumping down the ledge, can jesus jump back to the ladder 1 screen below
|
||||
self._addEntranceRequirement("mambo", r.jesus_jump) # jesus jump from (unlocked) d4 entrance to mambo's cave entrance
|
||||
outside_raft_house.connect(below_right_taltal, r.jesus_jump, one_way=True) # jesus jump from the ledge at raft to the staircase 1 screen south
|
||||
|
||||
self._addEntranceRequirement("multichest_left", r.jesus_jump) # jesus jump past staircase leading up the mountain
|
||||
outside_rooster_house.connect(lower_right_taltal, r.jesus_jump) # jesus jump (1 or 2 screen depending if angler key is used) to staircase leading up the mountain
|
||||
d7_platau.connect(water_cave_hole, None, one_way=True) # use save and quit menu to gain control while falling to dodge the water cave hole
|
||||
mountain_bridge_staircase.connect(outside_rooster_house, AND(PEGASUS_BOOTS, FEATHER)) # cross bridge to staircase with pit buffer to clip bottom wall and jump across
|
||||
bird_key.connect(bird_cave, AND(FEATHER, HOOKSHOT)) # hookshot jump across the big pits room
|
||||
right_taltal_connector2.connect(right_taltal_connector3, None, one_way=True) # 2 seperate pit buffers so not obnoxious to get past the two pit rooms before d7 area. 2nd pits can pit buffer on top right screen, bottom wall to scroll on top of the wall on bottom screen
|
||||
left_right_connector_cave_exit.connect(left_right_connector_cave_entrance, AND(HOOKSHOT, FEATHER), one_way=True) # pass through the passage in reverse using a superjump to get out of the dead end
|
||||
obstacle_cave_inside.connect(mountain_heartpiece, BOMB, one_way=True) # bomb trigger from boots crystal cave
|
||||
self._addEntranceRequirement("d8", OR(BOMB, AND(OCARINA, SONG3))) # bomb trigger the head and walk trough, or play the ocarina song 3 and walk through
|
||||
mountain_bridge_staircase.connect(outside_rooster_house, AND(r.boots_jump, r.pit_buffer)) # cross bridge to staircase with pit buffer to clip bottom wall and jump across. added boots_jump to not require going through this section with just feather
|
||||
bird_key.connect(bird_cave, r.hookshot_jump) # hookshot jump across the big pits room
|
||||
right_taltal_connector2.connect(right_taltal_connector3, OR(r.pit_buffer, ROOSTER), one_way=True) # trigger a quick fall on the screen above the exit by transitioning down on the leftmost/rightmost pit and then buffering sq menu for control while in the air. or pick up the rooster while dropping off the ledge at exit
|
||||
left_right_connector_cave_exit.connect(left_right_connector_cave_entrance, AND(HOOKSHOT, r.super_jump_feather), one_way=True) # pass through the passage in reverse using a superjump to get out of the dead end
|
||||
obstacle_cave_inside.connect(mountain_heartpiece, r.bomb_trigger, one_way=True) # bomb trigger from boots crystal cave
|
||||
self._addEntranceRequirement("d8", OR(r.bomb_trigger, AND(OCARINA, SONG3))) # bomb trigger the head and walk through, or play the ocarina song 3 and walk through
|
||||
|
||||
if options.logic == 'hell':
|
||||
dream_hut_right.connect(dream_hut, None) # alternate diagonal movement with orthogonal movement to control the mimics. Get them clipped into the walls to walk past
|
||||
swamp.connect(forest_toadstool, None) # damage boost from toadstool area across the pit
|
||||
swamp.connect(forest, AND(r.bush, OR(PEGASUS_BOOTS, HOOKSHOT))) # boots bonk / hookshot spam over the pits right of forest_rear_chest
|
||||
swamp.connect(forest_toadstool, r.damage_boost) # damage boost from toadstool area across the pit
|
||||
swamp.connect(forest, AND(r.bush, OR(r.boots_bonk_pit, r.hookshot_spam_pit))) # boots bonk / hookshot spam over the pits right of forest_rear_chest
|
||||
forest.connect(forest_heartpiece, PEGASUS_BOOTS, one_way=True) # boots bonk across the pits
|
||||
forest_cave_crystal_chest.connect(forest_cave, AND(r.super_jump_feather, r.hookshot_clip_block, r.sideways_block_push)) # superjump off the bottom wall to get between block and crystal, than use 3 keese to hookshot clip while facing right to get a sideways blockpush off
|
||||
log_cave_heartpiece.connect(forest_cave, BOOMERANG) # clip the boomerang through the corner gaps on top right to grab the item
|
||||
log_cave_heartpiece.connect(forest_cave, AND(ROOSTER, OR(PEGASUS_BOOTS, SWORD, BOW, MAGIC_ROD))) # boots rooster hop in bottom left corner to "superjump" into the area. use buffers after picking up rooster to gain height / time to throw rooster again facing up
|
||||
writes_hut_outside.connect(swamp, None) # damage boost with moblin arrow next to telephone booth
|
||||
writes_cave_left_chest.connect(writes_cave, None) # damage boost off the zol to get across the pit.
|
||||
graveyard.connect(crazy_tracy_hut, HOOKSHOT, one_way=True) # use hookshot spam to clip the rock on the right with the crow
|
||||
graveyard.connect(forest, OR(PEGASUS_BOOTS, HOOKSHOT)) # boots bonk witches hut, or hookshot spam across the pit
|
||||
graveyard_cave_left.connect(graveyard_cave_right, HOOKSHOT) # hookshot spam over the pit
|
||||
graveyard_cave_right.connect(graveyard_cave_left, PEGASUS_BOOTS, one_way=True) # boots bonk off the cracked block
|
||||
|
||||
self._addEntranceRequirementEnter("mamu", AND(PEGASUS_BOOTS, POWER_BRACELET)) # can clear the gaps at the start with multiple pit buffers, can reach bottom left sign with bonking along the bottom wall
|
||||
self._addEntranceRequirement("castle_jump_cave", PEGASUS_BOOTS) # pit buffer to clip bottom wall and boots bonk across
|
||||
prairie_cave_secret_exit.connect(prairie_cave, AND(BOMB, OR(PEGASUS_BOOTS, HOOKSHOT))) # hookshot spam or boots bonk across pits can go from left to right by pit buffering on top of the bottom wall then boots bonk across
|
||||
richard_cave_chest.connect(richard_cave, None) # use the zol on the other side of the pit to damage boost across (requires damage from pit + zol)
|
||||
castle_secret_entrance_right.connect(castle_secret_entrance_left, AND(PEGASUS_BOOTS, "MEDICINE2")) # medicine iframe abuse to get across spikes with a boots bonk
|
||||
left_bay_area.connect(ghost_hut_outside, PEGASUS_BOOTS) # multiple pit buffers to bonk across the bottom wall
|
||||
tiny_island.connect(left_bay_area, AND(PEGASUS_BOOTS, r.bush)) # jesus jump around with boots bonks, then one final bonk off the bottom wall to get on the staircase (needs to be centered correctly)
|
||||
self._addEntranceRequirement("prairie_madbatter_connector_entrance", AND(PEGASUS_BOOTS, OR(MAGIC_POWDER, BOMB, SWORD, MAGIC_ROD, BOOMERANG))) # Boots bonk across the bottom wall, then remove one of the bushes to get on land
|
||||
self._addEntranceRequirementExit("prairie_madbatter_connector_entrance", AND(PEGASUS_BOOTS, r.bush)) # if exiting, you can pick up the bushes by normal means and boots bonk across the bottom wall
|
||||
log_cave_heartpiece.connect(forest_cave, OR(r.super_jump_rooster, r.boots_roosterhop)) # boots rooster hop in bottom left corner to "superjump" into the area. use buffers after picking up rooster to gain height / time to throw rooster again facing up
|
||||
writes_hut_outside.connect(swamp, r.damage_boost) # damage boost with moblin arrow next to telephone booth
|
||||
writes_cave_left_chest.connect(writes_cave, r.damage_boost) # damage boost off the zol to get across the pit.
|
||||
graveyard.connect(crazy_tracy_hut, r.hookshot_spam_pit, one_way=True) # use hookshot spam to clip the rock on the right with the crow
|
||||
graveyard.connect(forest, OR(r.boots_bonk_pit, r.hookshot_spam_pit)) # boots bonk over pits by witches hut, or hookshot spam across the pit
|
||||
graveyard_cave_left.connect(graveyard_cave_right, r.hookshot_spam_pit) # hookshot spam over the pit
|
||||
graveyard_cave_right.connect(graveyard_cave_left, OR(r.damage_boost, r.boots_bonk_pit), one_way=True) # boots bonk off the cracked block, or set up a damage boost with the keese
|
||||
|
||||
self._addEntranceRequirementEnter("mamu", AND(r.pit_buffer_itemless, r.pit_buffer_boots, POWER_BRACELET)) # can clear the gaps at the start with multiple pit buffers, can reach bottom left sign with bonking along the bottom wall
|
||||
self._addEntranceRequirement("castle_jump_cave", r.pit_buffer_boots) # pit buffer to clip bottom wall and boots bonk across
|
||||
prairie_cave_secret_exit.connect(prairie_cave, AND(BOMB, OR(r.boots_bonk_pit, r.hookshot_spam_pit))) # hookshot spam or boots bonk across pits can go from left to right by pit buffering on top of the bottom wall then boots bonk across
|
||||
richard_cave_chest.connect(richard_cave, r.damage_boost) # use the zol on the other side of the pit to damage boost across (requires damage from pit + zol)
|
||||
castle_secret_entrance_right.connect(castle_secret_entrance_left, r.boots_bonk_2d_spikepit) # medicine iframe abuse to get across spikes with a boots bonk
|
||||
left_bay_area.connect(ghost_hut_outside, r.pit_buffer_boots) # multiple pit buffers to bonk across the bottom wall
|
||||
left_bay_area.connect(ukuku_prairie, r.hookshot_clip_block, one_way=True) # clip through the donuts blocking the path next to prairie plateau cave by hookshotting up and killing the two moblins that way which clips you further up two times. This is enough to move right
|
||||
tiny_island.connect(left_bay_area, AND(r.jesus_buffer, r.boots_bonk_pit, r.bush)) # jesus jump around with boots bonks, then one final bonk off the bottom wall to get on the staircase (needs to be centered correctly)
|
||||
left_bay_area.connect(outside_bay_madbatter_entrance, AND(r.pit_buffer_boots, OR(MAGIC_POWDER, SWORD, MAGIC_ROD, BOOMERANG))) # Boots bonk across the bottom wall, then remove one of the bushes to get on land
|
||||
left_bay_area.connect(outside_bay_madbatter_entrance, AND(r.pit_buffer, r.hookshot_spam_pit, r.bush)) # hookshot spam to cross one pit at the top, then buffer until on top of the bush to be able to break it
|
||||
outside_bay_madbatter_entrance.connect(left_bay_area, AND(r.pit_buffer_boots, r.bush), one_way=True) # if exiting, you can pick up the bushes by normal means and boots bonk across the bottom wall
|
||||
|
||||
# bay_water connectors, only left_bay_area, ukuku_prairie and animal_village have to be connected with jesus jumps. below_right_taltal, d6_armos_island and armos_fairy_entrance are accounted for via ukuku prairie in glitch logic
|
||||
left_bay_area.connect(bay_water, FEATHER) # jesus jump (can always reach bay_water with jesus jumping from every way to enter bay_water, so no one_way)
|
||||
animal_village.connect(bay_water, FEATHER) # jesus jump (can always reach bay_water with jesus jumping from every way to enter bay_water, so no one_way)
|
||||
ukuku_prairie.connect(bay_water, FEATHER, one_way=True) # jesus jump
|
||||
bay_water.connect(d5_entrance, FEATHER) # jesus jump into d5 entrance (wall clip), wall clip + jesus jump to get out
|
||||
|
||||
crow_gold_leaf.connect(castle_courtyard, BOMB) # bird on tree at left side kanalet, place a bomb against the tree and the crow flies off. With well placed second bomb the crow can be killed
|
||||
mermaid_statue.connect(animal_village, AND(TRADING_ITEM_SCALE, FEATHER)) # early mermaid statue by buffering on top of the right ledge, then superjumping to the left (horizontal pixel perfect)
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, PEGASUS_BOOTS) # boots bonk across bottom wall (both at entrance and in item room)
|
||||
left_bay_area.connect(bay_water, OR(r.jesus_jump, r.jesus_rooster)) # jesus jump/rooster (can always reach bay_water with jesus jumping from every way to enter bay_water, so no one_way)
|
||||
animal_village.connect(bay_water, OR(r.jesus_jump, r.jesus_rooster)) # jesus jump/rooster (can always reach bay_water with jesus jumping from every way to enter bay_water, so no one_way)
|
||||
ukuku_prairie.connect(bay_water, OR(r.jesus_jump, r.jesus_rooster), one_way=True) # jesus jump/rooster
|
||||
bay_water.connect(d5_entrance, OR(r.jesus_jump, r.jesus_rooster)) # jesus jump/rooster into d5 entrance (wall clip), wall clip + jesus jump to get out
|
||||
|
||||
prairie_island_seashell.connect(ukuku_prairie, AND(r.jesus_rooster, r.bush)) # jesus rooster from right side, screen transition on top of the water to reach the island
|
||||
bay_madbatter_connector_exit.connect(bay_madbatter_connector_entrance, r.jesus_rooster, one_way=True) # jesus rooster (3 screen) through the underground passage leading to martha's bay mad batter
|
||||
# fisher_under_bridge.connect(bay_water, AND(TRADING_ITEM_FISHING_HOOK, OR(FEATHER, SWORD, BOW), FLIPPERS)) # just swing/shoot at fisher, if photographer is on screen it is dumb
|
||||
fisher_under_bridge.connect(bay_water, AND(TRADING_ITEM_FISHING_HOOK, FLIPPERS)) # face the fisherman from the left, get within 4 pixels (a range, not exact) of his left side, hold up, and mash a until you get the textbox.
|
||||
|
||||
#TODO: add jesus rooster to trick list
|
||||
|
||||
below_right_taltal.connect(next_to_castle, r.jesus_buffer, one_way=True) # face right, boots bonk and get far enough left to jesus buffer / boots bonk across the bottom wall to the stairs
|
||||
crow_gold_leaf.connect(castle_courtyard, BOMB) # bird on tree at left side kanalet, place a bomb against the tree and the crow flies off. With well placed second bomb the crow can be killed
|
||||
mermaid_statue.connect(animal_village, AND(TRADING_ITEM_SCALE, r.super_jump_feather)) # early mermaid statue by buffering on top of the right ledge, then superjumping to the left (horizontal pixel perfect)
|
||||
animal_village_connector_right.connect(animal_village_connector_left, r.shaq_jump) # shaq jump off the obstacle to get through
|
||||
animal_village_connector_left.connect(animal_village_connector_right, r.hookshot_clip_block, one_way=True) # use hookshot with an enemy to clip through the obstacle
|
||||
animal_village_bombcave_heartpiece.connect(animal_village_bombcave, r.pit_buffer_boots) # boots bonk across bottom wall (both at entrance and in item room)
|
||||
|
||||
d6_armos_island.connect(ukuku_prairie, OR(r.jesus_jump, r.jesus_rooster)) # jesus jump / rooster (3 screen) from seashell mansion to armos island
|
||||
armos_fairy_entrance.connect(d6_armos_island, r.jesus_buffer, one_way=True) # jesus jump from top (fairy bomb cave) to armos island with fast falling
|
||||
d6_connector_right.connect(d6_connector_left, r.pit_buffer_boots) # boots bonk across bottom wall at water and pits (can do both ways)
|
||||
d6_entrance.connect(ukuku_prairie, r.jesus_rooster, one_way=True) # jesus rooster (2 screen) from d6 entrance bottom ledge to ukuku prairie
|
||||
d6_entrance.connect(armos_fairy_entrance, r.jesus_rooster, one_way=True) # jesus rooster (2 screen) from d6 entrance top ledge to armos fairy entrance
|
||||
armos_fairy_entrance.connect(d6_armos_island, r.jesus_rooster, one_way=True) # jesus rooster from top (fairy bomb cave) to armos island
|
||||
armos_fairy_entrance.connect(raft_exit, r.jesus_rooster) # jesus rooster (2-ish screen) from fairy cave to lower raft connector
|
||||
|
||||
obstacle_cave_entrance.connect(obstacle_cave_inside, OR(r.hookshot_clip_block, r.shaq_jump)) # get past crystal rocks by hookshotting into top pushable block, or boots dashing into top wall where the pushable block is to superjump down
|
||||
obstacle_cave_entrance.connect(obstacle_cave_inside, r.boots_roosterhop) # get past crystal rocks pushing the top pushable block, then boots dashing up picking up the rooster before bonking. Pause buffer until rooster is fully picked up then throw it down before bonking into wall
|
||||
d4_entrance.connect(below_right_taltal, OR(r.jesus_jump, r.jesus_rooster), one_way=True) # jesus jump/rooster 5 screens to staircase below damp cave
|
||||
lower_right_taltal.connect(below_right_taltal, OR(r.jesus_jump, r.jesus_rooster), one_way=True) # jesus jump/rooster to upper ledges, jump off, enter and exit s+q menu to regain pauses, then jesus jump 4 screens to staircase below damp cave
|
||||
below_right_taltal.connect(outside_swim_cave, r.jesus_rooster) # jesus rooster into the cave entrance after jumping down the ledge, can jesus jump back to the ladder 1 screen below
|
||||
outside_mambo.connect(below_right_taltal, OR(r.jesus_rooster, r.jesus_jump)) # jesus jump/rooster to mambo's cave entrance
|
||||
if options.hardmode != "oracle": # don't take damage from drowning in water. Could get it with more health probably but standard 3 hearts is not enough
|
||||
mambo.connect(inside_mambo, AND(OCARINA, r.bomb_trigger)) # while drowning, buffer a bomb and after it explodes, buffer another bomb out of the save and quit menu.
|
||||
outside_raft_house.connect(below_right_taltal, r.jesus_rooster, one_way=True) # jesus rooster from the ledge at raft to the staircase 1 screen south
|
||||
lower_right_taltal.connect(outside_multichest_left, r.jesus_rooster) # jesus rooster past staircase leading up the mountain
|
||||
outside_rooster_house.connect(lower_right_taltal, r.jesus_rooster, one_way=True) # jesus rooster down to staircase below damp cave
|
||||
|
||||
d6_armos_island.connect(ukuku_prairie, FEATHER) # jesus jump (3 screen) from seashell mansion to armos island
|
||||
armos_fairy_entrance.connect(d6_armos_island, PEGASUS_BOOTS, one_way=True) # jesus jump from top (fairy bomb cave) to armos island with fast falling
|
||||
d6_connector_right.connect(d6_connector_left, PEGASUS_BOOTS) # boots bonk across bottom wall at water and pits (can do both ways)
|
||||
|
||||
obstacle_cave_entrance.connect(obstacle_cave_inside, OR(HOOKSHOT, AND(FEATHER, PEGASUS_BOOTS, OR(SWORD, MAGIC_ROD, BOW)))) # get past crystal rocks by hookshotting into top pushable block, or boots dashing into top wall where the pushable block is to superjump down
|
||||
obstacle_cave_entrance.connect(obstacle_cave_inside, AND(PEGASUS_BOOTS, ROOSTER)) # get past crystal rocks pushing the top pushable block, then boots dashing up picking up the rooster before bonking. Pause buffer until rooster is fully picked up then throw it down before bonking into wall
|
||||
d4_entrance.connect(below_right_taltal, FEATHER) # jesus jump a long way
|
||||
if options.entranceshuffle in ("default", "simple"): # connector cave from armos d6 area to raft shop may not be randomized to add a flippers path since flippers stop you from jesus jumping
|
||||
below_right_taltal.connect(raft_game, AND(FEATHER, r.attack_hookshot_powder), one_way=True) # jesus jump from heartpiece water cave, around the island and clip past the diagonal gap in the rock, then jesus jump all the way down the waterfall to the chests (attack req for hardlock flippers+feather scenario)
|
||||
outside_raft_house.connect(below_right_taltal, AND(FEATHER, PEGASUS_BOOTS)) #superjump from ledge left to right, can buffer to land on ledge instead of water, then superjump right which is pixel perfect
|
||||
bridge_seashell.connect(outside_rooster_house, AND(PEGASUS_BOOTS, POWER_BRACELET)) # boots bonk
|
||||
bird_key.connect(bird_cave, AND(FEATHER, PEGASUS_BOOTS)) # boots jump above wall, use multiple pit buffers to get across
|
||||
mountain_bridge_staircase.connect(outside_rooster_house, OR(PEGASUS_BOOTS, FEATHER)) # cross bridge to staircase with pit buffer to clip bottom wall and jump or boots bonk across
|
||||
left_right_connector_cave_entrance.connect(left_right_connector_cave_exit, AND(PEGASUS_BOOTS, FEATHER), one_way=True) # boots jump to bottom left corner of pits, pit buffer and jump to left
|
||||
left_right_connector_cave_exit.connect(left_right_connector_cave_entrance, AND(ROOSTER, OR(PEGASUS_BOOTS, SWORD, BOW, MAGIC_ROD)), one_way=True) # pass through the passage in reverse using a boots rooster hop or rooster superjump in the one way passage area
|
||||
|
||||
below_right_taltal.connect(raft_game, AND(OR(r.jesus_jump, r.jesus_rooster), r.attack_hookshot_powder), one_way=True) # jesus jump from heartpiece water cave, around the island and clip past the diagonal gap in the rock, then jesus jump all the way down the waterfall to the chests (attack req for hardlock flippers+feather scenario)
|
||||
outside_raft_house.connect(below_right_taltal, AND(r.super_jump, PEGASUS_BOOTS)) #superjump from ledge left to right, can buffer to land on ledge instead of water, then superjump right which is pixel perfect. Boots to get out of wall after landing
|
||||
bridge_seashell.connect(outside_rooster_house, AND(OR(r.hookshot_spam_pit, r.boots_bonk_pit), POWER_BRACELET)) # boots bonk or hookshot spam over the pit to get to the rock
|
||||
bird_key.connect(bird_cave, AND(r.boots_jump, r.pit_buffer)) # boots jump above wall, use multiple pit buffers to get across
|
||||
right_taltal_connector2.connect(right_taltal_connector3, r.pit_buffer_itemless, one_way=True) # 2 separate pit buffers so not obnoxious to get past the two pit rooms before d7 area. 2nd pits can pit buffer on top right screen, bottom wall to scroll on top of the wall on bottom screen
|
||||
mountain_bridge_staircase.connect(outside_rooster_house, r.pit_buffer_boots) # cross bridge to staircase with pit buffer to clip bottom wall and jump or boots bonk across
|
||||
left_right_connector_cave_entrance.connect(left_right_connector_cave_exit, AND(r.boots_jump, r.pit_buffer), one_way=True) # boots jump to bottom left corner of pits, pit buffer and jump to left
|
||||
left_right_connector_cave_exit.connect(left_right_connector_cave_entrance, AND(ROOSTER, OR(r.boots_roosterhop, r.super_jump_rooster)), one_way=True) # pass through the passage in reverse using a boots rooster hop or rooster superjump in the one way passage area
|
||||
|
||||
windfish.connect(nightmare, AND(SWORD, OR(BOOMERANG, BOW, BOMB, COUNT(SWORD, 2), AND(OCARINA, OR(SONG1, SONG3))))) # sword quick kill blob, can kill dethl with bombs or sword beams, and can use ocarina to freeze one of ganon's bats to skip dethl eye phase
|
||||
|
||||
self.start = start_house
|
||||
self.egg = windfish_egg
|
||||
self.nightmare = nightmare
|
||||
@@ -659,7 +718,7 @@ class EntranceExterior:
|
||||
self.requirement = requirement
|
||||
self.one_way_enter_requirement = one_way_enter_requirement
|
||||
self.one_way_exit_requirement = one_way_exit_requirement
|
||||
|
||||
|
||||
def addRequirement(self, new_requirement):
|
||||
self.requirement = OR(self.requirement, new_requirement)
|
||||
|
||||
@@ -674,9 +733,9 @@ class EntranceExterior:
|
||||
self.one_way_enter_requirement = new_requirement
|
||||
else:
|
||||
self.one_way_enter_requirement = OR(self.one_way_enter_requirement, new_requirement)
|
||||
|
||||
|
||||
def enterIsSet(self):
|
||||
return self.one_way_enter_requirement != "UNSET"
|
||||
|
||||
|
||||
def exitIsSet(self):
|
||||
return self.one_way_exit_requirement != "UNSET"
|
||||
|
||||
@@ -254,17 +254,62 @@ def isConsumable(item) -> bool:
|
||||
class RequirementsSettings:
|
||||
def __init__(self, options):
|
||||
self.bush = OR(SWORD, MAGIC_POWDER, MAGIC_ROD, POWER_BRACELET, BOOMERANG)
|
||||
self.pit_bush = OR(SWORD, MAGIC_POWDER, MAGIC_ROD, BOOMERANG, BOMB) # unique
|
||||
self.attack = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG)
|
||||
self.attack_hookshot = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # switches, hinox, shrouded stalfos
|
||||
self.attack_hookshot = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # hinox, shrouded stalfos
|
||||
self.hit_switch = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # hit switches in dungeons
|
||||
self.attack_hookshot_powder = OR(SWORD, BOMB, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT, MAGIC_POWDER) # zols, keese, moldorm
|
||||
self.attack_no_bomb = OR(SWORD, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # ?
|
||||
self.attack_hookshot_no_bomb = OR(SWORD, BOW, MAGIC_ROD, BOOMERANG, HOOKSHOT) # vire
|
||||
self.attack_no_boomerang = OR(SWORD, BOMB, BOW, MAGIC_ROD, HOOKSHOT) # teleporting owls
|
||||
self.attack_skeleton = OR(SWORD, BOMB, BOW, BOOMERANG, HOOKSHOT) # cannot kill skeletons with the fire rod
|
||||
self.attack_gibdos = OR(SWORD, BOMB, BOW, BOOMERANG, AND(MAGIC_ROD, HOOKSHOT)) # gibdos are only stunned with hookshot, but can be burnt to jumping stalfos first with magic rod
|
||||
self.attack_pols_voice = OR(BOMB, MAGIC_ROD, AND(OCARINA, SONG1)) # BOW works, but isn't as reliable as it needs 4 arrows.
|
||||
self.attack_wizrobe = OR(BOMB, MAGIC_ROD) # BOW works, but isn't as reliable as it needs 4 arrows.
|
||||
self.stun_wizrobe = OR(BOOMERANG, MAGIC_POWDER, HOOKSHOT)
|
||||
self.rear_attack = OR(SWORD, BOMB) # mimic
|
||||
self.rear_attack_range = OR(MAGIC_ROD, BOW) # mimic
|
||||
self.fire = OR(MAGIC_POWDER, MAGIC_ROD) # torches
|
||||
self.push_hardhat = OR(SHIELD, SWORD, HOOKSHOT, BOOMERANG)
|
||||
self.shuffled_magnifier = TRADING_ITEM_MAGNIFYING_GLASS # overwritten if vanilla trade items
|
||||
|
||||
self.throw_pot = POWER_BRACELET # grab pots to kill enemies
|
||||
self.throw_enemy = POWER_BRACELET # grab stunned enemies to kill enemies
|
||||
self.tight_jump = FEATHER # jumps that are possible but are tight to make it across
|
||||
self.super_jump = AND(FEATHER, OR(SWORD, BOW, MAGIC_ROD)) # standard superjump for glitch logic
|
||||
self.super_jump_boots = AND(PEGASUS_BOOTS, FEATHER, OR(SWORD, BOW, MAGIC_ROD)) # boots dash into wall for unclipped superjump
|
||||
self.super_jump_feather = FEATHER # using only feather to align and jump off walls
|
||||
self.super_jump_sword = AND(FEATHER, SWORD) # unclipped superjumps
|
||||
self.super_jump_rooster = AND(ROOSTER, OR(SWORD, BOW, MAGIC_ROD)) # use rooster instead of feather to superjump off walls (only where rooster is allowed to be used)
|
||||
self.shaq_jump = FEATHER # use interactable objects (keyblocks / pushable blocks)
|
||||
self.boots_superhop = AND(PEGASUS_BOOTS, OR(MAGIC_ROD, BOW)) # dash into walls, pause, unpause and use weapon + hold direction away from wall. Only works in peg rooms
|
||||
self.boots_roosterhop = AND(PEGASUS_BOOTS, ROOSTER) # dash towards a wall, pick up the rooster and throw it away from the wall before hitting the wall to get a superjump
|
||||
self.jesus_jump = FEATHER # pause on the frame of hitting liquid (water / lava) to be able to jump again on unpause
|
||||
self.jesus_buffer = PEGASUS_BOOTS # use a boots bonk to get on top of liquid (water / lava), then use buffers to get into positions
|
||||
self.damage_boost_special = options.hardmode == "none" # use damage to cross pits / get through forced barriers without needing an enemy that can be eaten by bowwow
|
||||
self.damage_boost = (options.bowwow == "normal") & (options.hardmode == "none") # Use damage to cross pits / get through forced barriers
|
||||
self.sideways_block_push = True # wall clip pushable block, get against the edge and push block to move it sideways
|
||||
self.wall_clip = True # push into corners to get further into walls, to avoid collision with enemies along path (see swamp flowers for example) or just getting a better position for jumps
|
||||
self.pit_buffer_itemless = True # walk on top of pits and buffer down
|
||||
self.pit_buffer = FEATHER # jump on top of pits and buffer to cross vertical gaps
|
||||
self.pit_buffer_boots = OR(PEGASUS_BOOTS, FEATHER) # use boots or feather to cross gaps
|
||||
self.boots_jump = AND(PEGASUS_BOOTS, FEATHER) # use boots jumps to cross 4 gap spots or other hard to reach spots
|
||||
self.boots_bonk = PEGASUS_BOOTS # bonk against walls in 2d sections to get to higher places (no pits involved usually)
|
||||
self.boots_bonk_pit = PEGASUS_BOOTS # use boots bonks to cross 1 tile gaps
|
||||
self.boots_bonk_2d_spikepit = AND(PEGASUS_BOOTS, "MEDICINE2") # use iframes from medicine to get a boots dash going in 2d spike pits (kanalet secret passage, d3 2d section to boss)
|
||||
self.boots_bonk_2d_hell = PEGASUS_BOOTS # seperate boots bonks from hell logic which are harder?
|
||||
self.boots_dash_2d = PEGASUS_BOOTS # use boots to dash over 1 tile gaps in 2d sections
|
||||
self.hookshot_spam_pit = HOOKSHOT # use hookshot with spam to cross 1 tile gaps
|
||||
self.hookshot_clip = AND(HOOKSHOT, options.superweapons == False) # use hookshot at specific angles to hookshot past blocks (see forest north log cave, dream shrine entrance for example)
|
||||
self.hookshot_clip_block = HOOKSHOT # use hookshot spam with enemies to clip through entire blocks (d5 room before gohma, d2 pots room before boss)
|
||||
self.hookshot_over_pit = HOOKSHOT # use hookshot while over a pit to reach certain areas (see d3 vacuum room, d5 north of crossroads for example)
|
||||
self.hookshot_jump = AND(HOOKSHOT, FEATHER) # while over pits, on the first frame after the hookshot is retracted you can input a jump to cross big pit gaps
|
||||
self.bookshot = AND(FEATHER, HOOKSHOT) # use feather on A, hookshot on B on the same frame to get a speedy hookshot that can be used to clip past blocks
|
||||
self.bomb_trigger = BOMB # drop two bombs at the same time to trigger cutscenes or pickup items (can use pits, or screen transitions
|
||||
self.shield_bump = SHIELD # use shield to knock back enemies or knock off enemies when used in combination with superjumps
|
||||
self.text_clip = False & options.nagmessages # trigger a text box on keyblock or rock or obstacle while holding diagonal to clip into the side. Removed from logic for now
|
||||
self.jesus_rooster = AND(ROOSTER, options.hardmode != "oracle") # when transitioning on top of water, buffer the rooster out of sq menu to spawn it. Then do an unbuffered pickup of the rooster as soon as you spawn again to pick it up
|
||||
self.zoomerang = AND(PEGASUS_BOOTS, FEATHER, BOOMERANG) # after starting a boots dash, buffer boomerang (on b), feather and the direction you're dashing in to get boosted in certain directions
|
||||
|
||||
self.boss_requirements = [
|
||||
SWORD, # D1 boss
|
||||
@@ -282,7 +327,7 @@ class RequirementsSettings:
|
||||
"HINOX": self.attack_hookshot,
|
||||
"DODONGO": BOMB,
|
||||
"CUE_BALL": SWORD,
|
||||
"GHOMA": OR(BOW, HOOKSHOT),
|
||||
"GHOMA": OR(BOW, HOOKSHOT, MAGIC_ROD, BOOMERANG),
|
||||
"SMASHER": POWER_BRACELET,
|
||||
"GRIM_CREEPER": self.attack_hookshot_no_bomb,
|
||||
"BLAINO": SWORD,
|
||||
@@ -293,9 +338,15 @@ class RequirementsSettings:
|
||||
}
|
||||
|
||||
# Adjust for options
|
||||
if options.bowwow != 'normal':
|
||||
if not options.tradequest:
|
||||
self.shuffled_magnifier = True # completing trade quest not required
|
||||
if options.hardmode == "ohko":
|
||||
self.miniboss_requirements["ROLLING_BONES"] = OR(BOW, MAGIC_ROD, BOOMERANG, AND(FEATHER, self.attack_hookshot)) # should not deal with roller damage
|
||||
if options.bowwow != "normal":
|
||||
# We cheat in bowwow mode, we pretend we have the sword, as bowwow can pretty much do all what the sword ca$ # Except for taking out bushes (and crystal pillars are removed)
|
||||
self.bush.remove(SWORD)
|
||||
self.pit_bush.remove(SWORD)
|
||||
self.hit_switch.remove(SWORD)
|
||||
if options.logic == "casual":
|
||||
# In casual mode, remove the more complex kill methods
|
||||
self.bush.remove(MAGIC_POWDER)
|
||||
@@ -305,14 +356,18 @@ class RequirementsSettings:
|
||||
self.attack_hookshot_powder.remove(BOMB)
|
||||
self.attack_no_boomerang.remove(BOMB)
|
||||
self.attack_skeleton.remove(BOMB)
|
||||
if options.logic == "hard":
|
||||
|
||||
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
|
||||
self.boss_requirements[1] = AND(OR(SWORD, MAGIC_ROD, BOMB), POWER_BRACELET) # bombs + bracelet genie
|
||||
self.boss_requirements[3] = AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW, BOMB)) # bomb angler fish
|
||||
self.boss_requirements[6] = OR(MAGIC_ROD, AND(BOMB, BOW), COUNT(SWORD, 2), AND(OR(SWORD, HOOKSHOT, BOW), SHIELD)) # evil eagle 3 cycle magic rod / bomb arrows / l2 sword, and bow kill
|
||||
if options.logic == "glitched":
|
||||
self.boss_requirements[3] = AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW, BOMB)) # bomb angler fish
|
||||
self.attack_pols_voice = OR(BOMB, MAGIC_ROD, AND(OCARINA, SONG1), AND(self.stun_wizrobe, self.throw_enemy, BOW)) # wizrobe stun has same req as pols voice stun
|
||||
self.attack_wizrobe = OR(BOMB, MAGIC_ROD, AND(self.stun_wizrobe, self.throw_enemy, BOW))
|
||||
|
||||
if options.logic == 'glitched' or options.logic == 'hell':
|
||||
self.boss_requirements[6] = OR(MAGIC_ROD, BOMB, BOW, HOOKSHOT, COUNT(SWORD, 2), AND(SWORD, SHIELD)) # evil eagle off screen kill or 3 cycle with bombs
|
||||
|
||||
if options.logic == "hell":
|
||||
self.boss_requirements[3] = AND(FLIPPERS, OR(SWORD, MAGIC_ROD, BOW, BOMB)) # bomb angler fish
|
||||
self.boss_requirements[6] = OR(MAGIC_ROD, BOMB, BOW, HOOKSHOT, COUNT(SWORD, 2), AND(SWORD, SHIELD)) # evil eagle off screen kill or 3 cycle with bombs
|
||||
self.boss_requirements[7] = OR(MAGIC_ROD, COUNT(SWORD, 2)) # hot head sword beams
|
||||
self.miniboss_requirements["GHOMA"] = OR(BOW, HOOKSHOT, MAGIC_ROD, BOOMERANG, AND(OCARINA, BOMB, OR(SONG1, SONG3))) # use bombs to kill gohma, with ocarina to get good timings
|
||||
self.miniboss_requirements["GIANT_BUZZ_BLOB"] = OR(MAGIC_POWDER, COUNT(SWORD,2)) # use sword beams to damage buzz blob
|
||||
|
||||
@@ -75,7 +75,7 @@ def addBank34(rom, item_list):
|
||||
.notCavesA:
|
||||
add hl, de
|
||||
ret
|
||||
""" + pkgutil.get_data(__name__, os.path.join("bank3e.asm", "message.asm")).decode().replace("\r", ""), 0x4000), fill_nop=True)
|
||||
""" + pkgutil.get_data(__name__, "bank3e.asm/message.asm").decode().replace("\r", ""), 0x4000), fill_nop=True)
|
||||
|
||||
nextItemLookup = ItemNameStringBufferStart
|
||||
nameLookup = {
|
||||
|
||||
@@ -56,7 +56,7 @@ def addBank3E(rom, seed, player_id, player_name_list):
|
||||
"""))
|
||||
|
||||
def get_asm(name):
|
||||
return pkgutil.get_data(__name__, os.path.join("bank3e.asm", name)).decode().replace("\r", "")
|
||||
return pkgutil.get_data(__name__, "bank3e.asm/" + name).decode().replace("\r", "")
|
||||
|
||||
rom.patch(0x3E, 0x0000, 0x2F00, ASM("""
|
||||
call MainJumpTable
|
||||
|
||||
@@ -25,3 +25,16 @@ def addBetaRoom(rom):
|
||||
re.store(rom)
|
||||
|
||||
rom.room_sprite_data_indoor[0x0FC] = rom.room_sprite_data_indoor[0x1A1]
|
||||
|
||||
|
||||
def tweakBirdKeyRoom(rom):
|
||||
# Make the bird key accessible without the rooster
|
||||
re = RoomEditor(rom, 0x27A)
|
||||
re.removeObject(1, 6)
|
||||
re.removeObject(2, 6)
|
||||
re.removeObject(3, 5)
|
||||
re.removeObject(3, 6)
|
||||
re.moveObject(1, 5, 2, 6)
|
||||
re.moveObject(2, 5, 3, 6)
|
||||
re.addEntity(3, 5, 0x9D)
|
||||
re.store(rom)
|
||||
|
||||
@@ -72,6 +72,10 @@ def upgradeMarin(rom):
|
||||
rst 8
|
||||
"""), fill_nop=True)
|
||||
|
||||
# Load marin singing even if you have the marin date
|
||||
rom.patch(0x03, 0x0A91, ASM("jp nz, $3F8D"), "", fill_nop=True)
|
||||
rom.patch(0x05, 0x0E6E, ASM("jp nz, $7B4B"), "", fill_nop=True)
|
||||
|
||||
|
||||
def upgradeManbo(rom):
|
||||
# Instead of checking if we have the song, check if we have a specific room flag set
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from ..assembler import ASM
|
||||
|
||||
|
||||
def patchTradeSequence(rom, boomerang_option):
|
||||
def patchTradeSequence(rom, settings):
|
||||
patchTrendy(rom)
|
||||
patchPapahlsWife(rom)
|
||||
patchYipYip(rom)
|
||||
@@ -16,7 +16,7 @@ def patchTradeSequence(rom, boomerang_option):
|
||||
patchMermaid(rom)
|
||||
patchMermaidStatue(rom)
|
||||
patchSharedCode(rom)
|
||||
patchVarious(rom, boomerang_option)
|
||||
patchVarious(rom, settings)
|
||||
patchInventoryMenu(rom)
|
||||
|
||||
|
||||
@@ -265,8 +265,11 @@ def patchMermaidStatue(rom):
|
||||
and $10 ; scale
|
||||
ret z
|
||||
ldh a, [$F8]
|
||||
and $20
|
||||
and $20 ; ROOM_STATUS_EVENT_2
|
||||
ret nz
|
||||
|
||||
ld hl, wTradeSequenceItem2
|
||||
res 4, [hl] ; take the trade item
|
||||
"""), fill_nop=True)
|
||||
|
||||
|
||||
@@ -317,7 +320,7 @@ notSideScroll:
|
||||
rom.patch(0x07, 0x3F7F, "00" * 7, ASM("ldh a, [$F8]\nor $20\nldh [$F8], a\nret"))
|
||||
|
||||
|
||||
def patchVarious(rom, boomerang_option):
|
||||
def patchVarious(rom, settings):
|
||||
# Make the zora photo work with the magnifier
|
||||
rom.patch(0x18, 0x09F3, 0x0A02, ASM("""
|
||||
ld a, [wTradeSequenceItem2]
|
||||
@@ -330,22 +333,71 @@ def patchVarious(rom, boomerang_option):
|
||||
jp z, $3F8D ; UnloadEntity
|
||||
"""), fill_nop=True)
|
||||
# Mimic invisibility
|
||||
rom.patch(0x18, 0x2AC8, 0x2ACE, "", fill_nop=True)
|
||||
rom.patch(0x19, 0x2AC0, ASM("""
|
||||
cp $97
|
||||
jr z, mermaidStatueCave
|
||||
cp $98
|
||||
jr nz, visible
|
||||
mermaidStatueCave:
|
||||
ld a, [$DB7F]
|
||||
and a
|
||||
jr nz, 6
|
||||
visible:
|
||||
"""), ASM("""
|
||||
dec a ; save one byte by only doing one cp
|
||||
or $01
|
||||
cp $97
|
||||
jr nz, visible
|
||||
mermaidStatueCave:
|
||||
ld a, [wTradeSequenceItem2]
|
||||
and $20 ; MAGNIFYING_GLASS
|
||||
jr z, 6
|
||||
visible:
|
||||
"""))
|
||||
# Zol invisibility
|
||||
rom.patch(0x06, 0x3BE9, ASM("""
|
||||
cp $97
|
||||
jr z, mermaidStatueCave
|
||||
cp $98
|
||||
ret nz ; visible
|
||||
mermaidStatueCave:
|
||||
ld a, [$DB7F]
|
||||
and a
|
||||
ret z
|
||||
"""), ASM("""
|
||||
dec a ; save one byte by only doing one cp
|
||||
or $01
|
||||
cp $97
|
||||
ret nz ; visible
|
||||
mermaidStatueCave:
|
||||
ld a, [wTradeSequenceItem2]
|
||||
and $20 ; MAGNIFYING_GLASS
|
||||
ret nz
|
||||
"""))
|
||||
# Ignore trade quest state for marin at beach
|
||||
rom.patch(0x18, 0x219E, 0x21A6, "", fill_nop=True)
|
||||
# Shift the magnifier 8 pixels
|
||||
rom.patch(0x03, 0x0F68, 0x0F6F, ASM("""
|
||||
ldh a, [$F6] ; map room
|
||||
cp $97 ; check if we are in the maginfier room
|
||||
cp $97 ; check if we are in the magnifier room
|
||||
jp z, $4F83
|
||||
"""), fill_nop=True)
|
||||
# Something with the photographer
|
||||
rom.patch(0x36, 0x0948, 0x0950, "", fill_nop=True)
|
||||
|
||||
if boomerang_option not in {'trade', 'gift'}: # Boomerang cave is not patched, so adjust it
|
||||
# Boomerang trade guy
|
||||
# if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}:
|
||||
if settings.tradequest:
|
||||
# Update magnifier checks
|
||||
rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njp nz, $7E61"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njp z, $7E61")) # show the guy
|
||||
rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njr nz, $06"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njr z, $06")) # load the proper room layout
|
||||
rom.patch(0x19, 0x05F4, 0x05FB, "", fill_nop=True)
|
||||
else:
|
||||
# Monkey bridge patch, always have the bridge there.
|
||||
rom.patch(0x00, 0x333D, ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True)
|
||||
# Always have the boomerang trade guy enabled (magnifier not needed)
|
||||
rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E"), ASM("ld a, $0E\ncp $0E"), fill_nop=True) # show the guy
|
||||
rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E"), ASM("ld a, $0E\ncp $0E"), fill_nop=True) # load the proper room layout
|
||||
rom.patch(0x19, 0x05F4, ASM("ld a, [wTradeSequenceItem2]\nand a"), ASM("xor a"), fill_nop=True)
|
||||
|
||||
|
||||
def patchInventoryMenu(rom):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from BaseClasses import Region, Entrance, Location, CollectionState
|
||||
|
||||
import typing
|
||||
|
||||
from .LADXR.checkMetadata import checkMetadataTable
|
||||
from .Common import *
|
||||
@@ -25,6 +25,39 @@ links_awakening_dungeon_names = [
|
||||
def meta_to_name(meta):
|
||||
return f"{meta.name} ({meta.area})"
|
||||
|
||||
def get_location_name_groups() -> typing.Dict[str, typing.Set[str]]:
|
||||
groups = {
|
||||
"Instrument Pedestals": {
|
||||
"Full Moon Cello (Tail Cave)",
|
||||
"Conch Horn (Bottle Grotto)",
|
||||
"Sea Lily's Bell (Key Cavern)",
|
||||
"Surf Harp (Angler's Tunnel)",
|
||||
"Wind Marimba (Catfish's Maw)",
|
||||
"Coral Triangle (Face Shrine)",
|
||||
"Organ of Evening Calm (Eagle's Tower)",
|
||||
"Thunder Drum (Turtle Rock)",
|
||||
},
|
||||
"Boss Rewards": {
|
||||
"Moldorm Heart Container (Tail Cave)",
|
||||
"Genie Heart Container (Bottle Grotto)",
|
||||
"Slime Eye Heart Container (Key Cavern)",
|
||||
"Angler Fish Heart Container (Angler's Tunnel)",
|
||||
"Slime Eel Heart Container (Catfish's Maw)",
|
||||
"Facade Heart Container (Face Shrine)",
|
||||
"Evil Eagle Heart Container (Eagle's Tower)",
|
||||
"Hot Head Heart Container (Turtle Rock)",
|
||||
"Tunic Fairy Item 1 (Color Dungeon)",
|
||||
"Tunic Fairy Item 2 (Color Dungeon)",
|
||||
},
|
||||
}
|
||||
# Add region groups
|
||||
for s, v in checkMetadataTable.items():
|
||||
if s == "None":
|
||||
continue
|
||||
groups.setdefault(v.area, set()).add(meta_to_name(v))
|
||||
return groups
|
||||
|
||||
links_awakening_location_name_groups = get_location_name_groups()
|
||||
|
||||
def get_locations_to_id():
|
||||
ret = {
|
||||
|
||||
@@ -3,7 +3,7 @@ from dataclasses import dataclass
|
||||
import os.path
|
||||
import typing
|
||||
import logging
|
||||
from Options import Choice, Toggle, DefaultOnToggle, Range, FreeText, PerGameCommonOptions, OptionGroup
|
||||
from Options import Choice, Toggle, DefaultOnToggle, Range, FreeText, PerGameCommonOptions, OptionGroup, Removed
|
||||
from collections import defaultdict
|
||||
import Utils
|
||||
|
||||
@@ -58,7 +58,7 @@ class TextShuffle(DefaultOffToggle):
|
||||
class Rooster(DefaultOnToggle, LADXROption):
|
||||
"""
|
||||
[On] Adds the rooster to the item pool.
|
||||
[Off] The rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.
|
||||
[Off] The rooster spot is still a check giving an item. But you will never find the rooster. In that case, any rooster spot is accessible without rooster by other means.
|
||||
"""
|
||||
display_name = "Rooster"
|
||||
ladxr_name = "rooster"
|
||||
@@ -486,20 +486,24 @@ class Music(Choice, LADXROption):
|
||||
return self.ladxr_name, s
|
||||
|
||||
|
||||
class WarpImprovements(DefaultOffToggle):
|
||||
class Warps(Choice):
|
||||
"""
|
||||
[On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
||||
[Off] No change
|
||||
[Improved] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
||||
[Improved Additional] Improved warps, and adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower.
|
||||
"""
|
||||
display_name = "Warp Improvements"
|
||||
display_name = "Warps"
|
||||
option_vanilla = 0
|
||||
option_improved = 1
|
||||
option_improved_additional = 2
|
||||
default = option_vanilla
|
||||
|
||||
|
||||
class AdditionalWarpPoints(DefaultOffToggle):
|
||||
class InGameHints(DefaultOnToggle):
|
||||
"""
|
||||
[On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower
|
||||
[Off] No change
|
||||
When enabled, owl statues and library books may indicate the location of your items in the multiworld.
|
||||
"""
|
||||
display_name = "Additional Warp Points"
|
||||
display_name = "In-game Hints"
|
||||
|
||||
|
||||
ladx_option_groups = [
|
||||
OptionGroup("Goal Options", [
|
||||
@@ -515,13 +519,13 @@ ladx_option_groups = [
|
||||
ShuffleStoneBeaks
|
||||
]),
|
||||
OptionGroup("Warp Points", [
|
||||
WarpImprovements,
|
||||
AdditionalWarpPoints,
|
||||
Warps,
|
||||
]),
|
||||
OptionGroup("Miscellaneous", [
|
||||
TradeQuest,
|
||||
Rooster,
|
||||
TrendyGame,
|
||||
InGameHints,
|
||||
NagMessages,
|
||||
BootsControls
|
||||
]),
|
||||
@@ -562,8 +566,7 @@ class LinksAwakeningOptions(PerGameCommonOptions):
|
||||
# 'bowwow': Bowwow,
|
||||
# 'overworld': Overworld,
|
||||
link_palette: LinkPalette
|
||||
warp_improvements: WarpImprovements
|
||||
additional_warp_points: AdditionalWarpPoints
|
||||
warps: Warps
|
||||
trendy_game: TrendyGame
|
||||
gfxmod: GfxMod
|
||||
palette: Palette
|
||||
@@ -579,3 +582,7 @@ class LinksAwakeningOptions(PerGameCommonOptions):
|
||||
nag_messages: NagMessages
|
||||
ap_title_screen: APTitleScreen
|
||||
boots_controls: BootsControls
|
||||
in_game_hints: InGameHints
|
||||
|
||||
warp_improvements: Removed
|
||||
additional_warp_points: Removed
|
||||
|
||||
@@ -13,7 +13,8 @@ from Fill import fill_restrictive
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from .Common import *
|
||||
from .Items import (DungeonItemData, DungeonItemType, ItemName, LinksAwakeningItem, TradeItemData,
|
||||
ladxr_item_to_la_item_name, links_awakening_items, links_awakening_items_by_name)
|
||||
ladxr_item_to_la_item_name, links_awakening_items, links_awakening_items_by_name,
|
||||
links_awakening_item_name_groups)
|
||||
from .LADXR import generator
|
||||
from .LADXR.itempool import ItemPool as LADXRItemPool
|
||||
from .LADXR.locations.constants import CHEST_ITEMS
|
||||
@@ -23,7 +24,8 @@ from .LADXR.main import get_parser
|
||||
from .LADXR.settings import Settings as LADXRSettings
|
||||
from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
|
||||
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
||||
create_regions_from_ladxr, get_locations_to_id)
|
||||
create_regions_from_ladxr, get_locations_to_id,
|
||||
links_awakening_location_name_groups)
|
||||
from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups
|
||||
from .Rom import LADXDeltaPatch, get_base_rom_path
|
||||
|
||||
@@ -66,6 +68,15 @@ class LinksAwakeningWebWorld(WebWorld):
|
||||
)]
|
||||
theme = "dirt"
|
||||
option_groups = ladx_option_groups
|
||||
options_presets: typing.Dict[str, typing.Dict[str, typing.Any]] = {
|
||||
"Keysanity": {
|
||||
"shuffle_nightmare_keys": "any_world",
|
||||
"shuffle_small_keys": "any_world",
|
||||
"shuffle_maps": "any_world",
|
||||
"shuffle_compasses": "any_world",
|
||||
"shuffle_stone_beaks": "any_world",
|
||||
}
|
||||
}
|
||||
|
||||
class LinksAwakeningWorld(World):
|
||||
"""
|
||||
@@ -98,12 +109,9 @@ class LinksAwakeningWorld(World):
|
||||
|
||||
# Items can be grouped using their names to allow easy checking if any item
|
||||
# from that group has been collected. Group names can also be used for !hint
|
||||
item_name_groups = {
|
||||
"Instruments": {
|
||||
"Full Moon Cello", "Conch Horn", "Sea Lily's Bell", "Surf Harp",
|
||||
"Wind Marimba", "Coral Triangle", "Organ of Evening Calm", "Thunder Drum"
|
||||
},
|
||||
}
|
||||
item_name_groups = links_awakening_item_name_groups
|
||||
|
||||
location_name_groups = links_awakening_location_name_groups
|
||||
|
||||
prefill_dungeon_items = None
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class TestD6(LADXTestBase):
|
||||
|
||||
def test_keylogic(self):
|
||||
keys = self.get_items_by_name(ItemName.KEY6)
|
||||
self.collect_by_name([ItemName.FACE_KEY, ItemName.HOOKSHOT, ItemName.POWER_BRACELET, ItemName.BOMB, ItemName.FEATHER, ItemName.FLIPPERS])
|
||||
self.collect_by_name([ItemName.FACE_KEY, ItemName.HOOKSHOT, ItemName.POWER_BRACELET, ItemName.BOMB, ItemName.PEGASUS_BOOTS, ItemName.FEATHER, ItemName.FLIPPERS])
|
||||
# Can reach an un-keylocked item in the dungeon
|
||||
self.assertTrue(self.can_reach_location("L2 Bracelet Chest (Face Shrine)"))
|
||||
|
||||
@@ -18,18 +18,18 @@ class TestD6(LADXTestBase):
|
||||
location_1 = "Tile Room Key (Face Shrine)"
|
||||
location_2 = "Top Right Horse Heads Chest (Face Shrine)"
|
||||
location_3 = "Pot Locked Chest (Face Shrine)"
|
||||
self.assertFalse(self.can_reach_location(location_1))
|
||||
self.assertFalse(self.can_reach_location(location_2))
|
||||
self.assertFalse(self.can_reach_location(location_3))
|
||||
self.assertFalse(self.can_reach_location(location_1), "Tile Room Key, 0 keys")
|
||||
self.assertFalse(self.can_reach_location(location_2), "Top Right Horse Heads Chest, 0 keys")
|
||||
self.assertFalse(self.can_reach_location(location_3), "Pot Locked Chest, 0 keys")
|
||||
self.collect(keys[0])
|
||||
self.assertTrue(self.can_reach_location(location_1))
|
||||
self.assertFalse(self.can_reach_location(location_2))
|
||||
self.assertFalse(self.can_reach_location(location_3))
|
||||
self.assertTrue(self.can_reach_location(location_1), "Tile Room Key, 1 key")
|
||||
self.assertFalse(self.can_reach_location(location_2), "Top Right Horse Heads Chest, 1 key")
|
||||
self.assertFalse(self.can_reach_location(location_3), "Pot Locked Chest, 1 key")
|
||||
self.collect(keys[1])
|
||||
self.assertTrue(self.can_reach_location(location_1))
|
||||
self.assertTrue(self.can_reach_location(location_2))
|
||||
self.assertFalse(self.can_reach_location(location_3))
|
||||
self.assertTrue(self.can_reach_location(location_1), "Tile Room Key, 2 keys")
|
||||
self.assertTrue(self.can_reach_location(location_2), "Top Right Horse Heads Chest, 2 keys")
|
||||
self.assertFalse(self.can_reach_location(location_3), "Pot Locked Chest, 2 keys")
|
||||
self.collect(keys[2])
|
||||
self.assertTrue(self.can_reach_location(location_1))
|
||||
self.assertTrue(self.can_reach_location(location_2))
|
||||
self.assertTrue(self.can_reach_location(location_3))
|
||||
self.assertTrue(self.can_reach_location(location_1), "Tile Room Key, 3 keys")
|
||||
self.assertTrue(self.can_reach_location(location_2), "Top Right Horse Heads Chest, 3 keys")
|
||||
self.assertTrue(self.can_reach_location(location_3), "Pot Locked Chest, 3 keys")
|
||||
|
||||
@@ -412,7 +412,7 @@ class LingoPlayerLogic:
|
||||
required_painting_rooms += REQUIRED_PAINTING_WHEN_NO_DOORS_ROOMS
|
||||
req_exits = [painting_id for painting_id, painting in PAINTINGS.items() if painting.required_when_no_doors]
|
||||
|
||||
def is_req_enterable(painting_id: str, painting: Painting) -> bool:
|
||||
def is_req_enterable(painting: Painting) -> bool:
|
||||
if painting.exit_only or painting.disable or painting.req_blocked\
|
||||
or painting.room in required_painting_rooms:
|
||||
return False
|
||||
@@ -433,7 +433,7 @@ class LingoPlayerLogic:
|
||||
return True
|
||||
|
||||
req_enterable = [painting_id for painting_id, painting in PAINTINGS.items()
|
||||
if is_req_enterable(painting_id, painting)]
|
||||
if is_req_enterable(painting)]
|
||||
req_exits += [painting_id for painting_id, painting in PAINTINGS.items()
|
||||
if painting.exit_only and painting.required]
|
||||
req_entrances = world.random.sample(req_enterable, len(req_exits))
|
||||
|
||||
@@ -107,7 +107,7 @@ def load_static_data_from_file():
|
||||
return getattr(safe_builtins, name)
|
||||
raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")
|
||||
|
||||
file = pkgutil.get_data(__name__, os.path.join("data", "generated.dat"))
|
||||
file = pkgutil.get_data(__name__, "data/generated.dat")
|
||||
pickdata = RenameUnpickler(BytesIO(file)).load()
|
||||
|
||||
HASHES.update(pickdata["HASHES"])
|
||||
|
||||
@@ -11,7 +11,6 @@ from datatypes import Door, DoorType, EntranceType, Painting, Panel, PanelDoor,
|
||||
|
||||
import hashlib
|
||||
import pickle
|
||||
import sys
|
||||
import Utils
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import json
|
||||
import pkgutil
|
||||
|
||||
def load_data_file(*args) -> dict:
|
||||
fname = os.path.join("data", *args)
|
||||
fname = "/".join(["data", *args])
|
||||
return json.loads(pkgutil.get_data(__name__, fname).decode())
|
||||
|
||||
# For historical reasons, these values are different.
|
||||
|
||||
@@ -126,7 +126,7 @@ class MM2ProcedurePatch(APProcedurePatch, APTokenMixin):
|
||||
|
||||
|
||||
def patch_rom(world: "MM2World", patch: MM2ProcedurePatch) -> None:
|
||||
patch.write_file("mm2_basepatch.bsdiff4", pkgutil.get_data(__name__, os.path.join("data", "mm2_basepatch.bsdiff4")))
|
||||
patch.write_file("mm2_basepatch.bsdiff4", pkgutil.get_data(__name__, "data/mm2_basepatch.bsdiff4"))
|
||||
# text writing
|
||||
patch.write_bytes(0x37E2A, MM2TextEntry("FOR ", 0xCB).resolve())
|
||||
patch.write_bytes(0x37EAA, MM2TextEntry("GET EQUIPPED ", 0x0B).resolve())
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
### Features
|
||||
|
||||
- Added many new item and location groups.
|
||||
- Added a Swedish translation of the setup guide.
|
||||
- The client communicates map transitions to any trackers connected to the slot.
|
||||
- Added the player's Normalize Encounter Rates option to slot data for trackers.
|
||||
|
||||
@@ -297,6 +297,12 @@ class PokemonEmeraldWorld(World):
|
||||
"Safari Zone SE - Hidden Item in South Grass 2",
|
||||
"Safari Zone SE - Item in Grass",
|
||||
])
|
||||
|
||||
# Sacred ash is on Navel Rock, which is locked behind the event tickets
|
||||
if not self.options.event_tickets:
|
||||
exclude_locations([
|
||||
"Navel Rock Top - Hidden Item Sacred Ash",
|
||||
])
|
||||
elif self.options.goal == Goal.option_steven:
|
||||
exclude_locations([
|
||||
"Meteor Falls 1F - Rival Steven",
|
||||
@@ -629,21 +635,34 @@ class PokemonEmeraldWorld(World):
|
||||
|
||||
spoiler_handle.write(f"\n\nWild Pokemon ({self.player_name}):\n\n")
|
||||
|
||||
slot_to_rod_suffix = {
|
||||
0: " (Old Rod)",
|
||||
1: " (Old Rod)",
|
||||
2: " (Good Rod)",
|
||||
3: " (Good Rod)",
|
||||
4: " (Good Rod)",
|
||||
5: " (Super Rod)",
|
||||
6: " (Super Rod)",
|
||||
7: " (Super Rod)",
|
||||
8: " (Super Rod)",
|
||||
9: " (Super Rod)",
|
||||
}
|
||||
|
||||
species_maps = defaultdict(set)
|
||||
for map in self.modified_maps.values():
|
||||
if map.land_encounters is not None:
|
||||
for encounter in map.land_encounters.slots:
|
||||
species_maps[encounter].add(map.name[4:])
|
||||
species_maps[encounter].add(map.label + " (Land)")
|
||||
|
||||
if map.water_encounters is not None:
|
||||
for encounter in map.water_encounters.slots:
|
||||
species_maps[encounter].add(map.name[4:])
|
||||
species_maps[encounter].add(map.label + " (Water)")
|
||||
|
||||
if map.fishing_encounters is not None:
|
||||
for encounter in map.fishing_encounters.slots:
|
||||
species_maps[encounter].add(map.name[4:])
|
||||
for slot, encounter in enumerate(map.fishing_encounters.slots):
|
||||
species_maps[encounter].add(map.label + slot_to_rod_suffix[slot])
|
||||
|
||||
lines = [f"{emerald_data.species[species].label}: {', '.join(maps)}\n"
|
||||
lines = [f"{emerald_data.species[species].label}: {', '.join(sorted(maps))}\n"
|
||||
for species, maps in species_maps.items()]
|
||||
lines.sort()
|
||||
for line in lines:
|
||||
@@ -655,35 +674,35 @@ class PokemonEmeraldWorld(World):
|
||||
if self.options.dexsanity:
|
||||
from collections import defaultdict
|
||||
|
||||
slot_to_rod = {
|
||||
0: "_OLD_ROD",
|
||||
1: "_OLD_ROD",
|
||||
2: "_GOOD_ROD",
|
||||
3: "_GOOD_ROD",
|
||||
4: "_GOOD_ROD",
|
||||
5: "_SUPER_ROD",
|
||||
6: "_SUPER_ROD",
|
||||
7: "_SUPER_ROD",
|
||||
8: "_SUPER_ROD",
|
||||
9: "_SUPER_ROD",
|
||||
slot_to_rod_suffix = {
|
||||
0: " (Old Rod)",
|
||||
1: " (Old Rod)",
|
||||
2: " (Good Rod)",
|
||||
3: " (Good Rod)",
|
||||
4: " (Good Rod)",
|
||||
5: " (Super Rod)",
|
||||
6: " (Super Rod)",
|
||||
7: " (Super Rod)",
|
||||
8: " (Super Rod)",
|
||||
9: " (Super Rod)",
|
||||
}
|
||||
|
||||
species_maps = defaultdict(set)
|
||||
for map in self.modified_maps.values():
|
||||
if map.land_encounters is not None:
|
||||
for encounter in map.land_encounters.slots:
|
||||
species_maps[encounter].add(map.name[4:] + "_GRASS")
|
||||
species_maps[encounter].add(map.label + " (Land)")
|
||||
|
||||
if map.water_encounters is not None:
|
||||
for encounter in map.water_encounters.slots:
|
||||
species_maps[encounter].add(map.name[4:] + "_WATER")
|
||||
species_maps[encounter].add(map.label + " (Water)")
|
||||
|
||||
if map.fishing_encounters is not None:
|
||||
for slot, encounter in enumerate(map.fishing_encounters.slots):
|
||||
species_maps[encounter].add(map.name[4:] + slot_to_rod[slot])
|
||||
species_maps[encounter].add(map.label + slot_to_rod_suffix[slot])
|
||||
|
||||
hint_data[self.player] = {
|
||||
self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(maps)
|
||||
self.location_name_to_id[f"Pokedex - {emerald_data.species[species].label}"]: ", ".join(sorted(maps))
|
||||
for species, maps in species_maps.items()
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,7 @@ class EncounterTableData(NamedTuple):
|
||||
@dataclass
|
||||
class MapData:
|
||||
name: str
|
||||
label: str
|
||||
header_address: int
|
||||
land_encounters: Optional[EncounterTableData]
|
||||
water_encounters: Optional[EncounterTableData]
|
||||
@@ -357,6 +358,8 @@ def load_json_data(data_name: str) -> Union[List[Any], Dict[str, Any]]:
|
||||
|
||||
|
||||
def _init() -> None:
|
||||
import re
|
||||
|
||||
extracted_data: Dict[str, Any] = load_json_data("extracted_data.json")
|
||||
data.constants = extracted_data["constants"]
|
||||
data.ram_addresses = extracted_data["misc_ram_addresses"]
|
||||
@@ -366,6 +369,7 @@ def _init() -> None:
|
||||
|
||||
# Create map data
|
||||
for map_name, map_json in extracted_data["maps"].items():
|
||||
assert isinstance(map_name, str)
|
||||
if map_name in IGNORABLE_MAPS:
|
||||
continue
|
||||
|
||||
@@ -389,8 +393,35 @@ def _init() -> None:
|
||||
map_json["fishing_encounters"]["address"]
|
||||
)
|
||||
|
||||
# Derive a user-facing label
|
||||
label = []
|
||||
for word in map_name[4:].split("_"):
|
||||
# 1F, B1F, 2R, etc.
|
||||
re_match = re.match("^B?\d+[FRP]$", word)
|
||||
if re_match:
|
||||
label.append(word)
|
||||
continue
|
||||
|
||||
# Route 103, Hall 1, House 5, etc.
|
||||
re_match = re.match("^([A-Z]+)(\d+)$", word)
|
||||
if re_match:
|
||||
label.append(re_match.group(1).capitalize())
|
||||
label.append(re_match.group(2).lstrip("0"))
|
||||
continue
|
||||
|
||||
if word == "OF":
|
||||
label.append("of")
|
||||
continue
|
||||
|
||||
if word == "SS":
|
||||
label.append("S.S.")
|
||||
continue
|
||||
|
||||
label.append(word.capitalize())
|
||||
|
||||
data.maps[map_name] = MapData(
|
||||
map_name,
|
||||
" ".join(label),
|
||||
map_json["header_address"],
|
||||
land_encounters,
|
||||
water_encounters,
|
||||
|
||||
@@ -170,6 +170,8 @@ def process_pokemon_locations(self):
|
||||
encounter_slots = encounter_slots_master.copy()
|
||||
|
||||
zone_mapping = {}
|
||||
zone_placed_mons = {}
|
||||
|
||||
if self.options.randomize_wild_pokemon:
|
||||
mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
|
||||
or self.options.randomize_legendary_pokemon.value == 3]
|
||||
@@ -180,11 +182,13 @@ def process_pokemon_locations(self):
|
||||
zone = " - ".join(location.name.split(" - ")[:-1])
|
||||
if zone not in zone_mapping:
|
||||
zone_mapping[zone] = {}
|
||||
if zone not in zone_placed_mons:
|
||||
zone_placed_mons[zone] = []
|
||||
original_mon = slot.original_item
|
||||
if self.options.area_1_to_1_mapping and original_mon in zone_mapping[zone]:
|
||||
mon = zone_mapping[zone][original_mon]
|
||||
else:
|
||||
mon = randomize_pokemon(self, original_mon, mons_list,
|
||||
mon = randomize_pokemon(self, original_mon, [m for m in mons_list if m not in zone_placed_mons[zone]],
|
||||
self.options.randomize_wild_pokemon.value, self.random)
|
||||
#
|
||||
while ("Pokemon Tower 6F" in slot.name and
|
||||
@@ -201,6 +205,7 @@ def process_pokemon_locations(self):
|
||||
location.item.location = location
|
||||
locations.append(location)
|
||||
zone_mapping[zone][original_mon] = mon
|
||||
zone_placed_mons[zone].append(mon)
|
||||
|
||||
mons_to_add = []
|
||||
remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
|
||||
@@ -270,4 +275,4 @@ def process_pokemon_locations(self):
|
||||
location.item = self.create_item(slot.original_item)
|
||||
location.locked = True
|
||||
location.item.location = location
|
||||
placed_mons[location.item.name] += 1
|
||||
placed_mons[location.item.name] += 1
|
||||
|
||||
@@ -401,7 +401,7 @@ location_data = [
|
||||
LocationData("Cerulean Cave B1F-E", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22), inclusion=hidden_items),
|
||||
LocationData("Power Plant", "Hidden Item Central Dead End", "Max Elixir", rom_addresses['Hidden_Item_Power_Plant_1'], Hidden(23), inclusion=hidden_items),
|
||||
LocationData("Power Plant", "Hidden Item Before Zapdos", "PP Up", rom_addresses['Hidden_Item_Power_Plant_2'], Hidden(24), inclusion=hidden_items),
|
||||
LocationData("Seafoam Islands B2F-NW", "Hidden Item Rock", "Nugget", rom_addresses['Hidden_Item_Seafoam_Islands_B2F'], Hidden(25), inclusion=hidden_items),
|
||||
LocationData("Seafoam Islands B2F-SW", "Hidden Item Rock", "Nugget", rom_addresses['Hidden_Item_Seafoam_Islands_B2F'], Hidden(25), inclusion=hidden_items),
|
||||
LocationData("Seafoam Islands B4F-W", "Hidden Item Corner Island", "Ultra Ball", rom_addresses['Hidden_Item_Seafoam_Islands_B4F'], Hidden(26), inclusion=hidden_items),
|
||||
LocationData("Pokemon Mansion 1F", "Hidden Item Block Near Entrance Carpet", "Moon Stone", rom_addresses['Hidden_Item_Pokemon_Mansion_1F'], Hidden(27), inclusion=hidden_moon_stones),
|
||||
LocationData("Pokemon Mansion 3F-SW", "Hidden Item Behind Burglar", "Max Revive", rom_addresses['Hidden_Item_Pokemon_Mansion_3F'], Hidden(28), inclusion=hidden_items),
|
||||
|
||||
@@ -57,10 +57,6 @@ class RaftWorld(World):
|
||||
frequencyItems.append(raft_item)
|
||||
else:
|
||||
pool.append(raft_item)
|
||||
if isFillingFrequencies:
|
||||
if not hasattr(self.multiworld, "raft_frequencyItemsPerPlayer"):
|
||||
self.multiworld.raft_frequencyItemsPerPlayer = {}
|
||||
self.multiworld.raft_frequencyItemsPerPlayer[self.player] = frequencyItems
|
||||
|
||||
extraItemNamePool = []
|
||||
extras = len(location_table) - len(item_table) - 1 # Victory takes up 1 unaccounted-for slot
|
||||
@@ -109,17 +105,15 @@ class RaftWorld(World):
|
||||
self.multiworld.get_location("Utopia Complete", self.player).place_locked_item(
|
||||
RaftItem("Victory", ItemClassification.progression, None, player=self.player))
|
||||
|
||||
if frequencyItems:
|
||||
self.place_frequencyItems(frequencyItems)
|
||||
|
||||
def set_rules(self):
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def create_regions(self):
|
||||
create_regions(self.multiworld, self.player)
|
||||
|
||||
def get_pre_fill_items(self):
|
||||
if self.options.island_frequency_locations.is_filling_frequencies_in_world():
|
||||
return [loc.item for loc in self.multiworld.get_filled_locations()]
|
||||
return []
|
||||
|
||||
def create_item_replaceAsNecessary(self, name: str) -> Item:
|
||||
isFrequency = "Frequency" in name
|
||||
shouldUseProgressive = bool((isFrequency and self.options.island_frequency_locations == self.options.island_frequency_locations.option_progressive)
|
||||
@@ -152,23 +146,34 @@ class RaftWorld(World):
|
||||
|
||||
return super(RaftWorld, self).collect_item(state, item, remove)
|
||||
|
||||
def pre_fill(self):
|
||||
def place_frequencyItems(self, frequencyItems):
|
||||
def setLocationItem(location: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, frequencyItems))
|
||||
frequencyItems.remove(itemToUse)
|
||||
self.get_location(location).place_locked_item(itemToUse)
|
||||
|
||||
def setLocationItemFromRegion(region: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, frequencyItems))
|
||||
frequencyItems.remove(itemToUse)
|
||||
location = self.random.choice(list(loc for loc in location_table if loc["region"] == region))
|
||||
self.get_location(location["name"]).place_locked_item(itemToUse)
|
||||
|
||||
if self.options.island_frequency_locations == self.options.island_frequency_locations.option_vanilla:
|
||||
self.setLocationItem("Radio Tower Frequency to Vasagatan", "Vasagatan Frequency")
|
||||
self.setLocationItem("Vasagatan Frequency to Balboa", "Balboa Island Frequency")
|
||||
self.setLocationItem("Relay Station quest", "Caravan Island Frequency")
|
||||
self.setLocationItem("Caravan Island Frequency to Tangaroa", "Tangaroa Frequency")
|
||||
self.setLocationItem("Tangaroa Frequency to Varuna Point", "Varuna Point Frequency")
|
||||
self.setLocationItem("Varuna Point Frequency to Temperance", "Temperance Frequency")
|
||||
self.setLocationItem("Temperance Frequency to Utopia", "Utopia Frequency")
|
||||
setLocationItem("Radio Tower Frequency to Vasagatan", "Vasagatan Frequency")
|
||||
setLocationItem("Vasagatan Frequency to Balboa", "Balboa Island Frequency")
|
||||
setLocationItem("Relay Station quest", "Caravan Island Frequency")
|
||||
setLocationItem("Caravan Island Frequency to Tangaroa", "Tangaroa Frequency")
|
||||
setLocationItem("Tangaroa Frequency to Varuna Point", "Varuna Point Frequency")
|
||||
setLocationItem("Varuna Point Frequency to Temperance", "Temperance Frequency")
|
||||
setLocationItem("Temperance Frequency to Utopia", "Utopia Frequency")
|
||||
elif self.options.island_frequency_locations == self.options.island_frequency_locations.option_random_on_island:
|
||||
self.setLocationItemFromRegion("RadioTower", "Vasagatan Frequency")
|
||||
self.setLocationItemFromRegion("Vasagatan", "Balboa Island Frequency")
|
||||
self.setLocationItemFromRegion("BalboaIsland", "Caravan Island Frequency")
|
||||
self.setLocationItemFromRegion("CaravanIsland", "Tangaroa Frequency")
|
||||
self.setLocationItemFromRegion("Tangaroa", "Varuna Point Frequency")
|
||||
self.setLocationItemFromRegion("Varuna Point", "Temperance Frequency")
|
||||
self.setLocationItemFromRegion("Temperance", "Utopia Frequency")
|
||||
setLocationItemFromRegion("RadioTower", "Vasagatan Frequency")
|
||||
setLocationItemFromRegion("Vasagatan", "Balboa Island Frequency")
|
||||
setLocationItemFromRegion("BalboaIsland", "Caravan Island Frequency")
|
||||
setLocationItemFromRegion("CaravanIsland", "Tangaroa Frequency")
|
||||
setLocationItemFromRegion("Tangaroa", "Varuna Point Frequency")
|
||||
setLocationItemFromRegion("Varuna Point", "Temperance Frequency")
|
||||
setLocationItemFromRegion("Temperance", "Utopia Frequency")
|
||||
elif self.options.island_frequency_locations in [
|
||||
self.options.island_frequency_locations.option_random_island_order,
|
||||
self.options.island_frequency_locations.option_random_on_island_random_order
|
||||
@@ -201,22 +206,11 @@ class RaftWorld(World):
|
||||
currentLocation = availableLocationList[0] # Utopia (only one left in list)
|
||||
availableLocationList.remove(currentLocation)
|
||||
if self.options.island_frequency_locations == self.options.island_frequency_locations.option_random_island_order:
|
||||
self.setLocationItem(locationToVanillaFrequencyLocationMap[previousLocation], locationToFrequencyItemMap[currentLocation])
|
||||
setLocationItem(locationToVanillaFrequencyLocationMap[previousLocation], locationToFrequencyItemMap[currentLocation])
|
||||
elif self.options.island_frequency_locations == self.options.island_frequency_locations.option_random_on_island_random_order:
|
||||
self.setLocationItemFromRegion(previousLocation, locationToFrequencyItemMap[currentLocation])
|
||||
setLocationItemFromRegion(previousLocation, locationToFrequencyItemMap[currentLocation])
|
||||
previousLocation = currentLocation
|
||||
|
||||
def setLocationItem(self, location: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.multiworld.raft_frequencyItemsPerPlayer[self.player]))
|
||||
self.multiworld.raft_frequencyItemsPerPlayer[self.player].remove(itemToUse)
|
||||
self.multiworld.get_location(location, self.player).place_locked_item(itemToUse)
|
||||
|
||||
def setLocationItemFromRegion(self, region: str, itemName: str):
|
||||
itemToUse = next(filter(lambda itm: itm.name == itemName, self.multiworld.raft_frequencyItemsPerPlayer[self.player]))
|
||||
self.multiworld.raft_frequencyItemsPerPlayer[self.player].remove(itemToUse)
|
||||
location = self.random.choice(list(loc for loc in location_table if loc["region"] == region))
|
||||
self.multiworld.get_location(location["name"], self.player).place_locked_item(itemToUse)
|
||||
|
||||
def fill_slot_data(self):
|
||||
return {
|
||||
"IslandGenerationDistance": self.options.island_generation_distance.value,
|
||||
|
||||
258
worlds/saving_princess/Client.py
Normal file
258
worlds/saving_princess/Client.py
Normal file
@@ -0,0 +1,258 @@
|
||||
import argparse
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
import bsdiff4
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import secrets
|
||||
import shutil
|
||||
import subprocess
|
||||
from tkinter import messagebox
|
||||
from typing import Any, Dict, Set
|
||||
import urllib
|
||||
import urllib.parse
|
||||
|
||||
import Utils
|
||||
from .Constants import *
|
||||
from . import SavingPrincessWorld
|
||||
|
||||
files_to_clean: Set[str] = {
|
||||
"D3DX9_43.dll",
|
||||
"data.win",
|
||||
"m_boss.ogg",
|
||||
"m_brainos.ogg",
|
||||
"m_coldarea.ogg",
|
||||
"m_escape.ogg",
|
||||
"m_hotarea.ogg",
|
||||
"m_hsis_dark.ogg",
|
||||
"m_hsis_power.ogg",
|
||||
"m_introarea.ogg",
|
||||
"m_malakhov.ogg",
|
||||
"m_miniboss.ogg",
|
||||
"m_ninja.ogg",
|
||||
"m_purple.ogg",
|
||||
"m_space_idle.ogg",
|
||||
"m_stonearea.ogg",
|
||||
"m_swamp.ogg",
|
||||
"m_zzz.ogg",
|
||||
"options.ini",
|
||||
"Saving Princess v0_8.exe",
|
||||
"splash.png",
|
||||
"gm-apclientpp.dll",
|
||||
"LICENSE",
|
||||
"original_data.win",
|
||||
"versions.json",
|
||||
}
|
||||
|
||||
file_hashes: Dict[str, str] = {
|
||||
"D3DX9_43.dll": "86e39e9161c3d930d93822f1563c280d",
|
||||
"Saving Princess v0_8.exe": "cc3ad10c782e115d93c5b9fbc5675eaf",
|
||||
"original_data.win": "f97b80204bd9ae535faa5a8d1e5eb6ca",
|
||||
}
|
||||
|
||||
|
||||
class UrlResponse:
|
||||
def __init__(self, response_code: int, data: Any):
|
||||
self.response_code = response_code
|
||||
self.data = data
|
||||
|
||||
|
||||
def get_date(target_asset: str) -> str:
|
||||
"""Provided the name of an asset, fetches its update date"""
|
||||
try:
|
||||
with open("versions.json", "r") as versions_json:
|
||||
return json.load(versions_json)[target_asset]
|
||||
except (FileNotFoundError, KeyError, json.decoder.JSONDecodeError):
|
||||
return "2000-01-01T00:00:00Z" # arbitrary old date
|
||||
|
||||
|
||||
def set_date(target_asset: str, date: str) -> None:
|
||||
"""Provided the name of an asset and a date, sets it update date"""
|
||||
try:
|
||||
with open("versions.json", "r") as versions_json:
|
||||
versions = json.load(versions_json)
|
||||
versions[target_asset] = date
|
||||
except (FileNotFoundError, KeyError, json.decoder.JSONDecodeError):
|
||||
versions = {target_asset: date}
|
||||
with open("versions.json", "w") as versions_json:
|
||||
json.dump(versions, versions_json)
|
||||
|
||||
|
||||
def get_timestamp(date: str) -> float:
|
||||
"""Parses a GitHub REST API date into a timestamp"""
|
||||
return datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ").timestamp()
|
||||
|
||||
|
||||
def send_request(request_url: str) -> UrlResponse:
|
||||
"""Fetches status code and json response from given url"""
|
||||
response = requests.get(request_url)
|
||||
if response.status_code == 200: # success
|
||||
try:
|
||||
data = response.json()
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
raise RuntimeError(f"Unable to fetch data. (status code {response.status_code}).")
|
||||
else:
|
||||
data = {}
|
||||
return UrlResponse(response.status_code, data)
|
||||
|
||||
|
||||
def update(target_asset: str, url: str) -> bool:
|
||||
"""
|
||||
Returns True if the data was fetched and installed
|
||||
(or it was already on the latest version, or the user refused the update)
|
||||
Returns False if rate limit was exceeded
|
||||
"""
|
||||
try:
|
||||
logging.info(f"Checking for {target_asset} updates.")
|
||||
response = send_request(url)
|
||||
if response.response_code == 403: # rate limit exceeded
|
||||
return False
|
||||
assets = response.data[0]["assets"]
|
||||
for asset in assets:
|
||||
if target_asset in asset["name"]:
|
||||
newest_date: str = asset["updated_at"]
|
||||
release_url: str = asset["browser_download_url"]
|
||||
break
|
||||
else:
|
||||
raise RuntimeError(f"Failed to locate {target_asset} amongst the assets.")
|
||||
except (KeyError, IndexError, TypeError, RuntimeError):
|
||||
update_error = f"Failed to fetch latest {target_asset}."
|
||||
messagebox.showerror("Failure", update_error)
|
||||
raise RuntimeError(update_error)
|
||||
try:
|
||||
update_available = get_timestamp(newest_date) > get_timestamp(get_date(target_asset))
|
||||
if update_available and messagebox.askyesnocancel(f"New {target_asset}",
|
||||
"Would you like to install the new version now?"):
|
||||
# unzip and patch
|
||||
with urllib.request.urlopen(release_url) as download:
|
||||
with zipfile.ZipFile(BytesIO(download.read())) as zf:
|
||||
zf.extractall()
|
||||
patch_game()
|
||||
set_date(target_asset, newest_date)
|
||||
except (ValueError, RuntimeError, urllib.error.HTTPError):
|
||||
update_error = f"Failed to apply update."
|
||||
messagebox.showerror("Failure", update_error)
|
||||
raise RuntimeError(update_error)
|
||||
return True
|
||||
|
||||
|
||||
def patch_game() -> None:
|
||||
"""Applies the patch to data.win"""
|
||||
logging.info("Proceeding to patch.")
|
||||
with open(PATCH_NAME, "rb") as patch:
|
||||
with open("original_data.win", "rb") as data:
|
||||
patched_data = bsdiff4.patch(data.read(), patch.read())
|
||||
with open("data.win", "wb") as data:
|
||||
data.write(patched_data)
|
||||
logging.info("Done!")
|
||||
|
||||
|
||||
def is_install_valid() -> bool:
|
||||
"""Checks that the mandatory files that we cannot replace do exist in the current folder"""
|
||||
for file_name, expected_hash in file_hashes.items():
|
||||
if not os.path.exists(file_name):
|
||||
return False
|
||||
with open(file_name, "rb") as clean:
|
||||
current_hash = hashlib.md5(clean.read()).hexdigest()
|
||||
if not secrets.compare_digest(current_hash, expected_hash):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def install() -> None:
|
||||
"""Extracts all the game files into the mod installation folder"""
|
||||
logging.info("Mod installation missing or corrupted, proceeding to reinstall.")
|
||||
# get the cab file and extract it into the installation folder
|
||||
with open(SavingPrincessWorld.settings.exe_path, "rb") as exe:
|
||||
# find the cab header
|
||||
logging.info("Looking for cab archive inside exe.")
|
||||
cab_found: bool = False
|
||||
while not cab_found:
|
||||
cab_found = exe.read(1) == b'M' and exe.read(1) == b'S' and exe.read(1) == b'C' and exe.read(1) == b'F'
|
||||
exe.read(4) # skip reserved1, always 0
|
||||
cab_size: int = int.from_bytes(exe.read(4), "little") # read size in bytes
|
||||
exe.seek(-12, 1) # move the cursor back to the start of the cab file
|
||||
logging.info(f"Archive found at offset {hex(exe.seek(0, 1))}, size: {hex(cab_size)}.")
|
||||
logging.info("Extracting cab archive from exe.")
|
||||
with open("saving_princess.cab", "wb") as cab:
|
||||
cab.write(exe.read(cab_size))
|
||||
|
||||
# clean up files from previous installations
|
||||
for file_name in files_to_clean:
|
||||
if os.path.exists(file_name):
|
||||
os.remove(file_name)
|
||||
|
||||
logging.info("Extracting files from cab archive.")
|
||||
if Utils.is_windows:
|
||||
subprocess.run(["Extrac32", "/Y", "/E", "saving_princess.cab"])
|
||||
else:
|
||||
if shutil.which("wine") is not None:
|
||||
subprocess.run(["wine", "Extrac32", "/Y", "/E", "saving_princess.cab"])
|
||||
elif shutil.which("7z") is not None:
|
||||
subprocess.run(["7z", "e", "saving_princess.cab"])
|
||||
else:
|
||||
error = "Could not find neither wine nor 7z.\n\nPlease install either the wine or the p7zip package."
|
||||
messagebox.showerror("Missing package!", f"Error: {error}")
|
||||
raise RuntimeError(error)
|
||||
os.remove("saving_princess.cab") # delete the cab file
|
||||
|
||||
shutil.copyfile("data.win", "original_data.win") # and make a copy of data.win
|
||||
logging.info("Done!")
|
||||
|
||||
|
||||
def launch(*args: str) -> Any:
|
||||
"""Check args, then the mod installation, then launch the game"""
|
||||
name: str = ""
|
||||
password: str = ""
|
||||
server: str = ""
|
||||
if args:
|
||||
parser = argparse.ArgumentParser(description=f"{GAME_NAME} Client Launcher")
|
||||
parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.")
|
||||
args = parser.parse_args(args)
|
||||
|
||||
# handle if text client is launched using the "archipelago://name:pass@host:port" url from webhost
|
||||
if args.url:
|
||||
url = urllib.parse.urlparse(args.url)
|
||||
if url.scheme == "archipelago":
|
||||
server = f'--server="{url.hostname}:{url.port}"'
|
||||
if url.username:
|
||||
name = f'--name="{urllib.parse.unquote(url.username)}"'
|
||||
if url.password:
|
||||
password = f'--password="{urllib.parse.unquote(url.password)}"'
|
||||
else:
|
||||
parser.error(f"bad url, found {args.url}, expected url in form of archipelago://archipelago.gg:38281")
|
||||
|
||||
Utils.init_logging(CLIENT_NAME, exception_logger="Client")
|
||||
|
||||
os.chdir(SavingPrincessWorld.settings.install_folder)
|
||||
|
||||
# check that the mod installation is valid
|
||||
if not is_install_valid():
|
||||
if messagebox.askyesnocancel(f"Mod installation missing or corrupted!",
|
||||
"Would you like to reinstall now?"):
|
||||
install()
|
||||
# if there is no mod installation, and we are not installing it, then there isn't much to do
|
||||
else:
|
||||
return
|
||||
|
||||
# check for updates
|
||||
if not update(DOWNLOAD_NAME, DOWNLOAD_URL):
|
||||
messagebox.showinfo("Rate limit exceeded",
|
||||
"GitHub REST API limit exceeded, could not check for updates.\n\n"
|
||||
"This will not prevent the game from being played if it was already playable.")
|
||||
|
||||
# and try to launch the game
|
||||
if SavingPrincessWorld.settings.launch_game:
|
||||
logging.info("Launching game.")
|
||||
try:
|
||||
subprocess.Popen(f"{SavingPrincessWorld.settings.launch_command} {name} {password} {server}")
|
||||
except FileNotFoundError:
|
||||
error = ("Could not run the game!\n\n"
|
||||
"Please check that launch_command in options.yaml or host.yaml is set up correctly.")
|
||||
messagebox.showerror("Command error!", f"Error: {error}")
|
||||
raise RuntimeError(error)
|
||||
97
worlds/saving_princess/Constants.py
Normal file
97
worlds/saving_princess/Constants.py
Normal file
@@ -0,0 +1,97 @@
|
||||
GAME_NAME: str = "Saving Princess"
|
||||
BASE_ID: int = 0x53565052494E # SVPRIN
|
||||
|
||||
# client installation data
|
||||
CLIENT_NAME = f"{GAME_NAME.replace(' ', '')}Client"
|
||||
GAME_HASH = "35a111d0149fae1f04b7b3fea42c5319"
|
||||
PATCH_NAME = "saving_princess_basepatch.bsdiff4"
|
||||
DOWNLOAD_NAME = "saving_princess_archipelago.zip"
|
||||
DOWNLOAD_URL = "https://api.github.com/repos/LeonarthCG/saving-princess-archipelago/releases"
|
||||
|
||||
# item names
|
||||
ITEM_WEAPON_CHARGE: str = "Powered Blaster"
|
||||
ITEM_WEAPON_FIRE: str = "Flamethrower"
|
||||
ITEM_WEAPON_ICE: str = "Ice Spreadshot"
|
||||
ITEM_WEAPON_VOLT: str = "Volt Laser"
|
||||
ITEM_MAX_HEALTH: str = "Life Extension"
|
||||
ITEM_MAX_AMMO: str = "Clip Extension"
|
||||
ITEM_RELOAD_SPEED: str = "Faster Reload"
|
||||
ITEM_SPECIAL_AMMO: str = "Special Extension"
|
||||
ITEM_JACKET: str = "Jacket"
|
||||
|
||||
EP_ITEM_GUARD_GONE: str = "Cave Key"
|
||||
EP_ITEM_CLIFF_GONE: str = "Volcanic Key"
|
||||
EP_ITEM_ACE_GONE: str = "Arctic Key"
|
||||
EP_ITEM_SNAKE_GONE: str = "Swamp Key"
|
||||
EP_ITEM_POWER_ON: str = "System Power"
|
||||
|
||||
FILLER_ITEM_HEAL: str = "Full Heal"
|
||||
FILLER_ITEM_QUICK_FIRE: str = "Quick-fire Mode"
|
||||
FILLER_ITEM_ACTIVE_CAMO: str = "Active Camouflage"
|
||||
|
||||
TRAP_ITEM_ICE: str = "Ice Trap"
|
||||
TRAP_ITEM_SHAKES: str = "Shake Trap"
|
||||
TRAP_ITEM_NINJA: str = "Ninja Trap"
|
||||
|
||||
EVENT_ITEM_GUARD_GONE: str = "Guard neutralized"
|
||||
EVENT_ITEM_CLIFF_GONE: str = "Cliff neutralized"
|
||||
EVENT_ITEM_ACE_GONE: str = "Ace neutralized"
|
||||
EVENT_ITEM_SNAKE_GONE: str = "Snake neutralized"
|
||||
EVENT_ITEM_POWER_ON: str = "Power restored"
|
||||
EVENT_ITEM_VICTORY: str = "PRINCESS"
|
||||
|
||||
# location names, EP stands for Expanded Pool
|
||||
LOCATION_CAVE_AMMO: str = "Cave: After Wallboss"
|
||||
LOCATION_CAVE_RELOAD: str = "Cave: Balcony"
|
||||
LOCATION_CAVE_HEALTH: str = "Cave: Spike pit"
|
||||
LOCATION_CAVE_WEAPON: str = "Cave: Powered Blaster chest"
|
||||
LOCATION_VOLCANIC_RELOAD: str = "Volcanic: Hot coals"
|
||||
LOCATION_VOLCANIC_HEALTH: str = "Volcanic: Under bridge"
|
||||
LOCATION_VOLCANIC_AMMO: str = "Volcanic: Behind wall"
|
||||
LOCATION_VOLCANIC_WEAPON: str = "Volcanic: Flamethrower chest"
|
||||
LOCATION_ARCTIC_AMMO: str = "Arctic: Before pipes"
|
||||
LOCATION_ARCTIC_RELOAD: str = "Arctic: After Guard"
|
||||
LOCATION_ARCTIC_HEALTH: str = "Arctic: Under snow"
|
||||
LOCATION_ARCTIC_WEAPON: str = "Arctic: Ice Spreadshot chest"
|
||||
LOCATION_JACKET: str = "Arctic: Jacket chest"
|
||||
LOCATION_HUB_AMMO: str = "Hub: Hidden near Arctic"
|
||||
LOCATION_HUB_HEALTH: str = "Hub: Hidden near Cave"
|
||||
LOCATION_HUB_RELOAD: str = "Hub: Hidden near Swamp"
|
||||
LOCATION_SWAMP_AMMO: str = "Swamp: Bramble room"
|
||||
LOCATION_SWAMP_HEALTH: str = "Swamp: Down the chimney"
|
||||
LOCATION_SWAMP_RELOAD: str = "Swamp: Wall maze"
|
||||
LOCATION_SWAMP_SPECIAL: str = "Swamp: Special Extension chest"
|
||||
LOCATION_ELECTRICAL_RELOAD: str = "Electrical: Near generator"
|
||||
LOCATION_ELECTRICAL_HEALTH: str = "Electrical: Behind wall"
|
||||
LOCATION_ELECTRICAL_AMMO: str = "Electrical: Before Malakhov"
|
||||
LOCATION_ELECTRICAL_WEAPON: str = "Electrical: Volt Laser chest"
|
||||
|
||||
EP_LOCATION_CAVE_MINIBOSS: str = "Cave: Wallboss (Boss)"
|
||||
EP_LOCATION_CAVE_BOSS: str = "Cave: Guard (Boss)"
|
||||
EP_LOCATION_VOLCANIC_BOSS: str = "Volcanic: Cliff (Boss)"
|
||||
EP_LOCATION_ARCTIC_BOSS: str = "Arctic: Ace (Boss)"
|
||||
EP_LOCATION_HUB_CONSOLE: str = "Hub: Console login"
|
||||
EP_LOCATION_HUB_NINJA_SCARE: str = "Hub: Ninja scare (Boss?)"
|
||||
EP_LOCATION_SWAMP_BOSS: str = "Swamp: Snake (Boss)"
|
||||
EP_LOCATION_ELEVATOR_NINJA_FIGHT: str = "Elevator: Ninja (Boss)"
|
||||
EP_LOCATION_ELECTRICAL_EXTRA: str = "Electrical: Tesla orb"
|
||||
EP_LOCATION_ELECTRICAL_MINIBOSS: str = "Electrical: Generator (Boss)"
|
||||
EP_LOCATION_ELECTRICAL_BOSS: str = "Electrical: Malakhov (Boss)"
|
||||
EP_LOCATION_ELECTRICAL_FINAL_BOSS: str = "Electrical: BRAINOS (Boss)"
|
||||
|
||||
EVENT_LOCATION_GUARD_GONE: str = "Cave status"
|
||||
EVENT_LOCATION_CLIFF_GONE: str = "Volcanic status"
|
||||
EVENT_LOCATION_ACE_GONE: str = "Arctic status"
|
||||
EVENT_LOCATION_SNAKE_GONE: str = "Swamp status"
|
||||
EVENT_LOCATION_POWER_ON: str = "Generator status"
|
||||
EVENT_LOCATION_VICTORY: str = "Mission objective"
|
||||
|
||||
# region names
|
||||
REGION_MENU: str = "Menu"
|
||||
REGION_CAVE: str = "Cave"
|
||||
REGION_VOLCANIC: str = "Volcanic"
|
||||
REGION_ARCTIC: str = "Arctic"
|
||||
REGION_HUB: str = "Hub"
|
||||
REGION_SWAMP: str = "Swamp"
|
||||
REGION_ELECTRICAL: str = "Electrical"
|
||||
REGION_ELECTRICAL_POWERED: str = "Electrical (Power On)"
|
||||
98
worlds/saving_princess/Items.py
Normal file
98
worlds/saving_princess/Items.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from typing import Optional, Dict, Tuple
|
||||
|
||||
from BaseClasses import Item, ItemClassification as ItemClass
|
||||
|
||||
from .Constants import *
|
||||
|
||||
|
||||
class SavingPrincessItem(Item):
|
||||
game: str = GAME_NAME
|
||||
|
||||
|
||||
class ItemData:
|
||||
item_class: ItemClass
|
||||
code: Optional[int]
|
||||
count: int # Number of copies for the item that will be made of class item_class
|
||||
count_extra: int # Number of extra copies for the item that will be made as useful
|
||||
|
||||
def __init__(self, item_class: ItemClass, code: Optional[int] = None, count: int = 1, count_extra: int = 0):
|
||||
self.item_class = item_class
|
||||
|
||||
self.code = code
|
||||
if code is not None:
|
||||
self.code += BASE_ID
|
||||
|
||||
# if this is filler, a trap or an event, ignore the count
|
||||
if self.item_class == ItemClass.filler or self.item_class == ItemClass.trap or code is None:
|
||||
self.count = 0
|
||||
self.count_extra = 0
|
||||
else:
|
||||
self.count = count
|
||||
self.count_extra = count_extra
|
||||
|
||||
def create_item(self, player: int):
|
||||
return SavingPrincessItem(item_data_names[self], self.item_class, self.code, player)
|
||||
|
||||
|
||||
item_dict_weapons: Dict[str, ItemData] = {
|
||||
ITEM_WEAPON_CHARGE: ItemData(ItemClass.progression, 0),
|
||||
ITEM_WEAPON_FIRE: ItemData(ItemClass.progression, 1),
|
||||
ITEM_WEAPON_ICE: ItemData(ItemClass.progression, 2),
|
||||
ITEM_WEAPON_VOLT: ItemData(ItemClass.progression, 3),
|
||||
}
|
||||
|
||||
item_dict_upgrades: Dict[str, ItemData] = {
|
||||
ITEM_MAX_HEALTH: ItemData(ItemClass.progression, 4, 2, 4),
|
||||
ITEM_MAX_AMMO: ItemData(ItemClass.progression, 5, 2, 4),
|
||||
ITEM_RELOAD_SPEED: ItemData(ItemClass.progression, 6, 4, 2),
|
||||
ITEM_SPECIAL_AMMO: ItemData(ItemClass.useful, 7),
|
||||
}
|
||||
|
||||
item_dict_base: Dict[str, ItemData] = {
|
||||
**item_dict_weapons,
|
||||
**item_dict_upgrades,
|
||||
ITEM_JACKET: ItemData(ItemClass.useful, 8),
|
||||
}
|
||||
|
||||
item_dict_keys: Dict[str, ItemData] = {
|
||||
EP_ITEM_GUARD_GONE: ItemData(ItemClass.progression, 9),
|
||||
EP_ITEM_CLIFF_GONE: ItemData(ItemClass.progression, 10),
|
||||
EP_ITEM_ACE_GONE: ItemData(ItemClass.progression, 11),
|
||||
EP_ITEM_SNAKE_GONE: ItemData(ItemClass.progression, 12),
|
||||
}
|
||||
|
||||
item_dict_expanded: Dict[str, ItemData] = {
|
||||
**item_dict_base,
|
||||
**item_dict_keys,
|
||||
EP_ITEM_POWER_ON: ItemData(ItemClass.progression, 13),
|
||||
}
|
||||
|
||||
item_dict_filler: Dict[str, ItemData] = {
|
||||
FILLER_ITEM_HEAL: ItemData(ItemClass.filler, 14),
|
||||
FILLER_ITEM_QUICK_FIRE: ItemData(ItemClass.filler, 15),
|
||||
FILLER_ITEM_ACTIVE_CAMO: ItemData(ItemClass.filler, 16),
|
||||
}
|
||||
|
||||
item_dict_traps: Dict[str, ItemData] = {
|
||||
TRAP_ITEM_ICE: ItemData(ItemClass.trap, 17),
|
||||
TRAP_ITEM_SHAKES: ItemData(ItemClass.trap, 18),
|
||||
TRAP_ITEM_NINJA: ItemData(ItemClass.trap, 19),
|
||||
}
|
||||
|
||||
item_dict_events: Dict[str, ItemData] = {
|
||||
EVENT_ITEM_GUARD_GONE: ItemData(ItemClass.progression),
|
||||
EVENT_ITEM_CLIFF_GONE: ItemData(ItemClass.progression),
|
||||
EVENT_ITEM_ACE_GONE: ItemData(ItemClass.progression),
|
||||
EVENT_ITEM_SNAKE_GONE: ItemData(ItemClass.progression),
|
||||
EVENT_ITEM_POWER_ON: ItemData(ItemClass.progression),
|
||||
EVENT_ITEM_VICTORY: ItemData(ItemClass.progression),
|
||||
}
|
||||
|
||||
item_dict: Dict[str, ItemData] = {
|
||||
**item_dict_expanded,
|
||||
**item_dict_filler,
|
||||
**item_dict_traps,
|
||||
**item_dict_events,
|
||||
}
|
||||
|
||||
item_data_names: Dict[ItemData, str] = {value: key for key, value in item_dict.items()}
|
||||
82
worlds/saving_princess/Locations.py
Normal file
82
worlds/saving_princess/Locations.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from typing import Optional, Dict
|
||||
|
||||
from BaseClasses import Location
|
||||
|
||||
from .Constants import *
|
||||
|
||||
|
||||
class SavingPrincessLocation(Location):
|
||||
game: str = GAME_NAME
|
||||
|
||||
|
||||
class LocData:
|
||||
code: Optional[int]
|
||||
|
||||
def __init__(self, code: Optional[int] = None):
|
||||
if code is not None:
|
||||
self.code = code + BASE_ID
|
||||
else:
|
||||
self.code = None
|
||||
|
||||
|
||||
location_dict_base: Dict[str, LocData] = {
|
||||
LOCATION_CAVE_AMMO: LocData(0),
|
||||
LOCATION_CAVE_RELOAD: LocData(1),
|
||||
LOCATION_CAVE_HEALTH: LocData(2),
|
||||
LOCATION_CAVE_WEAPON: LocData(3),
|
||||
LOCATION_VOLCANIC_RELOAD: LocData(4),
|
||||
LOCATION_VOLCANIC_HEALTH: LocData(5),
|
||||
LOCATION_VOLCANIC_AMMO: LocData(6),
|
||||
LOCATION_VOLCANIC_WEAPON: LocData(7),
|
||||
LOCATION_ARCTIC_AMMO: LocData(8),
|
||||
LOCATION_ARCTIC_RELOAD: LocData(9),
|
||||
LOCATION_ARCTIC_HEALTH: LocData(10),
|
||||
LOCATION_ARCTIC_WEAPON: LocData(11),
|
||||
LOCATION_JACKET: LocData(12),
|
||||
LOCATION_HUB_AMMO: LocData(13),
|
||||
LOCATION_HUB_HEALTH: LocData(14),
|
||||
LOCATION_HUB_RELOAD: LocData(15),
|
||||
LOCATION_SWAMP_AMMO: LocData(16),
|
||||
LOCATION_SWAMP_HEALTH: LocData(17),
|
||||
LOCATION_SWAMP_RELOAD: LocData(18),
|
||||
LOCATION_SWAMP_SPECIAL: LocData(19),
|
||||
LOCATION_ELECTRICAL_RELOAD: LocData(20),
|
||||
LOCATION_ELECTRICAL_HEALTH: LocData(21),
|
||||
LOCATION_ELECTRICAL_AMMO: LocData(22),
|
||||
LOCATION_ELECTRICAL_WEAPON: LocData(23),
|
||||
}
|
||||
|
||||
location_dict_expanded: Dict[str, LocData] = {
|
||||
**location_dict_base,
|
||||
EP_LOCATION_CAVE_MINIBOSS: LocData(24),
|
||||
EP_LOCATION_CAVE_BOSS: LocData(25),
|
||||
EP_LOCATION_VOLCANIC_BOSS: LocData(26),
|
||||
EP_LOCATION_ARCTIC_BOSS: LocData(27),
|
||||
EP_LOCATION_HUB_CONSOLE: LocData(28),
|
||||
EP_LOCATION_HUB_NINJA_SCARE: LocData(29),
|
||||
EP_LOCATION_SWAMP_BOSS: LocData(30),
|
||||
EP_LOCATION_ELEVATOR_NINJA_FIGHT: LocData(31),
|
||||
EP_LOCATION_ELECTRICAL_EXTRA: LocData(32),
|
||||
EP_LOCATION_ELECTRICAL_MINIBOSS: LocData(33),
|
||||
EP_LOCATION_ELECTRICAL_BOSS: LocData(34),
|
||||
EP_LOCATION_ELECTRICAL_FINAL_BOSS: LocData(35),
|
||||
}
|
||||
|
||||
location_dict_event_expanded: Dict[str, LocData] = {
|
||||
EVENT_LOCATION_VICTORY: LocData(),
|
||||
}
|
||||
|
||||
# most event locations are only relevant without expanded pool
|
||||
location_dict_events: Dict[str, LocData] = {
|
||||
EVENT_LOCATION_GUARD_GONE: LocData(),
|
||||
EVENT_LOCATION_CLIFF_GONE: LocData(),
|
||||
EVENT_LOCATION_ACE_GONE: LocData(),
|
||||
EVENT_LOCATION_SNAKE_GONE: LocData(),
|
||||
EVENT_LOCATION_POWER_ON: LocData(),
|
||||
**location_dict_event_expanded,
|
||||
}
|
||||
|
||||
location_dict: Dict[str, LocData] = {
|
||||
**location_dict_expanded,
|
||||
**location_dict_events,
|
||||
}
|
||||
183
worlds/saving_princess/Options.py
Normal file
183
worlds/saving_princess/Options.py
Normal file
@@ -0,0 +1,183 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any
|
||||
|
||||
from Options import PerGameCommonOptions, DeathLink, StartInventoryPool, Choice, DefaultOnToggle, Range, Toggle, \
|
||||
OptionGroup
|
||||
|
||||
|
||||
class ExpandedPool(DefaultOnToggle):
|
||||
"""
|
||||
Determines if places other than chests and special weapons will be locations.
|
||||
This includes boss fights as well as powering the tesla orb and completing the console login.
|
||||
In Expanded Pool, system power is instead restored when receiving the System Power item.
|
||||
Similarly, the final area door will open once the four Key items, one for each main area, have been found.
|
||||
"""
|
||||
display_name = "Expanded Item Pool"
|
||||
|
||||
|
||||
class InstantSaving(DefaultOnToggle):
|
||||
"""
|
||||
When enabled, save points activate with no delay when touched.
|
||||
This makes saving much faster, at the cost of being unable to pick and choose when to save in order to save warp.
|
||||
"""
|
||||
display_name = "Instant Saving"
|
||||
|
||||
|
||||
class SprintAvailability(Choice):
|
||||
"""
|
||||
Determines under which conditions the debug sprint is made accessible to the player.
|
||||
To sprint, hold down Ctrl if playing on keyboard, or Left Bumper if on gamepad (remappable).
|
||||
With Jacket: you will not be able to sprint until after the Jacket item has been found.
|
||||
"""
|
||||
display_name = "Sprint Availability"
|
||||
option_never_available = 0
|
||||
option_always_available = 1
|
||||
option_available_with_jacket = 2
|
||||
default = option_available_with_jacket
|
||||
|
||||
|
||||
class CliffWeaponUpgrade(Choice):
|
||||
"""
|
||||
Determines which weapon Cliff uses against you, base or upgraded.
|
||||
This does not change the available strategies all that much.
|
||||
Vanilla: Cliff adds fire to his grenades if Ace has been defeated.
|
||||
If playing with the expanded pool, the Arctic Key will trigger the change instead.
|
||||
"""
|
||||
display_name = "Cliff Weapon Upgrade"
|
||||
option_never_upgraded = 0
|
||||
option_always_upgraded = 1
|
||||
option_vanilla = 2
|
||||
default = option_always_upgraded
|
||||
|
||||
|
||||
class AceWeaponUpgrade(Choice):
|
||||
"""
|
||||
Determines which weapon Ace uses against you, base or upgraded.
|
||||
Ace with his base weapon is very hard to dodge, the upgraded weapon offers a more balanced experience.
|
||||
Vanilla: Ace uses ice attacks if Cliff has been defeated.
|
||||
If playing with the expanded pool, the Volcanic Key will trigger the change instead.
|
||||
"""
|
||||
display_name = "Ace Weapon Upgrade"
|
||||
option_never_upgraded = 0
|
||||
option_always_upgraded = 1
|
||||
option_vanilla = 2
|
||||
default = option_always_upgraded
|
||||
|
||||
|
||||
class ScreenShakeIntensity(Range):
|
||||
"""
|
||||
Percentage multiplier for screen shake effects.
|
||||
0% means the screen will not shake at all.
|
||||
100% means the screen shake will be the same as in vanilla.
|
||||
"""
|
||||
display_name = "Screen Shake Intensity %"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 50
|
||||
|
||||
|
||||
class IFramesDuration(Range):
|
||||
"""
|
||||
Percentage multiplier for Portia's invincibility frames.
|
||||
0% means you will have no invincibility frames.
|
||||
100% means invincibility frames will be the same as vanilla.
|
||||
"""
|
||||
display_name = "IFrame Duration %"
|
||||
range_start = 0
|
||||
range_end = 400
|
||||
default = 100
|
||||
|
||||
|
||||
class TrapChance(Range):
|
||||
"""
|
||||
Likelihood of a filler item becoming a trap.
|
||||
"""
|
||||
display_name = "Trap Chance"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 50
|
||||
|
||||
|
||||
class MusicShuffle(Toggle):
|
||||
"""
|
||||
Enables music shuffling.
|
||||
The title screen song is not shuffled, as it plays before the client connects.
|
||||
"""
|
||||
display_name = "Music Shuffle"
|
||||
|
||||
|
||||
@dataclass
|
||||
class SavingPrincessOptions(PerGameCommonOptions):
|
||||
# generation options
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
expanded_pool: ExpandedPool
|
||||
trap_chance: TrapChance
|
||||
# gameplay options
|
||||
death_link: DeathLink
|
||||
instant_saving: InstantSaving
|
||||
sprint_availability: SprintAvailability
|
||||
cliff_weapon_upgrade: CliffWeaponUpgrade
|
||||
ace_weapon_upgrade: AceWeaponUpgrade
|
||||
iframes_duration: IFramesDuration
|
||||
# aesthetic options
|
||||
shake_intensity: ScreenShakeIntensity
|
||||
music_shuffle: MusicShuffle
|
||||
|
||||
|
||||
groups = [
|
||||
OptionGroup("Generation Options", [
|
||||
ExpandedPool,
|
||||
TrapChance,
|
||||
]),
|
||||
OptionGroup("Gameplay Options", [
|
||||
DeathLink,
|
||||
InstantSaving,
|
||||
SprintAvailability,
|
||||
CliffWeaponUpgrade,
|
||||
AceWeaponUpgrade,
|
||||
IFramesDuration,
|
||||
]),
|
||||
OptionGroup("Aesthetic Options", [
|
||||
ScreenShakeIntensity,
|
||||
MusicShuffle,
|
||||
]),
|
||||
]
|
||||
|
||||
presets = {
|
||||
"Vanilla-like": {
|
||||
"expanded_pool": False,
|
||||
"trap_chance": 0,
|
||||
"death_link": False,
|
||||
"instant_saving": False,
|
||||
"sprint_availability": SprintAvailability.option_never_available,
|
||||
"cliff_weapon_upgrade": CliffWeaponUpgrade.option_vanilla,
|
||||
"ace_weapon_upgrade": AceWeaponUpgrade.option_vanilla,
|
||||
"iframes_duration": 100,
|
||||
"shake_intensity": 100,
|
||||
"music_shuffle": False,
|
||||
},
|
||||
"Easy": {
|
||||
"expanded_pool": True,
|
||||
"trap_chance": 0,
|
||||
"death_link": False,
|
||||
"instant_saving": True,
|
||||
"sprint_availability": SprintAvailability.option_always_available,
|
||||
"cliff_weapon_upgrade": CliffWeaponUpgrade.option_never_upgraded,
|
||||
"ace_weapon_upgrade": AceWeaponUpgrade.option_always_upgraded,
|
||||
"iframes_duration": 200,
|
||||
"shake_intensity": 50,
|
||||
"music_shuffle": False,
|
||||
},
|
||||
"Hard": {
|
||||
"expanded_pool": True,
|
||||
"trap_chance": 100,
|
||||
"death_link": True,
|
||||
"instant_saving": True,
|
||||
"sprint_availability": SprintAvailability.option_never_available,
|
||||
"cliff_weapon_upgrade": CliffWeaponUpgrade.option_always_upgraded,
|
||||
"ace_weapon_upgrade": AceWeaponUpgrade.option_never_upgraded,
|
||||
"iframes_duration": 50,
|
||||
"shake_intensity": 100,
|
||||
"music_shuffle": False,
|
||||
}
|
||||
}
|
||||
110
worlds/saving_princess/Regions.py
Normal file
110
worlds/saving_princess/Regions.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from BaseClasses import MultiWorld, Region, Entrance
|
||||
|
||||
from . import Locations
|
||||
from .Constants import *
|
||||
|
||||
|
||||
region_dict: Dict[str, List[str]] = {
|
||||
REGION_MENU: [],
|
||||
REGION_CAVE: [
|
||||
LOCATION_CAVE_AMMO,
|
||||
LOCATION_CAVE_RELOAD,
|
||||
LOCATION_CAVE_HEALTH,
|
||||
LOCATION_CAVE_WEAPON,
|
||||
EP_LOCATION_CAVE_MINIBOSS,
|
||||
EP_LOCATION_CAVE_BOSS,
|
||||
EVENT_LOCATION_GUARD_GONE,
|
||||
],
|
||||
REGION_VOLCANIC: [
|
||||
LOCATION_VOLCANIC_RELOAD,
|
||||
LOCATION_VOLCANIC_HEALTH,
|
||||
LOCATION_VOLCANIC_AMMO,
|
||||
LOCATION_VOLCANIC_WEAPON,
|
||||
EP_LOCATION_VOLCANIC_BOSS,
|
||||
EVENT_LOCATION_CLIFF_GONE,
|
||||
],
|
||||
REGION_ARCTIC: [
|
||||
LOCATION_ARCTIC_AMMO,
|
||||
LOCATION_ARCTIC_RELOAD,
|
||||
LOCATION_ARCTIC_HEALTH,
|
||||
LOCATION_ARCTIC_WEAPON,
|
||||
LOCATION_JACKET,
|
||||
EP_LOCATION_ARCTIC_BOSS,
|
||||
EVENT_LOCATION_ACE_GONE,
|
||||
],
|
||||
REGION_HUB: [
|
||||
LOCATION_HUB_AMMO,
|
||||
LOCATION_HUB_HEALTH,
|
||||
LOCATION_HUB_RELOAD,
|
||||
EP_LOCATION_HUB_CONSOLE,
|
||||
EP_LOCATION_HUB_NINJA_SCARE,
|
||||
],
|
||||
REGION_SWAMP: [
|
||||
LOCATION_SWAMP_AMMO,
|
||||
LOCATION_SWAMP_HEALTH,
|
||||
LOCATION_SWAMP_RELOAD,
|
||||
LOCATION_SWAMP_SPECIAL,
|
||||
EP_LOCATION_SWAMP_BOSS,
|
||||
EVENT_LOCATION_SNAKE_GONE,
|
||||
],
|
||||
REGION_ELECTRICAL: [
|
||||
EP_LOCATION_ELEVATOR_NINJA_FIGHT,
|
||||
LOCATION_ELECTRICAL_WEAPON,
|
||||
EP_LOCATION_ELECTRICAL_MINIBOSS,
|
||||
EP_LOCATION_ELECTRICAL_EXTRA,
|
||||
EVENT_LOCATION_POWER_ON,
|
||||
],
|
||||
REGION_ELECTRICAL_POWERED: [
|
||||
LOCATION_ELECTRICAL_RELOAD,
|
||||
LOCATION_ELECTRICAL_HEALTH,
|
||||
LOCATION_ELECTRICAL_AMMO,
|
||||
EP_LOCATION_ELECTRICAL_BOSS,
|
||||
EP_LOCATION_ELECTRICAL_FINAL_BOSS,
|
||||
EVENT_LOCATION_VICTORY,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def set_region_locations(region: Region, location_names: List[str], is_pool_expanded: bool):
|
||||
location_pool = {**Locations.location_dict_base, **Locations.location_dict_events}
|
||||
if is_pool_expanded:
|
||||
location_pool = {**Locations.location_dict_expanded, **Locations.location_dict_event_expanded}
|
||||
region.locations = [
|
||||
Locations.SavingPrincessLocation(
|
||||
region.player,
|
||||
name,
|
||||
Locations.location_dict[name].code,
|
||||
region
|
||||
) for name in location_names if name in location_pool.keys()
|
||||
]
|
||||
|
||||
|
||||
def create_regions(multiworld: MultiWorld, player: int, is_pool_expanded: bool):
|
||||
for region_name, location_names in region_dict.items():
|
||||
region = Region(region_name, player, multiworld)
|
||||
set_region_locations(region, location_names, is_pool_expanded)
|
||||
multiworld.regions.append(region)
|
||||
connect_regions(multiworld, player)
|
||||
|
||||
|
||||
def connect_regions(multiworld: MultiWorld, player: int):
|
||||
# and add a connection from the menu to the hub region
|
||||
menu = multiworld.get_region(REGION_MENU, player)
|
||||
hub = multiworld.get_region(REGION_HUB, player)
|
||||
connection = Entrance(player, f"{REGION_HUB} entrance", menu)
|
||||
menu.exits.append(connection)
|
||||
connection.connect(hub)
|
||||
|
||||
# now add an entrance from every other region to hub
|
||||
for region_name in [REGION_CAVE, REGION_VOLCANIC, REGION_ARCTIC, REGION_SWAMP, REGION_ELECTRICAL]:
|
||||
connection = Entrance(player, f"{region_name} entrance", hub)
|
||||
hub.exits.append(connection)
|
||||
connection.connect(multiworld.get_region(region_name, player))
|
||||
|
||||
# and finally, the connection between the final region and its powered version
|
||||
electrical = multiworld.get_region(REGION_ELECTRICAL, player)
|
||||
connection = Entrance(player, f"{REGION_ELECTRICAL_POWERED} entrance", electrical)
|
||||
electrical.exits.append(connection)
|
||||
connection.connect(multiworld.get_region(REGION_ELECTRICAL_POWERED, player))
|
||||
132
worlds/saving_princess/Rules.py
Normal file
132
worlds/saving_princess/Rules.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from BaseClasses import CollectionState, Location, Entrance
|
||||
from worlds.generic.Rules import set_rule
|
||||
from .Constants import *
|
||||
if TYPE_CHECKING:
|
||||
from . import SavingPrincessWorld
|
||||
|
||||
|
||||
def set_rules(world: "SavingPrincessWorld"):
|
||||
def get_location(name: str) -> Location:
|
||||
return world.get_location(name)
|
||||
|
||||
def get_region_entrance(name: str) -> Entrance:
|
||||
return world.get_entrance(f"{name} entrance")
|
||||
|
||||
def can_hover(state: CollectionState) -> bool:
|
||||
# portia can hover if she has a weapon other than the powered blaster and 4 reload speed upgrades
|
||||
return (
|
||||
state.has(ITEM_RELOAD_SPEED, world.player, 4)
|
||||
and state.has_any({ITEM_WEAPON_FIRE, ITEM_WEAPON_ICE, ITEM_WEAPON_VOLT}, world.player)
|
||||
)
|
||||
|
||||
# guarantees that the player will have some upgrades before having to face the area bosses, except for cave
|
||||
def nice_check(state: CollectionState) -> bool:
|
||||
return (
|
||||
state.has(ITEM_MAX_HEALTH, world.player)
|
||||
and state.has(ITEM_MAX_AMMO, world.player)
|
||||
and state.has(ITEM_RELOAD_SPEED, world.player, 2)
|
||||
)
|
||||
|
||||
# same as above, but for the final area
|
||||
def super_nice_check(state: CollectionState) -> bool:
|
||||
return (
|
||||
state.has(ITEM_MAX_HEALTH, world.player, 2)
|
||||
and state.has(ITEM_MAX_AMMO, world.player, 2)
|
||||
and state.has(ITEM_RELOAD_SPEED, world.player, 4)
|
||||
and state.has(ITEM_WEAPON_CHARGE, world.player)
|
||||
# at least one special weapon, other than powered blaster
|
||||
and state.has_any({ITEM_WEAPON_FIRE, ITEM_WEAPON_ICE, ITEM_WEAPON_VOLT}, world.player)
|
||||
)
|
||||
|
||||
# all special weapons required so that the boss' weapons can be targeted
|
||||
def all_weapons(state: CollectionState) -> bool:
|
||||
return state.has_all({ITEM_WEAPON_FIRE, ITEM_WEAPON_ICE, ITEM_WEAPON_VOLT}, world.player)
|
||||
|
||||
def is_gate_unlocked(state: CollectionState) -> bool:
|
||||
# the gate unlocks with all 4 boss keys, although this only applies to extended pool
|
||||
if world.is_pool_expanded:
|
||||
# in expanded, the final area requires all the boss keys
|
||||
return (
|
||||
state.has_all(
|
||||
{EP_ITEM_GUARD_GONE, EP_ITEM_CLIFF_GONE, EP_ITEM_ACE_GONE, EP_ITEM_SNAKE_GONE},
|
||||
world.player
|
||||
) and super_nice_check(state)
|
||||
)
|
||||
else:
|
||||
# in base pool, check that the main area bosses can be defeated
|
||||
return state.has_all(
|
||||
{EVENT_ITEM_GUARD_GONE, EVENT_ITEM_CLIFF_GONE, EVENT_ITEM_ACE_GONE, EVENT_ITEM_SNAKE_GONE},
|
||||
world.player
|
||||
) and super_nice_check(state)
|
||||
|
||||
def is_power_on(state: CollectionState) -> bool:
|
||||
# in expanded pool, the power item is what determines this, else it happens when the generator is powered
|
||||
return state.has(EP_ITEM_POWER_ON if world.is_pool_expanded else EVENT_ITEM_POWER_ON, world.player)
|
||||
|
||||
# set the location rules
|
||||
# this is behind the blast door to arctic
|
||||
set_rule(get_location(LOCATION_HUB_AMMO), lambda state: state.has(ITEM_WEAPON_CHARGE, world.player))
|
||||
# these are behind frozen doors
|
||||
for location_name in [LOCATION_ARCTIC_HEALTH, LOCATION_JACKET]:
|
||||
set_rule(get_location(location_name), lambda state: state.has(ITEM_WEAPON_FIRE, world.player))
|
||||
# these would require damage boosting otherwise
|
||||
set_rule(get_location(LOCATION_VOLCANIC_RELOAD),
|
||||
lambda state: state.has(ITEM_WEAPON_ICE, world.player) or can_hover(state))
|
||||
set_rule(get_location(LOCATION_SWAMP_AMMO), lambda state: can_hover(state))
|
||||
if world.is_pool_expanded:
|
||||
# does not spawn until the guard has been defeated
|
||||
set_rule(get_location(EP_LOCATION_HUB_NINJA_SCARE), lambda state: state.has(EP_ITEM_GUARD_GONE, world.player))
|
||||
# generator cannot be turned on without the volt laser
|
||||
set_rule(
|
||||
get_location(EP_LOCATION_ELECTRICAL_EXTRA if world.is_pool_expanded else EVENT_LOCATION_POWER_ON),
|
||||
lambda state: state.has(ITEM_WEAPON_VOLT, world.player)
|
||||
)
|
||||
# the roller is not very intuitive to get past without 4 ammo
|
||||
set_rule(get_location(LOCATION_CAVE_WEAPON), lambda state: state.has(ITEM_MAX_AMMO, world.player))
|
||||
set_rule(
|
||||
get_location(EP_LOCATION_CAVE_BOSS if world.is_pool_expanded else EVENT_LOCATION_GUARD_GONE),
|
||||
lambda state: state.has(ITEM_MAX_AMMO, world.player)
|
||||
)
|
||||
|
||||
# guarantee some upgrades to be found before bosses
|
||||
boss_locations = [LOCATION_VOLCANIC_WEAPON, LOCATION_ARCTIC_WEAPON, LOCATION_SWAMP_SPECIAL]
|
||||
if world.is_pool_expanded:
|
||||
boss_locations += [EP_LOCATION_VOLCANIC_BOSS, EP_LOCATION_ARCTIC_BOSS, EP_LOCATION_SWAMP_BOSS]
|
||||
else:
|
||||
boss_locations += [EVENT_LOCATION_CLIFF_GONE, EVENT_LOCATION_ACE_GONE, EVENT_LOCATION_SNAKE_GONE]
|
||||
for location_name in boss_locations:
|
||||
set_rule(get_location(location_name), lambda state: nice_check(state))
|
||||
|
||||
# set the basic access rules for the regions, these are all behind blast doors
|
||||
for region_name in [REGION_VOLCANIC, REGION_ARCTIC, REGION_SWAMP]:
|
||||
set_rule(get_region_entrance(region_name), lambda state: state.has(ITEM_WEAPON_CHARGE, world.player))
|
||||
|
||||
# now for the final area regions, which have different rules based on if ep is on
|
||||
set_rule(get_region_entrance(REGION_ELECTRICAL), lambda state: is_gate_unlocked(state))
|
||||
set_rule(get_region_entrance(REGION_ELECTRICAL_POWERED), lambda state: is_power_on(state))
|
||||
|
||||
# brainos requires all weapons, cannot destroy the cannons otherwise
|
||||
if world.is_pool_expanded:
|
||||
set_rule(get_location(EP_LOCATION_ELECTRICAL_FINAL_BOSS), lambda state: all_weapons(state))
|
||||
# and we need to beat brainos to beat the game
|
||||
set_rule(get_location(EVENT_LOCATION_VICTORY), lambda state: all_weapons(state))
|
||||
|
||||
# if not expanded pool, place the events for the boss kills and generator
|
||||
if not world.is_pool_expanded:
|
||||
# accessible with no items
|
||||
cave_item = world.create_item(EVENT_ITEM_GUARD_GONE)
|
||||
get_location(EVENT_LOCATION_GUARD_GONE).place_locked_item(cave_item)
|
||||
volcanic_item = world.create_item(EVENT_ITEM_CLIFF_GONE)
|
||||
get_location(EVENT_LOCATION_CLIFF_GONE).place_locked_item(volcanic_item)
|
||||
arctic_item = world.create_item(EVENT_ITEM_ACE_GONE)
|
||||
get_location(EVENT_LOCATION_ACE_GONE).place_locked_item(arctic_item)
|
||||
swamp_item = world.create_item(EVENT_ITEM_SNAKE_GONE)
|
||||
get_location(EVENT_LOCATION_SNAKE_GONE).place_locked_item(swamp_item)
|
||||
power_item = world.create_item(EVENT_ITEM_POWER_ON)
|
||||
get_location(EVENT_LOCATION_POWER_ON).place_locked_item(power_item)
|
||||
|
||||
# and, finally, set the victory event
|
||||
victory_item = world.create_item(EVENT_ITEM_VICTORY)
|
||||
get_location(EVENT_LOCATION_VICTORY).place_locked_item(victory_item)
|
||||
world.multiworld.completion_condition[world.player] = lambda state: state.has(EVENT_ITEM_VICTORY, world.player)
|
||||
174
worlds/saving_princess/__init__.py
Normal file
174
worlds/saving_princess/__init__.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from typing import ClassVar, Dict, Any, Type, List, Union
|
||||
|
||||
import Utils
|
||||
from BaseClasses import Tutorial, ItemClassification as ItemClass
|
||||
from Options import PerGameCommonOptions, OptionError
|
||||
from settings import Group, UserFilePath, LocalFolderPath, Bool
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from worlds.LauncherComponents import components, Component, launch_subprocess, Type as ComponentType
|
||||
from . import Options, Items, Locations
|
||||
from .Constants import *
|
||||
|
||||
|
||||
def launch_client(*args: str):
|
||||
from .Client import launch
|
||||
launch_subprocess(launch(*args), name=CLIENT_NAME)
|
||||
|
||||
|
||||
components.append(
|
||||
Component(f"{GAME_NAME} Client", game_name=GAME_NAME, func=launch_client, component_type=ComponentType.CLIENT, supports_uri=True)
|
||||
)
|
||||
|
||||
|
||||
class SavingPrincessSettings(Group):
|
||||
class GamePath(UserFilePath):
|
||||
"""Path to the game executable from which files are extracted"""
|
||||
description = "the Saving Princess game executable"
|
||||
is_exe = True
|
||||
md5s = [GAME_HASH]
|
||||
|
||||
class InstallFolder(LocalFolderPath):
|
||||
"""Path to the mod installation folder"""
|
||||
description = "the folder to install Saving Princess Archipelago to"
|
||||
|
||||
class LaunchGame(Bool):
|
||||
"""Set this to false to never autostart the game"""
|
||||
|
||||
class LaunchCommand(str):
|
||||
"""
|
||||
The console command that will be used to launch the game
|
||||
The command will be executed with the installation folder as the current directory
|
||||
"""
|
||||
|
||||
exe_path: GamePath = GamePath("Saving Princess.exe")
|
||||
install_folder: InstallFolder = InstallFolder("Saving Princess")
|
||||
launch_game: Union[LaunchGame, bool] = True
|
||||
launch_command: LaunchCommand = LaunchCommand('"Saving Princess v0_8.exe"' if Utils.is_windows
|
||||
else 'wine "Saving Princess v0_8.exe"')
|
||||
|
||||
|
||||
class SavingPrincessWeb(WebWorld):
|
||||
theme = "partyTime"
|
||||
bug_report_page = "https://github.com/LeonarthCG/saving-princess-archipelago/issues"
|
||||
setup_en = Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up Saving Princess for Archipelago multiworld.",
|
||||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["LeonarthCG"]
|
||||
)
|
||||
tutorials = [setup_en]
|
||||
options_presets = Options.presets
|
||||
option_groups = Options.groups
|
||||
|
||||
|
||||
class SavingPrincessWorld(World):
|
||||
"""
|
||||
Explore a space station crawling with rogue machines and even rival bounty hunters
|
||||
with the same objective as you - but with far, far different intentions!
|
||||
|
||||
Expand your arsenal as you collect upgrades to your trusty arm cannon and armor!
|
||||
""" # Excerpt from itch
|
||||
game = GAME_NAME
|
||||
web = SavingPrincessWeb()
|
||||
required_client_version = (0, 5, 0)
|
||||
|
||||
topology_present = False
|
||||
|
||||
item_name_to_id = {
|
||||
key: value.code for key, value in (Items.item_dict.items() - Items.item_dict_events.items())
|
||||
}
|
||||
location_name_to_id = {
|
||||
key: value.code for key, value in (Locations.location_dict.items() - Locations.location_dict_events.items())
|
||||
}
|
||||
|
||||
item_name_groups = {
|
||||
"Weapons": {key for key in Items.item_dict_weapons.keys()},
|
||||
"Upgrades": {key for key in Items.item_dict_upgrades.keys()},
|
||||
"Keys": {key for key in Items.item_dict_keys.keys()},
|
||||
"Filler": {key for key in Items.item_dict_filler.keys()},
|
||||
"Traps": {key for key in Items.item_dict_traps.keys()},
|
||||
}
|
||||
|
||||
options_dataclass: ClassVar[Type[PerGameCommonOptions]] = Options.SavingPrincessOptions
|
||||
options: Options.SavingPrincessOptions
|
||||
settings_key = "saving_princess_settings"
|
||||
settings: ClassVar[SavingPrincessSettings]
|
||||
|
||||
is_pool_expanded: bool = False
|
||||
music_table: List[int] = list(range(16))
|
||||
|
||||
def generate_early(self) -> None:
|
||||
if not self.player_name.isascii():
|
||||
raise OptionError(f"{self.player_name}'s name must be only ASCII.")
|
||||
self.is_pool_expanded = self.options.expanded_pool > 0
|
||||
if self.options.music_shuffle:
|
||||
self.random.shuffle(self.music_table)
|
||||
# find zzz and purple and swap them back to their original positions
|
||||
for song_id in [9, 13]:
|
||||
song_index = self.music_table.index(song_id)
|
||||
t = self.music_table[song_id]
|
||||
self.music_table[song_id] = song_id
|
||||
self.music_table[song_index] = t
|
||||
|
||||
def create_regions(self) -> None:
|
||||
from .Regions import create_regions
|
||||
create_regions(self.multiworld, self.player, self.is_pool_expanded)
|
||||
|
||||
def create_items(self) -> None:
|
||||
items_made: int = 0
|
||||
|
||||
# now, for each item
|
||||
item_dict = Items.item_dict_expanded if self.is_pool_expanded else Items.item_dict_base
|
||||
for item_name, item_data in item_dict.items():
|
||||
# create count copies of the item
|
||||
for i in range(item_data.count):
|
||||
self.multiworld.itempool.append(self.create_item(item_name))
|
||||
items_made += item_data.count
|
||||
# and create count_extra useful copies of the item
|
||||
original_item_class: ItemClass = item_data.item_class
|
||||
item_data.item_class = ItemClass.useful
|
||||
for i in range(item_data.count_extra):
|
||||
self.multiworld.itempool.append(self.create_item(item_name))
|
||||
item_data.item_class = original_item_class
|
||||
items_made += item_data.count_extra
|
||||
|
||||
# get the number of unfilled locations, that is, locations for items - items generated
|
||||
location_count = len(Locations.location_dict_base)
|
||||
if self.is_pool_expanded:
|
||||
location_count = len(Locations.location_dict_expanded)
|
||||
junk_count: int = location_count - items_made
|
||||
|
||||
# and generate as many junk items as unfilled locations
|
||||
for i in range(junk_count):
|
||||
self.multiworld.itempool.append(self.create_item(self.get_filler_item_name()))
|
||||
|
||||
def create_item(self, name: str) -> Items.SavingPrincessItem:
|
||||
return Items.item_dict[name].create_item(self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
filler_list = list(Items.item_dict_filler.keys())
|
||||
# check if this is going to be a trap
|
||||
if self.random.randint(0, 99) < self.options.trap_chance:
|
||||
filler_list = list(Items.item_dict_traps.keys())
|
||||
# and return one of the names at random
|
||||
return self.random.choice(filler_list)
|
||||
|
||||
def set_rules(self):
|
||||
from .Rules import set_rules
|
||||
set_rules(self)
|
||||
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
slot_data = self.options.as_dict(
|
||||
"death_link",
|
||||
"expanded_pool",
|
||||
"instant_saving",
|
||||
"sprint_availability",
|
||||
"cliff_weapon_upgrade",
|
||||
"ace_weapon_upgrade",
|
||||
"shake_intensity",
|
||||
"iframes_duration",
|
||||
)
|
||||
slot_data["music_table"] = self.music_table
|
||||
return slot_data
|
||||
55
worlds/saving_princess/docs/en_Saving Princess.md
Normal file
55
worlds/saving_princess/docs/en_Saving Princess.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Saving Princess
|
||||
|
||||
## Quick Links
|
||||
- [Setup Guide](/tutorial/Saving%20Princess/setup/en)
|
||||
- [Options Page](/games/Saving%20Princess/player-options)
|
||||
- [Saving Princess Archipelago GitHub](https://github.com/LeonarthCG/saving-princess-archipelago)
|
||||
|
||||
## What changes have been made?
|
||||
|
||||
The game has had several changes made to add new features and prevent issues. The most important changes are the following:
|
||||
- There is an in-game connection settings menu, autotracker and client console.
|
||||
- New save files are created and used automatically for each seed and slot played.
|
||||
- The game window can now be dragged and a new integer scaling option has been added.
|
||||
|
||||
## What items and locations get shuffled?
|
||||
|
||||
The chest contents and special weapons are the items and locations that get shuffled.
|
||||
|
||||
Additionally, there are new items to work as filler and traps, ranging from a full health and ammo restore to spawning a Ninja on top of you.
|
||||
|
||||
The Expanded Pool option, which is enabled by default, adds a few more items and locations:
|
||||
- Completing the intro sequence, powering the generator with the Volt Laser and defeating each boss become locations.
|
||||
- 4 Keys will be shuffled, which serve to open the door to the final area in place of defeating the main area bosses.
|
||||
- A System Power item will be shuffled, which restores power to the final area instead of this happening when the generator is powered.
|
||||
|
||||
## What does another world's item look like in Saving Princess?
|
||||
|
||||
Some locations, such as boss kills, have no visual representation, but those that do will have the Archipelago icon.
|
||||
|
||||
Once the item is picked up, a textbox will inform you of the item that was found as well as the player that will be receiving it.
|
||||
|
||||
These textboxes will have colored backgrounds and comments about the item category.
|
||||
For example, progression items will have a purple background and say "Looks plenty important!".
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
|
||||
When you receive an item, a textbox will show up.
|
||||
This textbox shows both which item you got and which player sent it to you.
|
||||
|
||||
If you send an item to yourself, however, the sending player will be omitted.
|
||||
|
||||
## Unique Local Commands
|
||||
|
||||
The following commands are only available when using the in-game console in Saving Princess:
|
||||
- `/help` Returns the help listing.
|
||||
- `/options` Lists currently applied options.
|
||||
- `/resync` Manually triggers a resync. This also resends all found locations.
|
||||
- `/unstuck` Sets save point to the first save point. Portia is then killed.
|
||||
- `/deathlink [on|off]` Toggles or sets death link mode.
|
||||
- `/instantsaving [on|off]` Toggles or sets instant saving.
|
||||
- `/sprint {never|always|jacket}` Sets sprint mode.
|
||||
- `/cliff {never|always|vanilla}` Sets Cliff's weapon upgrade condition.
|
||||
- `/ace {never|always|vanilla}` Sets Ace's weapon upgrade condition.
|
||||
- `/iframes n` Sets the iframe duration % multiplier to n, where 0 <= n <= 400.
|
||||
- `/shake n` Sets the shake intensity % multiplier to n, where 0 <= n <= 100.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user