Implemented District ER

This commit is contained in:
codemann8
2024-01-10 05:57:39 -06:00
parent 17532160ee
commit 2bec50b26b
12 changed files with 255 additions and 49 deletions

View File

@@ -318,7 +318,7 @@ class World(object):
elif self.open_pyramid[player] == 'no':
return False
else:
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']:
return False
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']:
return True
@@ -3464,7 +3464,7 @@ class Pot(object):
# byte 0: DDDE EEEE (DR, ER)
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4}
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10}
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10, "district": 11}
# byte 1: LLLW WSS? (logic, mode, sword)
logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5}

View File

@@ -986,7 +986,8 @@ def balance_prices(world, player):
def check_hints(world, player):
if world.shuffle[player] in ['simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity']:
if (world.shuffle[player] in ['simple', 'restricted', 'full', 'district', 'swapped', 'crossed', 'insanity']
or (world.shuffle[player] in ['lite', 'lean'] and world.shopsanity[player])):
for shop, location_list in shop_to_location_table.items():
if shop in ['Capacity Upgrade', 'Paradox Shop', 'Potion Shop']:
continue # near the queen, near potions, and near 7 chests are fine

View File

@@ -1072,7 +1072,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, forced_flips, player):
attempts -= 1
continue
# ensure sanc can be placed in LW in certain modes
if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'swapped', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
if not do_grouped and world.shuffle[player] in ['simple', 'restricted', 'full', 'district'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'):
free_dw_drops = parity[5] + (1 if world.shuffle_ganon[player] else 0)
free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon[player] else 0)
if free_dw_drops == free_drops:
@@ -1124,7 +1124,7 @@ def define_tile_groups(world, do_grouped, player):
return False
# sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen
if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district'] \
and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \
or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')):
return False
@@ -1138,24 +1138,31 @@ def define_tile_groups(world, do_grouped, player):
groups.append([0x80])
groups.append([0x81])
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple']:
merge_groups([[0x03, 0x0a], [0x28, 0x29]])
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']:
merge_groups([[0x13, 0x14]])
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted']:
merge_groups([[0x05, 0x07]])
if world.shuffle[player] == 'vanilla' or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']):
# hyrule castle and sanctuary connector
if world.shuffle[player] in ['vanilla', 'district'] or (world.mode[player] == 'standard' and world.shuffle[player] in ['dungeonssimple', 'dungeonsfull']):
merge_groups([[0x13, 0x14, 0x1b]])
# sanctuary and grave connector
if world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite']:
merge_groups([[0x13, 0x14]])
# cross-screen connector
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple', 'district']:
merge_groups([[0x03, 0x0a], [0x28, 0x29]])
# turtle rock connector
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'simple', 'restricted', 'district']:
merge_groups([[0x05, 0x07]])
# all non-parallel screens
if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]])
# special case: non-parallel keep similar
if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x28, 0x29]])
# whirlpool screens
if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped):
merge_groups([[0x0f, 0x35], [0x12, 0x15, 0x33, 0x3f]])
@@ -1562,7 +1569,7 @@ def validate_layout(world, player):
for dest_region in entrance_connectors[region_name]:
if dest_region not in explored_regions:
explore_region(dest_region)
if world.shuffle[player] not in ['insanity'] and region_name in sane_connectors:
if world.shuffle[player] not in ['district', 'insanity'] and region_name in sane_connectors:
for dest_region in sane_connectors[region_name]:
if dest_region not in explored_regions:
explore_region(dest_region)
@@ -1619,12 +1626,13 @@ def validate_layout(world, player):
break
# check if entrances in region could be used to access region
if world.shuffle[player] != 'vanilla':
# TODO: For District ER, we need to check if there is a dropdown or connector that is able to connect
for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']:
or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['insanity']) \
if (entrance.name == 'Links House' and ((not world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \
or (entrance.name == 'Big Bomb Shop' and ((world.is_bombshop_start(player) and not world.shufflelinks[player]) or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \
or (entrance.name == 'Ganons Tower' and (not world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \
or (entrance.name == 'Agahnims Tower' and (world.is_atgt_swapped(player) and not world.shuffle_ganon[player])) \
or (entrance.name in ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)'] and world.shuffle[player] not in ['district', 'insanity']) \
or (entrance.name == 'Tavern North' and not world.shuffletavern[player]):
continue # these are fixed entrances and cannot be used for gaining access to region
if entrance.name not in drop_entrances \

18
Rom.py
View File

@@ -1667,7 +1667,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
# rom.write_byte(snes_to_pc(0x0DB730), 0x08) # allows chickens to travel across water
# allow smith into multi-entrance caves in appropriate shuffles
if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'):
if world.shuffle[player] in ['restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'] or (world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'):
rom.write_byte(0x18004C, 0x01)
# set correct flag for hera basement item
@@ -2142,7 +2142,7 @@ def write_strings(rom, world, player, team):
tt.removeUnwantedText()
# Let's keep this guy's text accurate to the shuffle setting.
if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple']:
if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'lite', 'lean']:
tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.'
tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.'
@@ -2193,7 +2193,7 @@ def write_strings(rom, world, player, team):
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
if world.shuffle[player] not in ['lite', 'lean']:
entrances_to_hint.update(InconvenientOtherEntrances)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'swapped']:
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']:
hint_count = 0
elif world.shuffle[player] in ['simple', 'restricted']:
hint_count = 2
@@ -2227,7 +2227,7 @@ def write_strings(rom, world, player, team):
entrances_to_hint.update(OtherEntrances)
if world.mode[player] != 'inverted':
entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'})
if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean']:
if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'district']:
if world.shufflelinks[player]:
entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'})
entrances_to_hint.update({'Links House': 'The hero\'s old residence'})
@@ -2244,7 +2244,7 @@ def write_strings(rom, world, player, team):
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else:
entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 0
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 0
hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
@@ -2263,7 +2263,7 @@ def write_strings(rom, world, player, team):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
locations_to_hint.extend(InconvenientVanillaLocations)
random.shuffle(locations_to_hint)
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 5
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 5
hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
del locations_to_hint[hint_count:]
for location in locations_to_hint:
@@ -2338,7 +2338,7 @@ def write_strings(rom, world, player, team):
if world.bigkeyshuffle[player]:
items_to_hint.extend(BigKeys)
random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 8
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8
hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
hint_count += 1 if world.owShuffle[player] != 'vanilla' or world.owCrossed[player] != 'none' or world.owMixed[player] else 0
while hint_count > 0 and len(items_to_hint) > 0:
@@ -2382,11 +2382,11 @@ def write_strings(rom, world, player, team):
choices.clear()
choices.append(location_item)
if hint_type == 'foolish':
if district.dungeons and world.shuffle[player] != 'vanilla':
if district.dungeons and world.shuffle[player] not in ['vanilla', 'district']:
choices.extend(district.dungeons)
hint_type = 'dungeon_path'
elif district.access_points and world.shuffle[player] not in ['vanilla', 'dungeonssimple',
'dungeonsfull']:
'dungeonsfull', 'district']:
choices.extend([x.hint_text for x in district.access_points])
hint_type = 'connector'
if hint_type == 'foolish':

View File

@@ -1067,7 +1067,7 @@ def ow_inverted_rules(world, player):
else:
set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has('Cape', player) or state.has_beam_sword(player)) # barrier gets removed after killing agahnim, rule for that added later
set_rule(world.get_entrance('GT Approach', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player))
set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'))
set_rule(world.get_entrance('GT Leave', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player) or state.world.shuffle[player] in ('restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'))
if world.is_tile_swapped(0x03, player):
set_rule(world.get_entrance('Spectacle Rock Approach', player), lambda state: world.logic[player] in ['noglitches', 'minorglitches'] and state.has_Pearl(player))

View File

@@ -51,6 +51,7 @@ def main(args=None):
test("Full ", "--shuffle full")
test("Lite ", "--shuffle lite")
test("Lean ", "--shuffle lean")
test("District ", "--shuffle district")
test("Swapped ", "--shuffle swapped")
test("Crossed ", "--shuffle crossed")
test("Insanity ", "--shuffle insanity")

View File

@@ -14,7 +14,7 @@ ALL_SETTINGS = {
'mode': ['open', 'standard', 'inverted'],
'goal': ['ganon', 'pedestal', 'triforcehunt', 'trinity', 'crystals', 'dungeons'],
'swords': ['random', 'swordless', 'assured'],
'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','swapped','crossed','insanity'],
'shuffle': ['vanilla','simple','restricted','full','dungeonssimple','dungeonsfull','lite','lean','district','swapped','crossed','insanity'],
'shufflelinks': [True, False],
'shuffleganon': [True, False],
'door_shuffle': ['vanilla', 'basic', 'crossed'],
@@ -39,7 +39,7 @@ SETTINGS = {
'goal': ['ganon'],
'swords': ['random'],
'shuffle': ['vanilla',
'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'swapped', 'crossed', 'insanity'
'dungeonssimple', 'dungeonsfull', 'simple', 'restricted', 'full', 'lite', 'lean', 'district', 'swapped', 'crossed', 'insanity'
],
'shufflelinks': [True, False],
'shuffleganon': [True, False],

View File

@@ -209,6 +209,7 @@
"full",
"lite",
"lean",
"district",
"swapped",
"crossed",
"insanity",

View File

@@ -212,6 +212,7 @@
" item locations are shuffled in separate pools. Non-item",
" locations remain vanilla. Connectors are same-world.",
"Lean: Same as Lite, except connectors can travel cross worlds.",
"District: Entrances are shuffled within their overworld districts.",
"Crossed: Mix cave and dungeon entrances freely while allowing",
" caves to cross between worlds.",
"Swapped: Same as Crossed, but entrances switch places in pairs.",

View File

@@ -181,6 +181,7 @@
"randomizer.entrance.entranceshuffle.restricted": "Restricted",
"randomizer.entrance.entranceshuffle.full": "Full",
"randomizer.entrance.entranceshuffle.lean": "Lean",
"randomizer.entrance.entranceshuffle.district": "District",
"randomizer.entrance.entranceshuffle.swapped": "Swapped",
"randomizer.entrance.entranceshuffle.crossed": "Crossed",
"randomizer.entrance.entranceshuffle.insanity": "Insanity",

View File

@@ -9,6 +9,7 @@
"full",
"lite",
"lean",
"district",
"swapped",
"crossed",
"insanity",

View File

@@ -13,6 +13,7 @@ class EntrancePool(object):
self.inverted = False
self.coupled = True
self.swapped = False
self.assumed_loose_caves = False
self.keep_drops_together = True
self.default_map = {}
self.one_way_map = {}
@@ -55,6 +56,7 @@ def link_entrances_new(world, player):
avail_pool.entrances = set(i_drop_map.keys()).union(i_entrance_map.keys()).union(i_single_ent_map.keys())
avail_pool.exits = set(i_entrance_map.values()).union(i_drop_map.values()).union(i_single_ent_map.values())
avail_pool.inverted = world.mode[player] == 'inverted'
avail_pool.assumed_loose_caves = world.shuffle[player] == 'district'
inverted_substitution(avail_pool, avail_pool.entrances, True, True)
inverted_substitution(avail_pool, avail_pool.exits, False, True)
avail_pool.original_entrances.update(avail_pool.entrances)
@@ -128,6 +130,14 @@ def link_entrances_new(world, player):
do_limited_shuffle_exclude_drops(pool, avail_pool, False)
elif special_shuffle == 'vanilla':
do_vanilla_connect(pool, avail_pool)
elif special_shuffle == 'district':
drops = []
world_limiter = LW_Entrances if pool['condition'] == 'lightworld' else DW_Entrances
entrances = [e for e in pool['entrances'] if e in world_limiter]
if 'drops' in pool:
drops = [e for e in pool['drops'] if combine_linked_drop_map[e] in world_limiter]
entrances, exits = find_entrances_and_exits(avail_pool, entrances+drops)
do_main_shuffle(entrances, exits, avail_pool, mode_cfg)
elif special_shuffle == 'skull':
entrances, exits = find_entrances_and_exits(avail_pool, pool['entrances'])
rem_ent = None
@@ -151,6 +161,8 @@ def link_entrances_new(world, player):
do_vanilla_connections(avail_pool)
elif undefined_behavior in ['shuffle', 'swap']:
do_main_shuffle(set(avail_pool.entrances), set(avail_pool.exits), avail_pool, mode_cfg)
elif undefined_behavior == 'error':
assert len(avail_pool.entrances)+len(avail_pool.exits) == 0, 'Not all entrances were placed in their districts'
# afterward
@@ -310,7 +322,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
return not avail.is_standard() or x != 'Bonk Fairy (Light)'
# old man S&Q cave
if not cross_world:
if not cross_world and not avail.assumed_loose_caves:
#TODO: Add Swapped ER support for this
# OM Cave entrance in lw/dw if cross_world off
if 'Old Man Cave Exit (West)' in rem_exits:
@@ -382,7 +394,7 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
def do_old_man_cave_exit(entrances, exits, avail, cross_world):
if 'Old Man Cave Exit (East)' in exits:
from EntranceShuffle import build_accessible_region_list
if not avail.world.is_tile_swapped(0x03, avail.player):
if not avail.world.is_tile_swapped(0x03, avail.player) or avail.world.shuffle[avail.player] == 'district':
region_name = 'West Death Mountain (Top)'
else:
region_name = 'West Dark Death Mountain (Top)'
@@ -420,10 +432,13 @@ def do_blacksmith(entrances, exits, avail):
if avail.world.logic[avail.player] in ['noglitches', 'minorglitches'] and (avail.world.is_tile_swapped(0x29, avail.player) == avail.inverted):
assumed_inventory.append('Titans Mitts')
blacksmith_options = list()
if not avail.world.is_bombshop_start(avail.player):
links_region = avail.world.get_entrance('Links House Exit', avail.player).connected_region.name
links_region = avail.world.get_entrance('Links House Exit', avail.player).connected_region
else:
links_region = avail.world.get_entrance('Big Bomb Shop Exit', avail.player).connected_region.name
links_region = avail.world.get_entrance('Big Bomb Shop Exit', avail.player).connected_region
if links_region is not None:
links_region = links_region.name
blacksmith_options = list(get_accessible_entrances(links_region, avail, assumed_inventory, False, True, True))
if avail.inverted:
@@ -440,6 +455,9 @@ def do_blacksmith(entrances, exits, avail):
blacksmith_options = [e for e in blacksmith_options if e not in Forbidden_Swap_Entrances]
blacksmith_options = [x for x in blacksmith_options if x in entrances]
if avail.world.shuffle[avail.player] == 'district' and not len(blacksmith_options):
blacksmith_options = [e for e in entrances if e not in Forbidden_Swap_Entrances or not avail.swapped]
assert len(blacksmith_options), 'No available entrances left to place Blacksmith'
blacksmith_choice = random.choice(blacksmith_options)
connect_entrance(blacksmith_choice, 'Blacksmiths Hut', avail)
@@ -577,6 +595,8 @@ def do_dark_sanc(entrances, exits, avail):
forbidden.extend(Forbidden_Swap_Entrances)
if not avail.world.is_bombshop_start(avail.player):
forbidden.append('Links House')
else:
forbidden.append('Big Bomb Shop')
if avail.world.owShuffle[avail.player] == 'vanilla':
choices = [e for e in avail.world.districts[avail.player]['Northwest Dark World'].entrances if e not in forbidden and e in entrances]
else:
@@ -612,7 +632,7 @@ def do_links_house(entrances, exits, avail, cross_world):
forbidden.append('Mimic Cave')
if avail.world.is_bombshop_start(avail.player) and (avail.inverted == avail.world.is_tile_swapped(0x03, avail.player)):
forbidden.extend(['Spectacle Rock Cave', 'Spectacle Rock Cave (Bottom)'])
if avail.inverted:
if avail.inverted and avail.world.shuffle[avail.player] != 'district':
dark_sanc_region = avail.world.get_entrance('Dark Sanctuary Hint Exit', avail.player).connected_region.name
forbidden.extend(get_nearby_entrances(avail, dark_sanc_region))
else:
@@ -654,7 +674,7 @@ def do_links_house(entrances, exits, avail, cross_world):
sanc_spawn_can_be_dark = (not avail.inverted and avail.world.doorShuffle[avail.player] in ['partitioned', 'crossed']
and avail.world.intensity[avail.player] >= 3)
if cross_world and not sanc_spawn_can_be_dark:
if (cross_world and not sanc_spawn_can_be_dark) or avail.world.shuffle[avail.player] == 'district':
possible = [e for e in entrance_pool if e not in forbidden]
else:
world_list = LW_Entrances if not avail.inverted else DW_Entrances
@@ -686,7 +706,7 @@ def do_links_house(entrances, exits, avail, cross_world):
return
if avail.world.shuffle[avail.player] in ['lite', 'lean']:
rem_exits = [e for e in avail.exits if e in Connector_Exit_Set and e not in Dungeon_Exit_Set]
multi_exit_caves = figure_out_connectors(rem_exits)
multi_exit_caves = figure_out_connectors(rem_exits, avail)
if cross_world:
possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List]
possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots]
@@ -695,7 +715,7 @@ def do_links_house(entrances, exits, avail, cross_world):
possible_dm_exits = [e for e in avail.entrances if e not in entrances and e in LH_DM_Connector_List and e in world_list]
possible_exits = [e for e in avail.entrances if e not in entrances and e not in dm_spots and e in world_list]
else:
multi_exit_caves = figure_out_connectors(exits)
multi_exit_caves = figure_out_connectors(exits, avail)
entrance_pool = entrances if avail.coupled else avail.decoupled_entrances
if cross_world:
possible_dm_exits = [e for e in entrances if e in LH_DM_Connector_List]
@@ -842,12 +862,22 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo
return found_entrances
def figure_out_connectors(exits):
def figure_out_connectors(exits, avail):
multi_exit_caves = []
for item in Connector_List:
cave_list = list(Connector_List)
if avail.assumed_loose_caves:
sw_list = ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']
random.shuffle(sw_list)
cave_list.extend([sw_list])
cave_list.extend([[entrance_map[e]] for e in linked_drop_map.values() if 'Inverted ' not in e])
for item in cave_list:
if all(x in exits for x in item):
remove_from_list(exits, item)
multi_exit_caves.append(list(item))
elif avail.assumed_loose_caves and any(x in exits for x in item):
remaining = [i for i in item if i in exits]
remove_from_list(exits, remaining)
multi_exit_caves.append(list(remaining))
return multi_exit_caves
@@ -1025,7 +1055,7 @@ def figure_out_must_exits_same_world(entrances, exits, avail):
for x in entrances:
lw_entrances.append(x) if x in LW_Entrances else dw_entrances.append(x)
multi_exit_caves = figure_out_connectors(exits)
multi_exit_caves = figure_out_connectors(exits, avail)
must_exit_lw, must_exit_dw = must_exits_helper(avail)
must_exit_lw = must_exit_filter(avail, must_exit_lw, lw_entrances)
@@ -1035,7 +1065,7 @@ def figure_out_must_exits_same_world(entrances, exits, avail):
def figure_out_must_exits_cross_world(entrances, exits, avail):
multi_exit_caves = figure_out_connectors(exits)
multi_exit_caves = figure_out_connectors(exits, avail)
must_exit_lw, must_exit_dw = must_exits_helper(avail)
must_exit = must_exit_filter(avail, must_exit_lw + must_exit_dw, entrances)
@@ -1374,7 +1404,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
# find multi exit cave
candidates = []
for candidate in cave_options:
if not isinstance(candidate, str) and len(candidate) > 1 and (candidate in used_caves
allow_single = avail.assumed_loose_caves or len(candidate) > 1
if not isinstance(candidate, str) and allow_single and (candidate in used_caves
or len(candidate) < len(entrances) - required_entrances):
if not avail.swapped or (combine_map[exit] not in candidate and not any(e for e in must_exit if combine_map[e] in candidate)): #maybe someday allow these, but we need to disallow mutual locks in Swapped
candidates.append(candidate)
@@ -1408,6 +1439,10 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
if entrance in invalid_connections:
for exit2 in invalid_connections[entrance]:
invalid_connections[exit2] = invalid_connections[exit2].union(invalid_connections[exit]).union(invalid_cave_connections[tuple(cave)])
elif len(cave) == 1 and avail.assumed_loose_caves:
#TODO: keep track of caves we use for must exits that are unaccounted here
# the other exits of the cave should NOT be used to satisfy must-exit later
pass
elif cave[-1] == 'Spectacle Rock Cave Exit': # Spectacle rock only has one exit
cave_entrances = []
for cave_exit in rnd_cave[:-1]:
@@ -1532,14 +1567,12 @@ def find_entrances_and_exits(avail_pool, entrance_pool):
entrances, targets = [], []
inverted_substitution(avail_pool, entrance_pool, True)
for item in entrance_pool:
if item == 'Ganons Tower' and not avail_pool.world.shuffle_ganon[avail_pool.player]:
continue
if item in avail_pool.entrances:
entrances.append(item)
if item in entrance_map and entrance_map[item] in avail_pool.exits:
targets.append(entrance_map[item])
elif item in single_entrance_map and single_entrance_map[item] in avail_pool.exits:
targets.append(single_entrance_map[item])
if item in avail_pool.default_map and avail_pool.default_map[item] in avail_pool.exits:
targets.append(avail_pool.default_map[item])
elif item in avail_pool.one_way_map and avail_pool.one_way_map[item] in avail_pool.exits:
targets.append(avail_pool.one_way_map[item])
return entrances, targets
@@ -2077,6 +2110,157 @@ modes = {
},
}
},
'district': {
'undefined': 'error',
'keep_drops_together': 'off',
'cross_world': 'off',
'pools': {
'northwest_hyrule': {
'special': 'district',
'condition': 'lightworld',
'drops': ['Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'North Fairy Cave Drop',
'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)',
'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole'],
'entrances': ['Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', 'North Fairy Cave',
'Lost Woods Gamble', 'Lumberjack House', 'Old Man Cave (West)', 'Death Mountain Return Cave (West)',
'Fortune Teller (Light)', 'Bonk Rock Cave', 'Graveyard Cave', 'Kings Grave',
'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Dark Lumberjack Shop',
'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint',
'Red Shield Shop']
},
'northwest_dark_world': {
'special': 'district',
'condition': 'darkworld',
'drops': ['Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)',
'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole',
'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave', 'North Fairy Cave Drop',
'Kakariko Well Drop', 'Bat Cave Drop'],
'entrances': ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
'Skull Woods Second Section Door (West)', 'Skull Woods Final Section', 'Dark Lumberjack Shop',
'Bumper Cave (Bottom)', 'Bumper Cave (Top)', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint',
'Chest Game', 'Thieves Town', 'C-Shaped House', 'Dark World Shop', 'Brewery',
'Red Shield Shop', 'Hammer Peg Cave',
'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Sanctuary', 'North Fairy Cave',
'Kakariko Well Cave', 'Bat Cave Cave', 'Lost Woods Gamble', 'Lumberjack House', 'Fortune Teller (Light)',
'Old Man Cave (West)', 'Death Mountain Return Cave (West)', 'Bonk Rock Cave', 'Graveyard Cave',
'Kings Grave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)', 'Snitch Lady (West)',
'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Bush Covered House', 'Light World Bomb Hut',
'Kakariko Shop', 'Tavern North', 'Tavern (Front)', 'Blacksmiths Hut']
},
'central_hyrule': {
'special': 'district',
'condition': 'lightworld',
'drops': ['Hyrule Castle Secret Entrance Drop', 'Inverted Pyramid Hole',
'Pyramid Hole'],
'entrances': ['Hyrule Castle Secret Entrance Stairs', 'Inverted Pyramid Entrance', 'Agahnims Tower',
'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (South)',
'Bonk Fairy (Light)', 'Links House', 'Cave 45', 'Light Hype Fairy', 'Dam',
'Pyramid Entrance', 'Pyramid Fairy', 'Bonk Fairy (Dark)', 'Big Bomb Shop', 'Hype Cave', 'Swamp Palace']
},
'kakariko': {
'special': 'district',
'condition': 'lightworld',
'drops': ['Kakariko Well Drop', 'Bat Cave Drop'],
'entrances': ['Kakariko Well Cave', 'Bat Cave Cave', 'Blinds Hideout', 'Elder House (West)', 'Elder House (East)',
'Snitch Lady (West)', 'Snitch Lady (East)', 'Chicken House', 'Sick Kids House', 'Bush Covered House',
'Light World Bomb Hut', 'Kakariko Shop', 'Tavern North', 'Tavern (Front)', 'Blacksmiths Hut',
'Two Brothers House (West)', 'Two Brothers House (East)', 'Library', 'Kakariko Gamble Game',
'Chest Game', 'Thieves Town', 'C-Shaped House', 'Dark World Shop', 'Brewery',
'Hammer Peg Cave', 'Archery Game']
},
'eastern_hyrule': {
'special': 'district',
'condition': 'lightworld',
'entrances': ['Waterfall of Wishing', 'Potion Shop', 'Sahasrahlas Hut', 'Eastern Palace', 'Lake Hylia Fairy',
'Long Fairy Cave',
'Dark Potion Shop', 'Palace of Darkness Hint', 'Palace of Darkness', 'Dark Lake Hylia Fairy',
'East Dark World Hint']
},
'lake_hylia': {
'special': 'district',
'condition': 'lightworld',
'entrances': ['Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Capacity Upgrade', 'Mini Moldorm Cave',
'Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave',
'Dark Lake Hylia Shop', 'Ice Palace', 'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint',
'Dark Lake Hylia Ledge Spike Cave']
},
'desert': {
'special': 'district',
'condition': 'lightworld',
'entrances': ['Desert Palace Entrance (North)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (South)',
'Desert Palace Entrance (East)', 'Checkerboard Cave', 'Aginahs Cave', 'Desert Fairy', '50 Rupee Cave',
'Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint']
},
'death_mountain': {
'special': 'district',
'condition': 'lightworld',
'entrances': ['Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave',
'Death Mountain Return Cave (East)', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
'Spiral Cave', 'Spiral Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Bottom)',
'Mimic Cave', 'Hookshot Fairy', 'Paradox Cave (Top)', 'Paradox Cave (Middle)', 'Paradox Cave (Bottom)',
'Ganons Tower', 'Dark Death Mountain Fairy', 'Spike Cave', 'Superbunny Cave (Bottom)', 'Superbunny Cave (Top)',
'Dark Death Mountain Shop', 'Hookshot Cave', 'Hookshot Cave Back Entrance',
'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock']
},
'dark_death_mountain': {
'special': 'district',
'condition': 'darkworld',
'entrances': ['Ganons Tower', 'Dark Death Mountain Fairy', 'Spike Cave', 'Superbunny Cave (Bottom)', 'Superbunny Cave (Top)',
'Dark Death Mountain Shop', 'Hookshot Cave', 'Hookshot Cave Back Entrance',
'Dark Death Mountain Ledge (West)', 'Dark Death Mountain Ledge (East)', 'Turtle Rock Isolated Ledge Entrance', 'Turtle Rock',
'Tower of Hera', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Spectacle Rock Cave',
'Death Mountain Return Cave (East)', 'Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
'Spiral Cave', 'Spiral Cave (Bottom)', 'Fairy Ascension Cave (Top)', 'Fairy Ascension Cave (Bottom)',
'Mimic Cave', 'Hookshot Fairy', 'Paradox Cave (Top)', 'Paradox Cave (Middle)', 'Paradox Cave (Bottom)']
},
'south_dark_world': {
'special': 'district',
'condition': 'darkworld',
'entrances': ['Archery Game', 'Bonk Fairy (Dark)', 'Big Bomb Shop', 'Hype Cave', 'Dark Lake Hylia Shop', 'Ice Palace',
'Dark Lake Hylia Ledge Fairy', 'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Ledge Spike Cave',
'Swamp Palace',
'Two Brothers House (West)', 'Two Brothers House (East)', 'Library', 'Kakariko Gamble Game',
'Bonk Fairy (Light)', 'Links House', 'Cave 45', 'Desert Fairy', '50 Rupee Cave', 'Dam',
'Light Hype Fairy', 'Lake Hylia Fortune Teller', 'Lake Hylia Shop', 'Capacity Upgrade',
'Mini Moldorm Cave', 'Ice Rod Cave', 'Good Bee Cave', '20 Rupee Cave']
},
'east_dark_world': {
'special': 'district',
'condition': 'darkworld',
'drops': ['Pyramid Hole',
'Hyrule Castle Secret Entrance Drop', 'Inverted Pyramid Hole'],
'entrances': ['Pyramid Entrance', 'Pyramid Fairy', 'Dark Potion Shop', 'Palace of Darkness Hint', 'Palace of Darkness',
'Dark Lake Hylia Fairy', 'East Dark World Hint',
'Hyrule Castle Secret Entrance Stairs', 'Inverted Pyramid Entrance', 'Waterfall of Wishing', 'Potion Shop',
'Agahnims Tower', 'Hyrule Castle Entrance (West)', 'Hyrule Castle Entrance (East)',
'Hyrule Castle Entrance (South)', 'Sahasrahlas Hut', 'Eastern Palace', 'Lake Hylia Fairy', 'Long Fairy Cave']
},
'mire': {
'special': 'district',
'condition': 'darkworld',
'entrances': ['Mire Shed', 'Misery Mire', 'Mire Fairy', 'Mire Hint',
'Desert Palace Entrance (North)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (South)',
'Desert Palace Entrance (East)', 'Checkerboard Cave', 'Aginahs Cave']
}
}
},
'swapped': {
'undefined': 'swap',
'keep_drops_together': 'on',
@@ -2149,6 +2333,13 @@ linked_drop_map = {
'Inverted Pyramid Hole': 'Inverted Pyramid Entrance'
}
sw_linked_drop_map = {
'Skull Woods Second Section Hole': 'Skull Woods Second Section Door (West)',
'Skull Woods First Section Hole (North)': 'Skull Woods First Section Door',
'Skull Woods First Section Hole (West)': 'Skull Woods First Section Door',
'Skull Woods First Section Hole (East)': 'Skull Woods First Section Door'
}
entrance_map = {
'Desert Palace Entrance (South)': 'Desert Palace Exit (South)',
'Desert Palace Entrance (West)': 'Desert Palace Exit (West)',
@@ -2257,6 +2448,7 @@ single_entrance_map = {
}
combine_map = {**entrance_map, **single_entrance_map, **drop_map}
combine_linked_drop_map = {**linked_drop_map, **sw_linked_drop_map}
LW_Entrances = []
DW_Entrances = []