Fix issues with dynamic flute and mirror exits
This commit is contained in:
13
Main.py
13
Main.py
@@ -17,7 +17,7 @@ from OverworldGlitchRules import create_owg_connections
|
|||||||
from PotShuffle import shuffle_pots, shuffle_pot_switches
|
from PotShuffle import shuffle_pots, shuffle_pot_switches
|
||||||
from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations
|
from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations
|
||||||
from OWEdges import create_owedges
|
from OWEdges import create_owedges
|
||||||
from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_exits
|
from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_flute_exits, create_dynamic_mirror_exits
|
||||||
from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string
|
||||||
from Doors import create_doors
|
from Doors import create_doors
|
||||||
from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
||||||
@@ -172,7 +172,6 @@ def main(args, seed=None, fish=None):
|
|||||||
create_shops(world, player)
|
create_shops(world, player)
|
||||||
update_world_regions(world, player)
|
update_world_regions(world, player)
|
||||||
mark_light_dark_world_regions(world, player)
|
mark_light_dark_world_regions(world, player)
|
||||||
create_dynamic_exits(world, player)
|
|
||||||
|
|
||||||
init_districts(world)
|
init_districts(world)
|
||||||
|
|
||||||
@@ -809,13 +808,13 @@ def copy_world(world):
|
|||||||
update_world_regions(ret, player)
|
update_world_regions(ret, player)
|
||||||
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
||||||
create_owg_connections(ret, player)
|
create_owg_connections(ret, player)
|
||||||
create_dynamic_exits(ret, player)
|
|
||||||
create_dungeon_regions(ret, player)
|
create_dungeon_regions(ret, player)
|
||||||
create_owedges(ret, player)
|
create_owedges(ret, player)
|
||||||
create_shops(ret, player)
|
create_shops(ret, player)
|
||||||
#create_doors(ret, player)
|
|
||||||
create_rooms(ret, player)
|
create_rooms(ret, player)
|
||||||
create_dungeons(ret, player)
|
create_dungeons(ret, player)
|
||||||
|
create_dynamic_mirror_exits(ret, player)
|
||||||
|
create_dynamic_flute_exits(ret, player)
|
||||||
|
|
||||||
# there are region references here they must be migrated to preserve integrity
|
# there are region references here they must be migrated to preserve integrity
|
||||||
# ret.exp_cache = world.exp_cache.copy()
|
# ret.exp_cache = world.exp_cache.copy()
|
||||||
@@ -940,7 +939,7 @@ def copy_world(world):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def copy_world_premature(world, player):
|
def copy_world_premature(world, player, create_flute_exits=True):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
||||||
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
||||||
@@ -1032,13 +1031,15 @@ def copy_world_premature(world, player):
|
|||||||
update_world_regions(ret, player)
|
update_world_regions(ret, player)
|
||||||
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
||||||
create_owg_connections(ret, player)
|
create_owg_connections(ret, player)
|
||||||
create_dynamic_exits(ret, player)
|
|
||||||
create_dungeon_regions(ret, player)
|
create_dungeon_regions(ret, player)
|
||||||
create_owedges(ret, player)
|
create_owedges(ret, player)
|
||||||
create_shops(ret, player)
|
create_shops(ret, player)
|
||||||
create_doors(ret, player)
|
create_doors(ret, player)
|
||||||
create_rooms(ret, player)
|
create_rooms(ret, player)
|
||||||
create_dungeons(ret, player)
|
create_dungeons(ret, player)
|
||||||
|
create_dynamic_mirror_exits(ret, player) # assumes these have already been added to world
|
||||||
|
if create_flute_exits:
|
||||||
|
create_dynamic_flute_exits(ret, player)
|
||||||
|
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
parent = ret.get_region('Menu', player)
|
parent = ret.get_region('Menu', player)
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ def link_overworld(world, player):
|
|||||||
connect_simple(world, exitname, regionname, player)
|
connect_simple(world, exitname, regionname, player)
|
||||||
|
|
||||||
categorize_world_regions(world, player)
|
categorize_world_regions(world, player)
|
||||||
|
create_dynamic_mirror_exits(world, player)
|
||||||
|
|
||||||
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
if world.logic[player] in ('owglitches', 'hybridglitches', 'nologic'):
|
||||||
create_owg_connections(world, player)
|
create_owg_connections(world, player)
|
||||||
@@ -424,8 +425,6 @@ def link_overworld(world, player):
|
|||||||
assert len(forward_set) == len(back_set)
|
assert len(forward_set) == len(back_set)
|
||||||
for (forward_edge, back_edge) in zip(forward_set, back_set):
|
for (forward_edge, back_edge) in zip(forward_set, back_set):
|
||||||
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
|
||||||
|
|
||||||
world.owsectors[player] = build_sectors(world, player)
|
|
||||||
else:
|
else:
|
||||||
if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel':
|
if world.owKeepSimilar[player] and world.owShuffle[player] == 'parallel':
|
||||||
for exitname, destname in parallelsimilar_connections:
|
for exitname, destname in parallelsimilar_connections:
|
||||||
@@ -557,13 +556,14 @@ def link_overworld(world, player):
|
|||||||
connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges)
|
connect_set(forward_edge_sets[0], back_edge_sets[0], connected_edges)
|
||||||
remove_connected(forward_edge_sets, back_edge_sets)
|
remove_connected(forward_edge_sets, back_edge_sets)
|
||||||
assert len(connected_edges) == len(default_connections) * 2, connected_edges
|
assert len(connected_edges) == len(default_connections) * 2, connected_edges
|
||||||
|
|
||||||
world.owsectors[player] = build_sectors(world, player)
|
|
||||||
valid_layout = validate_layout(world, player)
|
valid_layout = validate_layout(world, player)
|
||||||
|
|
||||||
tries -= 1
|
tries -= 1
|
||||||
assert valid_layout, 'Could not find a valid OW layout'
|
assert valid_layout, 'Could not find a valid OW layout'
|
||||||
|
|
||||||
|
world.owsectors[player] = build_sectors(world, player)
|
||||||
|
|
||||||
# flute shuffle
|
# flute shuffle
|
||||||
logging.getLogger('').debug('Shuffling flute spots')
|
logging.getLogger('').debug('Shuffling flute spots')
|
||||||
def connect_flutes(flute_destinations):
|
def connect_flutes(flute_destinations):
|
||||||
@@ -725,6 +725,8 @@ def link_overworld(world, player):
|
|||||||
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
|
||||||
world.spoiler.set_map('flute', text_output, new_spots, player)
|
world.spoiler.set_map('flute', text_output, new_spots, player)
|
||||||
|
|
||||||
|
create_dynamic_flute_exits(world, player)
|
||||||
|
|
||||||
def connect_custom(world, connected_edges, groups, forced, player):
|
def connect_custom(world, connected_edges, groups, forced, player):
|
||||||
forced_crossed, forced_noncrossed = forced
|
forced_crossed, forced_noncrossed = forced
|
||||||
def remove_pair_from_pool(edgename1, edgename2, is_crossed):
|
def remove_pair_from_pool(edgename1, edgename2, is_crossed):
|
||||||
@@ -1292,7 +1294,7 @@ def adjust_edge_groups(world, trimmed_groups, edges_to_swap, player):
|
|||||||
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
groups[(mode, wrld, dir, terrain, parallel, count, group_name)][i].extend(matches)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
def create_flute_exits(world, player):
|
def create_dynamic_flute_exits(world, player):
|
||||||
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
||||||
if not flute_in_pool:
|
if not flute_in_pool:
|
||||||
return
|
return
|
||||||
@@ -1303,6 +1305,7 @@ def create_flute_exits(world, player):
|
|||||||
exit.spot_type = 'Flute'
|
exit.spot_type = 'Flute'
|
||||||
exit.connect(world.get_region('Flute Sky', player))
|
exit.connect(world.get_region('Flute Sky', player))
|
||||||
region.exits.append(exit)
|
region.exits.append(exit)
|
||||||
|
world.initialize_regions()
|
||||||
|
|
||||||
def get_mirror_exit_name(from_region, to_region):
|
def get_mirror_exit_name(from_region, to_region):
|
||||||
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
if from_region in mirror_connections and to_region in mirror_connections[from_region]:
|
||||||
@@ -1329,7 +1332,7 @@ def get_mirror_edges(world, region, player):
|
|||||||
mirror_exits.append(tuple([get_mirror_exit_name(other_world_region_name, region.name), region.name]))
|
mirror_exits.append(tuple([get_mirror_exit_name(other_world_region_name, region.name), region.name]))
|
||||||
return mirror_exits
|
return mirror_exits
|
||||||
|
|
||||||
def create_mirror_exits(world, player):
|
def create_dynamic_mirror_exits(world, player):
|
||||||
mirror_exits = set()
|
mirror_exits = set()
|
||||||
for region in (r for r in world.regions if r.player == player and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
for region in (r for r in world.regions if r.player == player and r.name not in ['Zoras Domain', 'Master Sword Meadow', 'Hobo Bridge']):
|
||||||
if region.type == (RegionType.DarkWorld if world.mode[player] != 'inverted' else RegionType.LightWorld):
|
if region.type == (RegionType.DarkWorld if world.mode[player] != 'inverted' else RegionType.LightWorld):
|
||||||
@@ -1350,12 +1353,6 @@ def create_mirror_exits(world, player):
|
|||||||
region.exits.append(exit)
|
region.exits.append(exit)
|
||||||
|
|
||||||
mirror_exits.add(exitname)
|
mirror_exits.add(exitname)
|
||||||
elif region.terrain == Terrain.Land:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def create_dynamic_exits(world, player):
|
|
||||||
create_flute_exits(world, player)
|
|
||||||
create_mirror_exits(world, player)
|
|
||||||
world.initialize_regions()
|
world.initialize_regions()
|
||||||
|
|
||||||
def categorize_world_regions(world, player):
|
def categorize_world_regions(world, player):
|
||||||
@@ -1433,7 +1430,7 @@ def build_sectors(world, player):
|
|||||||
# perform accessibility check on duplicate world
|
# perform accessibility check on duplicate world
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world_premature(world, player)
|
base_world = copy_world_premature(world, player, create_flute_exits=False)
|
||||||
|
|
||||||
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
||||||
regions = list(OWTileRegions.copy().keys())
|
regions = list(OWTileRegions.copy().keys())
|
||||||
@@ -1510,7 +1507,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
|
|||||||
if build_copy_world:
|
if build_copy_world:
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world_premature(world, player)
|
base_world = copy_world_premature(world, player, create_flute_exits=True)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
else:
|
else:
|
||||||
base_world = world
|
base_world = world
|
||||||
@@ -1554,11 +1551,9 @@ def validate_layout(world, player):
|
|||||||
'Pyramid Area': ['Pyramid Exit Ledge']
|
'Pyramid Area': ['Pyramid Exit Ledge']
|
||||||
}
|
}
|
||||||
|
|
||||||
from Main import copy_world_premature
|
|
||||||
from Utils import stack_size3a
|
|
||||||
# TODO: Find a better source for the below lists, original sourced was deprecated
|
# TODO: Find a better source for the below lists, original sourced was deprecated
|
||||||
from source.overworld.EntranceData import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
|
from source.overworld.EntranceData 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]
|
dungeon_entrances = list(zip(*default_dungeon_connections + [('Ganons Tower', '')]))[0]
|
||||||
connector_entrances = list(zip(*default_connector_connections))[0]
|
connector_entrances = list(zip(*default_connector_connections))[0]
|
||||||
item_entrances = list(zip(*default_item_connections))[0]
|
item_entrances = list(zip(*default_item_connections))[0]
|
||||||
@@ -1567,12 +1562,11 @@ def validate_layout(world, player):
|
|||||||
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
flute_in_pool = True if player not in world.customitemarray else any(i for i, n in world.customitemarray[player].items() if i == 'flute' and n > 0)
|
||||||
|
|
||||||
def explore_region(region_name, region=None):
|
def explore_region(region_name, region=None):
|
||||||
if stack_size3a() > 500:
|
if region_name in explored_regions:
|
||||||
raise GenerationException(f'Infinite loop detected for "{region_name}" located at \'validate_layout\'')
|
return
|
||||||
|
explored_regions.add(region_name)
|
||||||
explored_regions.append(region_name)
|
|
||||||
if not region:
|
if not region:
|
||||||
region = base_world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
for exit in region.exits:
|
for exit in region.exits:
|
||||||
if exit.connected_region is not None and exit.connected_region.name not in explored_regions \
|
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]:
|
and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
|
||||||
@@ -1586,11 +1580,8 @@ def validate_layout(world, player):
|
|||||||
for dest_region in sane_connectors[region_name]:
|
for dest_region in sane_connectors[region_name]:
|
||||||
if dest_region not in explored_regions:
|
if dest_region not in explored_regions:
|
||||||
explore_region(dest_region)
|
explore_region(dest_region)
|
||||||
|
|
||||||
for p in range(1, world.players + 1):
|
explored_regions = set()
|
||||||
world.key_logic[p] = {}
|
|
||||||
base_world = copy_world_premature(world, player)
|
|
||||||
explored_regions = list()
|
|
||||||
|
|
||||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
||||||
if not world.is_bombshop_start(player):
|
if not world.is_bombshop_start(player):
|
||||||
@@ -1616,14 +1607,14 @@ def validate_layout(world, player):
|
|||||||
start_region = 'Hyrule Castle Ledge'
|
start_region = 'Hyrule Castle Ledge'
|
||||||
explore_region(start_region)
|
explore_region(start_region)
|
||||||
|
|
||||||
unreachable_regions = OrderedDict()
|
unreachable_regions = {}
|
||||||
unreachable_count = -1
|
unreachable_count = -1
|
||||||
while unreachable_count != len(unreachable_regions):
|
while unreachable_count != len(unreachable_regions):
|
||||||
# find unreachable regions
|
# find unreachable regions
|
||||||
unreachable_regions = {}
|
unreachable_regions = {}
|
||||||
for region_name in list(OWTileRegions.copy().keys()):
|
for region_name in list(OWTileRegions.copy().keys()):
|
||||||
if region_name not in explored_regions and region_name not in isolated_regions:
|
if region_name not in explored_regions and region_name not in isolated_regions:
|
||||||
region = base_world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
unreachable_regions[region_name] = region
|
unreachable_regions[region_name] = region
|
||||||
|
|
||||||
# loop thru unreachable regions to check if some can be excluded
|
# loop thru unreachable regions to check if some can be excluded
|
||||||
|
|||||||
@@ -864,7 +864,7 @@ def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_wo
|
|||||||
|
|
||||||
for p in range(1, avail.world.players + 1):
|
for p in range(1, avail.world.players + 1):
|
||||||
avail.world.key_logic[p] = {}
|
avail.world.key_logic[p] = {}
|
||||||
base_world = copy_world_premature(avail.world, avail.player)
|
base_world = copy_world_premature(avail.world, avail.player, create_flute_exits=True)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
|
|
||||||
connect_simple(base_world, 'Links House S&Q', start_region, avail.player)
|
connect_simple(base_world, 'Links House S&Q', start_region, avail.player)
|
||||||
|
|||||||
Reference in New Issue
Block a user