Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2021-12-06 09:21:53 -06:00
10 changed files with 270 additions and 191 deletions

View File

@@ -76,11 +76,12 @@ class World(object):
self.dynamic_locations = []
self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1
self.owswaps = {}
self.owwhirlpools = {}
self.owedges = []
self._owedge_cache = {}
self.owswaps = {}
self.owwhirlpools = {}
self.owflutespots = {}
self.owsectors = {}
self.doors = []
self._door_cache = {}
self.paired_doors = {}
@@ -108,6 +109,7 @@ class World(object):
set_player_attr('player_names', [])
set_player_attr('owswaps', [[],[],[]])
set_player_attr('owwhirlpools', [])
set_player_attr('owsectors', None)
set_player_attr('remote_items', False)
set_player_attr('required_medallions', ['Ether', 'Quake'])
set_player_attr('bottle_refills', ['Bottle (Green Potion)', 'Bottle (Green Potion)'])

View File

@@ -1,5 +1,10 @@
# Changelog
### 0.2.3.3
- Added OW Layout validation that reduces the cases where some screens are unreachable
- Fixed issue with mirror portals showing up in DW in Crossed OW
- Corrected Lost/Skull Woods Pass regions to be more accurate
### 0.2.3.0/1/2
- Fixed issue in Crossed OW where mirror portal sprites would disappear when changing worlds
- Added Big Red Bomb logic to support using residual mirror portals for later re-entry

View File

@@ -1,7 +1,8 @@
import logging
from collections import defaultdict
import RaceRandom as random
from BaseClasses import CollectionState, RegionType, Terrain
from BaseClasses import CollectionState, RegionType
from OverworldShuffle import build_accessible_region_list
from OWEdges import OWTileRegions
entrance_pool = list()
@@ -28,7 +29,9 @@ def link_entrances(world, player):
Old_Man_House = Old_Man_House_Base.copy()
Cave_Three_Exits = Cave_Three_Exits_Base.copy()
sectors = build_sectors(world, player)
from OverworldShuffle import build_sectors
if not world.owsectors[player]:
world.owsectors[player] = build_sectors(world, player)
# modifications to lists
if invFlag == (0x1b in world.owswaps[player][0] and world.owMixed[player]):
@@ -241,10 +244,10 @@ def link_entrances(world, player):
if invFlag:
# place dark sanc
place_dark_sanc(world, sectors, player)
place_dark_sanc(world, player)
# place links house
links_house = place_links_house(world, sectors, player)
links_house = place_links_house(world, player)
# place blacksmith, has limited options
place_blacksmith(world, links_house, player)
@@ -271,10 +274,10 @@ def link_entrances(world, player):
# place dark sanc
if invFlag:
place_dark_sanc(world, sectors, player)
place_dark_sanc(world, player)
# place links house
links_house = place_links_house(world, sectors, player)
links_house = place_links_house(world, player)
# place blacksmith, has limited options
place_blacksmith(world, links_house, player)
@@ -331,10 +334,10 @@ def link_entrances(world, player):
# place dark sanc
if invFlag:
place_dark_sanc(world, sectors, player, list(zip(*drop_connections + dropexit_connections))[0])
place_dark_sanc(world, player, list(zip(*drop_connections + dropexit_connections))[0])
# place links house
links_house = place_links_house(world, sectors, player, list(zip(*drop_connections + dropexit_connections))[0])
links_house = place_links_house(world, player, list(zip(*drop_connections + dropexit_connections))[0])
# determine pools
lw_entrances = list()
@@ -408,7 +411,7 @@ def link_entrances(world, player):
scramble_holes(world, player)
# place links house
links_house = place_links_house(world, sectors, player)
links_house = place_links_house(world, player)
# place blacksmith, has limited options
place_blacksmith(world, links_house, player)
@@ -494,7 +497,7 @@ def link_entrances(world, player):
scramble_holes(world, player)
# place links house
links_house = place_links_house(world, sectors, player)
links_house = place_links_house(world, player)
# place blacksmith, has limited options
place_blacksmith(world, links_house, player)
@@ -542,10 +545,10 @@ def link_entrances(world, player):
# place dark sanc
if invFlag:
place_dark_sanc(world, sectors, player)
place_dark_sanc(world, player)
# place links house
links_house = place_links_house(world, sectors, player)
links_house = place_links_house(world, player)
# place blacksmith, has limited options
place_blacksmith(world, links_house, player)
@@ -616,10 +619,10 @@ def link_entrances(world, player):
# place dark sanc
if invFlag:
place_dark_sanc(world, sectors, player)
place_dark_sanc(world, player)
# place links house
links_house = place_links_house(world, sectors, player)
links_house = place_links_house(world, player)
# place blacksmith, place sanc exit first for additional blacksmith candidates
doors = list(entrance_pool)
@@ -1371,7 +1374,7 @@ def full_shuffle_dungeons(world, Dungeon_Exits, player):
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
def place_links_house(world, sectors, player, ignore_list=[]):
def place_links_house(world, player, ignore_list=[]):
invFlag = world.mode[player] == 'inverted'
if world.mode[player] == 'standard' or not world.shufflelinks[player]:
links_house = 'Links House' if not invFlag else 'Big Bomb Shop'
@@ -1383,9 +1386,9 @@ def place_links_house(world, sectors, player, ignore_list=[]):
break
if invFlag and isinstance(dark_sanc, str):
links_house_doors = [i for i in get_distant_entrances(world, dark_sanc, sectors, player) if i in entrance_pool]
links_house_doors = [i for i in get_distant_entrances(world, dark_sanc, player) if i in entrance_pool]
else:
links_house_doors = [i for i in get_starting_entrances(world, sectors, player, world.shuffle[player] != 'insanity') if i in entrance_pool]
links_house_doors = [i for i in get_starting_entrances(world, player, world.shuffle[player] != 'insanity') if i in entrance_pool]
if world.shuffle[player] in ['lite', 'lean']:
links_house_doors = [e for e in links_house_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]]
@@ -1397,11 +1400,11 @@ def place_links_house(world, sectors, player, ignore_list=[]):
return links_house
def place_dark_sanc(world, sectors, player, ignore_list=[]):
def place_dark_sanc(world, player, ignore_list=[]):
if not world.shufflelinks[player]:
sanc_doors = [i for i in get_distant_entrances(world, 'Big Bomb Shop', sectors, player) if i in entrance_pool]
sanc_doors = [i for i in get_distant_entrances(world, 'Big Bomb Shop', player) if i in entrance_pool]
else:
sanc_doors = [i for i in get_starting_entrances(world, sectors, player, world.shuffle[player] != 'insanity') if i in entrance_pool]
sanc_doors = [i for i in get_starting_entrances(world, player, world.shuffle[player] != 'insanity') if i in entrance_pool]
if world.shuffle[player] in ['lite', 'lean']:
sanc_doors = [e for e in sanc_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]]
@@ -1634,100 +1637,6 @@ def unbias_dungeons(Dungeon_Exits):
tuplize_lists_in_list(Dungeon_Exits)
def build_sectors(world, player):
from Main import copy_world
from OWEdges import OWTileRegions
# perform accessibility check on duplicate world
for player in range(1, world.players + 1):
world.key_logic[player] = {}
base_world = copy_world(world)
world.key_logic = {}
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
regions = list(OWTileRegions.copy().keys())
sectors = list()
while(len(regions) > 0):
explored_regions = build_accessible_region_list(base_world, regions[0], player, False, False, False, False)
regions = [r for r in regions if r not in explored_regions]
unique_regions = [_ for i in range(len(sectors)) for _ in sectors[i]]
if (any(r in unique_regions for r in explored_regions)):
for s in range(len(sectors)):
if (any(r in sectors[s] for r in explored_regions)):
sectors[s] = set(list(sectors[s]) + list(explored_regions))
break
else:
sectors.append(explored_regions)
# remove water regions if Flippers not in starting inventory
if not any(map(lambda i: i.name == 'Flippers', world.precollected_items)):
for s in range(len(sectors)):
terrains = list()
for regionname in sectors[s]:
region = world.get_region(regionname, player)
if region.terrain == Terrain.Land:
terrains.append(regionname)
sectors[s] = terrains
# within each group, split into contiguous regions accessible only with starting inventory
for s in range(len(sectors)):
regions = list(sectors[s]).copy()
sectors2 = list()
while(len(regions) > 0):
explored_regions = build_accessible_region_list(base_world, regions[0], player, False, False, True, False)
regions = [r for r in regions if r not in explored_regions]
unique_regions = [_ for i in range(len(sectors2)) for _ in sectors2[i]]
if (any(r in unique_regions for r in explored_regions)):
for s2 in range(len(sectors2)):
if (any(r in sectors2[s2] for r in explored_regions)):
sectors2[s2] = set(list(sectors2[s2]) + list(explored_regions))
break
else:
sectors2.append(explored_regions)
sectors[s] = sectors2
return sectors
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False):
from Main import copy_world
from Items import ItemFactory
def explore_region(region_name, region=None):
explored_regions.add(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:
if any(map(lambda i: i.name == 'Ocarina', base_world.precollected_items)) and exit.spot_type == 'Flute':
fluteregion = exit.connected_region
for flutespot in fluteregion.exits:
if flutespot.connected_region and flutespot.connected_region.name not in explored_regions:
explore_region(flutespot.connected_region.name, flutespot.connected_region)
elif exit.connected_region.name not in explored_regions \
and (exit.connected_region.type == region.type or (cross_world and exit.connected_region.type in [RegionType.LightWorld, RegionType.DarkWorld])) \
and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.spot_type != 'Ledge'):
explore_region(exit.connected_region.name, exit.connected_region)
if build_copy_world:
for player in range(1, world.players + 1):
world.key_logic[player] = {}
base_world = copy_world(world)
base_world.override_bomb_check = True
world.key_logic = {}
else:
base_world = world
connect_simple(base_world, 'Links House S&Q', start_region, player)
blank_state = CollectionState(base_world)
if base_world.mode[player] == 'standard':
blank_state.collect(ItemFactory('Zelda Delivered', player), True)
explored_regions = set()
explore_region(start_region)
return explored_regions
def build_accessible_entrance_list(world, start_region, player, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False):
from Main import copy_world
from Items import ItemFactory
@@ -1766,7 +1675,7 @@ def build_accessible_entrance_list(world, start_region, player, assumed_inventor
return entrances
def get_starting_entrances(world, sectors, player, force_starting_world=True):
def get_starting_entrances(world, player, force_starting_world=True):
invFlag = world.mode[player] == 'inverted'
# find largest walkable sector
@@ -1775,7 +1684,7 @@ def get_starting_entrances(world, sectors, player, force_starting_world=True):
entrances = list()
while not len(entrances):
while (sector is None):
sector = max(sectors, key=lambda x: len(x) - (0 if x not in invalid_sectors else 1000))
sector = max(world.owsectors[player], key=lambda x: len(x) - (0 if x not in invalid_sectors else 1000))
if not ((world.owCrossed[player] == 'polar' and world.owMixed[player]) or world.owCrossed[player] not in ['none', 'polar']) \
and world.get_region(next(iter(next(iter(sector)))), player).type != (RegionType.LightWorld if not invFlag else RegionType.DarkWorld):
invalid_sectors.append(sector)
@@ -1799,10 +1708,10 @@ def get_starting_entrances(world, sectors, player, force_starting_world=True):
return entrances
def get_distant_entrances(world, start_entrance, sectors, player):
def get_distant_entrances(world, start_entrance, player):
# get walkable sector in which initial entrance was placed
start_region = world.get_entrance(start_entrance, player).parent_region.name
regions = next(s for s in sectors if any(start_region in w for w in s))
regions = next(s for s in world.owsectors[player] if any(start_region in w for w in s))
regions = next(w for w in regions if start_region in w)
# eliminate regions surrounding the initial entrance until less than half of the candidate regions remain

View File

@@ -326,6 +326,8 @@ def create_owedges(world, player):
world.initialize_owedges(edges)
def create_owedge(player, name, owIndex, direction, terrain, edge_id, owSlotIndex=0xff):
if name not in OWExitTypes['OWEdge']:
OWExitTypes['OWEdge'].append(name)
return OWEdge(player, name, owIndex, direction, terrain, edge_id, owSlotIndex)
@@ -720,6 +722,7 @@ OWTileRegions = bidict({
'Lost Woods Pass West Area': 0x10,
'Lost Woods Pass East Top Area': 0x10,
'Lost Woods Pass Portal Area': 0x10,
'Lost Woods Pass East Bottom Area': 0x10,
'Kakariko Fortune Area': 0x11,
@@ -862,6 +865,7 @@ OWTileRegions = bidict({
'Skull Woods Pass West Area': 0x50,
'Skull Woods Pass East Top Area': 0x50,
'Skull Woods Pass Portal Area': 0x50,
'Skull Woods Pass East Bottom Area': 0x50,
'Dark Fortune Area': 0x51,
@@ -1390,6 +1394,7 @@ parallel_links = bidict({'Lost Woods SW': 'Skull Woods SW',
})
OWExitTypes = {
'OWEdge': [],
'Ledge': ['West Death Mountain Drop',
'Spectacle Rock Drop',
'East Death Mountain Spiral Ledge Drop',
@@ -1518,6 +1523,10 @@ OWExitTypes = {
'Zora Waterfall Water Entry',
'Waterfall of Wishing Cave Entry',
'Zora Waterfall Landing',
'Lost Woods Pass Hammer (North)',
'Lost Woods Pass Hammer (South)',
'Lost Woods Pass Rock (North)',
'Lost Woods Pass Rock (South)',
'Kings Grave Outer Rocks',
'Graveyard Ladder (Bottom)',
'Graveyard Ladder (Top)',
@@ -1587,8 +1596,10 @@ OWExitTypes = {
'Bumper Cave Entrance Rock',
'Skull Woods Pass Bush Row (West)',
'Skull Woods Pass Bush Row (East)',
'Skull Woods Pass Rock (Top)',
'Skull Woods Pass Rock (Bottom)',
'Skull Woods Pass Bush (North)',
'Skull Woods Pass Bush (South)',
'Skull Woods Pass Rock (North)',
'Skull Woods Pass Rock (South)',
'Dark Graveyard Bush (South)',
'Dark Graveyard Bush (North)',
'Qirn Jump East Water Drop',
@@ -1632,8 +1643,7 @@ OWExitTypes = {
'Portal': ['West Death Mountain Teleporter',
'East Death Mountain Teleporter',
'TR Pegs Teleporter',
'Kakariko Teleporter (Hammer)',
'Kakariko Teleporter (Rock)',
'Kakariko Teleporter',
'Top of Pyramid',
'Top of Pyramid (Inner)',
'East Hyrule Teleporter',
@@ -1643,8 +1653,7 @@ OWExitTypes = {
'Dark Death Mountain Teleporter (West)',
'Dark Death Mountain Teleporter (East)',
'Turtle Rock Teleporter',
'West Dark World Teleporter (Hammer)',
'West Dark World Teleporter (Rock)',
'West Dark World Teleporter',
'Post Aga Inverted Teleporter',
'East Dark World Teleporter',
'Misery Mire Teleporter',
@@ -1687,6 +1696,7 @@ OWExitTypes = {
'Catfish Mirror Spot',
'Skull Woods Pass West Mirror Spot',
'Skull Woods Pass East Top Mirror Spot',
'Skull Woods Pass Portal Mirror Spot',
'Skull Woods Pass East Bottom Mirror Spot',
'Outcast Fortune Mirror Spot',
'Outcast Pond Mirror Spot',
@@ -1777,6 +1787,7 @@ OWExitTypes = {
'Zora Waterfall Mirror Spot',
'Lost Woods Pass West Mirror Spot',
'Lost Woods Pass East Top Mirror Spot',
'Lost Woods Pass Portal Mirror Spot',
'Lost Woods Pass East Bottom Mirror Spot',
'Kakariko Fortune Mirror Spot',
'Kakariko Pond Mirror Spot',

View File

@@ -3,7 +3,7 @@ from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSl
from Regions import mark_dark_world_regions, mark_light_world_regions
from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
__version__ = '0.2.3.2-u'
__version__ = '0.2.3.3-u'
def link_overworld(world, player):
# setup mandatory connections
@@ -140,8 +140,8 @@ def link_overworld(world, player):
# update spoiler
s = list(map(lambda x: ' ' if x not in world.owswaps[player][0] else 'S', [i for i in range(0x40)]))
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05],
text_output = tile_swap_spoiler_table.replace('s', '%s') % ( s[0x02], s[0x07],
s[0x00], s[0x03], s[0x05],
s[0x00], s[0x02],s[0x03], s[0x05], s[0x07], s[0x0a], s[0x0f],
s[0x0a], s[0x0f],
s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17], s[0x10],s[0x11],s[0x12],s[0x13],s[0x14],s[0x15],s[0x16],s[0x17],
@@ -150,10 +150,10 @@ def link_overworld(world, player):
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f], s[0x18], s[0x1b], s[0x1e],
s[0x30], s[0x32],s[0x33],s[0x34],s[0x35], s[0x37], s[0x22], s[0x25],
s[0x3a],s[0x3b],s[0x3c], s[0x3f],
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
s[0x32],s[0x33],s[0x34], s[0x37],
s[0x30], s[0x35],
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
s[0x28],s[0x29],s[0x2a],s[0x2b],s[0x2c],s[0x2d],s[0x2e],s[0x2f],
s[0x32],s[0x33],s[0x34], s[0x37],
s[0x30], s[0x35],
s[0x3a],s[0x3b],s[0x3c], s[0x3f])
world.spoiler.set_map('swaps', text_output, world.owswaps[player][0], player)
# apply tile logical connections
@@ -280,48 +280,58 @@ def link_overworld(world, player):
trimmed_groups = remove_reserved(world, trimmed_groups, connected_edges, player)
groups = reorganize_groups(world, trimmed_groups, player)
if world.mode[player] == 'standard':
random.shuffle(groups[2:]) # keep first 2 groups (Standard) first
else:
random.shuffle(groups)
tries = 10
valid_layout = False
connected_edge_cache = connected_edges.copy()
while not valid_layout and tries > 0:
connected_edges = connected_edge_cache.copy()
for (forward_edge_sets, back_edge_sets) in groups:
assert len(forward_edge_sets) == len(back_edge_sets)
random.shuffle(forward_edge_sets)
random.shuffle(back_edge_sets)
if len(forward_edge_sets) > 0:
f = 0
b = 0
while f < len(forward_edge_sets) and b < len(back_edge_sets):
forward_set = forward_edge_sets[f]
back_set = back_edge_sets[b]
while forward_set[0] in connected_edges:
if world.mode[player] == 'standard':
random.shuffle(groups[2:]) # keep first 2 groups (Standard) first
else:
random.shuffle(groups)
for (forward_edge_sets, back_edge_sets) in groups:
assert len(forward_edge_sets) == len(back_edge_sets)
random.shuffle(forward_edge_sets)
random.shuffle(back_edge_sets)
if len(forward_edge_sets) > 0:
f = 0
b = 0
while f < len(forward_edge_sets) and b < len(back_edge_sets):
forward_set = forward_edge_sets[f]
back_set = back_edge_sets[b]
while forward_set[0] in connected_edges:
f += 1
if f < len(forward_edge_sets):
forward_set = forward_edge_sets[f]
else:
forward_set = None
break
f += 1
if f < len(forward_edge_sets):
forward_set = forward_edge_sets[f]
else:
forward_set = None
break
f += 1
while back_set[0] in connected_edges:
while back_set[0] in connected_edges:
b += 1
if b < len(back_edge_sets):
back_set = back_edge_sets[b]
else:
back_set = None
break
b += 1
if b < len(back_edge_sets):
back_set = back_edge_sets[b]
else:
back_set = None
break
b += 1
if forward_set is not None and back_set is not None:
assert len(forward_set) == len(back_set)
for (forward_edge, back_edge) in zip(forward_set, back_set):
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
elif forward_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0])
elif back_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0])
assert len(connected_edges) == len(default_connections) * 2, connected_edges
if forward_set is not None and back_set is not None:
assert len(forward_set) == len(back_set)
for (forward_edge, back_edge) in zip(forward_set, back_set):
connect_two_way(world, forward_edge, back_edge, player, connected_edges)
elif forward_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % forward_set[0])
elif back_set is not None:
logging.getLogger('').warning("Edge '%s' could not find a valid connection" % back_set[0])
assert len(connected_edges) == len(default_connections) * 2, connected_edges
world.owsectors[player] = build_sectors(world, player)
valid_layout = validate_layout(world, player)
# TODO: Reshuffle some areas if impossible to reach, exception if non-dungeon ER enabled or if area is LW with no portal and flute shuffle is enabled
tries -= 1
assert valid_layout, 'Could not find a valid OW layout'
# flute shuffle
def connect_flutes(flute_destinations):
@@ -820,6 +830,114 @@ def can_reach_smith(world, player):
explore_region('Dark Sanctuary Hint')
return found
def build_sectors(world, player):
from Main import copy_world
from OWEdges import OWTileRegions
# perform accessibility check on duplicate world
for player in range(1, world.players + 1):
world.key_logic[player] = {}
base_world = copy_world(world)
world.key_logic = {}
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
regions = list(OWTileRegions.copy().keys())
sectors = list()
while(len(regions) > 0):
explored_regions = build_accessible_region_list(base_world, regions[0], player, False, False, False, False)
regions = [r for r in regions if r not in explored_regions]
unique_regions = [_ for i in range(len(sectors)) for _ in sectors[i]]
if (any(r in unique_regions for r in explored_regions)):
for s in range(len(sectors)):
if (any(r in sectors[s] for r in explored_regions)):
sectors[s] = set(list(sectors[s]) + list(explored_regions))
break
else:
sectors.append(explored_regions)
# remove water regions if Flippers not in starting inventory
if not any(map(lambda i: i.name == 'Flippers', world.precollected_items)):
for s in range(len(sectors)):
terrains = list()
for regionname in sectors[s]:
region = world.get_region(regionname, player)
if region.terrain == Terrain.Land:
terrains.append(regionname)
sectors[s] = terrains
# within each group, split into contiguous regions accessible only with starting inventory
for s in range(len(sectors)):
regions = list(sectors[s]).copy()
sectors2 = list()
while(len(regions) > 0):
explored_regions = build_accessible_region_list(base_world, regions[0], player, False, False, True, False)
regions = [r for r in regions if r not in explored_regions]
unique_regions = [_ for i in range(len(sectors2)) for _ in sectors2[i]]
if (any(r in unique_regions for r in explored_regions)):
for s2 in range(len(sectors2)):
if (any(r in sectors2[s2] for r in explored_regions)):
sectors2[s2] = set(list(sectors2[s2]) + list(explored_regions))
break
else:
sectors2.append(explored_regions)
sectors[s] = sectors2
return sectors
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False):
from Main import copy_world
from BaseClasses import CollectionState
from Items import ItemFactory
def explore_region(region_name, region=None):
explored_regions.add(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:
if any(map(lambda i: i.name == 'Ocarina', base_world.precollected_items)) and exit.spot_type == 'Flute':
fluteregion = exit.connected_region
for flutespot in fluteregion.exits:
if flutespot.connected_region and flutespot.connected_region.name not in explored_regions:
explore_region(flutespot.connected_region.name, flutespot.connected_region)
elif exit.connected_region.name not in explored_regions \
and (exit.connected_region.type == region.type
or exit.name in OWExitTypes['OWEdge'] or (cross_world and exit.name in OWExitTypes['Portal'])) \
and (not region_rules or exit.access_rule(blank_state)) and (not ignore_ledges or exit.name not in OWExitTypes['Ledge']):
explore_region(exit.connected_region.name, exit.connected_region)
if build_copy_world:
for player in range(1, world.players + 1):
world.key_logic[player] = {}
base_world = copy_world(world)
base_world.override_bomb_check = True
world.key_logic = {}
else:
base_world = world
connect_simple(base_world, 'Links House S&Q', start_region, player)
blank_state = CollectionState(base_world)
if base_world.mode[player] == 'standard':
blank_state.collect(ItemFactory('Zelda Delivered', player), True)
explored_regions = set()
explore_region(start_region)
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
return True
test_connections = [
#('Links House ES', 'Octoballoon WS'),
#('Links House NE', 'Lost Woods Pass SW')
@@ -860,6 +978,10 @@ mandatory_connections = [# Intra-tile OW Connections
('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers
('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers
('Waterfall of Wishing Cave Entry', 'Waterfall of Wishing Cave'), #flippers
('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer
('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer
('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts
('Lost Woods Pass Rock (South)', 'Lost Woods Pass Portal Area'), #mitts
('Bonk Rock Ledge Drop', 'Sanctuary Area'),
('Graveyard Ledge Drop', 'Graveyard Area'),
('Kings Grave Outer Rocks', 'Kings Grave Area'), #mitts
@@ -947,8 +1069,10 @@ mandatory_connections = [# Intra-tile OW Connections
('Bumper Cave Entrance Drop', 'Bumper Cave Area'),
('Skull Woods Pass Bush Row (West)', 'Skull Woods Pass East Top Area'), #pearl
('Skull Woods Pass Bush Row (East)', 'Skull Woods Pass West Area'), #pearl
('Skull Woods Pass Rock (Top)', 'Skull Woods Pass East Bottom Area'), #mitts
('Skull Woods Pass Rock (Bottom)', 'Skull Woods Pass East Top Area'), #mitts
('Skull Woods Pass Bush (North)', 'Skull Woods Pass Portal Area'), #pearl
('Skull Woods Pass Bush (South)', 'Skull Woods Pass East Top Area'), #pearl
('Skull Woods Pass Rock (North)', 'Skull Woods Pass East Bottom Area'), #mitts
('Skull Woods Pass Rock (South)', 'Skull Woods Pass Portal Area'), #mitts
('Dark Graveyard Bush (South)', 'Dark Graveyard North'), #pearl
('Dark Graveyard Bush (North)', 'Dark Graveyard Area'), #pearl
('Qirn Jump Water Drop', 'Qirn Jump Water'), #flippers
@@ -1158,15 +1282,15 @@ ow_connections = {
0x10: ([
('Lost Woods Pass West Mirror Spot', 'Lost Woods Pass West Area'),
('Lost Woods Pass East Top Mirror Spot', 'Lost Woods Pass East Top Area'),
('Lost Woods Pass Portal Mirror Spot', 'Lost Woods Pass Portal Area'),
('Lost Woods Pass East Bottom Mirror Spot', 'Lost Woods Pass East Bottom Area'),
('Kakariko Teleporter (Hammer)', 'Skull Woods Pass East Top Area'),
('Kakariko Teleporter (Rock)', 'Skull Woods Pass East Top Area')
('Kakariko Teleporter', 'Skull Woods Pass Portal Area')
], [
('Skull Woods Pass West Mirror Spot', 'Skull Woods Pass West Area'),
('Skull Woods Pass East Top Mirror Spot', 'Skull Woods Pass East Top Area'),
('Skull Woods Pass Portal Mirror Spot', 'Skull Woods Pass Portal Area'),
('Skull Woods Pass East Bottom Mirror Spot', 'Skull Woods Pass East Bottom Area'),
('West Dark World Teleporter (Hammer)', 'Lost Woods Pass East Top Area'),
('West Dark World Teleporter (Rock)', 'Lost Woods Pass East Bottom Area')
('West Dark World Teleporter', 'Lost Woods Pass Portal Area')
]),
0x11: ([
('Kakariko Fortune Mirror Spot', 'Kakariko Fortune Area')
@@ -1570,6 +1694,11 @@ default_connections = [#('Lost Woods NW', 'Master Sword Meadow SC'),
('East Dark Death Mountain EN', 'Turtle Rock WN')
]
isolated_regions = [
'Death Mountain Floating Island',
'Mimic Cave Ledge'
]
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
0x09: (['Lost Woods East Area', 'Skull Woods Forest'], 0x00, 0x1042, 0x022e, 0x0202, 0x0290, 0x0288, 0x029b, 0x028f, 0xfff2, 0x000e, 0x0290, 0x0288, 0x0290, 0x0290),

View File

@@ -34,8 +34,9 @@ def create_regions(world, player):
create_lw_region(player, 'Waterfall of Wishing Cave', None, ['Zora Waterfall Water Drop', 'Waterfall of Wishing']),
create_lw_region(player, 'Zoras Domain', ['King Zora', 'Zora\'s Ledge'], ['Zoras Domain SW']),
create_lw_region(player, 'Lost Woods Pass West Area', None, ['Skull Woods Pass West Mirror Spot', 'Lost Woods Pass NW', 'Lost Woods Pass SW']),
create_lw_region(player, 'Lost Woods Pass East Top Area', None, ['Skull Woods Pass East Top Mirror Spot', 'Kakariko Teleporter (Hammer)', 'Lost Woods Pass NE']),
create_lw_region(player, 'Lost Woods Pass East Bottom Area', None, ['Skull Woods Pass East Bottom Mirror Spot', 'Kakariko Teleporter (Rock)', 'Lost Woods Pass SE']),
create_lw_region(player, 'Lost Woods Pass East Top Area', None, ['Skull Woods Pass East Top Mirror Spot', 'Lost Woods Pass Hammer (North)', 'Lost Woods Pass NE']),
create_lw_region(player, 'Lost Woods Pass Portal Area', None, ['Skull Woods Pass Portal Mirror Spot', 'Kakariko Teleporter', 'Lost Woods Pass Hammer (South)', 'Lost Woods Pass Rock (North)']),
create_lw_region(player, 'Lost Woods Pass East Bottom Area', None, ['Skull Woods Pass East Bottom Mirror Spot', 'Lost Woods Pass Rock (South)', 'Lost Woods Pass SE']),
create_lw_region(player, 'Kakariko Fortune Area', None, ['Fortune Teller (Light)', 'Outcast Fortune Mirror Spot', 'Kakariko Fortune NE', 'Kakariko Fortune EN', 'Kakariko Fortune ES', 'Kakariko Fortune SC']),
create_lw_region(player, 'Kakariko Pond Area', None, ['Outcast Pond Mirror Spot', 'Kakariko Pond NE', 'Kakariko Pond WN', 'Kakariko Pond WS', 'Kakariko Pond SW', 'Kakariko Pond SE', 'Kakariko Pond EN', 'Kakariko Pond ES', 'Kakariko Pond Whirlpool']),
create_lw_region(player, 'Sanctuary Area', None, ['Sanctuary', 'Dark Chapel Mirror Spot', 'Sanctuary WS', 'Sanctuary EC']),
@@ -144,8 +145,9 @@ def create_regions(world, player):
create_dw_region(player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'], ['Bumper Cave Entrance Drop', 'Bumper Cave (Top)', 'Mountain Entry Ledge Mirror Spot']),
create_dw_region(player, 'Catfish Area', ['Catfish'], ['Zora Waterfall Mirror Spot', 'Catfish SE']),
create_dw_region(player, 'Skull Woods Pass West Area', None, ['Skull Woods Pass Bush Row (West)', 'Lost Woods Pass West Mirror Spot', 'Skull Woods Pass NW', 'Skull Woods Pass SW']),
create_dw_region(player, 'Skull Woods Pass East Top Area', None, ['Skull Woods Pass Bush Row (East)', 'Skull Woods Pass Rock (Top)', 'West Dark World Teleporter (Hammer)', 'West Dark World Teleporter (Rock)', 'Lost Woods Pass East Top Mirror Spot', 'Skull Woods Pass NE']),
create_dw_region(player, 'Skull Woods Pass East Bottom Area', None, ['Skull Woods Pass Rock (Bottom)', 'Lost Woods Pass East Bottom Mirror Spot', 'Skull Woods Pass SE']),
create_dw_region(player, 'Skull Woods Pass East Top Area', None, ['Lost Woods Pass East Top Mirror Spot', 'Skull Woods Pass Bush Row (East)', 'Skull Woods Pass Bush (North)', 'Skull Woods Pass NE']),
create_dw_region(player, 'Skull Woods Pass Portal Area', None, ['Lost Woods Pass Portal Mirror Spot', 'West Dark World Teleporter', 'Skull Woods Pass Bush (South)', 'Skull Woods Pass Rock (North)']),
create_dw_region(player, 'Skull Woods Pass East Bottom Area', None, ['Lost Woods Pass East Bottom Mirror Spot', 'Skull Woods Pass Rock (South)', 'Skull Woods Pass SE']),
create_dw_region(player, 'Dark Fortune Area', None, ['Fortune Teller (Dark)', 'Kakariko Fortune Mirror Spot', 'Dark Fortune NE', 'Dark Fortune EN', 'Dark Fortune ES', 'Dark Fortune SC']),
create_dw_region(player, 'Outcast Pond Area', None, ['Kakariko Pond Mirror Spot', 'Outcast Pond NE', 'Outcast Pond WN', 'Outcast Pond WS', 'Outcast Pond SW', 'Outcast Pond SE', 'Outcast Pond EN', 'Outcast Pond ES']),
create_dw_region(player, 'Dark Chapel Area', None, ['Dark Sanctuary Hint', 'Sanctuary Mirror Spot', 'Bonk Rock Ledge Mirror Spot', 'Dark Chapel WN', 'Dark Chapel WS', 'Dark Chapel EC']),

2
Rom.py
View File

@@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '383fbac5f195f82d2b74b62113229fac'
RANDOMIZERBASEHASH = '607ec23701b1099fdeb64a0caff190b6'
class JsonRom(object):

View File

@@ -757,6 +757,10 @@ def default_rules(world, player):
set_rule(world.get_entrance('TR Pegs Ledge Leave', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Mountain Entry Entrance Rock (West)', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Mountain Entry Entrance Rock (East)', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Kings Grave Inner Rocks', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Potion Shop Rock (South)', player), lambda state: state.can_lift_rocks(player))
@@ -779,8 +783,8 @@ def default_rules(world, player):
set_rule(world.get_entrance('Skull Woods Bush Rock (West)', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Skull Woods Bush Rock (East)', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Bumper Cave Entrance Rock', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Skull Woods Pass Rock (Top)', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Skull Woods Pass Rock (Bottom)', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), lambda state: state.can_lift_heavy_rocks(player))
set_rule(world.get_entrance('Dark Witch Rock (North)', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Dark Witch Rock (South)', player), lambda state: state.can_lift_rocks(player))
set_rule(world.get_entrance('Catfish Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player))
@@ -930,15 +934,14 @@ def ow_rules(world, player):
if (world.mode[player] == 'inverted') == (0x10 in world.owswaps[player][0] and world.owMixed[player]):
set_rule(world.get_entrance('Lost Woods Pass West Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Lost Woods Pass East Top Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Lost Woods Pass Portal Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Lost Woods Pass East Bottom Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Kakariko Teleporter (Hammer)', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player)) # bunny cannot lift bushes
set_rule(world.get_entrance('Kakariko Teleporter (Rock)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) # bunny cannot lift bushes
set_rule(world.get_entrance('Kakariko Teleporter', player), lambda state: state.can_lift_rocks(player))
else:
set_rule(world.get_entrance('Skull Woods Pass West Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Skull Woods Pass East Top Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Skull Woods Pass Portal Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('Skull Woods Pass East Bottom Mirror Spot', player), lambda state: state.has_Mirror(player))
set_rule(world.get_entrance('West Dark World Teleporter (Hammer)', player), lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and state.has_Pearl(player))
set_rule(world.get_entrance('West Dark World Teleporter (Rock)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) # bunny cannot lift bushes
if (world.mode[player] == 'inverted') == (0x11 in world.owswaps[player][0] and world.owMixed[player]):
set_rule(world.get_entrance('Kakariko Fortune Mirror Spot', player), lambda state: state.has_Mirror(player))
@@ -1237,6 +1240,10 @@ def ow_bunny_rules(world, player):
add_bunny_rule(world.get_entrance('TR Pegs Ledge Entry', player), player)
add_bunny_rule(world.get_entrance('Mountain Entry Entrance Rock (West)', player), player)
add_bunny_rule(world.get_entrance('Mountain Entry Entrance Rock (East)', player), player)
add_bunny_rule(world.get_entrance('Lost Woods Pass Hammer (North)', player), player)
add_bunny_rule(world.get_entrance('Lost Woods Pass Hammer (South)', player), player)
add_bunny_rule(world.get_entrance('Lost Woods Pass Rock (North)', player), player)
add_bunny_rule(world.get_entrance('Lost Woods Pass Rock (South)', player), player)
add_bunny_rule(world.get_entrance('Kings Grave Outer Rocks', player), player)
add_bunny_rule(world.get_entrance('Kings Grave Inner Rocks', player), player)
add_bunny_rule(world.get_entrance('Potion Shop Rock (South)', player), player)
@@ -1273,8 +1280,10 @@ def ow_bunny_rules(world, player):
add_bunny_rule(world.get_entrance('Bumper Cave Entrance Rock', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (West)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Bush Row (East)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (Top)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (Bottom)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Bush (North)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Bush (South)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (North)', player), player)
add_bunny_rule(world.get_entrance('Skull Woods Pass Rock (South)', player), player)
add_bunny_rule(world.get_entrance('Dark Graveyard Bush (South)', player), player)
add_bunny_rule(world.get_entrance('Dark Graveyard Bush (North)', player), player)
add_bunny_rule(world.get_entrance('Dark Witch Rock (North)', player), player)

View File

@@ -148,8 +148,12 @@ OWPreserveMirrorSprite:
{
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla
rtl ; if OW Crossed, skip world check and continue
.vanilla
lda $7ef3ca : bne .deleteMirror
lda InvertedMode : beq +
lda $7ef3ca : beq .deleteMirror
rtl
+ lda $7ef3ca : bne .deleteMirror
rtl
.deleteMirror
@@ -407,6 +411,14 @@ OWWorldUpdate: ; x = owid of destination screen
{
lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return
sta.l $7ef3ca ; change world
; moving mirror portal off screen when in DW
cmp #0 : beq + : lda #1
+ cmp.l InvertedMode : bne +
lda $1acf : and #$0f : sta $1acf : bra .playSfx ; bring portal back into position
+ lda $1acf : eor #$80 : sta $1acf ; move portal off screen
.playSfx
lda #$38 : sta $012f ; play sfx - #$3b is an alternative
; toggle bunny mode

Binary file not shown.