Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2023-08-19 19:43:44 -05:00
12 changed files with 241 additions and 72 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## 0.3.2.2
- Added Customizer support for Flute Shuffle (thanks Catobat)
- Fixed bad Old Man rescue possibility in Swapped ER
- Fixed vanilla placement issue in Swapped ER
- Removed entrance hints in Swapped ER
- Fixed various generation/validation errors
## 0.3.2.1 ## 0.3.2.1
- \~Merged in DR v1.2.0.20~ - \~Merged in DR v1.2.0.20~
- Some minor Swapped ER improvements - Some minor Swapped ER improvements

View File

@@ -808,7 +808,7 @@ def create_playthrough(world):
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement prog_locations = [location for location in world.get_filled_locations() if location.item.advancement
or world.goal[location.player] == 'completionist'] or world.goal[location.player] == 'completionist']
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile', 'Flute Activation'] optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile', 'Flute Activation']
optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Pass Tree Pull']) # adding pre-aga tree pulls optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Pass Area Tree Pull']) # adding pre-aga tree pulls
optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs optional_locations.extend(['Lumberjack Area Crab Drop', 'South Pass Area Crab Drop']) # adding pre-aga bush crabs
state_cache = [None] state_cache = [None]
collection_spheres = [] collection_spheres = []

View File

@@ -7,7 +7,7 @@ from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitType
from OverworldGlitchRules import create_owg_connections from OverworldGlitchRules import create_owg_connections
from Utils import bidict from Utils import bidict
version_number = '0.3.2.1' version_number = '0.3.2.2'
# branch indicator is intentionally different across branches # branch indicator is intentionally different across branches
version_branch = '' version_branch = ''
@@ -407,9 +407,9 @@ def link_overworld(world, player):
logging.getLogger('').debug('Shuffling flute spots') logging.getLogger('').debug('Shuffling flute spots')
def connect_flutes(flute_destinations): def connect_flutes(flute_destinations):
for o in range(0, len(flute_destinations)): for o in range(0, len(flute_destinations)):
owslot = flute_destinations[o] owid = flute_destinations[o]
regions = flute_data[owslot][0] regions = flute_data[owid][0]
if not world.is_tile_swapped(flute_data[owslot][1], player): if not world.is_tile_swapped(owid, player):
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player) connect_simple(world, 'Flute Spot ' + str(o + 1), regions[0], player)
else: else:
connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player) connect_simple(world, 'Flute Spot ' + str(o + 1), regions[1], player)
@@ -417,11 +417,15 @@ def link_overworld(world, player):
if world.owFluteShuffle[player] == 'vanilla': if world.owFluteShuffle[player] == 'vanilla':
connect_flutes(default_flute_connections) connect_flutes(default_flute_connections)
else: else:
flute_spots = 8
flute_pool = list(flute_data.keys()) flute_pool = list(flute_data.keys())
new_spots = list() new_spots = list()
ignored_regions = set() ignored_regions = set()
used_flute_regions = []
forbidden_spots = []
forbidden_regions = []
def addSpot(owid, ignore_proximity): def addSpot(owid, ignore_proximity, forced):
if world.owFluteShuffle[player] == 'balanced': if world.owFluteShuffle[player] == 'balanced':
def getIgnored(regionname, base_owid, owid): def getIgnored(regionname, base_owid, owid):
region = world.get_region(regionname, player) region = world.get_region(regionname, player)
@@ -430,23 +434,28 @@ def link_overworld(world, player):
if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid): if exit.connected_region.name in OWTileRegions and (OWTileRegions[exit.connected_region.name] in [base_owid, owid] or OWTileRegions[regionname] == base_owid):
new_ignored.add(exit.connected_region.name) new_ignored.add(exit.connected_region.name)
getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name]) getIgnored(exit.connected_region.name, base_owid, OWTileRegions[exit.connected_region.name])
if regionname in one_way_ledges:
for ledge_region in one_way_ledges[regionname]:
if ledge_region not in new_ignored:
new_ignored.add(ledge_region)
getIgnored(ledge_region, base_owid, OWTileRegions[ledge_region])
if not world.is_tile_swapped(flute_data[owid][1], player): if not world.is_tile_swapped(owid, player):
new_region = flute_data[owid][0][0] new_region = flute_data[owid][0][0]
else: else:
new_region = flute_data[owid][0][1] new_region = flute_data[owid][0][1]
if new_region in ignored_regions: if new_region in ignored_regions and not forced:
return False return False
new_ignored = {new_region} new_ignored = {new_region}
getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region]) getIgnored(new_region, OWTileRegions[new_region], OWTileRegions[new_region])
if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): if not ignore_proximity and not forced and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions):
return False return False
ignored_regions.update(new_ignored) ignored_regions.update(new_ignored)
if owid in flute_pool: if owid in flute_pool:
flute_pool.remove(owid) flute_pool.remove(owid)
if ignore_proximity: if ignore_proximity and not forced:
logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}')
logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') logging.getLogger('').debug(f'Placing flute at: {hex(owid)}')
new_spots.append(owid) new_spots.append(owid)
@@ -455,23 +464,65 @@ def link_overworld(world, player):
logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}') logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}')
return True return True
if world.customizer:
custom_spots = world.customizer.get_owflutespots()
if custom_spots and player in custom_spots:
if 'force' in custom_spots[player]:
for id in custom_spots[player]['force']:
owid = id & 0xBF
addSpot(owid, True, True)
flute_spots -= 1
if not world.is_tile_swapped(owid, player):
used_flute_regions.append(flute_data[owid][0][0])
else:
used_flute_regions.append(flute_data[owid][0][1])
if 'forbid' in custom_spots[player]:
for id in custom_spots[player]['forbid']:
owid = id & 0xBF
if owid not in new_spots:
forbidden_spots.append(owid)
if not world.is_tile_swapped(owid, player):
forbidden_regions.append(flute_data[owid][0][0])
else:
forbidden_regions.append(flute_data[owid][0][1])
# determine sectors (isolated groups of regions) to place flute spots # determine sectors (isolated groups of regions) to place flute spots
flute_regions = {(f[0][0] if (f[1] not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items()} flute_regions = {(f[0][0] if (o not in world.owswaps[player][0]) != (world.mode[player] == 'inverted') else f[0][1]) : o for o, f in flute_data.items() if o not in new_spots and o not in forbidden_spots}
flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]] flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]]
flute_sectors = [s for s in flute_sectors if len(s[1]) > 0] flute_sectors = [s for s in flute_sectors if len(s[1]) > 0]
region_total = sum([c for c,_ in flute_sectors]) region_total = sum([c for c,_ in flute_sectors])
sector_total = len(flute_sectors) sector_total = len(flute_sectors)
empty_sector_total = 0
sector_has_spot = []
# reserve a number of flute spots for each sector # determine which sectors still need a flute spot
flute_spots = 8
for sector in flute_sectors: for sector in flute_sectors:
already_has_spot = any(region in sector for region in used_flute_regions)
sector_has_spot.append(already_has_spot)
if not already_has_spot:
empty_sector_total += 1
if flute_spots < empty_sector_total:
logging.getLogger('').warning(f'Warning: Not every sector can have a flute spot, generation might fail')
# pretend like some of the empty sectors already have a flute spot, don't know if they will be reachable
for i in range(len(flute_sectors)):
if not sector_has_spot[i]:
sector_has_spot[i] = True
empty_sector_total -= 1
if flute_spots == empty_sector_total:
break
# distribute flute spots for each sector
for i in range(len(flute_sectors)):
sector = flute_sectors[i]
sector_total -= 1 sector_total -= 1
spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) if not sector_has_spot[i]:
empty_sector_total -= 1
spots_to_place = min(flute_spots - empty_sector_total, max(0 if sector_has_spot[i] else 1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5)))
target_spots = len(new_spots) + spots_to_place target_spots = len(new_spots) + spots_to_place
logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)') logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)')
if 'Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]: if 0x30 in flute_pool and 0x30 not in forbidden_spots and len(new_spots) < target_spots and ('Desert Teleporter Ledge' in sector[1] or 'Mire Teleporter Ledge' in sector[1]):
addSpot(0x38, False) # guarantee desert/mire access addSpot(0x30, True, True) # guarantee desert/mire access
random.shuffle(sector[1]) random.shuffle(sector[1])
f = 0 f = 0
@@ -482,8 +533,9 @@ def link_overworld(world, player):
t += 1 t += 1
if t > 5: if t > 5:
raise GenerationException('Infinite loop detected in flute shuffle') raise GenerationException('Infinite loop detected in flute shuffle')
if sector[1][f] not in new_spots: owid = flute_regions[sector[1][f]]
addSpot(flute_regions[sector[1][f]], t > 0) if owid not in new_spots and owid not in forbidden_spots:
addSpot(owid, t > 0, False)
f += 1 f += 1
region_total -= sector[0] region_total -= sector[0]
@@ -495,7 +547,6 @@ def link_overworld(world, player):
connect_flutes(new_spots) connect_flutes(new_spots)
# update spoiler # update spoiler
new_spots = list(map(lambda o: flute_data[o][1], new_spots))
s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)])) s = list(map(lambda x: ' ' if x not in new_spots else 'F', [i for i in range(0x40)]))
text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07], text_output = flute_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05], s[0x00], s[0x03], s[0x05],
@@ -639,33 +690,33 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
if world.customizer: if world.customizer:
if not do_grouped: if not do_grouped:
custom_flips = world.customizer.get_owtileflips() custom_flips = world.customizer.get_owtileflips()
if custom_flips and player in custom_flips: if custom_flips and player in custom_flips:
custom_flips = custom_flips[player] custom_flips = custom_flips[player]
nonflipped_groups = list() nonflipped_groups = list()
forced_flips = list() forced_flips = list()
forced_nonflips = list() forced_nonflips = list()
if 'undefined_chance' in custom_flips: if 'undefined_chance' in custom_flips:
undefined_chance = custom_flips['undefined_chance'] undefined_chance = custom_flips['undefined_chance']
if 'force_flip' in custom_flips: if 'force_flip' in custom_flips:
forced_flips = custom_flips['force_flip'] forced_flips = custom_flips['force_flip']
if 'force_no_flip' in custom_flips: if 'force_no_flip' in custom_flips:
forced_nonflips = custom_flips['force_no_flip'] forced_nonflips = custom_flips['force_no_flip']
for group in groups: for group in groups:
if any(owid in group[0] for owid in forced_nonflips): if any(owid in group[0] for owid in forced_nonflips):
nonflipped_groups.append(group) nonflipped_groups.append(group)
if any(owid in group[0] for owid in forced_flips): if any(owid in group[0] for owid in forced_flips):
flipped_groups.append(group) flipped_groups.append(group)
# Check if there are any groups that appear in both sets # Check if there are any groups that appear in both sets
if any(group in flipped_groups for group in nonflipped_groups): if any(group in flipped_groups for group in nonflipped_groups):
raise GenerationException('Conflict found when flipping tiles') raise GenerationException('Conflict found when flipping tiles')
for g in nonflipped_groups: for g in nonflipped_groups:
always_removed.append(g)
if undefined_chance == 0:
for g in [g for g in groups if g not in flipped_groups + always_removed]:
always_removed.append(g) always_removed.append(g)
if undefined_chance == 0:
for g in [g for g in groups if g not in flipped_groups + always_removed]:
always_removed.append(g)
attempts = 1 attempts = 1
if 0 < undefined_chance < 100: if 0 < undefined_chance < 100:
@@ -706,8 +757,8 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
continue continue
# ensure sanc can be placed in LW in certain modes # 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] 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'):
free_dw_drops = parity[5] + (1 if world.shuffle_ganon else 0) 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 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: if free_dw_drops == free_drops:
attempts -= 1 attempts -= 1
continue continue
@@ -1191,7 +1242,7 @@ def validate_layout(world, player):
start_region = 'Big Bomb Shop Area' start_region = 'Big Bomb Shop Area'
explore_region(start_region) explore_region(start_region)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean'] and world.mode == 'inverted': if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean'] and world.mode[player] == 'inverted':
start_region = 'Dark Chapel Area' start_region = 'Dark Chapel Area'
explore_region(start_region) explore_region(start_region)
@@ -1221,19 +1272,19 @@ def validate_layout(world, player):
unreachable_count = len(unreachable_regions) unreachable_count = len(unreachable_regions)
for region_name in reversed(unreachable_regions): for region_name in reversed(unreachable_regions):
# check if can be accessed flute # check if can be accessed flute
if unreachable_regions[region_name].type == RegionType.LightWorld: if unreachable_regions[region_name].type == (RegionType.LightWorld if world.mode[player] != 'inverted' else RegionType.DarkWorld):
owid = OWTileRegions[region_name] 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 owid < 0x80 and owid % 40 in flute_data and region_name in flute_data[owid][0]:
if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in [0x03, 0x16, 0x18, 0x2c, 0x2f, 0x3b, 0x3f]: if world.owFluteShuffle[player] != 'vanilla' or owid % 0x40 in default_flute_connections:
unreachable_regions.pop(region_name) unreachable_regions.pop(region_name)
explore_region(region_name) explore_region(region_name)
break break
# check if entrances in region could be used to access region # check if entrances in region could be used to access region
if world.shuffle[player] != 'vanilla': if world.shuffle[player] != 'vanilla':
for entrance in [e for e in unreachable_regions[region_name].exits if e.spot_type == 'Entrance']: 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'])) \ if (entrance.name == 'Links House' and (world.mode[player] == '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 == 'Big Bomb Shop' and (world.mode[player] != '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 == 'Ganons Tower' and (world.mode[player] != '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 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': or entrance.name == 'Tavern North':
continue # these are fixed entrances and cannot be used for gaining access to region continue # these are fixed entrances and cannot be used for gaining access to region
@@ -1467,7 +1518,7 @@ default_whirlpool_connections = [
] ]
default_flute_connections = [ default_flute_connections = [
0x0b, 0x16, 0x18, 0x2c, 0x2f, 0x38, 0x3b, 0x3f 0x03, 0x16, 0x18, 0x2c, 0x2f, 0x30, 0x3b, 0x3f
] ]
ow_connections = { ow_connections = {
@@ -2131,10 +2182,10 @@ isolated_regions = [
flute_data = { flute_data = {
#Slot LW Region DW Region OWID VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX #Slot LW Region DW Region OWID VRAM BG Y BG X Link Y Link X Cam Y Cam X Unk1 Unk2 IconY IconX AltY AltX AltVRAM AltBGY AltBGX AltCamY AltCamX AltUnk1 AltUnk2 AltIconY AltIconX
0x09: (['Lost Woods East Area', 'Skull Woods Forest'], 0x00, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290), 0x00: (['Lost Woods East Area', 'Skull Woods Forest'], 0x09, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),
0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550), 0x02: (['Lumberjack Area', 'Dark Lumberjack Area'], 0x02, 0x059c, 0x00d6, 0x04e6, 0x0138, 0x0558, 0x0143, 0x0563, 0xfffa, 0xfffa, 0x0138, 0x0550),
0x0b: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x03, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850), 0x03: (['West Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'], 0x0b, 0x1600, 0x02ca, 0x060e, 0x0328, 0x0678, 0x0337, 0x0683, 0xfff6, 0xfff2, 0x035b, 0x0680, 0x0118, 0x0860, 0x05c0, 0x00b8, 0x07ec, 0x0127, 0x086b, 0xfff8, 0x0004, 0x0148, 0x0850),
0x0e: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x05, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8), 0x05: (['East Death Mountain (Bottom)', 'East Dark Death Mountain (Bottom)'], 0x0e, 0x1860, 0x031e, 0x0d00, 0x0388, 0x0da8, 0x038d, 0x0d7d, 0x0000, 0x0000, 0x0388, 0x0da8),
0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20), 0x07: (['Death Mountain TR Pegs Area', 'Turtle Rock Area'], 0x07, 0x0804, 0x0102, 0x0e1a, 0x0160, 0x0e90, 0x016f, 0x0e97, 0xfffe, 0x0006, 0x0160, 0x0f20),
0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488), 0x0a: (['Mountain Pass Area', 'Bumper Cave Area'], 0x0a, 0x0180, 0x0220, 0x0406, 0x0280, 0x0488, 0x028f, 0x0493, 0x0000, 0xfffa, 0x0280, 0x0488),
0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x02d0, 0x0f38), 0x0f: (['Zora Waterfall Area', 'Catfish Area'], 0x0f, 0x0316, 0x025c, 0x0eb2, 0x02c0, 0x0f28, 0x02cb, 0x0f2f, 0x0002, 0xfffe, 0x02d0, 0x0f38),
@@ -2150,7 +2201,7 @@ flute_data = {
0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0548), 0x1a: (['Forgotten Forest Area', 'Shield Shop Fence'], 0x1a, 0x081a, 0x070f, 0x04d2, 0x0770, 0x0548, 0x077c, 0x054f, 0xffff, 0xfffe, 0x0770, 0x0548),
0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07d8, 0x07f8), 0x1b: (['Hyrule Castle Courtyard', 'Pyramid Area'], 0x1b, 0x0c30, 0x077a, 0x0786, 0x07d8, 0x07f8, 0x07e7, 0x0803, 0x0006, 0xfffa, 0x07d8, 0x07f8),
0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0720, 0x0a80), 0x1d: (['Wooden Bridge Area', 'Broken Bridge Northeast'], 0x1d, 0x0602, 0x06c2, 0x0a0e, 0x0720, 0x0a80, 0x072f, 0x0a8b, 0xfffe, 0x0002, 0x0720, 0x0a80),
0x26: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x1e, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80), 0x1e: (['Eastern Palace Area', 'Palace of Darkness Area'], 0x26, 0x1802, 0x091e, 0x0c0e, 0x09c0, 0x0c80, 0x098b, 0x0c8b, 0x0000, 0x0002, 0x09c0, 0x0c80),
0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0908, 0x04d8), 0x22: (['Blacksmith Area', 'Hammer Pegs Area'], 0x22, 0x058c, 0x08aa, 0x0462, 0x0908, 0x04d8, 0x0917, 0x04df, 0x0006, 0xfffe, 0x0908, 0x04d8),
0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x08b8, 0x0b08), 0x25: (['Sand Dunes Area', 'Dark Dunes Area'], 0x25, 0x030e, 0x085a, 0x0a76, 0x08b8, 0x0ae8, 0x08c7, 0x0af3, 0x0006, 0xfffa, 0x08b8, 0x0b08),
0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0b88, 0x00b8), 0x28: (['Maze Race Area', 'Dig Game Area'], 0x28, 0x0908, 0x0b1e, 0x003a, 0x0b88, 0x00b8, 0x0b8d, 0x00bf, 0x0000, 0x0006, 0x0b88, 0x00b8),
@@ -2161,12 +2212,12 @@ flute_data = {
0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0), 0x2d: (['Stone Bridge South Area', 'Hammer Bridge South Area'], 0x2d, 0x0886, 0x0b1e, 0x0a2a, 0x0ba0, 0x0aa8, 0x0b8b, 0x0aaf, 0x0000, 0x0006, 0x0bc4, 0x0ad0),
0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58), 0x2e: (['Tree Line Area', 'Dark Tree Line Area'], 0x2e, 0x0100, 0x0a1a, 0x0c00, 0x0a78, 0x0c30, 0x0a87, 0x0c7d, 0x0006, 0x0000, 0x0a78, 0x0c58),
0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30), 0x2f: (['Eastern Nook Area', 'Darkness Nook Area'], 0x2f, 0x0798, 0x0afa, 0x0eb2, 0x0b58, 0x0f30, 0x0b67, 0x0f37, 0xfff6, 0x000e, 0x0b50, 0x0f30),
0x38: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x30, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070), 0x30: (['Desert Teleporter Ledge', 'Mire Teleporter Ledge'], 0x38, 0x1880, 0x0f1e, 0x0000, 0x0fa8, 0x0078, 0x0f8d, 0x008d, 0x0000, 0x0000, 0x0fb0, 0x0070),
0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0cd6, 0x0568), 0x32: (['Flute Boy Approach Area', 'Stumpy Approach Area'], 0x32, 0x03a0, 0x0c6c, 0x0500, 0x0cd0, 0x05a8, 0x0cdb, 0x0585, 0x0002, 0x0000, 0x0cd6, 0x0568),
0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0c80, 0x0628), 0x33: (['C Whirlpool Outer Area', 'Dark C Whirlpool Outer Area'], 0x33, 0x0180, 0x0c20, 0x0600, 0x0c80, 0x0628, 0x0c8f, 0x067d, 0x0000, 0x0000, 0x0c80, 0x0628),
0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0d60, 0x08d8), 0x34: (['Statues Area', 'Hype Cave Area'], 0x34, 0x088e, 0x0d00, 0x0866, 0x0d60, 0x08d8, 0x0d6f, 0x08e3, 0x0000, 0x000a, 0x0d60, 0x08d8),
#0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88), #0x35: (['Lake Hylia Northwest Bank', 'Ice Lake Northwest Bank'], 0x35, 0x0d00, 0x0da6, 0x0a06, 0x0e08, 0x0a80, 0x0e13, 0x0a8b, 0xfffa, 0xfffa, 0x0d88, 0x0a88),
0x3e: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x35, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0f90, 0x0da4), 0x35: (['Lake Hylia South Shore', 'Ice Lake Southeast Ledge'], 0x3e, 0x1860, 0x0f1e, 0x0d00, 0x0f98, 0x0da8, 0x0f8b, 0x0d85, 0x0000, 0x0000, 0x0f90, 0x0da4),
0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d48, 0x0ed0), 0x37: (['Ice Cave Area', 'Shopping Mall Area'], 0x37, 0x0786, 0x0cf6, 0x0e2e, 0x0d58, 0x0ea0, 0x0d63, 0x0eab, 0x000a, 0x0002, 0x0d48, 0x0ed0),
0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0e70, 0x0540), 0x3a: (['Desert Pass Area', 'Swamp Nook Area'], 0x3a, 0x001a, 0x0e08, 0x04c6, 0x0e70, 0x0540, 0x0e7d, 0x054b, 0x0006, 0x000a, 0x0e70, 0x0540),
0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0f30, 0x0770), 0x3b: (['Dam Area', 'Swamp Area'], 0x3b, 0x069e, 0x0edf, 0x06f2, 0x0f3d, 0x0778, 0x0f4c, 0x077f, 0xfff1, 0xfffe, 0x0f30, 0x0770),

17
Rom.py
View File

@@ -688,15 +688,16 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
flute_spots = world.owflutespots[player] flute_spots = world.owflutespots[player]
owFlags |= 0x0100 owFlags |= 0x0100
for o in range(0, len(flute_spots)): flute_writes = sorted([(f, flute_data[f][1]) for f in flute_spots], key = lambda f: f[1])
owslot = flute_spots[o] for o in range(0, len(flute_writes)):
owid = flute_writes[o][0]
offset = 0 offset = 0
data = flute_data[owslot] data = flute_data[owid]
if world.is_tile_swapped(data[1], player): if world.is_tile_swapped(data[1], player):
offset = 0x40 offset = 0x40
write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), data[1] + offset) # owid write_int16(rom, snes_to_pc(0x02E849 + (o * 2)), owid + offset) # owid
write_int16(rom, snes_to_pc(0x02E8D1 + (o * 2)), data[13] if offset > 0 and len(data) > 13 else data[5]) # link Y write_int16(rom, snes_to_pc(0x02E8D1 + (o * 2)), data[13] if offset > 0 and len(data) > 13 else data[5]) # link Y
write_int16(rom, snes_to_pc(0x02E8F3 + (o * 2)), data[14] if offset > 0 and len(data) > 13 else data[6]) # link X write_int16(rom, snes_to_pc(0x02E8F3 + (o * 2)), data[14] if offset > 0 and len(data) > 13 else data[6]) # link X
@@ -2185,7 +2186,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. # 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']: if world.shuffle[player] not in ['lite', 'lean']:
entrances_to_hint.update(InconvenientOtherEntrances) entrances_to_hint.update(InconvenientOtherEntrances)
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean']: if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lite', 'lean', 'swapped']:
hint_count = 0 hint_count = 0
elif world.shuffle[player] in ['simple', 'restricted']: elif world.shuffle[player] in ['simple', 'restricted']:
hint_count = 2 hint_count = 2
@@ -2236,7 +2237,7 @@ def write_strings(rom, world, player, team):
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'}) entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else: else:
entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'}) entrances_to_hint.update({'Pyramid Entrance': 'The pyramid ledge'})
hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 0 hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 0
hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0 hint_count -= 2 if world.shuffle[player] not in ['simple', 'restricted'] else 0
for entrance in all_entrances: for entrance in all_entrances:
if entrance.name in entrances_to_hint: if entrance.name in entrances_to_hint:
@@ -2255,7 +2256,7 @@ def write_strings(rom, world, player, team):
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']: if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
locations_to_hint.extend(InconvenientVanillaLocations) locations_to_hint.extend(InconvenientVanillaLocations)
random.shuffle(locations_to_hint) random.shuffle(locations_to_hint)
hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 5 hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 5
hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 hint_count -= 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
del locations_to_hint[hint_count:] del locations_to_hint[hint_count:]
for location in locations_to_hint: for location in locations_to_hint:
@@ -2330,7 +2331,7 @@ def write_strings(rom, world, player, team):
if world.bigkeyshuffle[player]: if world.bigkeyshuffle[player]:
items_to_hint.extend(BigKeys) items_to_hint.extend(BigKeys)
random.shuffle(items_to_hint) random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'swapped'] else 8
hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0 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 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: while hint_count > 0 and len(items_to_hint) > 0:

View File

@@ -89,7 +89,7 @@ You may define an item and a list of locations that an item should not be placed
### ow-tileflips ### ow-tileflips
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined`. This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have `ow_mixed: true` in the `settings` section in order for any values here to take effect. This section has three primary subsections: `force_flip`, `force_no_flip`, and `undefined_chance`.
#### force_flip / force_no_flip #### force_flip / force_no_flip
@@ -110,6 +110,14 @@ force_no_flip:
`undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped. `undefined_chance` should be used to determine how to handle all the remaining tiles that aren't explicitly defined in the earlier step. This represents the percent chance a tile will flip. This value can be set from 0 to 100 (default is 50). A value of 0 means there is a 0% chance it will be flipped.
### ow-flutespots
This must be defined by player. Each player number should be listed with the appropriate sections and each of these players MUST have some form of Flute Shuffle in order for any values here to take effect. This section has two subsections: `force` and `forbid`. Both are lists of OW Screen IDs, please refer to ow-tileflips above for more information.
Everything listed in `force` means that this screen must contain a flute spot.
Everything listed in `forbid` means that this screen must not contain a flute spot.
### entrances ### entrances
This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `entrances`, `exits`, and `two-way`. This must be defined by player. Each player number should be listed with the appropriate sections. This section has three primary subsections: `entrances`, `exits`, and `two-way`.

View File

@@ -77,6 +77,19 @@ ow-tileflips:
- 0x2c - 0x2c
- 0x18 - 0x18
undefined_chance: 50 undefined_chance: 50
ow-flutespots:
1:
force:
- 0x00
- 0x12
- 0x18
- 0x1a
- 0x2f
- 0x30
- 0x35
forbid:
- 0x03
- 0x05
entrances: entrances:
1: 1:
entrances: entrances:

View File

@@ -0,0 +1,22 @@
meta:
branch: OWR
seed_name: Swapkeys
seed_notes: Crosskeys but Swapped ER
settings:
1:
mode: open
logic: noglitches
goal: crystals
crystals_gt: "7"
crystals_ganon: "7"
accessibility: locations
mapshuffle: 1
compassshuffle: 1
keyshuffle: wild
bigkeyshuffle: 1
shuffle: swapped
shuffleganon: 1
shufflelinks: 0
shuffletavern: 1
experimental: 0
hints: 0

17
docs/presets/swapkeys.yml Normal file
View File

@@ -0,0 +1,17 @@
settings:
1:
description: Swapkeys
glitches_required: none
mode: open
goal: crystals
crystals_gt: "7"
crystals_ganon: "7"
weapons: randomized
accessibility: locations
entrance_shuffle: swapped
shufflelinks: off
shuffletavern: on
mapshuffle: on
compassshuffle: on
keyshuffle: wild
bigkeyshuffle: on

View File

@@ -0,0 +1,28 @@
doors:
1:
lobbies:
#Agahnims Tower: Tower Lobby S
Desert Back: Desert Back Lobby S
Desert East: Desert East Lobby S
Desert South: Desert Main Lobby S
Desert West: Desert West S
#Eastern: Eastern Lobby S
#Ganons Tower: GT Lobby S
#Hera: Hera Lobby S
Hyrule Castle East: Hyrule Castle East Lobby S
Hyrule Castle South: Hyrule Castle Lobby S
Hyrule Castle West: Hyrule Castle West Lobby S
#Ice: Ice Lobby SE
#Mire: Mire Lobby S
#Palace of Darkness: PoD Lobby S
#Sanctuary: Sanctuary S
#Skull 1: Skull 1 Lobby S
#Skull 2 East: Skull 2 East Lobby SW
#Skull 2 West: Skull 2 West Lobby S
#Skull 3: Skull 3 Lobby SW
#Swamp: Swamp Lobby S
#Thieves Town: Thieves Lobby S
Turtle Rock Chest: TR Big Chest Entrance SE
Turtle Rock Eye Bridge: TR Eye Bridge SW
Turtle Rock Lazy Eyes: TR Lazy Eyes SE
Turtle Rock Main: TR Main Lobby SE

View File

@@ -10,6 +10,7 @@ from pathlib import Path
import RaceRandom as random import RaceRandom as random
from BaseClasses import LocationType, DoorType from BaseClasses import LocationType, DoorType
from OverworldShuffle import default_flute_connections, flute_data
from source.tools.MysteryUtils import roll_settings, get_weights from source.tools.MysteryUtils import roll_settings, get_weights
@@ -200,6 +201,11 @@ class CustomSettings(object):
return self.file_source['ow-tileflips'] return self.file_source['ow-tileflips']
return None return None
def get_owflutespots(self):
if 'ow-flutespots' in self.file_source:
return self.file_source['ow-flutespots']
return None
def get_entrances(self): def get_entrances(self):
if 'entrances' in self.file_source: if 'entrances' in self.file_source:
return self.file_source['entrances'] return self.file_source['entrances']
@@ -356,6 +362,14 @@ class CustomSettings(object):
flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80) flips[p]['force_flip'] = list(HexInt(f) for f in world.owswaps[p][0] if f < 0x40 or f >= 0x80)
flips[p]['force_flip'].sort() flips[p]['force_flip'].sort()
flips[p]['undefined_chance'] = 0 flips[p]['undefined_chance'] = 0
self.world_rep['ow-flutespots'] = flute = {}
for p in self.player_range:
flute[p] = {}
if p in world.owflutespots:
flute[p]['force'] = list(HexInt(id) for id in sorted(world.owflutespots[p]))
else:
flute[p]['force'] = list(HexInt(id) for id in sorted(default_flute_connections))
flute[p]['forbid'] = []
def record_entrances(self, world): def record_entrances(self, world):
self.world_rep['entrances'] = entrances = {} self.world_rep['entrances'] = entrances = {}

View File

@@ -357,7 +357,7 @@ def determine_major_items(world, player):
major_item_set.add('Single Arrow') major_item_set.add('Single Arrow')
if world.keyshuffle[player] == 'universal': if world.keyshuffle[player] == 'universal':
major_item_set.add('Small Key (Universal)') major_item_set.add('Small Key (Universal)')
if world.goal in ['triforcehunt', 'trinity']: if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
major_item_set.add('Triforce Piece') major_item_set.add('Triforce Piece')
if world.bombbag[player]: if world.bombbag[player]:
major_item_set.add('Bomb Upgrade (+10)') major_item_set.add('Bomb Upgrade (+10)')

View File

@@ -265,9 +265,14 @@ def do_main_shuffle(entrances, exits, avail, mode_def):
else: else:
# cross world mandantory # cross world mandantory
entrance_list = list(entrances) entrance_list = list(entrances)
if avail.swapped:
forbidden = [e for e in Forbidden_Swap_Entrances if e in entrance_list]
entrance_list = [e for e in entrance_list if e not in forbidden]
must_exit, multi_exit_caves = figure_out_must_exits_cross_world(entrances, exits, avail) must_exit, multi_exit_caves = figure_out_must_exits_cross_world(entrances, exits, avail)
do_mandatory_connections(avail, entrance_list, multi_exit_caves, must_exit) do_mandatory_connections(avail, entrance_list, multi_exit_caves, must_exit)
rem_entrances.update(entrance_list) rem_entrances.update(entrance_list)
if avail.swapped:
rem_entrances.update(forbidden)
rem_exits.update([x for item in multi_exit_caves for x in item if x in avail.exits]) rem_exits.update([x for item in multi_exit_caves for x in item if x in avail.exits])
rem_exits.update(exits) rem_exits.update(exits)
@@ -1374,7 +1379,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
entrances.remove(swap_ent) entrances.remove(swap_ent)
if len(cave) == 2: if len(cave) == 2:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit]
and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit) and e not in invalid_cave_connections[tuple(cave)] and e not in must_exit
and (not avail.swapped or rnd_cave[0] != combine_map[e]))
entrances.remove(entrance) entrances.remove(entrance)
connect_two_way(entrance, rnd_cave[0], avail) connect_two_way(entrance, rnd_cave[0], avail)
if avail.swapped and combine_map[entrance] != rnd_cave[0]: if avail.swapped and combine_map[entrance] != rnd_cave[0]:
@@ -1393,7 +1399,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name entrance = avail.world.get_entrance(cave_exit, avail.player).parent_region.entrances[0].name
cave_entrances.append(entrance) cave_entrances.append(entrance)
else: else:
entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit) entrance = next(e for e in entrances[::-1] if e not in invalid_connections[exit] and e not in must_exit
and (not avail.swapped or cave_exit != combine_map[e]))
cave_entrances.append(entrance) cave_entrances.append(entrance)
entrances.remove(entrance) entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail) connect_two_way(entrance, cave_exit, avail)
@@ -1423,7 +1430,8 @@ def do_mandatory_connections(avail, entrances, cave_options, must_exit):
if avail.swapped and cave_exit not in avail.exits: if avail.swapped and cave_exit not in avail.exits:
continue continue
else: else:
entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]) entrance = next(e for e in entrances[::-1] if e not in invalid_cave_connections[tuple(cave)]
and (not avail.swapped or cave_exit != combine_map[e]))
invalid_cave_connections[tuple(cave)] = set() invalid_cave_connections[tuple(cave)] = set()
entrances.remove(entrance) entrances.remove(entrance)
connect_two_way(entrance, cave_exit, avail) connect_two_way(entrance, cave_exit, avail)