From 4c2887c0a14ead83a5025e17961ea95e8cfe4293 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 15 Nov 2021 16:23:53 -0700 Subject: [PATCH 01/33] Fix baserom --- Rom.py | 2 +- data/base2current.bps | Bin 136415 -> 136173 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index f3b3cb68..ad4ce2d4 100644 --- a/Rom.py +++ b/Rom.py @@ -32,7 +32,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '2cfa164d4b66a15406f53ca4750ef59a' +RANDOMIZERBASEHASH = '7511c811385f87c6ae0909e7bbe379cb' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 328d0f8196c9f52c4e8bb095e2ff83a9157dc2ea..12fb7ccd3056db6ef6002726b78a903d201b0ea0 100644 GIT binary patch delta 8553 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 delta 8925 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 From 500ec0717483bc0bd3b5643fc58f491bd317f945 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 19 Nov 2021 16:03:41 -0700 Subject: [PATCH 02/33] Add bomb assumptions to certain bosses --- Bosses.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Bosses.py b/Bosses.py index 90acafcf..d577c66d 100644 --- a/Bosses.py +++ b/Bosses.py @@ -32,7 +32,7 @@ def LanmolasDefeatRule(state, player): state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Cane of Somaria', player) or - state.has('Cane of Byrna', player) or + (state.has('Cane of Byrna', player) and state.can_use_bombs(player)) or state.can_shoot_arrows(player)) def MoldormDefeatRule(state, player): @@ -51,7 +51,7 @@ def ArrghusDefeatRule(state, player): return True return ((state.has('Fire Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 12))) or #assuming mostly gitting two puff with one shot - (state.has('Ice Rod', player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) + (state.has('Ice Rod', player) and state.can_use_bombs(player) and (state.can_shoot_arrows(player) or state.can_extend_magic(player, 16)))) def MothulaDefeatRule(state, player): @@ -92,7 +92,7 @@ def KholdstareDefeatRule(state, player): ) def VitreousDefeatRule(state, player): - return state.can_shoot_arrows(player) or state.has_blunt_weapon(player) + return (state.can_shoot_arrows(player) and state.can_use_bombs(player)) or state.has_blunt_weapon(player) def TrinexxDefeatRule(state, player): if not (state.has('Fire Rod', player) and state.has('Ice Rod', player)): From 10a440e4ee8280599218a3665ae7cab2c222a43e Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 19 Nov 2021 16:09:00 -0700 Subject: [PATCH 03/33] Potential fix for money balancing --- Fill.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Fill.py b/Fill.py index 2cf1f25e..6a5a8fc9 100644 --- a/Fill.py +++ b/Fill.py @@ -762,9 +762,12 @@ def balance_money_progression(world): if sphere_costs[player] > 0 and sphere_costs[player] > wallet[player]: insolvent.add(player) if len([p for p in solvent if len(locked_by_money[p]) > 0]) == 0: - target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p]) - difference = sphere_costs[target_player]-wallet[target_player] - logger.debug(f'Money balancing needed: Player {target_player} short {difference}') + if len(insolvent) > 0: + target_player = min(insolvent, key=lambda p: sphere_costs[p]-wallet[p]) + difference = sphere_costs[target_player]-wallet[target_player] + logger.debug(f'Money balancing needed: Player {target_player} short {difference}') + else: + difference = 0 while difference > 0: swap_targets = [x for x in unchecked_locations if x not in sphere_locations and x.item.name.startswith('Rupees') and x.item.player == target_player] if len(swap_targets) == 0: From 2826b1967f472ecbddd861697d98dd9d63d98d85 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 24 Nov 2021 15:58:19 -0600 Subject: [PATCH 04/33] Fix mirror portals getting erased when changing worlds --- asm/owrando.asm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/asm/owrando.asm b/asm/owrando.asm index 0fc9716a..f0465953 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -149,7 +149,10 @@ 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 From 745c82d95758a9102e5fe5bf7c38d75c26e034e6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 26 Nov 2021 00:36:24 -0600 Subject: [PATCH 05/33] Minor formatting --- OverworldShuffle.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index f5729ab3..bbfeb4ec 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -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 From 7861b4e8a764d491d2e05d08bf9e78ef619e7ee1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 27 Nov 2021 06:25:30 -0600 Subject: [PATCH 06/33] Moved OW sector and region search to OW class --- EntranceShuffle.py | 98 ++------------------------------------------- OverworldShuffle.py | 94 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 95 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 5b3b821d..f1047d7d 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -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,6 +29,7 @@ def link_entrances(world, player): Old_Man_House = Old_Man_House_Base.copy() Cave_Three_Exits = Cave_Three_Exits_Base.copy() + from OverworldShuffle import build_sectors sectors = build_sectors(world, player) # modifications to lists @@ -1634,100 +1636,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 diff --git a/OverworldShuffle.py b/OverworldShuffle.py index bbfeb4ec..e2e15dd2 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -820,6 +820,100 @@ 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 + from Utils import stack_size3a + + 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 + test_connections = [ #('Links House ES', 'Octoballoon WS'), #('Links House NE', 'Lost Woods Pass SW') From 3e43b12508e21a813079d242848c0afb2aedc2f3 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 30 Nov 2021 03:40:43 -0600 Subject: [PATCH 07/33] Caching OW sectors in World object for future shared use between ER and OWR --- BaseClasses.py | 6 ++++-- EntranceShuffle.py | 47 +++++++++++++++++++++++----------------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ad51f12d..9acbaeaf 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -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)']) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index f1047d7d..1017e6d7 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -30,7 +30,8 @@ def link_entrances(world, player): Cave_Three_Exits = Cave_Three_Exits_Base.copy() from OverworldShuffle import build_sectors - sectors = build_sectors(world, player) + 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]): @@ -243,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) @@ -273,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) @@ -333,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() @@ -410,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) @@ -496,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) @@ -544,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) @@ -618,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) @@ -1373,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' @@ -1385,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]] @@ -1399,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]] @@ -1674,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 @@ -1683,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) @@ -1707,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 From b4173044d7ddda09a1964ae98b152b0aa2c4c3ab Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 30 Nov 2021 11:27:44 -0600 Subject: [PATCH 08/33] Modeled Woods Pass regions more correctly --- OWEdges.py | 20 ++++++++++++++------ OverworldShuffle.py | 18 ++++++++++++------ Regions.py | 10 ++++++---- Rules.py | 25 +++++++++++++++++-------- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/OWEdges.py b/OWEdges.py index f13e3b32..8e1917cc 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -720,6 +720,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 +863,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, @@ -1518,6 +1520,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 +1593,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 +1640,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 +1650,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 +1693,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 +1784,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', diff --git a/OverworldShuffle.py b/OverworldShuffle.py index e2e15dd2..3154c961 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -954,6 +954,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 @@ -1041,8 +1045,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 @@ -1252,15 +1258,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') diff --git a/Regions.py b/Regions.py index a5d3309d..b4a48d73 100644 --- a/Regions.py +++ b/Regions.py @@ -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']), diff --git a/Rules.py b/Rules.py index 535eb6ec..efadaf6e 100644 --- a/Rules.py +++ b/Rules.py @@ -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) From 4e4b08da4f9590bbc37dddee745ed7d74e1df435 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 30 Nov 2021 11:37:20 -0600 Subject: [PATCH 09/33] Initial approach to eliminating inaccessible regions resulting from OW Layout Shuffle --- OverworldShuffle.py | 105 ++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 38 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 3154c961..1612b147 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -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): @@ -913,6 +923,20 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F 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'), @@ -1670,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), From db6403db2c0ac23cd7b6bdc22ace55698f38ffe6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 4 Dec 2021 17:20:02 -0600 Subject: [PATCH 10/33] Remove import --- OverworldShuffle.py | 1 - 1 file changed, 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 1612b147..48593531 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -888,7 +888,6 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F from Main import copy_world from BaseClasses import CollectionState from Items import ItemFactory - from Utils import stack_size3a def explore_region(region_name, region=None): explored_regions.add(region_name) From 838ada0a0bde94007b1c4149c11ed00bdbe56896 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 6 Dec 2021 07:43:38 -0600 Subject: [PATCH 11/33] Fixed mirror portals showing up in DW --- Rom.py | 2 +- asm/owrando.asm | 9 +++++++++ data/base2current.bps | Bin 141365 -> 141395 bytes 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 30729de4..2fe1bdfa 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '383fbac5f195f82d2b74b62113229fac' +RANDOMIZERBASEHASH = '607ec23701b1099fdeb64a0caff190b6' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index f0465953..f239b436 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -148,6 +148,7 @@ OWPreserveMirrorSprite: { lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla rtl ; if OW Crossed, skip world check and continue + .vanilla lda InvertedMode : beq + lda $7ef3ca : beq .deleteMirror @@ -410,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 diff --git a/data/base2current.bps b/data/base2current.bps index db61bf4dc810344f4da2cd6d315433687ca0d576..022ded6aaf463776b1c65a008fadf21a177ec305 100644 GIT binary patch delta 183 zcmdmbf#dQ8jt!oSEDJxx@i+T2ZuezmT*Ydc+rX;OHhC#)Yu6!zg#8S=R-Hffx$Z-H zKaf1h@PTP1<9V+Jg9}`1&r50Y_nwz(-~h5b8-T3og6xdq%3A!()_OXebhseAH1;IR z2bR^+mCNQf@B%sF7ntUAH^|?Q+3wBG*kG15;u0sY1`x$nvI)CbO-3QNp zAUWNDol#slk8jypPluBZ7lfC_o@DvJvO2zE+1v(RAV>TH(|qm*@dpaqv)CCM%rz#( zTdi5k{{R2?e?a&L2&c(etXcVA9|+&uudiQU-#2A)rJqd0c0qeatCt*p7P;?43N*9; D{-a15 From 37841ab311add9cf3026ce9af167f34332985133 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 6 Dec 2021 09:09:34 -0600 Subject: [PATCH 12/33] Fixed issue with OW Layout and OW Crossed failing layout validation --- OWEdges.py | 3 +++ OverworldShuffle.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/OWEdges.py b/OWEdges.py index 8e1917cc..fc307d8c 100644 --- a/OWEdges.py +++ b/OWEdges.py @@ -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) @@ -1392,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', diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 48593531..03614d59 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -901,8 +901,9 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F 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'): + 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: From aa2ea182325b927e1b5fd25dba6c2f54c204ae7b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 6 Dec 2021 09:21:35 -0600 Subject: [PATCH 13/33] Version bump 0.2.3.3 --- CHANGELOG.md | 5 +++++ OverworldShuffle.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 93f9b2f2..e5860f08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 03614d59..9a9ce9be 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -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 From 940837e7ee88908a01afbee0285bd7644ab1447f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 6 Dec 2021 09:29:57 -0600 Subject: [PATCH 14/33] Fixed issue with OW Layout and OW Crossed failing layout validation --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 9a9ce9be..d6c1e331 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -902,7 +902,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F 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'])) \ + or exit.name in OWExitTypes['OWEdge'] or (cross_world and exit.name in (OWExitTypes['Portal'] + OWExitTypes['Mirror']))) \ 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) From d710e08ea0de507da423c95944ee265a84c7bdf2 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 Dec 2021 15:25:55 -0700 Subject: [PATCH 15/33] Rules fixes - TT locations not foribben by default to have big key Can push paradox block with mirror in glitched modes --- Rules.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Rules.py b/Rules.py index cf19fbb1..83a2175b 100644 --- a/Rules.py +++ b/Rules.py @@ -274,9 +274,11 @@ def global_rules(world, player): set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: state.has('Hammer', player)) for entrance in ['Thieves Basement Block Path', 'Thieves Blocked Entry Path', 'Thieves Conveyor Block Path', 'Thieves Conveyor Bridge Block Path']: set_rule(world.get_entrance(entrance, player), lambda state: state.can_lift_rocks(player)) - for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: - forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player) - forbid_item(world.get_location('Thieves\' Town - Blind\'s Cell', player), 'Big Key (Thieves Town)', player) + + # I think these rules are unnecessary now - testing needed + # for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']: + # forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player) + # forbid_item(world.get_location('Thieves\' Town - Blind\'s Cell', player), 'Big Key (Thieves Town)', player) for location in ['Suspicious Maiden', 'Thieves\' Town - Blind\'s Cell']: set_rule(world.get_location(location, player), lambda state: state.has('Big Key (Thieves Town)', player)) set_rule(world.get_location('Revealing Light', player), lambda state: state.has('Shining Light', player) and state.has('Maiden Rescued', player)) @@ -712,7 +714,8 @@ def default_rules(world, player): set_rule(world.get_entrance('Broken Bridge (East)', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('East Death Mountain Teleporter', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Fairy Ascension Rocks', player), lambda state: state.can_lift_heavy_rocks(player)) - set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has('Mirror', player)) # can erase block + # can erase block - overridden in noglitches + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Death Mountain (Top)', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('Turtle Rock Teleporter', player), lambda state: state.can_lift_heavy_rocks(player) and state.has('Hammer', player)) set_rule(world.get_entrance('East Death Mountain (Top)', player), lambda state: state.has('Hammer', player)) @@ -849,7 +852,8 @@ def inverted_rules(world, player): set_rule(world.get_entrance('Broken Bridge (East)', player), lambda state: state.has('Hookshot', player) and state.has_Pearl(player)) set_rule(world.get_entrance('Dark Death Mountain Teleporter (East Bottom)', player), lambda state: state.can_lift_heavy_rocks(player)) set_rule(world.get_entrance('Fairy Ascension Rocks', player), lambda state: state.can_lift_heavy_rocks(player) and state.has_Pearl(player)) - set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has('Mirror', player)) # can erase block + # can erase block - overridden in noglitches + set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Death Mountain (Top)', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) set_rule(world.get_entrance('Dark Death Mountain Teleporter (East)', player), lambda state: state.can_lift_heavy_rocks(player) and state.has('Hammer', player) and state.has_Pearl(player)) # bunny cannot use hammer set_rule(world.get_entrance('East Death Mountain (Top)', player), lambda state: state.has('Hammer', player) and state.has_Pearl(player)) # bunny can not use hammer From 74155ec5ae4e29c8ed326ab7e03a760da007af1e Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 6 Dec 2021 15:28:19 -0700 Subject: [PATCH 16/33] Version and release note update --- Main.py | 2 +- RELEASENOTES.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 453e33e8..05363732 100644 --- a/Main.py +++ b/Main.py @@ -29,7 +29,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from Utils import output_path, parse_player_names -__version__ = '0.5.1.5-u' +__version__ = '0.5.1.6-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c1a7a62c..eaa45e2b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,6 +15,10 @@ CLI: ```--bombbag``` # Bug Fixes and Notes. +* 0.5.1.6 + * Rules fixes for TT (Boss and Cell) can now have TT Big Key if not otherwise required (boss shuffle + crossed dungeon) + * BUg fix for money balancing + * Add some bomb assumptions for bosses in bombbag mode * 0.5.1.5 * Fix for hard pool capacity upgrades missing * Bonk Fairy (Light) is no longer in logic for ER Standard and is forbidden to be a connector, so rain state isn't exitable From 7f871681bb0723ca10cdffb3b5c5f083f4878e96 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 10 Dec 2021 16:42:26 -0600 Subject: [PATCH 17/33] Added dynamic flute spot reservation --- OverworldShuffle.py | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index d6c1e331..62b7d6e4 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -265,6 +265,8 @@ def link_overworld(world, player): 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) + + world.owsectors[player] = build_sectors(world, player) else: if world.owKeepSimilar[player] and world.owShuffle[player] in ['vanilla', 'parallel']: for exitname, destname in parallelsimilar_connections: @@ -334,6 +336,7 @@ def link_overworld(world, player): assert valid_layout, 'Could not find a valid OW layout' # flute shuffle + logging.getLogger('').debug('Shuffling flute spots') def connect_flutes(flute_destinations): for o in range(0, len(flute_destinations)): owslot = flute_destinations[o] @@ -376,23 +379,37 @@ def link_overworld(world, player): flute_pool.remove(owid) new_spots.append(owid) return True + + # determine sectors (isolated groups of regions) to place flute spots + flute_regions = {(f[0][0] if f[1] not in world.owswaps[player][0] else f[0][1]) : o for o, f in flute_data.items()} + flute_sectors = [(len([r for l in s for r in l]), [r for l in s for r in l if r in flute_regions]) for s in world.owsectors[player]] + flute_sectors = [s for s in flute_sectors if len(s[1]) > 0] + region_total = sum([c for c,_ in flute_sectors]) + sector_total = len(flute_sectors) - # guarantee desert/mire access - addSpot(0x38) + # reserve a number of flute spots for each sector + flute_spots = 8 + for sector in flute_sectors: + sector_total -= 1 + spots_to_place = min(flute_spots - sector_total, max(1, round((sector[0] * (flute_spots - sector_total) / region_total) + 0.5))) + target_spots = len(new_spots) + spots_to_place + + if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]: + addSpot(0x38) # guarantee desert/mire access - # guarantee mountain access - if world.owShuffle[player] == 'vanilla': - mountainIds = [0x0b, 0x0e, 0x07] - addSpot(mountainIds[random.randint(0, 2)]) + random.shuffle(sector[1]) + f = 0 + while len(new_spots) < target_spots: + if f >= len(sector[1]): + f = 0 + if sector[1][f] not in new_spots: + addSpot(flute_regions[sector[1][f]]) + f += 1 - random.shuffle(flute_pool) - f = 0 - while len(new_spots) < 8: - if f >= len(flute_pool): - f = 0 - if flute_pool[f] not in new_spots: - addSpot(flute_pool[f]) - f += 1 + region_total -= sector[0] + flute_spots -= spots_to_place + + # connect new flute spots new_spots.sort() world.owflutespots[player] = new_spots connect_flutes(new_spots) From 4ae211e213991175c14e2b6ead0b24687f63b803 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sat, 11 Dec 2021 17:11:58 -0600 Subject: [PATCH 18/33] Fixed spoiler json structure for map output --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 9acbaeaf..f28a6d88 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2749,9 +2749,9 @@ class Spoiler(object): if type not in self.maps: self.maps[type] = {} if self.world.players == 1: - self.maps[type][player] = OrderedDict([('text', text), ('data', data)]) + self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)]) else: - self.maps[type][player] = OrderedDict([('player', player), ('text', text), ('data', data)]) + self.maps[(type, player)] = OrderedDict([('player', player), ('type', type), ('text', text), ('data', data)]) def set_entrance(self, entrance, exit, direction, player): if self.world.players == 1: From abfe08ab6ded91cf40563dfc82277cb858c2f4f6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 13 Dec 2021 23:17:29 -0600 Subject: [PATCH 19/33] Improvement to FF damage fix to insta-warp Link to destination when Link exceeds bounds of current screen --- Rom.py | 2 +- data/base2current.bps | Bin 141395 -> 141422 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 2fe1bdfa..6c57d091 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '607ec23701b1099fdeb64a0caff190b6' +RANDOMIZERBASEHASH = 'c5903112ffe302d1e9a3878ee720d1b3' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index 022ded6aaf463776b1c65a008fadf21a177ec305..e229d170797f13706d1ad802e3f48d116ebf9321 100644 GIT binary patch delta 7852 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 delta 7796 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 From c910785211b7cd573620fe29d4cb5ee069352afc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 16 Dec 2021 14:27:13 -0600 Subject: [PATCH 20/33] Fixed Lake Shop not having Blue Potion in Inverted --- Regions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Regions.py b/Regions.py index b4a48d73..8f8ee418 100644 --- a/Regions.py +++ b/Regions.py @@ -1121,9 +1121,11 @@ def mark_dark_world_regions(world, player): def create_shops(world, player): world.shops[player] = [] for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram) in shop_table.items(): - if (world.mode[player] == 'inverted') != (0x35 in world.owswaps[player][0] and world.owMixed[player]) and region_name == 'Dark Lake Hylia Shop': - locked = True - inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] + if world.mode[player] == 'inverted': + if (0x35 not in world.owswaps[player][0] and region_name == 'Dark Lake Hylia Shop') \ + or (0x35 in world.owswaps[player][0] and region_name == 'Cave Shop (Lake Hylia)'): + locked = True + inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] region = world.get_region(region_name, player) shop = Shop(region, room_id, type, shopkeeper, custom, locked, sram) region.shop = shop From fdbda7a9c1dc515d34ae1ad5da17ca2ed39bee87 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 16 Dec 2021 15:43:10 -0600 Subject: [PATCH 21/33] Fixed issue with spoiler output of OW Tile Swaps --- BaseClasses.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index f28a6d88..6fd15ba7 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2746,8 +2746,6 @@ class Spoiler(object): self.overworlds[(entrance, direction, player)] = OrderedDict([('player', player), ('entrance', entrance), ('exit', exit), ('direction', direction)]) def set_map(self, type, text, data, player): - if type not in self.maps: - self.maps[type] = {} if self.world.players == 1: self.maps[(type, player)] = OrderedDict([('type', type), ('text', text), ('data', data)]) else: @@ -3032,12 +3030,15 @@ class Spoiler(object): if self.overworlds: outfile.write('\n\nOverworld:\n\n') # overworld tile swaps - if 'swaps' in self.maps: - outfile.write('OW Tile Swaps:\n') - for player in self.maps['swaps']: + for player in range(1, self.world.players + 1): + if ('swaps', player) in self.maps: + outfile.write('OW Tile Swaps:\n') + break + for player in range(1, self.world.players + 1): + if ('swaps', player) in self.maps: if self.world.players > 1: outfile.write(str('(Player ' + str(player) + ')\n')) # player name - outfile.write(self.maps['swaps'][player]['text'] + '\n\n') + outfile.write(self.maps[('swaps', player)]['text'] + '\n\n') # overworld transitions outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()])) From 07a956a3a4d6fc56213244bd70239c07b3820943 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 16 Dec 2021 16:17:51 -0600 Subject: [PATCH 22/33] Fixed issue with logic not correctly placing Blue potion in Dark Lake Shop --- Main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Main.py b/Main.py index 2ef4ccc6..a4dc5d68 100644 --- a/Main.py +++ b/Main.py @@ -175,6 +175,7 @@ def main(args, seed=None, fish=None): for player in range(1, world.players + 1): link_overworld(world, player) + create_shops(world, player) update_world_regions(world, player) create_flute_exits(world, player) From f2600db3f3abdc90a544369a18bfe4ea80418ef6 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 19 Dec 2021 19:55:44 -0600 Subject: [PATCH 23/33] Added new OW Crossed option None (Allowed) to support legacy functionality of when invalid values are used --- README.md | 2 ++ resources/app/cli/args.json | 1 + resources/app/cli/lang/en.json | 1 + resources/app/gui/lang/en.json | 1 + resources/app/gui/randomize/overworld/widgets.json | 1 + 5 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 5eb15aea..bfd37cb6 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ OW Transitions are shuffled within each world separately. This allows OW connections to be shuffled cross-world. +'None (Allowed)' allows entrance connectors and whirlpools to result in cross-world behavior, but edge transitions will not. This isn't a recommended option. + Polar and Grouped both are guaranteed to result in two separated planes of tiles. To navigate to the other plane, you have the following methods: 1) Normal portals 2) Mirroring on DW tiles 3) Fluting to a LW tile that was previously unreachable Limited and Chaos are not bound to follow a two-plane framework. This means that it could be possible to travel on foot to every tile without entering a normal portal. diff --git a/resources/app/cli/args.json b/resources/app/cli/args.json index 365e40e6..8e10afde 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -119,6 +119,7 @@ "ow_crossed": { "choices": [ "none", + "allowed", "polar", "grouped", "limited", diff --git a/resources/app/cli/lang/en.json b/resources/app/cli/lang/en.json index 59d9a9f3..2f5d962d 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -208,6 +208,7 @@ "ow_crossed": [ "This allows cross-world connections to occur on the overworld.", "None: No transitions are cross-world connections.", + "Allowed: Only entrances/whirlpools can end up cross-world.", "Polar: Only used when Mixed is enabled. This retains original", " connections even when overworld tiles are swapped.", "Limited: Exactly nine transitions are randomly chosen as", diff --git a/resources/app/gui/lang/en.json b/resources/app/gui/lang/en.json index 2e69ba8e..a057e5ab 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -119,6 +119,7 @@ "randomizer.overworld.crossed": "Crossed", "randomizer.overworld.crossed.none": "None", + "randomizer.overworld.crossed.allowed": "None (Allowed)", "randomizer.overworld.crossed.polar": "Polar", "randomizer.overworld.crossed.grouped": "Grouped", "randomizer.overworld.crossed.limited": "Limited", diff --git a/resources/app/gui/randomize/overworld/widgets.json b/resources/app/gui/randomize/overworld/widgets.json index f6751bc2..43dc9394 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -14,6 +14,7 @@ "default": "vanilla", "options": [ "none", + "allowed", "polar", "grouped", "limited", From 7699c1fc63474fb46d930c33d4fab35a5fb81143 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 19 Dec 2021 19:56:27 -0600 Subject: [PATCH 24/33] Fixed Blue Potion not showing up in Dark Lake Shop in Inverted --- Regions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Regions.py b/Regions.py index 8f8ee418..f1d372cf 100644 --- a/Regions.py +++ b/Regions.py @@ -1125,6 +1125,7 @@ def create_shops(world, player): if (0x35 not in world.owswaps[player][0] and region_name == 'Dark Lake Hylia Shop') \ or (0x35 in world.owswaps[player][0] and region_name == 'Cave Shop (Lake Hylia)'): locked = True + custom = True inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] region = world.get_region(region_name, player) shop = Shop(region, room_id, type, shopkeeper, custom, locked, sram) From d79adbf3c87c66f15345820ab4ceab0b5b2c0539 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 19 Dec 2021 19:59:30 -0600 Subject: [PATCH 25/33] Fix deterministic issues in repeat seed generation --- BaseClasses.py | 6 ++++-- DoorShuffle.py | 4 ++-- EntranceShuffle.py | 34 +++++++++++++++++----------------- OverworldShuffle.py | 15 ++++++++------- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 6fd15ba7..0c2925e5 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1658,7 +1658,8 @@ class Entrance(object): from OWEdges import OWTileRegions from OverworldShuffle import ow_connections owid = OWTileRegions[follower_region.name] - (mirror_map, other_world) = ow_connections[owid % 0x40] + (mirror_map_orig, other_world) = ow_connections[owid % 0x40] + mirror_map = list(mirror_map_orig).copy() mirror_map.extend(other_world) mirror_exit = None while len(mirror_map): @@ -1710,7 +1711,8 @@ class Entrance(object): from OWEdges import OWTileRegions from OverworldShuffle import ow_connections owid = OWTileRegions[dest_region.name] - (mirror_map, other_world) = ow_connections[owid % 0x40] + (mirror_map_orig, other_world) = ow_connections.copy()[owid % 0x40] + mirror_map = list(mirror_map_orig).copy() mirror_map.extend(other_world) mirror_map = [(x, d) for (x, d) in mirror_map if x in [e.name for e in dest_region.exits]] # loop thru potential places to leave a mirror portal diff --git a/DoorShuffle.py b/DoorShuffle.py index 6e80d19d..7964755e 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1820,7 +1820,7 @@ def find_inaccessible_regions(world, player): else: start_regions = ['Links House', 'Dark Sanctuary Hint'] regs = convert_regions(start_regions, world, player) - all_regions = set([r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon]) + all_regions = [r for r in world.regions if r.player == player and r.type is not RegionType.Dungeon] visited_regions = set() queue = deque(regs) while len(queue) > 0: @@ -1836,7 +1836,7 @@ def find_inaccessible_regions(world, player): if connect and connect not in queue and connect not in visited_regions: if connect.type is not RegionType.Dungeon or connect.name.endswith(' Portal'): queue.append(connect) - world.inaccessible_regions[player].extend([r.name for r in all_regions.difference(visited_regions) if valid_inaccessible_region(r)]) + world.inaccessible_regions[player].extend([r.name for r in all_regions if r not in visited_regions and valid_inaccessible_region(r)]) if (world.mode[player] == 'inverted') != (0x1b in world.owswaps[player][0] and world.owMixed[player]): ledge = world.get_region('Hyrule Castle Ledge', player) if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'): diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 1017e6d7..2b51eaaf 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1,5 +1,5 @@ import logging -from collections import defaultdict +from collections import defaultdict, OrderedDict import RaceRandom as random from BaseClasses import CollectionState, RegionType from OverworldShuffle import build_accessible_region_list @@ -432,21 +432,21 @@ def link_entrances(world, player): connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (dw_entrances if not invFlag else lw_entrances)] connect_inaccessible_regions(world, [], connector_entrances, caves, player) if invFlag: - lw_dungeons = list(set(lw_dungeons) & set(caves)) + lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) else: - dw_dungeons = list(set(dw_dungeons) & set(caves)) + dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) - caves = list(set(Cave_Base) & set(caves)) + (lw_dungeons if not invFlag else dw_dungeons) + caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + (lw_dungeons if not invFlag else dw_dungeons) connector_entrances = [e for e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] if e in (lw_entrances if not invFlag else dw_entrances)] connect_inaccessible_regions(world, connector_entrances, [], caves, player) if not invFlag: - lw_dungeons = list(set(lw_dungeons) & set(caves)) + lw_dungeons = list(OrderedDict.fromkeys(lw_dungeons + caves)) else: - dw_dungeons = list(set(dw_dungeons) & set(caves)) + dw_dungeons = list(OrderedDict.fromkeys(dw_dungeons + caves)) lw_dungeons = lw_dungeons + (Old_Man_House if not invFlag else []) dw_dungeons = dw_dungeons + ([] if not invFlag else Old_Man_House) - caves = list(set(Cave_Base) & set(caves)) + DW_Mid_Dungeon_Exits + caves = list(OrderedDict.fromkeys(Cave_Base + caves)) + DW_Mid_Dungeon_Exits # place old man, has limited options lw_entrances = [e for e in lw_entrances if e in list(zip(*default_connector_connections + default_dungeon_connections + open_default_dungeon_connections))[0] and e in entrance_pool] @@ -1429,10 +1429,10 @@ def place_blacksmith(world, links_house, player): if invFlag: dark_sanc = world.get_entrance('Dark Sanctuary Hint Exit', player).connected_region.name - blacksmith_doors = list(set(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) + blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, dark_sanc, player, assumed_inventory, False, True, True)))) elif world.doorShuffle[player] == 'vanilla' or world.intensity[player] < 3: sanc_region = world.get_entrance('Sanctuary Exit', player).connected_region.name - blacksmith_doors = list(set(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True)))) + blacksmith_doors = list(OrderedDict.fromkeys(blacksmith_doors + list(build_accessible_entrance_list(world, sanc_region, player, assumed_inventory, False, True, True)))) if world.shuffle[player] in ['lite', 'lean']: blacksmith_doors = [e for e in blacksmith_doors if e in list(zip(*(default_item_connections + (default_shop_connections if world.shopsanity[player] else []))))[0]] @@ -1491,7 +1491,7 @@ def junk_fill_inaccessible(world, player): accessible_regions.append(region_name) break for region_name in accessible_regions.copy(): - accessible_regions = list(set(accessible_regions + list(build_accessible_region_list(base_world, region_name, player, False, True, False, False)))) + accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(base_world, region_name, player, False, True, False, False)))) world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions] # get inaccessible entrances @@ -1527,7 +1527,7 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe accessible_regions.append(region_name) break for region_name in accessible_regions.copy(): - accessible_regions = list(set(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False)))) + accessible_regions = list(OrderedDict.fromkeys(accessible_regions + list(build_accessible_region_list(world, region_name, player, True, True, False, False)))) world.inaccessible_regions[player] = [r for r in world.inaccessible_regions[player] if r not in accessible_regions] # split inaccessible into 2 lists for each world @@ -1665,12 +1665,12 @@ def build_accessible_entrance_list(world, start_region, player, assumed_inventor new_regions.append(ledge) explored_regions.extend(new_regions) - entrances = set() + entrances = list() for region_name in explored_regions: region = base_world.get_region(region_name, player) for exit in region.exits: if exit.name in entrance_pool and (not exit_rules or exit.access_rule(blank_state)): - entrances.add(exit.name) + entrances.append(exit.name) return entrances @@ -1798,7 +1798,7 @@ Cave_Three_Exits_Base = [('Spectacle Rock Cave Exit (Peak)', 'Spectacle Rock Cav Old_Man_House_Base = [('Old Man House Exit (Bottom)', 'Old Man House Exit (Top)')] -Entrance_Pool_Base = {'Links House', +Entrance_Pool_Base = ['Links House', 'Desert Palace Entrance (South)', 'Desert Palace Entrance (West)', 'Desert Palace Entrance (East)', @@ -1936,9 +1936,9 @@ Entrance_Pool_Base = {'Links House', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (North)', - 'Pyramid Hole'} + 'Pyramid Hole'] -Exit_Pool_Base = {'Links House Exit', +Exit_Pool_Base = ['Links House Exit', 'Desert Palace Exit (South)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', @@ -2076,7 +2076,7 @@ Exit_Pool_Base = {'Links House Exit', 'Skull Left Drop', 'Skull Pinball', 'Skull Pot Circle', - 'Pyramid'} + 'Pyramid'] # these are connections that cannot be shuffled and always exist. They link together separate parts of the world we need to divide into regions mandatory_connections = [('Links House S&Q', 'Links House'), diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 62b7d6e4..55a063ec 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -1,4 +1,5 @@ import RaceRandom as random, logging, copy +from collections import OrderedDict from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWTileGroups, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel @@ -500,7 +501,7 @@ def shuffle_tiles(world, groups, result_list, player): exist_dw_regions.extend(dw_regions) # check whirlpool parity - valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len(set(new_results[0]) & set({0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f})) % 2 == 0 + valid_whirlpool_parity = world.owCrossed[player] not in ['none', 'grouped'] or len(OrderedDict.fromkeys(new_results[0] + [0x0f, 0x12, 0x15, 0x33, 0x35, 0x3f, 0x55, 0x7f])) % 2 == 0 (exist_owids, exist_lw_regions, exist_dw_regions) = result_list exist_owids.extend(new_results[0]) @@ -809,7 +810,7 @@ def can_reach_smith(world, player): def explore_region(region_name, region=None): nonlocal found - explored_regions.add(region_name) + explored_regions.append(region_name) if not found: if not region: region = world.get_region(region_name, player) @@ -838,7 +839,7 @@ def can_reach_smith(world, player): blank_state.collect(ItemFactory('Titans Mitts', player), True) found = False - explored_regions = set() + explored_regions = list() explore_region('Links House') if not found: if not invFlag: @@ -867,7 +868,7 @@ def build_sectors(world, player): 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)) + sectors[s] = list(list(sectors[s]) + list(explored_regions)) break else: sectors.append(explored_regions) @@ -893,7 +894,7 @@ def build_sectors(world, player): 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)) + sectors2[s2] = list(sectors2[s2] + explored_regions) break else: sectors2.append(explored_regions) @@ -907,7 +908,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F from Items import ItemFactory def explore_region(region_name, region=None): - explored_regions.add(region_name) + explored_regions.append(region_name) if not region: region = base_world.get_region(region_name, player) for exit in region.exits: @@ -936,7 +937,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F blank_state = CollectionState(base_world) if base_world.mode[player] == 'standard': blank_state.collect(ItemFactory('Zelda Delivered', player), True) - explored_regions = set() + explored_regions = list() explore_region(start_region) return explored_regions From 5e2bbe68cb0d1806dc9f1b0d7f87a354391fd0b8 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 19 Dec 2021 20:29:31 -0600 Subject: [PATCH 26/33] Version bump 0.2.3.4 --- CHANGELOG.md | 10 ++++++++++ OverworldShuffle.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5860f08..dd9cae91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +### 0.2.3.4 +- Fixed major issue with subsequent seeds using same seed/settings resulting different +- Flute Shuffle now awards separated regions a prorated number of flute spots based on size +- Fixed spoiler log, was missing OW Tile Swap map +- Fixed spoiler log JSON output +- Fake flipper damage fix improved to skip the long delay after the scroll +- Fixed missing Blue Potion in Lake Shop in Inverted +- Added legacy OW Crossed option 'None (Allowed)' to support old behavior when invalid option was used in Mystery +- ~~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~~ + ### 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 diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 55a063ec..04f2d575 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -4,7 +4,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.3-u' +__version__ = '0.2.3.4-u' def link_overworld(world, player): # setup mandatory connections From e600f472d2749f350dec654b3b05a451aeea922b Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 20 Dec 2021 14:32:30 -0600 Subject: [PATCH 27/33] Fix deterministic issues in repeat seed generation --- EntranceShuffle.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 2b51eaaf..3c5c4c35 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -40,9 +40,9 @@ def link_entrances(world, player): connect_simple(world, 'Other World S&Q', 'Pyramid Area', player) else: entrance_pool.remove('Pyramid Hole') - entrance_pool.add('Inverted Pyramid Hole') + entrance_pool.append('Inverted Pyramid Hole') entrance_pool.remove('Pyramid Entrance') - entrance_pool.add('Inverted Pyramid Entrance') + entrance_pool.append('Inverted Pyramid Entrance') drop_connections.append(tuple(('Inverted Pyramid Hole', 'Pyramid'))) dropexit_connections.append(tuple(('Inverted Pyramid Entrance', 'Pyramid Exit'))) connect_simple(world, 'Other World S&Q', 'Hyrule Castle Ledge', player) From a731bc07993cc54d4e44c11f91728b8d1a8ad745 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 21 Dec 2021 10:14:47 -0600 Subject: [PATCH 28/33] Fixed issue with player number getting overwritten during OWR/ER --- EntranceShuffle.py | 12 ++++++------ OverworldShuffle.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 3c5c4c35..337eb453 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -1476,8 +1476,8 @@ def junk_fill_inaccessible(world, player): from DoorShuffle import find_inaccessible_regions find_inaccessible_regions(world, player) - for player in range(1, world.players + 1): - world.key_logic[player] = {} + for p in range(1, world.players + 1): + world.key_logic[p] = {} base_world = copy_world(world) base_world.override_bomb_check = True world.key_logic = {} @@ -1641,8 +1641,8 @@ def build_accessible_entrance_list(world, start_region, player, assumed_inventor from Main import copy_world from Items import ItemFactory - for player in range(1, world.players + 1): - world.key_logic[player] = {} + for p in range(1, world.players + 1): + world.key_logic[p] = {} base_world = copy_world(world) base_world.override_bomb_check = True world.key_logic = {} @@ -1751,8 +1751,8 @@ def can_reach(world, entrance_name, region_name, player): from Items import ItemFactory from DoorShuffle import find_inaccessible_regions - for player in range(1, world.players + 1): - world.key_logic[player] = {} + for p in range(1, world.players + 1): + world.key_logic[p] = {} base_world = copy_world(world) base_world.override_bomb_check = True world.key_logic = {} diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 04f2d575..593e5a7c 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -853,8 +853,8 @@ def build_sectors(world, player): from OWEdges import OWTileRegions # perform accessibility check on duplicate world - for player in range(1, world.players + 1): - world.key_logic[player] = {} + for p in range(1, world.players + 1): + world.key_logic[p] = {} base_world = copy_world(world) world.key_logic = {} @@ -925,8 +925,8 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F 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] = {} + for p in range(1, world.players + 1): + world.key_logic[p] = {} base_world = copy_world(world) base_world.override_bomb_check = True world.key_logic = {} From 0df739b283fd184e89ed2d3617a69ab82e3bbafb Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 21 Dec 2021 10:35:54 -0600 Subject: [PATCH 29/33] Added infinite loop detection --- BaseClasses.py | 5 +++++ OverworldShuffle.py | 3 +++ Utils.py | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/BaseClasses.py b/BaseClasses.py index 0c2925e5..6e1ebd41 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1538,6 +1538,11 @@ class Region(object): self.crystal_switch = False def can_reach(self, state): + from Utils import stack_size3a + from DungeonGenerator import GenerationException + if stack_size3a() > 500: + raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'') + if state.stale[self.player]: state.update_reachable_regions(self.player) return self in state.reachable_regions[self.player] diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 593e5a7c..43495139 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -908,6 +908,9 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F from Items import ItemFactory def explore_region(region_name, region=None): + if stack_size3a() > 500: + raise GenerationException(f'Infinite loop detected for "{start_region}" located at \'build_accessible_region_list\'') + explored_regions.append(region_name) if not region: region = base_world.get_region(region_name, player) diff --git a/Utils.py b/Utils.py index deed090d..99702a98 100644 --- a/Utils.py +++ b/Utils.py @@ -7,6 +7,7 @@ import sys import xml.etree.ElementTree as ET from collections import defaultdict from functools import reduce +from itertools import count def int16_as_bytes(value): @@ -674,6 +675,21 @@ def extract_data_from_jp_rom(rom): print() +def stack_size3a(size=2): + # See reference: https://stackoverflow.com/questions/34115298/how-do-i-get-the-current-depth-of-the-python-interpreter-stack + """Get stack size for caller's frame.""" + frame = sys._getframe(size) + try: + for size in count(size, 8): + frame = frame.f_back.f_back.f_back.f_back.\ + f_back.f_back.f_back.f_back + except AttributeError: + while frame: + frame = frame.f_back + size += 1 + return size - 1 + + class bidict(dict): def __init__(self, *args, **kwargs): super(bidict, self).__init__(*args, **kwargs) From 3c74fcf9479d016a299bc0bf694529d1fdc2a8dc Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 22 Dec 2021 10:38:17 -0600 Subject: [PATCH 30/33] Added infinite loop detection --- OverworldShuffle.py | 1 + 1 file changed, 1 insertion(+) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 43495139..5c657e32 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -906,6 +906,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F from Main import copy_world from BaseClasses import CollectionState from Items import ItemFactory + from Utils import stack_size3a def explore_region(region_name, region=None): if stack_size3a() > 500: From 2b8e9d86020a35b3046c4004b8be3aa4b1797baf Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 22 Dec 2021 10:49:52 -0600 Subject: [PATCH 31/33] Move mirror portal off screen during mirror bonk in Crossed OW --- Rom.py | 2 +- asm/owrando.asm | 7 +++++++ data/base2current.bps | Bin 141422 -> 141435 bytes 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 6c57d091..8ff75fbd 100644 --- a/Rom.py +++ b/Rom.py @@ -33,7 +33,7 @@ from source.classes.SFX import randomize_sfx JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'c5903112ffe302d1e9a3878ee720d1b3' +RANDOMIZERBASEHASH = 'c1a00c60144c6cc5a6ef5f00617a9b38' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index f239b436..6b3e4200 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -160,6 +160,13 @@ OWPreserveMirrorSprite: pla : lda #$de : pha ; in vanilla, if in dark world, jump to $05afdf rtl } +OWMirrorSpriteMove: +{ + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return + lda $1acf : eor #$80 : sta $1acf + .return + lda #$2c : jml.l $07A985 ; what we wrote over +} OWFluteCancel: { diff --git a/data/base2current.bps b/data/base2current.bps index e229d170797f13706d1ad802e3f48d116ebf9321..bac70e1308719dda4ec7fba9a0618d359f79191c 100644 GIT binary patch delta 231 zcmV31W32HNV81=tOfzwv-$_c0SEwqja-3yteU zw;8vHs_H<9og$u?pdxU`q?R2=n(Gx-l8KkI4Gp38Rf(zINS|ynrNIcT&l*X9jn5jX zEL?@D2d@Q_x3vW#o#_H=uLe+m$pP>Lj~;-TsQ`}#P=9NdKnRGT&;(q)w+ik60+*Sk z@C>E$$w1HssR5I!uW6U{0|6xhQ?{2J1OY+;Ft=X>0lNYLHJ2L)0WTD%+>JPmF9DJz hvY9QfN`NrX2$yk(Wd{MbWd{NQAqWjuo_fb+mU}JEU)TTu delta 230 zcmV31mwUQKeJ5%tOfzav-$_c0RnJn$6;_gofwL10q4q}&fT`X{Yp(@>swn}` z1tOj40&A}ZP=Cn*@B|{A=>n+$j|NbGYnDI=h@sE~T)npn?f?RpGBc&6@C>E$$w1Hs zsR5I!uRWIu1OX)iE4P<01OY+;1h;bp0lNYLZI>?x0WTW}t Date: Wed, 22 Dec 2021 10:51:19 -0600 Subject: [PATCH 32/33] Move mirror portal off screen during mirror bonk in Crossed OW --- asm/owrando.asm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/asm/owrando.asm b/asm/owrando.asm index 6b3e4200..065cbfd8 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -162,10 +162,9 @@ OWPreserveMirrorSprite: } OWMirrorSpriteMove: { - lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return + lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq + lda $1acf : eor #$80 : sta $1acf - .return - lda #$2c : jml.l $07A985 ; what we wrote over + + lda #$2c : jml.l $07A985 ; what we wrote over } OWFluteCancel: From 76bb0e2aa19196b7b026badec72feaadd080288f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 24 Dec 2021 16:00:37 -0600 Subject: [PATCH 33/33] Version bump 0.2.3.5 --- CHANGELOG.md | 5 +++++ OverworldShuffle.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd9cae91..e2990d4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### 0.2.3.5 +- Fixed issue with multiworld generation +- Added infinite loop detection +- Move mirror portal off screen during mirror bonk in Crossed OW + ### 0.2.3.4 - Fixed major issue with subsequent seeds using same seed/settings resulting different - Flute Shuffle now awards separated regions a prorated number of flute spots based on size diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 5c657e32..3f753b85 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -4,7 +4,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.4-u' +__version__ = '0.2.3.5-u' def link_overworld(world, player): # setup mandatory connections