Merge branch 'OverworldShuffleDev' into OverworldShuffle

This commit is contained in:
codemann8
2022-11-03 13:47:55 -05:00
11 changed files with 216 additions and 171 deletions

View File

@@ -856,7 +856,21 @@ class CollectionState(object):
else:
door_candidates.append(door.name)
return door_candidates
return None
door_candidates, skip = [], set()
if state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name in state.world.key_logic[player]:
key_logic = state.world.key_logic[player][dungeon_name]
for door, paired in key_logic.sm_doors.items():
if door.name in key_logic.door_rules:
rule = key_logic.door_rules[door.name]
key = KeyRuleType.AllowSmall
if (key in rule.new_rules and key_total >= rule.new_rules[key] and door.name not in skip
and door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]):
if paired:
door_candidates.append((door.name, paired.name))
skip.add(paired.name)
else:
door_candidates.append(door.name)
return door_candidates if door_candidates else None
@staticmethod
def print_rrp(rrp):
@@ -995,29 +1009,30 @@ class CollectionState(object):
checked_locations = set([l for l in locations if l in self.locations_checked])
reachable_events = [location for location in locations if location.event and location.can_reach(self)]
reachable_events = self._do_not_flood_the_keys(reachable_events)
found_new = False
for event in reachable_events:
if event not in checked_locations:
self.events.append((event.name, event.player))
self.collect(event.item, True, event)
return len(reachable_events) > len(checked_locations)
found_new = True
return found_new
def sweep_for_events(self, key_only=False, locations=None):
# this may need improvement
if locations is None:
locations = self.world.get_filled_locations()
new_locations = True
checked_locations = 0
while new_locations:
reachable_events = [location for location in locations if location.event and
(not key_only or (not self.world.keyshuffle[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey))
and location.can_reach(self)]
reachable_events = self._do_not_flood_the_keys(reachable_events)
new_locations = False
for event in reachable_events:
if (event.name, event.player) not in self.events:
self.events.append((event.name, event.player))
self.collect(event.item, True, event)
new_locations = len(reachable_events) > checked_locations
checked_locations = len(reachable_events)
new_locations = True
def can_reach_blue(self, region, player):
@@ -3005,7 +3020,7 @@ class Spoiler(object):
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
outfile.write('OW Tile Flip (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player]))
@@ -3097,10 +3112,10 @@ class Spoiler(object):
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('flute', player)]['text'] + '\n\n')
# overworld tile swaps
# overworld tile flips
for player in range(1, self.world.players + 1):
if ('swaps', player) in self.maps:
outfile.write('OW Tile Swaps:\n')
outfile.write('OW Tile Flips:\n')
break
for player in range(1, self.world.players + 1):
if ('swaps', player) in self.maps:

View File

@@ -1,5 +1,21 @@
# Changelog
## 0.2.11.1
- Renamed mode: Tile Swap (Mixed) is now called Tile Flip (Mixed)
- Fixed generation errors due to issue with new Farmable item locations
## 0.2.11.0
- New OWR mode option: Free Terrain
- When used with OW Layout Shuffle, land and water transitions are combined into one pool and shuffled, this means land transitions can lead to water and vice versa. There is already tracker support for this change on DunkaTracker. Thanks @Catobat for the work on all of this.
- Glitched modes now have correct fake world behavior in all modes, including Inverted and even Mixed OWR
- Glitched + Mixed OWR now has correct logic (previously it was completely unimplemented)
- Lite ER is back and working!
- There was an issue with ER resulting in regions being inaccessible, this has been fixed.
- Changed Crossworld ER modes so that DW inaccessible areas are resolved before considering LW inaccessible areas, to give the algo a chance to make some of the LW areas accessible thru the DW
- Added new Bomb/Rupee farm logic, which uses pseudo-items, simplifies the graph searching and they even show up in the Playthru Calc (shows a logical path to Farmable Bombs if you ever question how you are able to get early bombs when the opening area is limited)
- Fixed issue with grabbing an item near Murahduhla and freezing the game
- Various logic corrections, including the DR Bumper Cave fix for pottery logic
## 0.2.10.1
- Merged DR v1.0.1.3
- Fixed Zelda despawn in TT Prison

View File

@@ -182,7 +182,7 @@ def link_entrances(world, player):
if 0x03 in world.owswaps[player][0] == 0x05 in world.owswaps[player][0]: # if WDM and EDM are in same world
connect_caves(world, lw_wdm_entrances + lw_edm_entrances, [], caves, player)
else:
# place Old Man House in WDM if not swapped
# place Old Man House in WDM if not flipped
if not world.is_tile_swapped(0x03, player):
connect_caves(world, lw_wdm_entrances, [], list(Old_Man_House), player)
else:
@@ -1440,12 +1440,12 @@ def place_old_man(world, pool, player, ignore_list=[]):
def junk_fill_inaccessible(world, player):
from Main import copy_world_limited
from Main import copy_world_premature
find_inaccessible_regions(world, player)
for p in range(1, world.players + 1):
world.key_logic[p] = {}
base_world = copy_world_limited(world)
base_world = copy_world_premature(world, player)
base_world.override_bomb_check = True
# remove regions that have a dungeon entrance
@@ -1602,12 +1602,12 @@ def unbias_dungeons(Dungeon_Exits):
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_limited
from Main import copy_world_premature
from Items import ItemFactory
for p in range(1, world.players + 1):
world.key_logic[p] = {}
base_world = copy_world_limited(world)
base_world = copy_world_premature(world, player)
base_world.override_bomb_check = True
connect_simple(base_world, 'Links House S&Q', start_region, player)
@@ -1710,12 +1710,12 @@ def get_distant_entrances(world, start_entrance, player):
def can_reach(world, entrance_name, region_name, player):
from Main import copy_world_limited
from Main import copy_world_premature
from Items import ItemFactory
for p in range(1, world.players + 1):
world.key_logic[p] = {}
base_world = copy_world_limited(world)
base_world = copy_world_premature(world, player)
base_world.override_bomb_check = True
entrance = world.get_entrance(entrance_name, player)
@@ -2138,8 +2138,8 @@ default_connector_connections = [('Old Man Cave (West)', 'Old Man Cave Exit (Wes
('Elder House (West)', 'Elder House Exit (West)'),
('Two Brothers House (East)', 'Two Brothers House Exit (East)'),
('Two Brothers House (West)', 'Two Brothers House Exit (West)'),
('Bumper Cave (Top)', 'Bumper Cave (top)'),
('Bumper Cave (Bottom)', 'Bumper Cave (bottom)'),
('Bumper Cave (Top)', 'Bumper Cave Exit (Top)'),
('Bumper Cave (Bottom)', 'Bumper Cave Exit (Bottom)'),
('Superbunny Cave (Top)', 'Superbunny Cave Exit (Top)'),
('Superbunny Cave (Bottom)', 'Superbunny Cave Exit (Bottom)'),
('Hookshot Cave', 'Hookshot Cave Front Exit'),

83
Main.py
View File

@@ -14,7 +14,7 @@ from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
from OverworldGlitchRules import create_owg_connections
from PotShuffle import shuffle_pots, shuffle_pot_switches
from Regions import create_regions, create_shops, mark_light_world_regions, mark_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 OverworldShuffle import link_overworld, update_world_regions, create_flute_exits
from EntranceShuffle import link_entrances
@@ -187,6 +187,7 @@ def main(args, seed=None, fish=None):
link_overworld(world, player)
create_shops(world, player)
update_world_regions(world, player)
mark_light_dark_world_regions(world, player)
create_flute_exits(world, player)
logger.info(world.fish.translate("cli","cli","shuffling.world"))
@@ -204,10 +205,7 @@ def main(args, seed=None, fish=None):
for player in range(1, world.players + 1):
link_doors(world, player)
if world.mode[player] != 'inverted':
mark_light_world_regions(world, player)
else:
mark_dark_world_regions(world, player)
mark_light_dark_world_regions(world, player)
logger.info(world.fish.translate("cli", "cli", "generating.itempool"))
for player in range(1, world.players + 1):
@@ -439,6 +437,8 @@ def copy_world(world):
ret.owFluteShuffle = world.owFluteShuffle.copy()
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.shufflelinks = world.shufflelinks.copy()
ret.shuffle_ganon = world.shuffle_ganon.copy()
ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy()
ret.enemy_health = world.enemy_health.copy()
@@ -456,6 +456,7 @@ def copy_world(world):
ret.owflutespots = world.owflutespots.copy()
ret.prizes = world.prizes.copy()
ret.restrict_boss_items = world.restrict_boss_items.copy()
ret.inaccessible_regions = world.inaccessible_regions.copy()
for player in range(1, world.players + 1):
create_regions(ret, player)
@@ -464,6 +465,7 @@ def copy_world(world):
create_owg_connections(ret, player)
create_flute_exits(ret, player)
create_dungeon_regions(ret, player)
create_owedges(ret, player)
create_shops(ret, player)
create_rooms(ret, player)
create_dungeons(ret, player)
@@ -502,6 +504,10 @@ def copy_world(world):
location.parent_region = copied_region
for entrance in region.entrances:
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
for exit in region.exits:
if exit.connected_region:
dest_region = ret.get_region(exit.connected_region.name, region.player)
ret.get_entrance(exit.name, exit.player).connect(dest_region)
# fill locations
for location in world.get_locations():
@@ -515,6 +521,8 @@ def copy_world(world):
new_location.event = True
if location.locked:
new_location.locked = True
if location.skip:
new_location.skip = True
# these need to be modified properly by set_rules
new_location.access_rule = lambda state: True
new_location.item_rule = lambda state: True
@@ -532,15 +540,21 @@ def copy_world(world):
ret.state.prog_items = world.state.prog_items.copy()
ret.state.stale = {player: True for player in range(1, world.players + 1)}
ret.owedges = world.owedges
for edge in world.owedges:
if edge.dest:
copiededge = ret.check_for_owedge(edge.name, edge.player)
copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player)
# everything below this line is changing the original object, seems to be complicated to replicate similar objects organically
ret.doors = world.doors
for door in ret.doors:
entrance = ret.check_for_entrance(door.name, door.player)
if entrance is not None:
entrance.door = door
copied_entrance = ret.check_for_entrance(door.entrance.name, door.player)
door.entrance = copied_entrance
if copied_entrance:
copied_entrance.door = door
ret.paired_doors = world.paired_doors
ret.rooms = world.rooms
ret.inaccessible_regions = world.inaccessible_regions
ret.dungeon_layouts = world.dungeon_layouts
ret.key_logic = world.key_logic
ret.dungeon_portals = world.dungeon_portals
@@ -558,7 +572,7 @@ def copy_world(world):
return ret
def copy_world_limited(world):
def copy_world_premature(world, player):
# ToDo: Not good yet
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,
@@ -601,6 +615,8 @@ def copy_world_limited(world):
ret.owFluteShuffle = world.owFluteShuffle.copy()
ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy()
ret.open_pyramid = world.open_pyramid.copy()
ret.shufflelinks = world.shufflelinks.copy()
ret.shuffle_ganon = world.shuffle_ganon.copy()
ret.boss_shuffle = world.boss_shuffle.copy()
ret.enemy_shuffle = world.enemy_shuffle.copy()
ret.enemy_health = world.enemy_health.copy()
@@ -618,10 +634,10 @@ def copy_world_limited(world):
ret.owflutespots = world.owflutespots.copy()
ret.prizes = world.prizes.copy()
ret.restrict_boss_items = world.restrict_boss_items.copy()
ret.key_logic = world.key_logic.copy()
ret.is_copied_world = True
for player in range(1, world.players + 1):
create_regions(ret, player)
update_world_regions(ret, player)
if world.logic[player] in ('owglitches', 'nologic'):
@@ -634,7 +650,6 @@ def copy_world_limited(world):
create_rooms(ret, player)
create_dungeons(ret, player)
for player in range(1, world.players + 1):
if world.mode[player] == 'standard':
parent = ret.get_region('Menu', player)
target = ret.get_region('Hyrule Castle Secret Entrance', player)
@@ -643,40 +658,44 @@ def copy_world_limited(world):
connection.connect(target)
# connect copied world
copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations
copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations() if loc.player == player} # caches all locations
for region in world.regions:
if region.player == player:
copied_region = ret.get_region(region.name, region.player)
copied_region.is_light_world = region.is_light_world
copied_region.is_dark_world = region.is_dark_world
copied_region.dungeon = region.dungeon
if region.dungeon:
copied_region.dungeon = ret.get_dungeon(region.dungeon.name, region.player)
copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations]
for location in copied_region.locations:
location.parent_region = copied_region
for entrance in region.entrances:
ret.get_entrance(entrance.name, entrance.player).connect(copied_region)
copied_region.entrances.append(ret.get_entrance(entrance.name, entrance.player))
for exit in region.exits:
if exit.connected_region:
dest_region = ret.get_region(exit.connected_region.name, region.player)
ret.get_entrance(exit.name, exit.player).connect(dest_region)
from OverworldShuffle import categorize_world_regions
categorize_world_regions(ret, player)
for item in world.precollected_items:
if item.player == player:
ret.push_precollected(ItemFactory(item.name, item.player))
for edge in world.owedges:
if edge.dest is not None:
if edge.player == player and edge.dest:
copiededge = ret.check_for_owedge(edge.name, edge.player)
if copiededge is not None:
copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player)
for door in world.doors:
entrance = ret.check_for_entrance(door.name, door.player)
if entrance is not None:
destdoor = ret.check_for_door(entrance.door.name, entrance.door.player)
entrance.door = destdoor
if destdoor is not None:
destdoor.entrance = entrance
if door.player == player:
copied_entrance = ret.check_for_entrance(door.entrance.name, door.player)
door.entrance = copied_entrance
if copied_entrance:
copied_entrance.door = door
for player, portals in world.dungeon_portals.items():
for portal in portals:
connect_portal(portal, ret, player)
ret.key_logic = world.key_logic.copy()
from OverworldShuffle import categorize_world_regions
for player in range(1, world.players + 1):
categorize_world_regions(ret, player)
set_rules(ret, player)
return ret
@@ -713,6 +732,8 @@ def create_playthrough(world):
# get locations containing progress items
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
optional_locations.extend(['Hyrule Castle Courtyard Tree Pull', 'Mountain Entry Area Tree Pull']) # adding pre-aga tree pulls
optional_locations.extend(['Lumberjack Area Bush Crab', 'South Pass Area Bush Crab']) # adding pre-aga bush crabs
state_cache = [None]
collection_spheres = []
state = CollectionState(world)

View File

@@ -334,7 +334,7 @@ def set_owg_rules(player, world, connections, default_rule):
glitch_regions = (['Central Cliffs', 'Eastern Cliff', 'Desert Northeast Cliffs'],
['Dark Central Cliffs', 'Darkness Cliff', 'Mire Northeast Cliffs'])
# same screen clips, no OWR tile swap implications
# same screen clips, no Tile Flip OWR implications
boots_clips_local = [ # (name, from_region, to_region)
('Hera Ascent Clip', 'West Death Mountain (Bottom)', 'West Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists
('WDDM Bomb Clip', 'West Dark Death Mountain (Bottom)', 'West Dark Death Mountain (Top)'), #cannot guarantee camera correction, but a bomb clip exists
@@ -386,7 +386,7 @@ boots_clips_local = [ # (name, from_region, to_region)
# Common structure for cross-screen connections:
# (name, from_region, to_region) <- each three consists of [LW, DW]
# This is so OWR Tile Swap can properly connect both connections, and simultaneously be aware of which one requires pearl
# This is so Tile Flip OWR can properly connect both connections, and simultaneously be aware of which one requires pearl
# Note: Some clips have no way to reach the OOB area, and others have no way to get from the OOB area
# to a proper destination, these are marked with 'None'; these connections will not be made
boots_clips = [

View File

@@ -2,12 +2,12 @@ import RaceRandom as random, logging, copy
from collections import OrderedDict, defaultdict
from DungeonGenerator import GenerationException
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
from Regions import mark_dark_world_regions, mark_light_world_regions
from Regions import mark_light_dark_world_regions
from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel
from OverworldGlitchRules import create_owg_connections
from Utils import bidict
version_number = '0.2.11.0'
version_number = '0.2.11.1'
# branch indicator is intentionally different across branches
version_branch = ''
@@ -73,7 +73,7 @@ def link_overworld(world, player):
raise Exception('Cannot move a parallel edge without the other')
new_mode = OpenStd.Open
if tuple((OpenStd.Open, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)) not in new_groups.keys():
# when Links House tile is swapped, the DW edges need to get put into existing Standard group
# when Links House tile is flipped, the DW edges need to get put into existing Standard group
new_mode = OpenStd.Standard
new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][0].append(forward_set)
new_groups[(new_mode, WorldType((int(wrld) + 1) % 2), dir, terrain, parallel, count)][1].append(back_set)
@@ -103,7 +103,7 @@ def link_overworld(world, player):
else:
raise NotImplementedError('Cannot move one side of a non-parallel connection')
else:
raise NotImplementedError('Invalid OW Edge swap scenario')
raise NotImplementedError('Invalid OW Edge flip scenario')
return new_groups
tile_groups = define_tile_groups(world, player, False)
@@ -143,7 +143,7 @@ def link_overworld(world, player):
trimmed_groups = reorganize_groups(world, trimmed_groups, player)
# tile shuffle
logging.getLogger('').debug('Swapping overworld tiles')
logging.getLogger('').debug('Flipping overworld tiles')
if world.owMixed[player]:
swapped_edges = shuffle_tiles(world, tile_groups, world.owswaps[player], False, player)
@@ -199,8 +199,8 @@ def link_overworld(world, player):
if world.owCrossed[player] in ['grouped', 'limited'] or (world.owShuffle[player] == 'vanilla' and world.owCrossed[player] == 'chaos'):
if world.owCrossed[player] == 'grouped':
# the idea is to XOR the new swaps with the ones from Mixed so that non-parallel edges still work
# Polar corresponds to Grouped with no swaps in ow_crossed_tiles_mask
# the idea is to XOR the new flips with the ones from Mixed so that non-parallel edges still work
# Polar corresponds to Grouped with no flips in ow_crossed_tiles_mask
ow_crossed_tiles_mask = [[],[],[]]
crossed_edges = shuffle_tiles(world, define_tile_groups(world, player, True), ow_crossed_tiles_mask, True, player)
ow_crossed_tiles = [i for i in range(0x82) if (i in world.owswaps[player][0]) != (i in ow_crossed_tiles_mask[0])]
@@ -252,7 +252,7 @@ def link_overworld(world, player):
elif edge in parallel_links_new.inverse:
crossed_edges.append(parallel_links_new.inverse[edge][0])
# after tile swap and crossed, determine edges that need to swap
# after tile flip and crossed, determine edges that need to flip
edges_to_swap = [e for e in swapped_edges+crossed_edges if (e not in swapped_edges) or (e not in crossed_edges)]
# whirlpool shuffle
@@ -307,9 +307,9 @@ def link_overworld(world, player):
logging.getLogger('').debug('Shuffling overworld layout')
if world.owShuffle[player] == 'vanilla':
# apply outstanding swaps
# apply outstanding flips
trimmed_groups = performSwap(trimmed_groups, edges_to_swap)
assert len(edges_to_swap) == 0, 'Not all edges were swapped successfully: ' + ', '.join(edges_to_swap)
assert len(edges_to_swap) == 0, 'Not all edges were flipped successfully: ' + ', '.join(edges_to_swap)
# vanilla transitions
groups = list(trimmed_groups.values())
@@ -619,8 +619,8 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player):
attempts = 1000
while True:
if attempts == 0: # expected to only occur with custom swaps
raise GenerationException('Could not find valid tile swaps')
if attempts == 0: # expected to only occur with custom flips
raise GenerationException('Could not find valid tile flips')
# tile shuffle happens here
removed = list()
@@ -702,7 +702,7 @@ def define_tile_groups(world, player, do_grouped):
if world.mode[player] == 'standard' and (0x1b in group or 0x2b in group or 0x2c in group):
return False
# sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen
# sanctuary/chapel should not be flipped if S+Q guaranteed to output on that screen
if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \
and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \
or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')):
@@ -843,8 +843,7 @@ def categorize_world_regions(world, player):
for exitname in OWExitTypes[type]:
world.get_entrance(exitname, player).spot_type = type
mark_light_world_regions(world, player)
mark_dark_world_regions(world, player)
mark_light_dark_world_regions(world, player)
def update_world_regions(world, player):
if world.owMixed[player]:
@@ -904,13 +903,13 @@ def can_reach_smith(world, player):
return found
def build_sectors(world, player):
from Main import copy_world_limited
from Main import copy_world_premature
from OWEdges import OWTileRegions
# perform accessibility check on duplicate world
for p in range(1, world.players + 1):
world.key_logic[p] = {}
base_world = copy_world_limited(world)
base_world = copy_world_premature(world, player)
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
regions = list(OWTileRegions.copy().keys())
@@ -970,7 +969,7 @@ def build_sectors(world, player):
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges = False):
from BaseClasses import CollectionState
from Main import copy_world_limited
from Main import copy_world_premature
from Items import ItemFactory
from Utils import stack_size3a
@@ -997,7 +996,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
if build_copy_world:
for p in range(1, world.players + 1):
world.key_logic[p] = {}
base_world = copy_world_limited(world)
base_world = copy_world_premature(world, player)
base_world.override_bomb_check = True
else:
base_world = world
@@ -1040,7 +1039,7 @@ def validate_layout(world, player):
'Pyramid Area': ['Pyramid Exit Ledge']
}
from Main import copy_world_limited
from Main import copy_world_premature
from Utils import stack_size3a
from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
@@ -1073,7 +1072,7 @@ def validate_layout(world, player):
for p in range(1, world.players + 1):
world.key_logic[p] = {}
base_world = copy_world_limited(world)
base_world = copy_world_premature(world, player)
explored_regions = list()
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:

View File

@@ -55,18 +55,18 @@ This is a common sentiment among those who are unfamiliar with OWR's offerings.
OWR definitely has a lot of options, and all of them by themselves are pretty simple to grasp, but combining multiple OWR options together increases the complexity and confusion in exponential fashion. Now, of course, some OWR options like Flute Shuffle can safely be combined at any level and isn't gonna make anything more complicated. But specifically, avoid combining these 3 options, at least when going for your first seed:
- OW Layout Shuffle
- OW Tile Swap (Mixed)
- OW Tile Flip (Mixed)
- Crossed OW
## "Any recommendations for a first-timer?"
For a first (and second) seed... *and I say "second" because I feel like both of these recommendations I'm about to make have VERY different vibes, have different levels of challenge, but are both, of their own right, worthy of being tried at least once.* Your first OWR experience can be combined with any mode combination that you are already familiar with and have a lot of experience in playing. If you like Crosskeys and feel very comfortable running that, feel free to turn on all those settings in addition to ONE of these two options:
1. `OW Tile Swap (Mixed)` - Overly, a pretty easy-breezy mode, it doesn't require too much big brain, and is pretty managable even without proper logic tracking, as long as you at least have a standard map tracker. This is actually my favorite way to run OWR today
1. `OW Tile Flip (Mixed)` - Overly, a pretty easy-breezy mode, it doesn't require too much big brain, and is pretty managable even without proper logic tracking, as long as you at least have a standard map tracker. This is actually my favorite way to run OWR today
- DO NOT turn on Layout or Whirlpool Shuffle, leave this on `Vanilla`
- DO NOT turn on Crossed OWR
- `Flute Shuffle` or `Bonk Drops` could be enabled if desired, altho I'd recommend against it, at least for a fresh viewpoint of Mixed OWR
2. `OW Layout Shuffle` - Set to `Parallel`. This is the original spirit and vision of OWR from the time of its own founding. It's definitely much more complicated to run than OW Tile Swap, so keep that in mind.
2. `OW Layout Shuffle` - Set to `Parallel`. This is the original spirit and vision of OWR from the time of its own founding. It's definitely much more complicated to run than OW Tile Flip, so keep that in mind.
- `Starting Boots` - Either actual boots or pseudoboots, you will be spending a lot of time navigating the OW, so it's best to do it with the ability to run fast.
- DO NOT turn on OW Tile Swap (Mixed)
- DO NOT turn on OW Tile Flip (Mixed)
- DO NOT turn on Crossed OWR
- Enable `Whirlpool Shuffle` - Recommended to always be enabled with Layout Shuffle
- Enable `Keep Similar Edges Together` - This just helps keep some of your sanity for a first experience
@@ -134,7 +134,7 @@ Note: These changes do impact the logic. If you use `CodeTracker`, these Inverte
Only settings specifically added by this Overworld Shuffle fork are found here. All door and entrance randomizer settings are supported. See their [readme](https://github.com/Aerinon/ALttPDoorRandomizer/blob/master/README.md)
## Overworld Layout Shuffle (--ow_shuffle)
OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://media.discordapp.net/attachments/783989090017738753/857299555183362078/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Swap)
OW Edge Transitions are shuffled to create new world layouts. A brief visual representation of this can be viewed [here](https://media.discordapp.net/attachments/783989090017738753/857299555183362078/ow-modes.gif). (This graphic also includes combinations of Crossed and Tile Flip)
### Vanilla
@@ -168,11 +168,11 @@ Transitions will remain same-world.
### Grouped
This option shuffles connections cross-world in the same manner as Tile Swap/Mixed, the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR.
This option shuffles connections cross-world in the same manner as Tile Flip (Mixed), the connections coming in and going out of a Tile Group (see `Terminology` section above) are crossed (ie. meaning it is impossible to take a different path to a tile and end up in the opposite world, unlike Limited and Chaos). This is considered the simplest way to play Crossed OWR.
### Polar
Only effective if Mixed/Tile Swap is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are swapped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Mixed/Tile Swap works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Swap/Mixed` section for more details)
Only effective if Tile Flip (Mixed) is enabled. Polar follows the same principle as Grouped, except that it preserves the original/vanilla connections even when tiles are flipped/mixed. This results in a completely vanilla overworld, except that some tiles will transform Link to a Bunny. Even though these tiles give the appearance of your normal LW tile, due to how Tile Flip works, those LW tiles give DW properties (such as bunnying, ability to mirror, and prevents flute usage). This offers an interesting twist on Mixed where you have a pre-conditioned knowledge of the terrain you will encounter, but not necessarily be able to do what you need to do there, due to bunny state. (see `Tile Flip / Mixed` section for more details)
### Limited
@@ -194,17 +194,32 @@ This keeps similar edge transitions together. ie. The 2 west land edges of Potio
Note: This affects OW Layout Shuffle mostly, but also affects Limited and Chaos modes in Crossed OW.
## Tile Swap / Mixed Overworld (--ow_mixed)
## Tile Flip / Mixed Overworld (--ow_mixed)
Tile Swap (often referred to as Mixed OWR) can be thought of as a hybrid of Open and Inverted, where OW tiles are randomly chosen to be swapped to become a part of the opposite world. When this occurs, that tile will use the Inverted version of that tile. For instance, if the Cave 45 tile becomes swapped, that means while walking around in the LW, you will find the screen that's south of Stumpy instead, and Cave 45 will instead be found in the DW; but like Inverted, the Cave 45 tile is modified to not have a ledge, this ensures that it will be possible to access it.
Tile Flip (often referred to as Mixed OWR) can be thought of as a hybrid of Open and Inverted, where OW tiles are randomly chosen to be flipped to become a part of the opposite world. When this occurs, that tile will use the Inverted version of that tile. For instance, if the Cave 45 tile becomes flipped, that means while walking around in the LW, you will find the screen that's south of Stumpy instead, and Cave 45 will instead be found in the DW; but like Inverted, the Cave 45 tile is modified to not have a ledge, this ensures that it will be possible to access it.
Being that this uses concepts from Inverted, it will be important to review the OWR-exclusive changes that have been made to Inverted (often referred to as Inverted 2.0). See `Inverted Changes` for more details.
During gameplay:
- When on the OW, there will be an L or D in the upper left corner, indicating which world you are currently in. Mirroring still works the same, you must be in the DW to mirror to the LW.
- When doing a map check (pressing X while on the OW), the tiles shown will reflect the swapped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world, beware of Desert/Mire and Eastern/PoD. Here is an image showing the difference of appearance when tiles are swapped on the [map check](https://media.discordapp.net/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif) screen.
- When doing a map check (pressing X while on the OW), the tiles shown will reflect the flipped tiles. This means that dungeon prizes will show the prizes for the dungeons that are now part of that world, beware of Desert/Mire and Eastern/PoD. Here is an image showing the difference of appearance when tiles are flipped on the [map check](https://media.discordapp.net/attachments/783989090017738753/970646558049714196/lttp-lw-mapcheck.gif) screen.
Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must swap together.
Note: Tiles are put into Tile Groups (see `Terminology`) that must be shuffled together when certain settings are enabled. For instance, if ER is disabled, then any tiles that have a connector cave that leads to a different tile, then those tiles must flip together.
## Tile Flip vs Crossed Explained
The above OWR options are very difficult to describe. The above descriptions are written in a way that is most correct even when these options are combined with more complicated modes. But, this section aims to simplify the explanation by assuming the user chooses a normal 'Open 7/7 Defeat Ganon' seed but with just one OWR setting enabled.
Both of these options are very similar and often confused from each other.
- Tile Flip is a mode where some DW tiles are moved and BECOME part of the LW (and the LW counterparts become part of the DW). What does it mean to "become" part of a world? It means that it will inherit (NOT bring over) the properties of that world it is moving to (such as being able to flute, ability to use the mirror, or being susceptible to bunnying).
- Crossed on the other hand doesn't change the properties of tiles, instead it transports Link *physically?* across worlds upon transitioning. This also means that Link can be transformed into a bunny moving from tile to tile.
tldr: Tile Flip moves the tiles, Crossed moves Link
So, let's run an example of 2 tiles, Link's House and the screen to the right of it. Transitioning right from Link's House: In vanilla, you get the Stone Bridge screen and Link stays his normal self and is just normal LW behavior. Now, let's assume Links House screen stays vanilla, but the tile to the right is getting Flipped or Crossed.
- In Tile Flip, you'd get the Hammer Bridge screen and Link would stay as Link and you'd be able to flute away from this screen if you had a flute.
- In Crossed, you'd get the same Hammer Bridge Screen, but this time Link would be transformed into a bunny, just like he'd normally be when on that tile.
- In Polar Crossed (when both Tile Flip and Crossed effects are applied together), you get the normal Stone Bridge screen, but Link is transformed to a bunny (because the Stone Bridge screen has moved to the DW AND Link is also moving across worlds).
As you can see, things get pretty complicated when mixing modes together. Doing this can definitely create a very unique and interesting experience, but one that is very hard to grasp. And then beyond that there is OW Layout Shuffle, which is where transition destinations are shuffled, so Link will get transported to a different tile entirely, but the same rules apply when you eventually find the Stone/Hammer Bridge screen, you just likely won't find that screen thru a transition on Link's House screen.
## Whirlpool Shuffle (--ow_whirlpool)
@@ -350,7 +365,7 @@ This keeps similar edge transitions paired together with other pairs of transiti
--ow_mixed
```
This gives each OW tile a random chance to be swapped to the opposite world
This gives each OW tile a random chance to be flipped to the opposite world
```
--ow_fluteshuffle <mode>

View File

@@ -1076,9 +1076,10 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address))
return ret
def mark_light_world_regions(world, player):
def mark_light_dark_world_regions(world, player):
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
# That is ok. the bunny logic will check for this case and incorporate special rules.
def mark_light():
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld)
seen = set(queue)
while queue:
@@ -1092,6 +1093,7 @@ def mark_light_world_regions(world, player):
seen.add(exit.connected_region)
queue.append(exit.connected_region)
def mark_dark():
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld)
seen = set(queue)
while queue:
@@ -1106,36 +1108,13 @@ def mark_light_world_regions(world, player):
seen.add(exit.connected_region)
queue.append(exit.connected_region)
def mark_dark_world_regions(world, player):
# cross world caves may have some sections marked as both in_light_world, and in_dark_work.
# That is ok. the bunny logic will check for this case and incorporate special rules.
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.DarkWorld)
seen = set(queue)
while queue:
current = queue.popleft()
current.is_dark_world = True
for exit in current.exits:
if exit.connected_region is None or exit.connected_region.type == RegionType.LightWorld: # todo: remove none check
# Don't venture into the light world
continue
if exit.connected_region not in seen:
seen.add(exit.connected_region)
queue.append(exit.connected_region)
queue = collections.deque(region for region in world.get_regions(player) if region.type == RegionType.LightWorld)
seen = set(queue)
while queue:
current = queue.popleft()
current.is_light_world = True
for exit in current.exits:
if exit.connected_region is not None:
if exit.connected_region.type == RegionType.DarkWorld:
# Don't venture into the dark world
continue
if exit.connected_region not in seen:
seen.add(exit.connected_region)
queue.append(exit.connected_region)
# Note: I don't see why the order would matter, but the original Inverted code reversed the order
if world.mode[player] != 'inverted':
mark_light()
mark_dark()
else:
mark_dark()
mark_light()
def create_shops(world, player):

View File

@@ -2111,7 +2111,7 @@ def eval_small_key_door_main(state, door_name, dungeon, player):
elif ruleType == KeyRuleType.AllowSmall:
if (door_rule.small_location.item and door_rule.small_location.item.name == key_logic.small_key_name
and door_rule.small_location.item.player == player):
return True # always okay if allow small is on
door_openable |= state.has_sm_key(key_logic.small_key_name, player, number)
elif isinstance(ruleType, tuple):
lock, lock_item = ruleType
# this doesn't track logical locks yet, i.e. hammer locks the item and hammer is there, but the item isn't

View File

@@ -221,8 +221,8 @@
"None: No transitions are cross-world connections.",
"Grouped: This ensures a two-plane separation so that you cannot",
" walk around and access the other plane version by walking.",
"Polar: Only used when Mixed is enabled. This retains original",
" connections even when overworld tiles are swapped.",
"Polar: Only used when Tile Flip is enabled. This retains original",
" connections even when overworld tiles are flipped.",
"Limited: Exactly nine transitions are randomly chosen as",
" cross-world connections (to emulate the nine portals).",
"Chaos: Every transition has a 50/50 chance to become a",

View File

@@ -142,7 +142,7 @@
"randomizer.overworld.keepsimilar": "Keep Similar Edges Together",
"randomizer.overworld.mixed": "Tile Swap (Mixed)",
"randomizer.overworld.mixed": "Tile Flip (Mixed)",
"randomizer.overworld.whirlpool": "Whirlpool Shuffle",