Implemented OWR reachability guarantee
This commit is contained in:
@@ -948,17 +948,148 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
|
||||
return explored_regions
|
||||
|
||||
def validate_layout(world, player):
|
||||
sectors = [[r for l in s for r in l] for s in world.owsectors[player]]
|
||||
for sector in sectors:
|
||||
entrances_present = False
|
||||
for region_name in sector:
|
||||
region = world.get_region(region_name, player)
|
||||
if any(x.spot_type == 'Entrance' for x in region.exits):
|
||||
entrances_present = True
|
||||
break
|
||||
if not entrances_present and not all(r in isolated_regions for r in sector):
|
||||
return False
|
||||
if world.accessibility[player] == 'beatable':
|
||||
return True
|
||||
|
||||
entrance_connectors = {
|
||||
'East Death Mountain (Bottom)': ['East Death Mountain (Top East)'],
|
||||
'Kakariko Suburb Area': ['Maze Race Ledge'],
|
||||
'Maze Race Ledge': ['Kakariko Suburb Area'],
|
||||
'Desert Area': ['Desert Ledge', 'Desert Palace Mouth'],
|
||||
'East Dark Death Mountain (Top)': ['Dark Death Mountain Floating Island'],
|
||||
'East Dark Death Mountain (Bottom)': ['East Dark Death Mountain (Top)'],
|
||||
'Turtle Rock Area': ['Dark Death Mountain Ledge',
|
||||
'Dark Death Mountain Isolated Ledge'],
|
||||
'Dark Death Mountain Ledge': ['Turtle Rock Area'],
|
||||
'Dark Death Mountain Isolated Ledge': ['Turtle Rock Area']
|
||||
}
|
||||
sane_connectors = {
|
||||
# guaranteed dungeon access
|
||||
'Skull Woods Forest': ['Skull Woods Forest (West)'],
|
||||
'Skull Woods Forest (West)': ['Skull Woods Forest'],
|
||||
# guaranteed dropdown access
|
||||
'Graveyard Area': ['Sanctuary Area'],
|
||||
'Pyramid Area': ['Pyramid Exit Ledge']
|
||||
}
|
||||
|
||||
if not world.is_tile_swapped(0x0a, player):
|
||||
if not world.is_tile_swapped(0x03, player):
|
||||
entrance_connectors['Mountain Entry Entrance'] = ['West Death Mountain (Bottom)']
|
||||
entrance_connectors['Mountain Entry Ledge'] = ['West Death Mountain (Bottom)']
|
||||
entrance_connectors['West Death Mountain (Bottom)'] = ['Mountain Entry Ledge']
|
||||
else:
|
||||
entrance_connectors['Mountain Entry Entrance'] = ['West Dark Death Mountain (Bottom)']
|
||||
entrance_connectors['Bumper Cave Entrance'] = ['Bumper Cave Ledge']
|
||||
else:
|
||||
if not world.is_tile_swapped(0x03, player):
|
||||
entrance_connectors['Bumper Cave Entrance'] = ['West Death Mountain (Bottom)']
|
||||
entrance_connectors['Bumper Cave Ledge'] = ['West Death Mountain (Bottom)']
|
||||
entrance_connectors['West Death Mountain (Bottom)'] = ['Bumper Cave Ledge']
|
||||
else:
|
||||
entrance_connectors['Bumper Cave Entrance'] = ['West Dark Death Mountain (Bottom)']
|
||||
entrance_connectors['Mountain Entry Entrance'] = ['Mountain Entry Ledge']
|
||||
|
||||
from Main import copy_world
|
||||
from Utils import stack_size3a
|
||||
from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
|
||||
|
||||
dungeon_entrances = list(zip(*default_dungeon_connections + [('Ganons Tower', '')]))[0]
|
||||
connector_entrances = list(zip(*default_connector_connections))[0]
|
||||
item_entrances = list(zip(*default_item_connections))[0]
|
||||
shop_entrances = list(zip(*default_shop_connections))[0]
|
||||
drop_entrances = list(zip(*default_drop_connections + default_dropexit_connections))[0]
|
||||
|
||||
def explore_region(region_name, region=None):
|
||||
if stack_size3a() > 500:
|
||||
raise GenerationException(f'Infinite loop detected for "{region_name}" located at \'validate_layout\'')
|
||||
|
||||
explored_regions.append(region_name)
|
||||
if not region:
|
||||
region = base_world.get_region(region_name, player)
|
||||
for exit in region.exits:
|
||||
if exit.connected_region is not None and exit.connected_region.name not in explored_regions \
|
||||
and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
|
||||
explore_region(exit.connected_region.name, exit.connected_region)
|
||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'simple'] \
|
||||
and region_name in entrance_connectors:
|
||||
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:
|
||||
for dest_region in sane_connectors[region_name]:
|
||||
if dest_region not in explored_regions:
|
||||
explore_region(dest_region)
|
||||
|
||||
for p in range(1, world.players + 1):
|
||||
world.key_logic[p] = {}
|
||||
base_world = copy_world(world)
|
||||
world.key_logic = {}
|
||||
explored_regions = list()
|
||||
|
||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
||||
if not world.is_tile_swapped(0x2c, player):
|
||||
start_region = 'Links House Area'
|
||||
else:
|
||||
start_region = 'Bomb Shop Area'
|
||||
explore_region(start_region)
|
||||
|
||||
if not world.is_tile_swapped(0x30, player):
|
||||
start_region = 'Desert Palace Teleporter Ledge'
|
||||
else:
|
||||
start_region = 'Misery Mire Teleporter Ledge'
|
||||
explore_region(start_region)
|
||||
|
||||
if not world.is_tile_swapped(0x1b, player):
|
||||
start_region = 'Pyramid Area'
|
||||
else:
|
||||
start_region = 'Hyrule Castle Ledge'
|
||||
explore_region(start_region)
|
||||
|
||||
unreachable_regions = {}
|
||||
unreachable_count = -1
|
||||
while unreachable_count != len(unreachable_regions):
|
||||
# find unreachable regions
|
||||
unreachable_regions = {}
|
||||
flat_sectors = [[r for l in s for r in l] for s in world.owsectors[player]]
|
||||
for sector in flat_sectors:
|
||||
for region_name in sector:
|
||||
if region_name not in explored_regions and region_name not in isolated_regions:
|
||||
region = base_world.get_region(region_name, player)
|
||||
unreachable_regions[region_name] = region
|
||||
|
||||
# loop thru unreachable regions to check if some can be excluded
|
||||
unreachable_count = len(unreachable_regions)
|
||||
for region_name in reversed(unreachable_regions):
|
||||
# check if can be accessed flute
|
||||
if unreachable_regions[region_name].type == RegionType.LightWorld:
|
||||
owid = OWTileRegions[region_name]
|
||||
if owid < 0x80 and any(f[1] == owid and region_name in f[0] for f in flute_data.values()):
|
||||
if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in [0x03, 0x16, 0x18, 0x2c, 0x2f, 0x3b, 0x3f]:
|
||||
unreachable_regions.pop(region_name)
|
||||
explore_region(region_name)
|
||||
break
|
||||
# check if entrances in region could be used to access region
|
||||
if world.shuffle[player] != 'vanilla':
|
||||
for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']:
|
||||
if (entrance.name == 'Links House' and (world.mode == 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \
|
||||
or (entrance.name == 'Big Bomb Shop' and (world.mode != 'inverted' or not world.shufflelinks[player] or world.shuffle[player] in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])) \
|
||||
or (entrance.name == 'Ganons Tower' and (world.mode != 'inverted' 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 ['insanity']) \
|
||||
or entrance.name == 'Tavern North':
|
||||
continue # these are fixed entrances and cannot be used for gaining access to region
|
||||
if entrance.name not in drop_entrances \
|
||||
and ((entrance.name in dungeon_entrances and world.shuffle[player] not in ['dungeonssimple', 'simple', 'restricted']) \
|
||||
or (entrance.name in connector_entrances and world.shuffle[player] not in ['dungeonssimple', 'dungeonsfull', 'simple']) \
|
||||
or (entrance.name in item_entrances + ([] if world.shopsanity[player] else shop_entrances) and world.shuffle[player] not in ['dungeonssimple', 'dungeonsfull', 'lite', 'lean'])):
|
||||
unreachable_regions.pop(region_name)
|
||||
explore_region(region_name)
|
||||
break
|
||||
if unreachable_count != len(unreachable_regions):
|
||||
break
|
||||
|
||||
if len(unreachable_regions):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
test_connections = [
|
||||
@@ -1719,7 +1850,17 @@ default_connections = [#('Lost Woods NW', 'Master Sword Meadow SC'),
|
||||
|
||||
isolated_regions = [
|
||||
'Death Mountain Floating Island',
|
||||
'Mimic Cave Ledge'
|
||||
'Mimic Cave Ledge',
|
||||
'Mountain Entry Ledge',
|
||||
'Maze Race Prize',
|
||||
'Maze Race Ledge',
|
||||
'Desert Ledge',
|
||||
'Desert Palace Entrance (North) Spot',
|
||||
'Desert Palace Mouth',
|
||||
'Dark Death Mountain Floating Island',
|
||||
'Dark Death Mountain Ledge',
|
||||
'Dark Death Mountain Isolated Ledge',
|
||||
'Bumper Cave Ledge'
|
||||
]
|
||||
|
||||
flute_data = {
|
||||
|
||||
Reference in New Issue
Block a user