New non-recursive ER Must-Exit fill algorithm
This commit is contained in:
@@ -1478,65 +1478,151 @@ def junk_fill_inaccessible(world, player):
|
||||
|
||||
|
||||
def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, player, ignore_list=[]):
|
||||
invFlag = world.mode[player] == 'inverted'
|
||||
resolved_regions = list()
|
||||
inaccessible_regions = list()
|
||||
def find_inacessible_ow_regions():
|
||||
nonlocal inaccessible_regions
|
||||
find_inaccessible_regions(world, player)
|
||||
inaccessible_regions = list(world.inaccessible_regions[player])
|
||||
|
||||
# find OW regions that don't have a multi-entrance dungeon exit connected
|
||||
glitch_regions = ['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs', 'Hyrule Castle Water',
|
||||
'Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs', 'Pyramid Water']
|
||||
multi_dungeon_exits = {
|
||||
'Hyrule Castle South Portal', 'Hyrule Castle West Portal', 'Hyrule Castle East Portal', 'Sanctuary Portal',
|
||||
'Desert South Portal', 'Desert West Portal',
|
||||
'Skull 2 East Portal', 'Skull 2 West Portal',
|
||||
'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Eye Bridge Portal'
|
||||
}
|
||||
for region_name in world.inaccessible_regions[player]:
|
||||
if region_name in resolved_regions \
|
||||
or (region_name == 'Pyramid Exit Ledge' and (world.shuffle[player] != 'insanity' or world.is_tile_swapped(0x1b, player))) \
|
||||
or (region_name == 'Spiral Mimic Ledge Extend' and not world.is_tile_swapped(0x05, player)) \
|
||||
or (world.logic[player] in ['noglitches', 'minorglitches'] and region_name in glitch_regions):
|
||||
# removing irrelevant and resolved regions
|
||||
inaccessible_regions.remove(region_name)
|
||||
continue
|
||||
region = world.get_region(region_name, player)
|
||||
if region.type not in [RegionType.LightWorld, RegionType.DarkWorld]:
|
||||
inaccessible_regions.remove(region_name)
|
||||
continue
|
||||
if world.shuffle[player] != 'insanity':
|
||||
for exit in region.exits:
|
||||
# because dungeon regions haven't been connected yet, the inaccessibility check won't be able to know it's reachable yet
|
||||
# TODO: Should also cascade to other regions and remove them as inaccessibles
|
||||
if exit.connected_region and exit.connected_region.name in multi_dungeon_exits:
|
||||
inaccessible_regions.remove(region_name)
|
||||
break
|
||||
|
||||
if stack_size3a() > 500:
|
||||
from DungeonGenerator import GenerationException
|
||||
raise GenerationException(f'Infinite loop detected at \'connect_inaccessible_regions\'')
|
||||
find_inacessible_ow_regions()
|
||||
|
||||
random.shuffle(lw_entrances)
|
||||
random.shuffle(dw_entrances)
|
||||
|
||||
find_inaccessible_regions(world, player)
|
||||
|
||||
# remove regions that have a dungeon entrance
|
||||
accessible_regions = list()
|
||||
for region_name in world.inaccessible_regions[player]:
|
||||
# keep track of neighboring regions for later consolidation
|
||||
must_exit_links = OrderedDict()
|
||||
for region_name in inaccessible_regions:
|
||||
region = world.get_region(region_name, player)
|
||||
for exit in region.exits:
|
||||
if exit.connected_region and exit.connected_region.type == RegionType.Dungeon:
|
||||
accessible_regions.append(region_name)
|
||||
break
|
||||
for region_name in accessible_regions.copy():
|
||||
accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False))))
|
||||
world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions]
|
||||
|
||||
# split inaccessible into 2 lists for each world
|
||||
inaccessible_regions = list(world.inaccessible_regions[player])
|
||||
must_exit_links[region_name] = [x.connected_region.name for x in region.exits if x.connected_region and x.connected_region.name in inaccessible_regions]
|
||||
|
||||
# group neighboring regions together, separated by one-ways
|
||||
def consolidate_group(region):
|
||||
processed_regions.append(region)
|
||||
must_exit_links_copy.pop(region)
|
||||
region_group.append(region)
|
||||
for dest_region in must_exit_links[region]:
|
||||
if region in must_exit_links[dest_region]:
|
||||
if dest_region not in processed_regions:
|
||||
consolidate_group(dest_region)
|
||||
else:
|
||||
one_ways.append(tuple((region, dest_region)))
|
||||
|
||||
processed_regions = list()
|
||||
must_exit_candidates = list()
|
||||
one_ways = list()
|
||||
must_exit_links_copy = must_exit_links.copy()
|
||||
while len(must_exit_links_copy):
|
||||
region_group = list()
|
||||
region_name = next(iter(must_exit_links_copy))
|
||||
consolidate_group(region_name)
|
||||
must_exit_candidates.append(region_group)
|
||||
|
||||
# get available entrances in each group
|
||||
for regions in must_exit_candidates:
|
||||
entrances = list()
|
||||
for region_name in regions:
|
||||
region = world.get_region(region_name, player)
|
||||
entrances = entrances + [x.name for x in region.exits if x.spot_type == 'Entrance' and not x.connected_region]
|
||||
entrances = [e for e in entrances if e in entrance_pool and e not in ignore_list]
|
||||
must_exit_candidates[must_exit_candidates.index(regions)] = tuple((regions, entrances))
|
||||
|
||||
# necessary for circular relations between region groups, it will pick the last group
|
||||
# and fill one of those entrances, and we don't want it to bias the same group
|
||||
random.shuffle(must_exit_candidates)
|
||||
|
||||
# remove must exit candidates that would be made accessible thru other region groups
|
||||
def find_group(region):
|
||||
for group in must_exit_candidates:
|
||||
regions, _ = group
|
||||
if region in regions:
|
||||
return group
|
||||
raise Exception(f'Could not find region group for {region}')
|
||||
|
||||
def cascade_ignore(group):
|
||||
nonlocal ignored_regions
|
||||
regions, _ = group
|
||||
ignored_regions = ignored_regions + regions
|
||||
for from_region, to_region in one_ways:
|
||||
if from_region in regions and to_region not in ignored_regions:
|
||||
cascade_ignore(find_group(to_region))
|
||||
|
||||
def process_group(group):
|
||||
nonlocal processed_regions, ignored_regions
|
||||
regions, entrances = group
|
||||
must_exit_candidates_copy.remove(group)
|
||||
processed_regions = processed_regions + regions
|
||||
if regions[0] not in ignored_regions:
|
||||
for from_region, to_region in one_ways:
|
||||
if to_region in regions and from_region not in ignored_regions + processed_regions:
|
||||
process_group(find_group(from_region)) # process the parent region group
|
||||
if regions[0] not in ignored_regions:
|
||||
# this is the top level region
|
||||
if len(entrances):
|
||||
# we will fulfill must exit here and cascade access to children
|
||||
must_exit_regions.append(group)
|
||||
cascade_ignore(group)
|
||||
else:
|
||||
ignored_regions = ignored_regions + regions
|
||||
|
||||
processed_regions = list()
|
||||
ignored_regions = list()
|
||||
must_exit_regions = list()
|
||||
otherworld_must_exit_regions = list()
|
||||
for region_name in inaccessible_regions.copy():
|
||||
region = world.get_region(region_name, player)
|
||||
if region.type not in [RegionType.LightWorld, RegionType.DarkWorld] or not any((not exit.connected_region and exit.spot_type == 'Entrance') for exit in region.exits) \
|
||||
or (region_name == 'Pyramid Exit Ledge' and (world.shuffle[player] != 'insanity' or world.is_tile_swapped(0x1b, player))) \
|
||||
or region_name in ['Hyrule Castle Water', 'Pyramid Water']:
|
||||
inaccessible_regions.remove(region_name)
|
||||
elif region.type == (RegionType.LightWorld if not invFlag else RegionType.DarkWorld):
|
||||
must_exit_regions.append(region_name)
|
||||
elif region.type == (RegionType.DarkWorld if not invFlag else RegionType.LightWorld):
|
||||
otherworld_must_exit_regions.append(region_name)
|
||||
|
||||
def connect_one(region_name, pool):
|
||||
inaccessible_entrances = list()
|
||||
region = world.get_region(region_name, player)
|
||||
for exit in region.exits:
|
||||
if not exit.connected_region and exit.name in [e for e in entrance_pool if e not in ignore_list] and (world.shuffle[player] not in ['lite', 'lean'] or exit.name in pool):
|
||||
inaccessible_entrances.append(exit.name)
|
||||
if len(inaccessible_entrances):
|
||||
random.shuffle(inaccessible_entrances)
|
||||
connect_mandatory_exits(world, pool, caves, [inaccessible_entrances.pop()], player)
|
||||
connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, player, ignore_list)
|
||||
|
||||
# connect one connector at a time to ensure multiple connectors aren't assigned to the same inaccessible set of regions
|
||||
pool = [e for e in (lw_entrances if world.shuffle[player] in ['lean', 'crossed', 'insanity'] else dw_entrances) if e in entrance_pool]
|
||||
if len(otherworld_must_exit_regions) > 0 and len(pool):
|
||||
random.shuffle(otherworld_must_exit_regions)
|
||||
connect_one(otherworld_must_exit_regions[0], pool)
|
||||
elif len(must_exit_regions) > 0:
|
||||
must_exit_candidates_copy = must_exit_candidates.copy()
|
||||
while len(must_exit_candidates_copy):
|
||||
region_group = next(iter(must_exit_candidates_copy))
|
||||
process_group(region_group)
|
||||
|
||||
# connect must exits
|
||||
random.shuffle(must_exit_regions)
|
||||
must_exits_lw = list()
|
||||
must_exits_dw = list()
|
||||
for regions, entrances in must_exit_regions:
|
||||
region = world.get_region(regions[0], player)
|
||||
if region.type == RegionType.LightWorld:
|
||||
must_exits_lw.append(random.choice(entrances))
|
||||
else:
|
||||
must_exits_dw.append(random.choice(entrances))
|
||||
if world.shuffle[player] in ['lean', 'crossed', 'insanity']: # cross world
|
||||
pool = [e for e in lw_entrances + dw_entrances if e in entrance_pool]
|
||||
connect_mandatory_exits(world, pool, caves, must_exits_lw + must_exits_dw, player)
|
||||
else:
|
||||
pool = [e for e in lw_entrances if e in entrance_pool]
|
||||
if len(pool):
|
||||
random.shuffle(must_exit_regions)
|
||||
connect_one(must_exit_regions[0], pool)
|
||||
connect_mandatory_exits(world, pool, caves, must_exits_lw, player)
|
||||
pool = [e for e in dw_entrances if e in entrance_pool]
|
||||
connect_mandatory_exits(world, pool, caves, must_exits_dw, player)
|
||||
|
||||
# check accessibility afterwards
|
||||
find_inacessible_ow_regions()
|
||||
if len(inaccessible_regions) > 0:
|
||||
logging.getLogger('').debug(f'Could not resolve inaccessible regions: [{", ".join(inaccessible_regions)}]')
|
||||
logging.getLogger('').debug(f'^ This is most often a false positive because Dungeon regions aren\'t connected yet')
|
||||
|
||||
|
||||
def unbias_some_entrances(Dungeon_Exits, Cave_Exits, Old_Man_House, Cave_Three_Exits):
|
||||
|
||||
Reference in New Issue
Block a user