From 4267c2dde5f4cb01c87fb3c3c7c47e5e328e2c57 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 25 Jul 2022 12:13:50 -0600 Subject: [PATCH 01/41] Minor bug in intensity 3, pottery setting not drop shuffle for Desert Tiles 2. --- DoorShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 0d36a2b0..ecb1b503 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -380,7 +380,7 @@ def choose_portals(world, player): if world.doorShuffle[player] in ['basic', 'crossed']: cross_flag = world.doorShuffle[player] == 'crossed' # key drops allow the big key in the right place in Desert Tiles 2 - bk_shuffle = world.bigkeyshuffle[player] or world.dropshuffle[player] + bk_shuffle = world.bigkeyshuffle[player] or world.pottery[player] not in ['none', 'cave'] std_flag = world.mode[player] == 'standard' # roast incognito doors world.get_room(0x60, player).delete(5) From 7fecbee3bb39cf1322a7f3ee2f4d9eebc60a107a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 1 Aug 2022 15:51:33 -0500 Subject: [PATCH 02/41] Removed 'allowed' Crossed OWR option --- 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 deletions(-) diff --git a/README.md b/README.md index c6e5187c..be396995 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,6 @@ 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 ebeedf53..d8166c48 100644 --- a/resources/app/cli/args.json +++ b/resources/app/cli/args.json @@ -148,7 +148,6 @@ "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 5d753a66..aabea6c0 100644 --- a/resources/app/cli/lang/en.json +++ b/resources/app/cli/lang/en.json @@ -217,7 +217,6 @@ "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 b25f4233..e1b5f497 100644 --- a/resources/app/gui/lang/en.json +++ b/resources/app/gui/lang/en.json @@ -133,7 +133,6 @@ "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 9595ea8e..dc0363c6 100644 --- a/resources/app/gui/randomize/overworld/widgets.json +++ b/resources/app/gui/randomize/overworld/widgets.json @@ -20,7 +20,6 @@ "default": "vanilla", "options": [ "none", - "allowed", "polar", "grouped", "limited", From 8a5a0925228b6035a8fb79b95e466e24dedf1078 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 1 Aug 2022 19:42:54 -0500 Subject: [PATCH 03/41] Adding attribute to world object to indicate whether world is a copy --- BaseClasses.py | 4 +++- Main.py | 1 + Rules.py | 13 ++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index e37b8d47..7efa1903 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -62,6 +62,8 @@ class World(object): self.aga_randomness = True self.lock_aga_door_in_escape = False self.save_and_quit_from_boss = True + self.override_bomb_check = False + self.is_copied_world = False self.accessibility = accessibility.copy() self.fix_skullwoods_exit = {} self.fix_palaceofdarkness_exit = {} @@ -1306,7 +1308,7 @@ class CollectionState(object): # In the future, this can be used to check if the player starts without bombs def can_use_bombs(self, player): - return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player) or self.has('Bomb Upgrade (+5)', player, 2)) and ((hasattr(self.world,"override_bomb_check") and self.world.override_bomb_check) or self.can_farm_bombs(player)) + return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player) or self.has('Bomb Upgrade (+5)', player, 2)) and (self.world.override_bomb_check or self.can_farm_bombs(player)) def can_hit_crystal(self, player): return (self.can_use_bombs(player) diff --git a/Main.py b/Main.py index 6241a830..37e67d6b 100644 --- a/Main.py +++ b/Main.py @@ -557,6 +557,7 @@ def copy_world(world, partial_copy=False): if partial_copy: # undo some of the things that unintentionally affect the original world object world.key_logic = {} + ret.is_copied_world = True return ret diff --git a/Rules.py b/Rules.py index 7e020507..cc9bead3 100644 --- a/Rules.py +++ b/Rules.py @@ -21,12 +21,12 @@ def set_rules(world, player): global_rules(world, player) default_rules(world, player) - ow_rules(world, player) + ow_inverted_rules(world, player) ow_bunny_rules(world, player) if world.mode[player] == 'standard': - if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld + if not world.is_copied_world: standard_rules(world, player) elif world.mode[player] == 'open' or world.mode[player] == 'inverted': open_rules(world, player) @@ -805,7 +805,6 @@ def pot_rules(world, player): add_rule(l, lambda state: state.can_hit_crystal(player)) - def default_rules(world, player): set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has_beaten_aga(player)) @@ -825,7 +824,7 @@ def default_rules(world, player): # Bonk Item Access if world.shuffle_bonk_drops[player]: - if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld + if not world.is_copied_world: from Regions import bonk_prize_table for location_name, (_, _, aga_required, _, _, _) in bonk_prize_table.items(): loc = world.get_location(location_name, player) @@ -958,7 +957,7 @@ def default_rules(world, player): swordless_rules(world, player) -def ow_rules(world, player): +def ow_inverted_rules(world, player): if world.is_atgt_swapped(player): set_rule(world.get_entrance('Agahnims Tower', player), lambda state: state.has_crystals(world.crystals_needed_for_gt[player], player)) else: @@ -1481,7 +1480,7 @@ def no_glitches_rules(world, player): # add_rule(world.get_location(location, player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Paradox Cave Push Block Reverse', player), lambda state: False) # no glitches does not require block override forbid_bomb_jump_requirements(world, player) - if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent underworld rules from applying when trying to search reachability in the overworld + if not world.is_copied_world: add_conditional_lamps(world, player) @@ -1744,7 +1743,7 @@ def standard_rules(world, player): add_rule(world.get_entrance('Bonk Fairy (Light)', player), lambda state: state.has('Zelda Delivered', player)) if world.shuffle_bonk_drops[player]: - if world.get_region('Big Bomb Shop', player).entrances: # just some location that is placed late in the ER algorithm, prevent standard rules from applying when trying to search reachability in the overworld + if not world.is_copied_world: add_rule(world.get_location('Hyrule Castle Tree', player), lambda state: state.has('Zelda Delivered', player)) add_rule(world.get_location('Central Bonk Rocks Tree', player), lambda state: state.has('Zelda Delivered', player)) From ddba1cd8139d6d7f1fefbaa47abdeaa0c0080936 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 1 Aug 2022 20:54:29 -0500 Subject: [PATCH 04/41] Creating a separate copy_world_limited for OWR/ER purposes --- EntranceShuffle.py | 19 +++--- Main.py | 139 +++++++++++++++++++++++++++++++++++++++++--- OverworldShuffle.py | 30 +++++++--- 3 files changed, 160 insertions(+), 28 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index b15a7438..758046c1 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -3,6 +3,7 @@ from collections import defaultdict, OrderedDict import RaceRandom as random from BaseClasses import CollectionState, RegionType from OverworldShuffle import build_accessible_region_list +from DoorShuffle import find_inaccessible_regions from OWEdges import OWTileRegions from Utils import stack_size3a @@ -831,8 +832,6 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player, must """This works inplace""" random.shuffle(entrances) random.shuffle(caves) - - from DoorShuffle import find_inaccessible_regions used_caves = [] required_entrances = 0 # Number of entrances reserved for used_caves @@ -1274,7 +1273,6 @@ def full_shuffle_dungeons(world, Dungeon_Exits, player): dw_entrances.extend([e for e in dungeon_owid_map[owid][0] if e in entrance_pool]) # determine must-exit entrances - from DoorShuffle import find_inaccessible_regions find_inaccessible_regions(world, player) lw_must_exit = list() @@ -1442,13 +1440,12 @@ def place_old_man(world, pool, player, ignore_list=[]): def junk_fill_inaccessible(world, player): - from Main import copy_world - from DoorShuffle import find_inaccessible_regions + from Main import copy_world_limited find_inaccessible_regions(world, player) for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world(world, True) + base_world = copy_world_limited(world) base_world.override_bomb_check = True # remove regions that have a dungeon entrance @@ -1488,7 +1485,6 @@ def connect_inaccessible_regions(world, lw_entrances, dw_entrances, caves, playe random.shuffle(lw_entrances) random.shuffle(dw_entrances) - from DoorShuffle import find_inaccessible_regions find_inaccessible_regions(world, player) # remove regions that have a dungeon entrance @@ -1611,12 +1607,12 @@ def unbias_dungeons(Dungeon_Exits): def build_accessible_entrance_list(world, start_region, player, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False): - from Main import copy_world + from Main import copy_world_limited from Items import ItemFactory for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world(world, True) + base_world = copy_world_limited(world) base_world.override_bomb_check = True connect_simple(base_world, 'Links House S&Q', start_region, player) @@ -1719,13 +1715,12 @@ def get_distant_entrances(world, start_entrance, player): def can_reach(world, entrance_name, region_name, player): - from Main import copy_world + from Main import copy_world_limited from Items import ItemFactory - from DoorShuffle import find_inaccessible_regions for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world(world, True) + base_world = copy_world_limited(world) base_world.override_bomb_check = True entrance = world.get_entrance(entrance_name, player) diff --git a/Main.py b/Main.py index 37e67d6b..1649b28b 100644 --- a/Main.py +++ b/Main.py @@ -396,7 +396,7 @@ def main(args, seed=None, fish=None): return world -def copy_world(world, partial_copy=False): +def copy_world(world): # ToDo: Not good yet ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, @@ -543,10 +543,9 @@ def copy_world(world, partial_copy=False): ret.dungeon_layouts = world.dungeon_layouts ret.key_logic = world.key_logic ret.dungeon_portals = world.dungeon_portals - if not partial_copy: - for player, portals in world.dungeon_portals.items(): - for portal in portals: - connect_portal(portal, ret, player) + for player, portals in world.dungeon_portals.items(): + for portal in portals: + connect_portal(portal, ret, player) ret.sanc_portal = world.sanc_portal from OverworldShuffle import categorize_world_regions @@ -554,10 +553,132 @@ def copy_world(world, partial_copy=False): categorize_world_regions(ret, player) set_rules(ret, player) - if partial_copy: - # undo some of the things that unintentionally affect the original world object - world.key_logic = {} - ret.is_copied_world = True + return ret + + +def copy_world_limited(world): + # ToDo: Not good yet + ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords, + world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, + world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints) + ret.teams = world.teams + ret.player_names = copy.deepcopy(world.player_names) + ret.remote_items = world.remote_items.copy() + ret.required_medallions = world.required_medallions.copy() + ret.bottle_refills = world.bottle_refills.copy() + ret.swamp_patch_required = world.swamp_patch_required.copy() + ret.ganon_at_pyramid = world.ganon_at_pyramid.copy() + ret.powder_patch_required = world.powder_patch_required.copy() + ret.ganonstower_vanilla = world.ganonstower_vanilla.copy() + ret.treasure_hunt_count = world.treasure_hunt_count.copy() + ret.treasure_hunt_icon = world.treasure_hunt_icon.copy() + ret.sewer_light_cone = world.sewer_light_cone.copy() + ret.light_world_light_cone = world.light_world_light_cone + ret.dark_world_light_cone = world.dark_world_light_cone + ret.seed = world.seed + ret.can_access_trock_eyebridge = world.can_access_trock_eyebridge.copy() + ret.can_access_trock_front = world.can_access_trock_front.copy() + ret.can_access_trock_big_chest = world.can_access_trock_big_chest.copy() + ret.can_access_trock_middle = world.can_access_trock_middle.copy() + ret.can_take_damage = world.can_take_damage + ret.difficulty_requirements = world.difficulty_requirements.copy() + ret.fix_fake_world = world.fix_fake_world.copy() + ret.lamps_needed_for_dark_rooms = world.lamps_needed_for_dark_rooms + ret.mapshuffle = world.mapshuffle.copy() + ret.compassshuffle = world.compassshuffle.copy() + ret.keyshuffle = world.keyshuffle.copy() + ret.bigkeyshuffle = world.bigkeyshuffle.copy() + ret.bombbag = world.bombbag.copy() + ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() + ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() + ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() + ret.crystals_gt_orig = world.crystals_gt_orig.copy() + ret.owKeepSimilar = world.owKeepSimilar.copy() + ret.owWhirlpoolShuffle = world.owWhirlpoolShuffle.copy() + ret.owFluteShuffle = world.owFluteShuffle.copy() + ret.shuffle_bonk_drops = world.shuffle_bonk_drops.copy() + ret.open_pyramid = world.open_pyramid.copy() + ret.boss_shuffle = world.boss_shuffle.copy() + ret.enemy_shuffle = world.enemy_shuffle.copy() + ret.enemy_health = world.enemy_health.copy() + ret.enemy_damage = world.enemy_damage.copy() + ret.beemizer = world.beemizer.copy() + ret.intensity = world.intensity.copy() + ret.experimental = world.experimental.copy() + ret.shopsanity = world.shopsanity.copy() + ret.dropshuffle = world.dropshuffle.copy() + ret.pottery = world.pottery.copy() + ret.potshuffle = world.potshuffle.copy() + ret.mixed_travel = world.mixed_travel.copy() + ret.standardize_palettes = world.standardize_palettes.copy() + ret.owswaps = world.owswaps.copy() + ret.owflutespots = world.owflutespots.copy() + ret.prizes = world.prizes.copy() + ret.restrict_boss_items = world.restrict_boss_items.copy() + + ret.is_copied_world = True + + for player in range(1, world.players + 1): + create_regions(ret, player) + update_world_regions(ret, player) + create_flute_exits(ret, player) + create_dungeon_regions(ret, player) + create_shops(ret, player) + create_rooms(ret, player) + create_dungeons(ret, player) + if world.logic[player] in ('owglitches', 'nologic'): + create_owg_connections(ret, player) + + # # there are region references here they must be migrated to preserve integrity + # # ret.exp_cache = world.exp_cache.copy() + + # copy_dynamic_regions_and_locations(world, ret) + for player in range(1, world.players + 1): + if world.mode[player] == 'standard': + parent = ret.get_region('Menu', player) + target = ret.get_region('Hyrule Castle Secret Entrance', player) + connection = Entrance(player, 'Uncle S&Q', parent) + parent.exits.append(connection) + connection.connect(target) + + # connect copied world + copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations + for region in world.regions: + copied_region = ret.get_region(region.name, region.player) + copied_region.is_light_world = region.is_light_world + copied_region.is_dark_world = region.is_dark_world + copied_region.dungeon = region.dungeon + copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations] + for location in copied_region.locations: + location.parent_region = copied_region + for entrance in region.entrances: + ret.get_entrance(entrance.name, entrance.player).connect(copied_region) + + for item in world.precollected_items: + ret.push_precollected(ItemFactory(item.name, item.player)) + + # copy progress items in state + ret.state.prog_items = world.state.prog_items.copy() + ret.state.stale = {player: True for player in range(1, world.players + 1)} + + ret.owedges = world.owedges.copy() + ret.doors = world.doors.copy() + for door in ret.doors: + entrance = ret.check_for_entrance(door.name, door.player) + if entrance is not None: + entrance.door = door + ret.paired_doors = world.paired_doors.copy() + ret.rooms = world.rooms.copy() + ret.inaccessible_regions = world.inaccessible_regions.copy() + ret.dungeon_layouts = world.dungeon_layouts.copy() + ret.key_logic = world.key_logic.copy() + ret.dungeon_portals = world.dungeon_portals.copy() + ret.sanc_portal = world.sanc_portal.copy() + + from OverworldShuffle import categorize_world_regions + for player in range(1, world.players + 1): + categorize_world_regions(ret, player) + set_rules(ret, player) return ret diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 1e819ae4..140cc346 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -289,6 +289,8 @@ def link_overworld(world, player): for whirlpools in whirlpool_candidates: random.shuffle(whirlpools) while len(whirlpools): + if len(whirlpools) % 2 == 1: + x=0 from_owid, from_whirlpool, from_region = whirlpools.pop() to_owid, to_whirlpool, to_region = whirlpools.pop() connect_simple(world, from_whirlpool, to_region, player) @@ -329,7 +331,7 @@ def link_overworld(world, player): # layout shuffle groups = adjust_edge_groups(world, trimmed_groups, edges_to_swap, player) - tries = 20 + tries = 100 valid_layout = False connected_edge_cache = connected_edges.copy() while not valid_layout and tries > 0: @@ -426,6 +428,7 @@ def link_overworld(world, player): flute_pool.remove(owid) if ignore_proximity: logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') + logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') new_spots.append(owid) return True @@ -442,6 +445,7 @@ def link_overworld(world, player): 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 + logging.getLogger('').debug(f'Sector of {sector[0]} regions gets {spots_to_place} spot(s)') if 'Desert Palace Teleporter Ledge' in sector[1] or 'Misery Mire Teleporter Ledge' in sector[1]: addSpot(0x38, False) # guarantee desert/mire access @@ -878,13 +882,13 @@ def can_reach_smith(world, player): return found def build_sectors(world, player): - from Main import copy_world + from Main import copy_world_limited from OWEdges import OWTileRegions # perform accessibility check on duplicate world for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world(world, True) + base_world = copy_world_limited(world) # build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances) regions = list(OWTileRegions.copy().keys()) @@ -928,11 +932,23 @@ def build_sectors(world, player): sectors2.append(explored_regions) sectors[s] = sectors2 + #TODO: Keep largest LW sector for Links House consideration, keep sector containing WDM for Old Man consideration + # sector_entrances = list() + # for sector in sectors: + # entrances = list() + # for s2 in sector: + # for region_name in s2: + # region = world.get_region(region_name, player) + # for exit in region.exits: + # if exit.spot_type == 'Entrance' and exit.name in entrance_pool: + # entrances.append(exit.name) + # sector_entrances.append(entrances) + 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 Main import copy_world_limited from Items import ItemFactory from Utils import stack_size3a @@ -959,7 +975,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F if build_copy_world: for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world(world, True) + base_world = copy_world_limited(world) base_world.override_bomb_check = True else: base_world = world @@ -1015,7 +1031,7 @@ def validate_layout(world, player): entrance_connectors['Bumper Cave Entrance'] = ['West Dark Death Mountain (Bottom)'] entrance_connectors['Mountain Entry Entrance'] = ['Mountain Entry Ledge'] - from Main import copy_world + from Main import copy_world_limited from Utils import stack_size3a from EntranceShuffle import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections @@ -1048,7 +1064,7 @@ def validate_layout(world, player): for p in range(1, world.players + 1): world.key_logic[p] = {} - base_world = copy_world(world, True) + base_world = copy_world_limited(world) explored_regions = list() if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]: From 64c65f680f8365d19d9f9955abd6fdea63179a0f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 1 Aug 2022 23:48:54 -0500 Subject: [PATCH 05/41] Creating a separate copy_world_limited for OWR/ER purposes --- Main.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Main.py b/Main.py index 1649b28b..e2a3efc7 100644 --- a/Main.py +++ b/Main.py @@ -629,10 +629,6 @@ def copy_world_limited(world): if world.logic[player] in ('owglitches', 'nologic'): create_owg_connections(ret, player) - # # there are region references here they must be migrated to preserve integrity - # # ret.exp_cache = world.exp_cache.copy() - - # copy_dynamic_regions_and_locations(world, ret) for player in range(1, world.players + 1): if world.mode[player] == 'standard': parent = ret.get_region('Menu', player) @@ -657,23 +653,13 @@ def copy_world_limited(world): for item in world.precollected_items: ret.push_precollected(ItemFactory(item.name, item.player)) - # copy progress items in state - ret.state.prog_items = world.state.prog_items.copy() - ret.state.stale = {player: True for player in range(1, world.players + 1)} - ret.owedges = world.owedges.copy() ret.doors = world.doors.copy() for door in ret.doors: entrance = ret.check_for_entrance(door.name, door.player) if entrance is not None: entrance.door = door - ret.paired_doors = world.paired_doors.copy() - ret.rooms = world.rooms.copy() - ret.inaccessible_regions = world.inaccessible_regions.copy() - ret.dungeon_layouts = world.dungeon_layouts.copy() ret.key_logic = world.key_logic.copy() - ret.dungeon_portals = world.dungeon_portals.copy() - ret.sanc_portal = world.sanc_portal.copy() from OverworldShuffle import categorize_world_regions for player in range(1, world.players + 1): From e847b213609fbd1fe9ff1ae56ea94b455d82e28c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 2 Aug 2022 00:14:08 -0500 Subject: [PATCH 06/41] Creating a separate copy_world_limited for OWR/ER purposes --- Main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Main.py b/Main.py index e2a3efc7..4e05fb83 100644 --- a/Main.py +++ b/Main.py @@ -659,6 +659,7 @@ def copy_world_limited(world): entrance = ret.check_for_entrance(door.name, door.player) if entrance is not None: entrance.door = door + door.entrance = entrance ret.key_logic = world.key_logic.copy() from OverworldShuffle import categorize_world_regions From bc4b16d910b90b68cfdd1ebbb2ab027418d28f8c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 2 Aug 2022 10:34:15 -0500 Subject: [PATCH 07/41] Creating a separate copy_world_limited for OWR/ER purposes --- Main.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Main.py b/Main.py index 4e05fb83..44b88ffd 100644 --- a/Main.py +++ b/Main.py @@ -621,13 +621,14 @@ def copy_world_limited(world): for player in range(1, world.players + 1): create_regions(ret, player) update_world_regions(ret, player) + if world.logic[player] in ('owglitches', 'nologic'): + create_owg_connections(ret, player) create_flute_exits(ret, player) create_dungeon_regions(ret, player) create_shops(ret, player) + create_doors(ret, player) create_rooms(ret, player) create_dungeons(ret, player) - if world.logic[player] in ('owglitches', 'nologic'): - create_owg_connections(ret, player) for player in range(1, world.players + 1): if world.mode[player] == 'standard': @@ -644,7 +645,7 @@ def copy_world_limited(world): copied_region.is_light_world = region.is_light_world copied_region.is_dark_world = region.is_dark_world copied_region.dungeon = region.dungeon - copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations] + copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations if (location.name, location.player) in copied_locations] for location in copied_region.locations: location.parent_region = copied_region for entrance in region.entrances: @@ -654,12 +655,10 @@ def copy_world_limited(world): ret.push_precollected(ItemFactory(item.name, item.player)) ret.owedges = world.owedges.copy() - ret.doors = world.doors.copy() for door in ret.doors: entrance = ret.check_for_entrance(door.name, door.player) if entrance is not None: entrance.door = door - door.entrance = entrance ret.key_logic = world.key_logic.copy() from OverworldShuffle import categorize_world_regions From 74912e3bedaba40ac0e272fb36bb7ef9aac5ea64 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 2 Aug 2022 10:56:09 -0500 Subject: [PATCH 08/41] Fixed issue with flute shuffle placing spots not in pool --- OverworldShuffle.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 140cc346..4ae0ee58 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -425,11 +425,15 @@ def link_overworld(world, player): if not ignore_proximity and random.randint(0, 31) != 0 and new_ignored.intersection(ignored_regions): return False ignored_regions.update(new_ignored) - flute_pool.remove(owid) - if ignore_proximity: - logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') - logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') - new_spots.append(owid) + if owid in flute_pool: + flute_pool.remove(owid) + if ignore_proximity: + logging.getLogger('').warning(f'Warning: Adding flute spot within proximity: {hex(owid)}') + logging.getLogger('').debug(f'Placing flute at: {hex(owid)}') + new_spots.append(owid) + else: + # TODO: Inspect later, seems to happen only with 'random' flute shuffle + logging.getLogger('').warning(f'Warning: Attempted to place flute spot not in pool: {hex(owid)}') return True # determine sectors (isolated groups of regions) to place flute spots From 85fe7d1d04f5d9bd674b999e955b66a66d60892c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 3 Aug 2022 17:52:22 -0500 Subject: [PATCH 09/41] Fixed bomb/rupee farming to not include caves if cave pots are shuffled --- BaseClasses.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7efa1903..dc943c2b 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1106,7 +1106,7 @@ class CollectionState(object): region = self.world.get_region(regionname, player) return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player)) - for region in rupee_farms: + for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']: if can_reach_non_bunny(region): return True @@ -1188,7 +1188,7 @@ class CollectionState(object): return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player)) # bomb pickups - for region in bush_bombs + bomb_caves: + for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []): if can_reach_non_bunny(region): return True From 32714b4c6a35f4bdd4779aed8619f43423b6c021 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 5 Aug 2022 16:48:34 -0600 Subject: [PATCH 10/41] Fix for colorized pots in "dark desert hint" aka mire storyteller --- Main.py | 2 +- PotShuffle.py | 13 +++++++------ RELEASENOTES.md | 4 +++- Rom.py | 25 +++++++++++++------------ 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/Main.py b/Main.py index 7aba1276..3787e81b 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.0.1.0-u' +__version__ = '1.0.1.1-u' from source.classes.BabelFish import BabelFish diff --git a/PotShuffle.py b/PotShuffle.py index a1c96783..7806f495 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -787,12 +787,13 @@ vanilla_pots = { Pot(230, 27, PotItem.Bomb, 'Light World Bomb Hut', obj=RoomObject(0x03EF5E, [0xCF, 0xDF, 0xFA]))], 0x108: [Pot(166, 19, PotItem.Chicken, 'Chicken House', obj=RoomObject(0x03EFA9, [0x4F, 0x9F, 0xFA]))], 0x10C: [Pot(88, 14, PotItem.Heart, 'Hookshot Fairy', obj=RoomObject(0x03F329, [0xB3, 0x73, 0xFA]))], - 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x23, 0xFA])), - Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x23, 0xFA])), - Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x2B, 0xFA])), - Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x2B, 0xFA])), - Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7AC, [0xBB, 0x53, 0xFA])), - Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7AF, [0xC3, 0x53, 0xFA]))], + # note: these addresses got moved thanks to waterfall fairy edit + 0x114: [Pot(92, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79A, [0xBB, 0x23, 0xFA])), + Pot(96, 4, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F79D, [0xC3, 0x23, 0xFA])), + Pot(92, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A0, [0xBB, 0x2B, 0xFA])), + Pot(96, 5, PotItem.Bomb, 'Dark Desert Hint', obj=RoomObject(0x03F7A3, [0xC3, 0x2B, 0xFA])), + Pot(92, 10, PotItem.FiveArrows, 'Dark Desert Hint', obj=RoomObject(0x03F7A6, [0xBB, 0x53, 0xFA])), + Pot(96, 10, PotItem.Heart, 'Dark Desert Hint', obj=RoomObject(0x03F7A9, [0xC3, 0x53, 0xFA]))], 0x117: [Pot(138, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB2, [0x17, 0x1F, 0xFA])), # 0x38A -> 38A Pot(142, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCB8, [0x1F, 0x1F, 0xFA])), Pot(166, 3, PotItem.Heart, 'Spike Cave', obj=RoomObject(0x03FCC1, [0x4F, 0x1F, 0xFA])), diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 65293af1..ce3e948c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -183,6 +183,8 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Unstable +* 1.0.1.1 + * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be * 1.0.1.0 * Large features * New pottery modes - see notes above @@ -235,7 +237,7 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Fixed a bug with shopsanity + district algorithm where pre-placed potions messed up the placeholder count * Fixed usestartinventory flag (can be use on a per player basis) * Sprite selector fix for systems with SSL issues - * Fix for Standard ER where locations in rain state could be in logic + * Fix for Standard ER where locations in rain state could be in logic * 1.0.0.3 * overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown) * Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix) diff --git a/Rom.py b/Rom.py index 7861a5cd..3a4661da 100644 --- a/Rom.py +++ b/Rom.py @@ -663,18 +663,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if world.mapshuffle[player]: rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle - if world.pottery[player] not in ['none']: - rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000)) - # make hammer pegs use different tiles - Room0127.write_to_rom(snes_to_pc(0x2A8000), rom) - - if world.pot_contents[player]: - colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] - and (world.colorizepots[player] - or world.pottery[player] in ['reduced', 'clustered'])) - if world.pot_contents[player].size() > 0x2800: - raise Exception('Pot table is too big for current area') - world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots) # fix for swamp drains if necessary swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player) if not swamp1location.pot.indicator: @@ -1545,6 +1533,19 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if room.player == player and room.modified: rom.write_bytes(room.address(), room.rom_data()) + if world.pottery[player] not in ['none']: + rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000)) + # make hammer pegs use different tiles + Room0127.write_to_rom(snes_to_pc(0x2A8000), rom) + + if world.pot_contents[player]: + colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] + and (world.colorizepots[player] + or world.pottery[player] in ['reduced', 'clustered'])) + if world.pot_contents[player].size() > 0x2800: + raise Exception('Pot table is too big for current area') + world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots) + write_strings(rom, world, player, team) # write initial sram From 2ed0a806092d19c8713abbdb70bf73ac75f52e13 Mon Sep 17 00:00:00 2001 From: aerinon Date: Thu, 11 Aug 2022 15:25:09 -0600 Subject: [PATCH 11/41] Fix for pot items to not reload with the supertile Key distribution change Unique boss shuffle make gt bosses also unique Removed text color in hints due to bug --- Bosses.py | 5 ++- Fill.py | 85 ++++++++++++++++++++++-------------------- RELEASENOTES.md | 7 +++- Rom.py | 10 ++--- data/base2current.bps | Bin 93156 -> 93186 bytes 5 files changed, 56 insertions(+), 51 deletions(-) diff --git a/Bosses.py b/Bosses.py index 53393d5f..206b797c 100644 --- a/Bosses.py +++ b/Bosses.py @@ -202,12 +202,15 @@ def place_bosses(world, player): place_boss(boss, level, loc, loc_text, world, player) elif world.boss_shuffle[player] == 'unique': bosses = list(placeable_bosses) + gt_bosses = [] for [loc, level] in boss_locations: loc_text = loc + (' ('+level+')' if level else '') try: if level: - boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)]) + boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level) + and b not in gt_bosses]) + gt_bosses.append(boss) else: boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)]) bosses.remove(boss) diff --git a/Fill.py b/Fill.py index 74274543..bb6e70cf 100644 --- a/Fill.py +++ b/Fill.py @@ -3,6 +3,7 @@ import collections import itertools import logging import math +from contextlib import suppress from BaseClasses import CollectionState, FillError, LocationType from Items import ItemFactory @@ -35,17 +36,6 @@ def dungeon_tracking(world): def fill_dungeons_restrictive(world, shuffled_locations): dungeon_tracking(world) - all_state_base = world.get_all_state() - - # for player in range(1, world.players + 1): - # pinball_room = world.get_location('Skull Woods - Pinball Room', player) - # if world.retro[player]: - # world.push_item(pinball_room, ItemFactory('Small Key (Universal)', player), False) - # else: - # world.push_item(pinball_room, ItemFactory('Small Key (Skull Woods)', player), False) - # pinball_room.event = True - # pinball_room.locked = True - # shuffled_locations.remove(pinball_room) # with shuffled dungeon items they are distributed as part of the normal item pool for item in world.get_items(): @@ -55,17 +45,28 @@ def fill_dungeons_restrictive(world, shuffled_locations): item.priority = True dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)] + bigs, smalls, others = [], [], [] + for i in dungeon_items: + (bigs if i.bigkey else smalls if i.smallkey else others).append(i) - # sort in the order Big Key, Small Key, Other before placing dungeon items - sort_order = {"BigKey": 3, "SmallKey": 2} - dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1)) + def fill(base_state, items, key_pool): + fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) - fill_restrictive(world, all_state_base, shuffled_locations, dungeon_items, - keys_in_itempool={player: not world.keyshuffle[player] for player in range(1, world.players+1)}, - single_player_placement=True) + all_state_base = world.get_all_state() + big_state_base = all_state_base.copy() + for x in smalls + others: + big_state_base.collect(x, True) + fill(big_state_base, bigs, smalls) + random.shuffle(shuffled_locations) + small_state_base = all_state_base.copy() + for x in others: + small_state_base.collect(x, True) + fill(small_state_base, smalls, smalls) + random.shuffle(shuffled_locations) + fill(all_state_base, others, None) -def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=None, single_player_placement=False, +def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False, vanilla=False): def sweep_from_pool(): new_state = base_state.copy() @@ -101,8 +102,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No item_locations = filter_locations(item_to_place, locations, world, vanilla) for location in item_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, maximum_exploration_state, - single_player_placement, perform_access_check, itempool, - keys_in_itempool, world) + single_player_placement, perform_access_check, key_pool, world) if spot_to_fill: break if spot_to_fill is None: @@ -111,7 +111,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No continue spot_to_fill = recovery_placement(item_to_place, locations, world, maximum_exploration_state, base_state, itempool, perform_access_check, item_locations, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if spot_to_fill is None: # we filled all reachable spots. Maybe the game can be beaten anyway? unplaced_items.insert(0, item_to_place) @@ -123,6 +123,10 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, False) + # todo: remove key item from key_pool + if item_to_place.smallkey: + with suppress(ValueError): + key_pool.remove(item_to_place) track_outside_keys(item_to_place, spot_to_fill, world) track_dungeon_items(item_to_place, spot_to_fill, world) locations.remove(spot_to_fill) @@ -132,7 +136,7 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool=No def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check, - itempool, keys_in_itempool, world): + key_pool, world): if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there location.item = item_to_place test_state = max_exp_state.copy() @@ -141,8 +145,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl test_state = max_exp_state if not single_player_placement or location.player == item_to_place.player: if location.can_fill(test_state, item_to_place, perform_access_check): - test_pool = itempool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool - if valid_key_placement(item_to_place, location, test_pool, world): + if valid_key_placement(item_to_place, location, key_pool, world): if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world): return location if item_to_place.smallkey or item_to_place.bigkey: @@ -150,7 +153,7 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl return None -def valid_key_placement(item, location, itempool, world): +def valid_key_placement(item, location, key_pool, world): if not valid_reserved_placement(item, location, world): return False if ((not item.smallkey and not item.bigkey) or item.player != location.player @@ -161,7 +164,7 @@ def valid_key_placement(item, location, itempool, world): if dungeon.name not in item.name and (dungeon.name != 'Hyrule Castle' or 'Escape' not in item.name): return True key_logic = world.key_logic[item.player][dungeon.name] - unplaced_keys = len([x for x in itempool if x.name == key_logic.small_key_name and x.player == item.player]) + unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player]) prize_loc = None if key_logic.prize_location: prize_loc = world.get_location(key_logic.prize_location, location.player) @@ -216,16 +219,16 @@ def is_dungeon_item(item, world): def recovery_placement(item_to_place, locations, world, state, base_state, itempool, perform_access_check, attempted, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): logging.getLogger('').debug(f'Could not place {item_to_place} attempting recovery') if world.algorithm in ['balanced', 'equitable']: - return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, keys_in_itempool, + return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool, single_player_placement) elif world.algorithm == 'vanilla_fill': if item_to_place.type == 'Crystal': possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal'] return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) else: i, config = 0, world.item_pool_config tried = set(attempted) @@ -235,7 +238,7 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp other_locs = [x for x in locations if x.name in fallback_locations] for location in other_locs: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill i += 1 @@ -244,14 +247,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp other_locations = vanilla_fallback(item_to_place, locations, world) for location in other_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill tried.update(other_locations) other_locations = [x for x in locations if x not in tried] for location in other_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill return None @@ -259,14 +262,14 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp other_locations = [x for x in locations if x not in attempted] for location in other_locations: spot_to_fill = verify_spot_to_fill(location, item_to_place, state, single_player_placement, - perform_access_check, itempool, keys_in_itempool, world) + perform_access_check, key_pool, world) if spot_to_fill: return spot_to_fill return None def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): def location_preference(loc): if not loc.item.advancement: return 1 @@ -284,21 +287,21 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite if x.item.type not in ['Event', 'Crystal'] and not x.forced_item] swap_locations = sorted(possible_swaps, key=location_preference) return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) def try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool, - keys_in_itempool=None, single_player_placement=False): + key_pool=None, single_player_placement=False): for location in swap_locations: old_item = location.item new_pool = list(itempool) + [old_item] new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if new_spot: restore_item = new_spot.item new_spot.item = item_to_place swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool, - keys_in_itempool, single_player_placement) + key_pool, single_player_placement) if swap_spot: logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}') world.push_item(swap_spot, old_item, False) @@ -414,13 +417,13 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None # Make sure the escape small key is placed first in standard with key shuffle to prevent running out of spots # todo: crossed progitempool.sort(key=lambda item: 1 if item.name == 'Small Key (Escape)' and world.keyshuffle[item.player] and world.mode[item.player] == 'standard' else 0) - keys_in_pool = {player: world.keyshuffle[player] or world.algorithm != 'balanced' for player in range(1, world.players + 1)} + key_pool = [x for x in progitempool if x.smallkey] # sort maps and compasses to the back -- this may not be viable in equitable & ambrosia progitempool.sort(key=lambda item: 0 if item.map or item.compass else 1) if world.algorithm == 'vanilla_fill': - fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool, vanilla=True) - fill_restrictive(world, world.state, fill_locations, progitempool, keys_in_pool) + fill_restrictive(world, world.state, fill_locations, progitempool, key_pool, vanilla=True) + fill_restrictive(world, world.state, fill_locations, progitempool, key_pool) random.shuffle(fill_locations) if world.algorithm == 'balanced': fast_fill(world, prioitempool, fill_locations) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ce3e948c..56ecca32 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -184,7 +184,11 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Unstable * 1.0.1.1 - * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be + * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be + * Certain pot items no longer reload when reloading the supertile (matches original pot behavior better) + * Changed the key distribution that made small keys placement more random when keys are in their own dungeon + * Unique boss shuffle no longer allows repeat bosses in GT (e.g. only one Trinexx in GT, so exactly 3 bosses are repeated in the seed. This is a difference process than full which does affect the probability distribution.) + * Removed text color in hints due to vanilla bug * 1.0.1.0 * Large features * New pottery modes - see notes above @@ -208,7 +212,6 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work. * Updated tourney winners (included Doors Async League winners) * Some textual changes for hints (capitalization standardization) - * Item will be highlighted in red if experimental is on. This will likely be removed. * Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill. * Expanded Mystery logic options (e.g. owglitches) * Updated indicators on keysanity menu for overworld map option diff --git a/Rom.py b/Rom.py index 3a4661da..c3312b9a 100644 --- a/Rom.py +++ b/Rom.py @@ -37,7 +37,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '9008f4335101689f01184e58295fdbc5' +RANDOMIZERBASEHASH = '0f96237c73cccaf7a250343fe3e8c887' class JsonRom(object): @@ -1979,8 +1979,6 @@ def write_strings(rom, world, player, team): else: if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon: hint = dest.dungeon.name - elif isinstance(dest, Item) and world.experimental[player]: - hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something' else: hint = dest.hint_text if dest.hint_text else "something" if dest.player != player: @@ -2149,8 +2147,7 @@ def write_strings(rom, world, player, team): if this_location: item_name = this_location[0].item.hint_text item_name = item_name[0].upper() + item_name[1:] - item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name - this_hint = f'{item_format} can be found {hint_text(this_location[0])}.' + this_hint = f'{item_name} can be found {hint_text(this_location[0])}.' tt[hint_locations.pop(0)] = this_hint hint_count -= 1 @@ -2204,8 +2201,7 @@ def write_strings(rom, world, player, team): elif hint_type == 'path': if item_count == 1: the_item = text_for_item(next(iter(choice_set)), world, player, team) - item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item - hint_candidates.append((hint_type, f'{name} conceals only {item_format}')) + hint_candidates.append((hint_type, f'{name} conceals only {the_item}')) else: hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items')) district_hints = min(len(hint_candidates), len(hint_locations)) diff --git a/data/base2current.bps b/data/base2current.bps index 464ceaf6105a2637981d2d107a868a08a143c8e4..55525ee50a0d8f30527d68ac4de47956a9acb906 100644 GIT binary patch delta 476 zcmV<20VDq8*9C&u1(1pYAF`2*#tV%w0t*F*sWpwta+6*GE&<)MjRB1X22-)9#EFEH zAP0W|xs#v=kU9;osOsvNl^?LEfC1_}h?OE4v8b>ImnkxVr3oTnu&5d$keDZ+l_F@b zsOstzfsG<3)SyrefRQ5FlWYh&6`iiA>U4mK1InOWfQc`$sA#jE03Nfu2tok}!m+68 zxQ!yyvjqxX0th>45vQ=0)Vp=Or+2X?XIYW3Xbs6Al8?{9062b34x6u z2aX~K?j{GQsR#hTHUQ8CsTKf$BEUod&;_X%0Du_4C;-p}sTcr&4!|)0&;_X(0DuII zoC|A6iVB4QsUHC40GA_xlS6%j#*m?CjhqW>Xq=o2pFVzrj@Oq`9RX?qZkN3s0b&7v zmmnSiDh!(p9FC3py>JJcD|we!9sws0N`8ZZ8LNVguObJ2n`MWWjUEAB0x%hu?H&Or z0qK_(9|0c`MzHi0fLuD#25Ta6vZ%*JR+mN}0Wtyqmw6unH39LLuO9(A0r8jW9|2+v z^sS3atGQ6It6(5%mslVHrU7V|{~!TE3mvn760?M{!XT;5mrNl6Hv$+fmxUn#Is!{$ Sx4|I+8%hYs1;u%{PR&`PFuCsl delta 471 zcmV;|0Vw{0*ahU*1(1pY?6HxI#tg+U0t*EI0RY>iBwLeR0WJZmvy1_a1qL9osKkj; zlOG3v0fv*G2ar74uBhtjnlHVT?60VR0qQ)6l_KV_sIUi@DKdej2_h}9s2U=Wm?xl> zA~mn5>gp7MjUp%1pim8fks_y)X$U$MYObj2bbyHi%Aj0;iTtprXtSOmT(h_cLIDVm zv8d{}jUu440t#LN2nepI9Dz%c;O1F0DR zjhqXgk&dI6aUB6_0SK4e9RXqi8J9mE0V)lbA{ma60Q$Xf2b({3mvtTiCl6$OgMk^V zf{m{t2Y#D>hL@`z0bT-17?%ql0VpxHv8ZTkB7y%NNWFj#$t2JnljMO4lY-R*sU(06 zXiM}Hu=EswTsqPQYa&0gsK;Mbmt-FSG6Bq&mmdK&0lt^b9|1Z6zLy9f0b&fnt&2;m zxgD{qU?4Y_b|3+!0X3H$Apt@Q>au_mvxKq2AgP*{Y9RqP0x~R@qagu00vBbs-ys1T NN(i8Xyg-eA7zmvww~7D& From 3aa97ee8bcc0496fc001adf786f7e43af800a6d9 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 12 Aug 2022 01:53:13 -0500 Subject: [PATCH 12/41] Fixed early rupee money balancing to make item free if <20 rupees available --- Fill.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Fill.py b/Fill.py index daa6f6bd..6bf95e64 100644 --- a/Fill.py +++ b/Fill.py @@ -750,12 +750,14 @@ def balance_multiworld_progression(world): raise RuntimeError('Not all required items reachable. Something went terribly wrong here.') -def check_shop_swap(l): +def check_shop_swap(l, make_item_free=False): if l.parent_region.name in shop_to_location_table: if l.name in shop_to_location_table[l.parent_region.name]: idx = shop_to_location_table[l.parent_region.name].index(l.name) inv_slot = l.parent_region.shop.inventory[idx] inv_slot['item'] = l.item.name + if make_item_free: + inv_slot['price'] = 0 elif l.parent_region in retro_shops: idx = retro_shops[l.parent_region.name].index(l.name) inv_slot = l.parent_region.shop.inventory[idx] @@ -921,12 +923,13 @@ def balance_money_progression(world): if len(increase_targets) == 0: raise Exception('No early sphere swaps for rupees - money grind would be required - bailing for now') best_target = min(increase_targets, key=lambda t: rupee_chart[t.item.name] if t.item.name in rupee_chart else 0) - old_value = rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0 + make_item_free = wallet[target_player] < 20 + old_value = 0 if make_item_free else (rupee_chart[best_target.item.name] if best_target.item.name in rupee_chart else 0) if best_swap is None: logger.debug(f'Upgrading {best_target.item.name} @ {best_target.name} for 300 Rupees') best_target.item = ItemFactory('Rupees (300)', best_target.item.player) best_target.item.location = best_target - check_shop_swap(best_target.item.location) + check_shop_swap(best_target.item.location, make_item_free) else: old_item = best_target.item logger.debug(f'Swapping {best_target.item.name} @ {best_target.name} for {best_swap.item.name} @ {best_swap.name}') @@ -934,7 +937,7 @@ def balance_money_progression(world): best_target.item.location = best_target best_swap.item = old_item best_swap.item.location = best_swap - check_shop_swap(best_target.item.location) + check_shop_swap(best_target.item.location, make_item_free) check_shop_swap(best_swap.item.location) increase = best_value - old_value difference -= increase From e5cff2e773881a41bacfd45ccaf497e4812056dd Mon Sep 17 00:00:00 2001 From: codemann8 Date: Fri, 12 Aug 2022 01:54:20 -0500 Subject: [PATCH 13/41] Fixed so Lean ER + Inverted Dark Chapel start is guaranteed to be in DW --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 4ae0ee58..304eba95 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -689,7 +689,7 @@ def define_tile_groups(world, player, do_grouped): # sanctuary/chapel should not be swapped if S+Q guaranteed to output on that screen if 0x13 in group and ((world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] \ and (world.mode[player] in ['standard', 'inverted'] or world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3)) \ - or (world.shuffle[player] == 'lite' and world.mode[player] == 'inverted')): + or (world.shuffle[player] in ['lite', 'lean'] and world.mode[player] == 'inverted')): return False return True From 6455cad3610b7764fa0c793c620c5b4331e2bb7f Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 17 Aug 2022 14:09:02 -0500 Subject: [PATCH 14/41] Fixed Mystery to detect Windows Paths over URLs --- Mystery.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Mystery.py b/Mystery.py index e1be0eb7..d313ac4e 100644 --- a/Mystery.py +++ b/Mystery.py @@ -4,6 +4,8 @@ import RaceRandom as random import urllib.request import urllib.parse import yaml +import os +from pathlib import Path from DungeonRandomizer import parse_cli from Main import main as DRMain @@ -107,10 +109,11 @@ def main(): def get_weights(path): try: - if urllib.parse.urlparse(path).scheme: + if os.path.exists(Path(path)): + with open(path, "r", encoding="utf-8") as f: + return yaml.load(f, Loader=yaml.SafeLoader) + elif urllib.parse.urlparse(path).scheme in ['http', 'https']: return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader) - with open(path, 'r', encoding='utf-8') as f: - return yaml.load(f, Loader=yaml.SafeLoader) except Exception as e: raise Exception(f'Failed to read weights file: {e}') From f2d7cdca1b8863dd18463053f8df1f9542b265d1 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Wed, 17 Aug 2022 14:38:19 -0500 Subject: [PATCH 15/41] Changed Standard+Inverted to include Sanc screen in fixed Std screens --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 304eba95..289bcf20 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -615,7 +615,7 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): # tile shuffle happens here removed = list() for group in groups: - # if 0x1b in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted + #if 0x1b in group[0] or 0x13 in group[0] or (0x1a in group[0] and world.owCrossed[player] == 'none'): # TODO: Standard + Inverted if random.randint(0, 1): removed.append(group) From 76c1fef4b67730328e94efbc7478128a1e2d0e8d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 22 Aug 2022 22:01:48 -0500 Subject: [PATCH 16/41] Absolutely no change whatsoever so help me God --- Rom.py | 2 +- asm/owrando.asm | 1 + data/base2current.bps | Bin 104377 -> 104379 bytes 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index d8fcd686..f41e59e8 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '94a8df9d13105abcd655c7d59294ad20' +RANDOMIZERBASEHASH = 'b1ded43b30364c3e5d9a94afa2f46599' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 04ffd047..68bdbb43 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -502,6 +502,7 @@ OWBonkDrops: .return PLA : PLA : PLB : RTL } +nop org $aa9000 OWDetectEdgeTransition: diff --git a/data/base2current.bps b/data/base2current.bps index af312a912f5341731add985f82cf58223b65bc3a..70c96830f3c9c8da43808270d9beebfb8fe81295 100644 GIT binary patch delta 85 zcmV-b0IL7FuLirX2C!iP1bU{0S%YN(w`BnVv^g%Nk26qzt7~Xzt81GP7^O#_Oevbt rocso_0)VOy1AwaV2q5i{t@;Kq2bK>HkGDlT0c>0d%^rTJJUp)B;Q1p7 delta 66 zcmV-I0KNaauLilV2C!iP1Z1b1Sc7E&w`BnVv^f%;k26qzt7~XzrV2ixL!UKmiI*)q Y0T%-;PO-N~I{|E52-yJo6j4r0&{7r{n*aa+ From 21b951433edf64a09a65185eee2ea4b275d5412a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 22 Aug 2022 22:04:29 -0500 Subject: [PATCH 17/41] Undoing absolutely no change whatsoever so help me God --- Rom.py | 2 +- asm/owrando.asm | 1 - data/base2current.bps | Bin 104379 -> 104377 bytes 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index f41e59e8..d8fcd686 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'b1ded43b30364c3e5d9a94afa2f46599' +RANDOMIZERBASEHASH = '94a8df9d13105abcd655c7d59294ad20' class JsonRom(object): diff --git a/asm/owrando.asm b/asm/owrando.asm index 68bdbb43..04ffd047 100644 --- a/asm/owrando.asm +++ b/asm/owrando.asm @@ -502,7 +502,6 @@ OWBonkDrops: .return PLA : PLA : PLB : RTL } -nop org $aa9000 OWDetectEdgeTransition: diff --git a/data/base2current.bps b/data/base2current.bps index 70c96830f3c9c8da43808270d9beebfb8fe81295..af312a912f5341731add985f82cf58223b65bc3a 100644 GIT binary patch delta 66 zcmV-I0KNaauLilV2C!iP1Z1b1Sc7E&w`BnVv^f%;k26qzt7~XzrV2ixL!UKmiI*)q Y0T%-;PO-N~I{|E52-yJo6j4r0&{7r{n*aa+ delta 85 zcmV-b0IL7FuLirX2C!iP1bU{0S%YN(w`BnVv^g%Nk26qzt7~Xzt81GP7^O#_Oevbt rocso_0)VOy1AwaV2q5i{t@;Kq2bK>HkGDlT0c>0d%^rTJJUp)B;Q1p7 From c9823f28af9527cf627ef5cc7e369c1511cfd127 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 22 Aug 2022 23:01:38 -0500 Subject: [PATCH 18/41] Moving Aerinon's room mod somewhere else --- Rom.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rom.py b/Rom.py index d8fcd686..7b437caa 100644 --- a/Rom.py +++ b/Rom.py @@ -1695,9 +1695,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(room.address(), room.rom_data()) if world.pottery[player] not in ['none']: - rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000)) + rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x299000)) # make hammer pegs use different tiles - Room0127.write_to_rom(snes_to_pc(0x2A8000), rom) + Room0127.write_to_rom(snes_to_pc(0x299000), rom) if world.pot_contents[player]: colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] From 270d10b6980b69ce3060945d46a3559cd56e7450 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 23 Aug 2022 16:52:43 -0500 Subject: [PATCH 19/41] Possible fix for SK placement errors --- Fill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fill.py b/Fill.py index fa6db391..8dfa69cc 100644 --- a/Fill.py +++ b/Fill.py @@ -61,7 +61,7 @@ def fill_dungeons_restrictive(world, shuffled_locations): small_state_base = all_state_base.copy() for x in others: small_state_base.collect(x, True) - fill(small_state_base, smalls, smalls) + fill(small_state_base, smalls, list(smalls)) random.shuffle(shuffled_locations) fill(all_state_base, others, None) From 0d5ea19ecda2a98809b53b44f8378db70424f608 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 23 Aug 2022 15:52:59 -0600 Subject: [PATCH 20/41] Fix for small key fill --- Fill.py | 3 +-- ItemList.py | 24 ------------------------ 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/Fill.py b/Fill.py index bb6e70cf..b2dc3400 100644 --- a/Fill.py +++ b/Fill.py @@ -61,7 +61,7 @@ def fill_dungeons_restrictive(world, shuffled_locations): small_state_base = all_state_base.copy() for x in others: small_state_base.collect(x, True) - fill(small_state_base, smalls, smalls) + fill(small_state_base, smalls, list(smalls)) random.shuffle(shuffled_locations) fill(all_state_base, others, None) @@ -123,7 +123,6 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, False) - # todo: remove key item from key_pool if item_to_place.smallkey: with suppress(ValueError): key_pool.remove(item_to_place) diff --git a/ItemList.py b/ItemList.py index dacbdf14..9dd69157 100644 --- a/ItemList.py +++ b/ItemList.py @@ -1099,27 +1099,3 @@ def test(): if __name__ == '__main__': test() - - -def fill_specific_items(world): - keypool = [item for item in world.itempool if item.smallkey] - cage = world.get_location('Tower of Hera - Basement Cage', 1) - c_dungeon = cage.parent_region.dungeon - key_item = next(x for x in keypool if c_dungeon.name in x.name or (c_dungeon.name == 'Hyrule Castle' and 'Escape' in x.name)) - world.itempool.remove(key_item) - all_state = world.get_all_state(True) - fill_restrictive(world, all_state, [cage], [key_item]) - - location = world.get_location('Tower of Hera - Map Chest', 1) - key_item = next(x for x in world.itempool if 'Byrna' in x.name) - world.itempool.remove(key_item) - fast_fill(world, [key_item], [location]) - - - # somaria = next(item for item in world.itempool if item.name == 'Cane of Somaria') - # shooter = world.get_location('Palace of Darkness - Shooter Room', 1) - # world.itempool.remove(somaria) - # all_state = world.get_all_state(True) - # fill_restrictive(world, all_state, [shooter], [somaria]) - - From 9c612b6c00511226374c1a9be4532892c4177ddd Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 23 Aug 2022 16:43:27 -0600 Subject: [PATCH 21/41] Minor edit to when pyramid hole auto pre-opens --- Rom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index c3312b9a..58a144ad 100644 --- a/Rom.py +++ b/Rom.py @@ -1263,7 +1263,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest - if world.open_pyramid[player] or world.goal[player] == 'trinity': + if world.open_pyramid[player] or (world.goal[player] in ['trinity', 'crystals'] and world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull']): rom.initial_sram.pre_open_pyramid_hole() if world.crystals_needed_for_gt[player] == 0: rom.initial_sram.pre_open_ganons_tower() From a7e9d6d43f9ed9f1266d6a6293cf3be71bad703d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 23 Aug 2022 18:19:17 -0500 Subject: [PATCH 22/41] Creating a separate copy_world_limited for OWR/ER purposes --- EntranceShuffle.py | 2 +- Main.py | 18 ++++++++++++------ Rules.py | 15 ++++++++------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 758046c1..d6c207f2 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -32,7 +32,7 @@ def link_entrances(world, player): Cave_Three_Exits = Cave_Three_Exits_Base.copy() from OverworldShuffle import build_sectors - if not world.owsectors[player]: + if not world.owsectors[player] and world.shuffle[player] != 'vanilla': world.owsectors[player] = build_sectors(world, player) # modifications to lists diff --git a/Main.py b/Main.py index 60bdb40f..0ebf52db 100644 --- a/Main.py +++ b/Main.py @@ -625,8 +625,8 @@ def copy_world_limited(world): create_owg_connections(ret, player) create_flute_exits(ret, player) create_dungeon_regions(ret, player) + create_owedges(ret, player) create_shops(ret, player) - create_doors(ret, player) create_rooms(ret, player) create_dungeons(ret, player) @@ -654,11 +654,17 @@ def copy_world_limited(world): for item in world.precollected_items: ret.push_precollected(ItemFactory(item.name, item.player)) - ret.owedges = world.owedges.copy() - for door in ret.doors: - entrance = ret.check_for_entrance(door.name, door.player) - if entrance is not None: - entrance.door = door + for edge in world.owedges: + copiededge = ret.check_for_owedge(edge.name, edge.player) + if copiededge is not None: + copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) + # for door in world.doors: + # entrance = ret.check_for_entrance(door.name, door.player) + # if entrance is not None: + # destdoor = ret.check_for_door(entrance.door.name, entrance.door.player) + # entrance.door = destdoor + # if destdoor is not None: + # destdoor.entrance = entrance ret.key_logic = world.key_logic.copy() from OverworldShuffle import categorize_world_regions diff --git a/Rules.py b/Rules.py index cc9bead3..260b2893 100644 --- a/Rules.py +++ b/Rules.py @@ -341,7 +341,7 @@ def global_rules(world, player): set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.can_melt_things(player)) set_rule(world.get_entrance('Ice Hookshot Ledge Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player)) - if not world.get_door('Ice Switch Room SE', player).entranceFlag: + if not world.is_copied_world and not world.get_door('Ice Switch Room SE', player).entranceFlag: set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player)) @@ -398,7 +398,7 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player)) - if not world.get_door('GT Speed Torch SE', player).entranceFlag: + if not world.is_copied_world and not world.get_door('GT Speed Torch SE', player).entranceFlag: set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player)) @@ -421,7 +421,7 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Gauntlet 2 EN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 2 SW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 3 NW', player), lambda state: state.can_kill_most_things(player)) - if not world.get_door('GT Gauntlet 3 SW', player).entranceFlag: + if not world.is_copied_world and not world.get_door('GT Gauntlet 3 SW', player).entranceFlag: set_rule(world.get_entrance('GT Gauntlet 3 SW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 4 NW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 4 SW', player), lambda state: state.can_kill_most_things(player)) @@ -441,10 +441,11 @@ def global_rules(world, player): set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) # crystal switch rules - if world.get_door('Thieves Attic ES', player).crystal == CrystalBarrier.Blue: - set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player)) - else: - set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player)) + if not world.is_copied_world: + if world.get_door('Thieves Attic ES', player).crystal == CrystalBarrier.Blue: + set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player)) + else: + set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player)) set_rule(world.get_entrance('Thieves Attic Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player)) set_rule(world.get_entrance('Thieves Attic Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player)) From 34c9972c74980d8625e533226934d774c9f89369 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 23 Aug 2022 18:20:23 -0500 Subject: [PATCH 23/41] Suppressing Inaccessible Regions debug output --- DoorShuffle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index b973d2eb..fe24f6b8 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1896,9 +1896,9 @@ def find_inaccessible_regions(world, player): if any(x for x in ledge.exits if x.connected_region and x.connected_region.name == 'Agahnims Tower Portal'): world.inaccessible_regions[player].append('Hyrule Castle Ledge') logger = logging.getLogger('') - logger.debug('Inaccessible Regions:') - for r in world.inaccessible_regions[player]: - logger.debug('%s', r) + #logger.debug('Inaccessible Regions:') + #for r in world.inaccessible_regions[player]: + # logger.debug('%s', r) def find_accessible_entrances(world, player, builder): From 56072e60940698c5a40fff98f45de276cacbb82d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 23 Aug 2022 18:39:05 -0500 Subject: [PATCH 24/41] Creating a separate copy_world_limited for OWR/ER purposes --- Main.py | 17 ++++++++++------- Rules.py | 15 +++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Main.py b/Main.py index 0ebf52db..8288bb52 100644 --- a/Main.py +++ b/Main.py @@ -627,6 +627,7 @@ def copy_world_limited(world): create_dungeon_regions(ret, player) create_owedges(ret, player) create_shops(ret, player) + create_doors(ret, player) create_rooms(ret, player) create_dungeons(ret, player) @@ -658,13 +659,15 @@ def copy_world_limited(world): copiededge = ret.check_for_owedge(edge.name, edge.player) if copiededge is not None: copiededge.dest = ret.check_for_owedge(edge.dest.name, edge.dest.player) - # for door in world.doors: - # entrance = ret.check_for_entrance(door.name, door.player) - # if entrance is not None: - # destdoor = ret.check_for_door(entrance.door.name, entrance.door.player) - # entrance.door = destdoor - # if destdoor is not None: - # destdoor.entrance = entrance + + for door in world.doors: + entrance = ret.check_for_entrance(door.name, door.player) + if entrance is not None: + destdoor = ret.check_for_door(entrance.door.name, entrance.door.player) + entrance.door = destdoor + if destdoor is not None: + destdoor.entrance = entrance + ret.key_logic = world.key_logic.copy() from OverworldShuffle import categorize_world_regions diff --git a/Rules.py b/Rules.py index 260b2893..cc9bead3 100644 --- a/Rules.py +++ b/Rules.py @@ -341,7 +341,7 @@ def global_rules(world, player): set_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: state.can_melt_things(player)) set_rule(world.get_entrance('Ice Hookshot Ledge Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Ice Hookshot Balcony Path', player), lambda state: state.has('Hookshot', player)) - if not world.is_copied_world and not world.get_door('Ice Switch Room SE', player).entranceFlag: + if not world.get_door('Ice Switch Room SE', player).entranceFlag: set_rule(world.get_entrance('Ice Switch Room SE', player), lambda state: state.has('Cane of Somaria', player) or state.has('Convenient Block', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Boss', player)) set_defeat_dungeon_boss_rule(world.get_location('Ice Palace - Prize', player)) @@ -398,7 +398,7 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Hope Room EN', player), lambda state: state.has('Cane of Somaria', player)) set_rule(world.get_entrance('GT Conveyor Cross WN', player), lambda state: state.has('Hammer', player)) set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player)) - if not world.is_copied_world and not world.get_door('GT Speed Torch SE', player).entranceFlag: + if not world.get_door('GT Speed Torch SE', player).entranceFlag: set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player)) set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player)) @@ -421,7 +421,7 @@ def global_rules(world, player): set_rule(world.get_entrance('GT Gauntlet 2 EN', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 2 SW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 3 NW', player), lambda state: state.can_kill_most_things(player)) - if not world.is_copied_world and not world.get_door('GT Gauntlet 3 SW', player).entranceFlag: + if not world.get_door('GT Gauntlet 3 SW', player).entranceFlag: set_rule(world.get_entrance('GT Gauntlet 3 SW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 4 NW', player), lambda state: state.can_kill_most_things(player)) set_rule(world.get_entrance('GT Gauntlet 4 SW', player), lambda state: state.can_kill_most_things(player)) @@ -441,11 +441,10 @@ def global_rules(world, player): set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player)) # crystal switch rules - if not world.is_copied_world: - if world.get_door('Thieves Attic ES', player).crystal == CrystalBarrier.Blue: - set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player)) - else: - set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player)) + if world.get_door('Thieves Attic ES', player).crystal == CrystalBarrier.Blue: + set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player)) + else: + set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player)) set_rule(world.get_entrance('Thieves Attic Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player)) set_rule(world.get_entrance('Thieves Attic Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player)) From 0d9d6f2ceba7bfe2aaccbf2f8768d798902daaab Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 24 Aug 2022 12:53:04 -0600 Subject: [PATCH 25/41] Copy world needs to copy location type --- Main.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Main.py b/Main.py index 3787e81b..bb2593be 100644 --- a/Main.py +++ b/Main.py @@ -549,11 +549,7 @@ def copy_dynamic_regions_and_locations(world, ret): for location in world.dynamic_locations: new_reg = ret.get_region(location.parent_region.name, location.parent_region.player) new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg) - # todo: this is potentially dangerous. later refactor so we - # can apply dynamic region rules on top of copied world like other rules - new_loc.access_rule = location.access_rule - new_loc.always_allow = location.always_allow - new_loc.item_rule = location.item_rule + new_loc.type = location.type new_reg.locations.append(new_loc) ret.clear_location_cache() From 5d419210afeb1ef9bb5e24743b24ff1be9e32c74 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 24 Aug 2022 13:18:01 -0600 Subject: [PATCH 26/41] Fix a bank conflict with OWR Release notes and version bump --- Main.py | 2 +- RELEASENOTES.md | 5 +++++ Rom.py | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Main.py b/Main.py index bb2593be..d47fbc46 100644 --- a/Main.py +++ b/Main.py @@ -31,7 +31,7 @@ from Utils import output_path, parse_player_names from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config from source.tools.BPS import create_bps_from_data -__version__ = '1.0.1.1-u' +__version__ = '1.0.1.2-u' from source.classes.BabelFish import BabelFish diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 56ecca32..0603d95e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -183,6 +183,11 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Unstable +* 1.0.1.2 + * Fixed an issue with small key bias rework + * Fixed an issue where trinity goal would open pyramid unexpectedly. (No longer does so if ER mdoe is shuffling holes). Crystals goal updated to match that behavior. + * Fixed a playthrough issue that was not respecting pot rules + * Fixed an issue that was conflicting with downstream OWR project * 1.0.1.1 * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be * Certain pot items no longer reload when reloading the supertile (matches original pot behavior better) diff --git a/Rom.py b/Rom.py index 58a144ad..4af847ad 100644 --- a/Rom.py +++ b/Rom.py @@ -1534,9 +1534,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): rom.write_bytes(room.address(), room.rom_data()) if world.pottery[player] not in ['none']: - rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000)) + rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2B8000)) # make hammer pegs use different tiles - Room0127.write_to_rom(snes_to_pc(0x2A8000), rom) + Room0127.write_to_rom(snes_to_pc(0x2B8000), rom) if world.pot_contents[player]: colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery'] From 0dfdbfd39ecfdbc2d6b93f30da58b874289f4c93 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 24 Aug 2022 15:00:48 -0600 Subject: [PATCH 27/41] Removed "good bee" from Mothula logic Fixed an issue with Mystery generation and windows file path --- BaseClasses.py | 20 ++++++++++---------- Bosses.py | 3 +-- Mystery.py | 12 ++++++------ RELEASENOTES.md | 2 ++ 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 7f6a00c6..36758575 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1093,16 +1093,16 @@ class CollectionState(object): return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player)) return self.has('Bow', player) - def can_get_good_bee(self, player): - cave = self.world.get_region('Good Bee Cave', player) - return ( - self.can_use_bombs(player) and - self.has_bottle(player) and - self.has('Bug Catching Net', player) and - (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and - cave.can_reach(self) and - self.is_not_bunny(cave, player) - ) + # def can_get_good_bee(self, player): + # cave = self.world.get_region('Good Bee Cave', player) + # return ( + # self.can_use_bombs(player) and + # self.has_bottle(player) and + # self.has('Bug Catching Net', player) and + # (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and + # cave.can_reach(self) and + # self.is_not_bunny(cave, player) + # ) def has_sword(self, player): return self.has('Fighter Sword', player) or self.has('Master Sword', player) or self.has('Tempered Sword', player) or self.has('Golden Sword', player) diff --git a/Bosses.py b/Bosses.py index 206b797c..d84df921 100644 --- a/Bosses.py +++ b/Bosses.py @@ -61,8 +61,7 @@ def MothulaDefeatRule(state, player): # TODO: Not sure how much (if any) extend magic is needed for these two, since they only apply # to non-vanilla locations, so are harder to test, so sticking with what VT has for now: (state.has('Cane of Somaria', player) and state.can_extend_magic(player, 16)) or - (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) or - state.can_get_good_bee(player) + (state.has('Cane of Byrna', player) and state.can_extend_magic(player, 16)) ) def BlindDefeatRule(state, player): diff --git a/Mystery.py b/Mystery.py index 20e0e2ca..568ff6a8 100644 --- a/Mystery.py +++ b/Mystery.py @@ -1,5 +1,7 @@ import argparse import logging +from pathlib import Path +import os import RaceRandom as random import urllib.request import urllib.parse @@ -104,13 +106,11 @@ def main(): DRMain(erargs, seed, BabelFish()) def get_weights(path): - try: - if urllib.parse.urlparse(path).scheme: - return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader) - with open(path, 'r', encoding='utf-8') as f: + if os.path.exists(Path(path)): + with open(path, "r", encoding="utf-8") as f: return yaml.load(f, Loader=yaml.SafeLoader) - except Exception as e: - raise Exception(f'Failed to read weights file: {e}') + elif urllib.parse.urlparse(path).scheme in ['http', 'https']: + return yaml.load(urllib.request.urlopen(path), Loader=yaml.FullLoader) def roll_settings(weights): def get_choice(option, root=None): diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0603d95e..dc790ea1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -184,6 +184,8 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o #### Unstable * 1.0.1.2 + * Removed "good bee" as an in-logic way of killing Mothula + * Fixed an issue with Mystery generation and Windows path * Fixed an issue with small key bias rework * Fixed an issue where trinity goal would open pyramid unexpectedly. (No longer does so if ER mdoe is shuffling holes). Crystals goal updated to match that behavior. * Fixed a playthrough issue that was not respecting pot rules From 01de92d10f741f3bb4809b425208de52ed1d618d Mon Sep 17 00:00:00 2001 From: codemann8 Date: Thu, 25 Aug 2022 00:27:38 -0500 Subject: [PATCH 28/41] Chickens now spawn on same layer as Link --- Rom.py | 2 +- data/base2current.bps | Bin 104377 -> 104378 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Rom.py b/Rom.py index 7b437caa..9d565be5 100644 --- a/Rom.py +++ b/Rom.py @@ -38,7 +38,7 @@ from source.dungeon.RoomList import Room0127 JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '94a8df9d13105abcd655c7d59294ad20' +RANDOMIZERBASEHASH = '831beb6f60c3c99467552493b3ce6f19' class JsonRom(object): diff --git a/data/base2current.bps b/data/base2current.bps index af312a912f5341731add985f82cf58223b65bc3a..b7f58d1854a76692e9455cf8918f0f71df013f5d 100644 GIT binary patch delta 806 zcmWlVZ)g*D7{~eLF6l|y+S=AM>0evhnks2cYYk$t+9XX_nu?oCjhUPCU*K@7pop!e z&$UJkZM?aMdaXA}@sM)sDYu*Zj^V4LfycSMw=%&q|>t2<_CSGGb3Y74hM^ zJv{MYYR|u%?~)OxOvI-j@AVPW%lS9kOz$q;hSXo~or#!Yk#@?(1>?fm7U--VpMsAJ zownU*^#3aC`s2^*9oBE=Ca0j%_>pqb;V4!Fx|uWQCvbltS#tV{5v9f2NItb#rqj$X3}w~TG&9fRIs7=(OaeF@sv*rd9Xd%*rt>dGHaW~Oo$6f zPJ9^ujr-aQ>5e3}>NgIGq$u14GejhNN5Lk4qIZ3^RL(Vd1Od=KqrW`WS?uWkO1Qtm z8kU7MNwsj6B%DvI$?edi_rRC^){?Ccr3B6`-?XqBF!-pI4C$vH`2$58n!mkvA^yW( X7JRJiTzXJOR>lkd|Hpa%TXCsLFeC8$B^meen z&TzTkXN~t3st*Y*)V4r@XBFkoz5-i3;hY2K)F83Sk*R3LAhn1UFyV2SyAOWpohG#8 z7dzmqrG(>`i^RJuHTJ93gd4uprsHAmZ6n!Gq_%7egm1H6FHTfmQfmY z!}Ip`T{G=C@tfGefs?uN)+u4rY|NSX*zLfq`;wkj|BxmzT!2%@i!f*AV+k!5FZNN= zI#!(_RN3)3B9z3is!kgoR`RmyG^r9PoI-{F0e&S!I_5aLHLHb>@fDhCegVEuNiJ5Z zWLYEQ@#b+=@TkK*?~uUdrL9F+yqIX0!@}M8#*y@Ct(zjklx}=*Bd}mJkUOgGAcbRA2Oi#$OzhO2QMM2#DQyK>9c~LB6Ya^Q+C^rkEemmC zmE1Q}2{oZ@$Og$!b*{%!2Wh+BDN+1!AyA|7ox zg^gmD(ztyrpRQfPaTrbwDBlj%TW$UW+_Q{c)s19@rR*DMThV7jxDf}GHF)=WgTD5) x5QaPB*LBC{Li0o=I-wk%U_6$^ Date: Thu, 25 Aug 2022 00:30:32 -0500 Subject: [PATCH 29/41] Changed Castle Gate to only require mirror when screen not swapped --- Rules.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Rules.py b/Rules.py index cc9bead3..0930270b 100644 --- a/Rules.py +++ b/Rules.py @@ -872,8 +872,6 @@ def default_rules(world, player): set_rule(world.get_entrance('Potion Shop Rock (North)', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Zora Approach Rocks (West)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) set_rule(world.get_entrance('Zora Approach Rocks (East)', player), lambda state: state.can_lift_heavy_rocks(player) or state.has_Boots(player)) - set_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has_Mirror(player)) - set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Hyrule Castle Inner East Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Hyrule Castle Outer East Rock', player), lambda state: state.can_lift_rocks(player)) set_rule(world.get_entrance('Bat Cave Ledge Peg', player), lambda state: state.has('Hammer', player)) @@ -1128,6 +1126,8 @@ def ow_inverted_rules(world, player): set_rule(world.get_entrance('HC East Entry Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Courtyard Left Mirror Spot', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('HC Area South Mirror Spot', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Hyrule Castle Main Gate (South)', player), lambda state: state.has_Mirror(player)) + set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has_Mirror(player)) set_rule(world.get_entrance('Top of Pyramid', player), lambda state: state.has_beaten_aga(player)) set_rule(world.get_entrance('Top of Pyramid (Inner)', player), lambda state: state.has_beaten_aga(player)) else: From 55364c071af640cc1b064b36ec9a05df6b4557a2 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 26 Aug 2022 14:42:06 -0600 Subject: [PATCH 30/41] Fixed issue with inverted and certain pottery settings. --- BaseClasses.py | 5 +++++ RELEASENOTES.md | 1 + Regions.py | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 36758575..12ef4ee3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -2805,6 +2805,11 @@ class Pot(object): item = self.item if not self.indicator else self.standing_item_code return [self.x, high_byte, item] + def get_region(self, world, player): + if world.mode[player] == 'inverted' and self.room == 'Links House': + return world.get_region('Inverted Links House', 1) + return world.get_region(self.room, 1) + def __eq__(self, other): return self.x == other.x and self.y == other.y and self.room == other.room diff --git a/RELEASENOTES.md b/RELEASENOTES.md index dc790ea1..76df07c1 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -190,6 +190,7 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Fixed an issue where trinity goal would open pyramid unexpectedly. (No longer does so if ER mdoe is shuffling holes). Crystals goal updated to match that behavior. * Fixed a playthrough issue that was not respecting pot rules * Fixed an issue that was conflicting with downstream OWR project + * Fixed an issue with inverted and certain pottery settings * 1.0.1.1 * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be * Certain pot items no longer reload when reloading the supertile (matches original pot behavior better) diff --git a/Regions.py b/Regions.py index c0ef0364..31a5ab92 100644 --- a/Regions.py +++ b/Regions.py @@ -1065,9 +1065,9 @@ def valid_pot_location(pot, pot_set, world, player): return True if world.pottery[player] in ['reduced', 'clustered'] and pot in pot_set: return True - if world.pottery[player] == 'dungeon' and world.get_region(pot.room, player).type == RegionType.Dungeon: + if world.pottery[player] == 'dungeon' and pot.get_region(world, player).type == RegionType.Dungeon: return True - if world.pottery[player] in ['cave', 'cavekeys'] and world.get_region(pot.room, player).type == RegionType.Cave: + if world.pottery[player] in ['cave', 'cavekeys'] and pot.get_region(world, player).type == RegionType.Cave: return True return False From 3e6f2bb79be31ad26bc949aa56b39b34018ccc45 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 26 Aug 2022 15:20:53 -0600 Subject: [PATCH 31/41] Fixed issue with small key shuffle when big keys aren't --- Fill.py | 3 +++ RELEASENOTES.md | 1 + 2 files changed, 4 insertions(+) diff --git a/Fill.py b/Fill.py index b2dc3400..e2ea4726 100644 --- a/Fill.py +++ b/Fill.py @@ -48,6 +48,9 @@ def fill_dungeons_restrictive(world, shuffled_locations): bigs, smalls, others = [], [], [] for i in dungeon_items: (bigs if i.bigkey else smalls if i.smallkey else others).append(i) + for i in world.itempool: + if i.smallkey and world.keyshuffle[i.player]: + smalls.append(i) def fill(base_state, items, key_pool): fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 76df07c1..1e059997 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -191,6 +191,7 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o * Fixed a playthrough issue that was not respecting pot rules * Fixed an issue that was conflicting with downstream OWR project * Fixed an issue with inverted and certain pottery settings + * Fixed an issue with small keys being shuffled and big keys not (key distribution) * 1.0.1.1 * Fixed the pots in Mire Storyteller/ Dark Desert Hint to be colorized when they should be * Certain pot items no longer reload when reloading the supertile (matches original pot behavior better) From ae0efad8307158d1b320227835659b5b3f0c5e21 Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 26 Aug 2022 15:48:07 -0600 Subject: [PATCH 32/41] More inverted pot fixes --- PotShuffle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PotShuffle.py b/PotShuffle.py index 7806f495..a2a10c0f 100644 --- a/PotShuffle.py +++ b/PotShuffle.py @@ -894,7 +894,7 @@ def shuffle_pots(world, player): new_pot.item = PotItem.FiveRupees if new_pot.item == PotItem.Key: - key = next(location for location in world.get_region(old_pot.room, player).locations if location.name in key_drop_data) + key = next(location for location in old_pot.get_region(world, player).locations if location.name in key_drop_data) key.pot = new_pot if new_pot.room != old_pot.room: # Move pot key to new room @@ -970,7 +970,7 @@ def choose_pots(world, player): dungeon_list = [] for super_tile, pot_list in vanilla_pots.items(): for pot in pot_list: - if world.get_region(pot.room, player).type == RegionType.Cave: + if pot.get_region(world, player).type == RegionType.Cave: pot_pool.add(pot) else: dungeon_list.append(pot) @@ -981,7 +981,7 @@ def choose_pots(world, player): dungeon_count = 0 for super_tile, pot_list in vanilla_pots.items(): for pot in pot_list: - if world.get_region(pot.room, player).type == RegionType.Cave: + if pot.get_region(world, player).type == RegionType.Cave: pot_pool.add(pot) else: dungeon_map[pot.room].append(pot) From 45395e9b15e67e8062e6df1b0eabcf1784d9b98d Mon Sep 17 00:00:00 2001 From: aerinon Date: Fri, 26 Aug 2022 16:08:18 -0600 Subject: [PATCH 33/41] Legacy pot shuffle fix --- Rules.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Rules.py b/Rules.py index c7b9ef44..a553fbfe 100644 --- a/Rules.py +++ b/Rules.py @@ -322,8 +322,10 @@ def global_rules(world, player): # byrna could work with sufficient magic set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) loc = world.get_location('Misery Mire - Spikes Pot Key', player) - if loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area - set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player)) + if loc.pot: + if loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area + set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) + or state.has('Cane of Byrna', player) or state.has('Cape', player)) set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player)) set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player)) set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player)) From 4aff460b22b1fe1af0ec9997b3d447b1aaa4f1d9 Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 30 Aug 2022 13:51:12 -0600 Subject: [PATCH 34/41] Fix for crossed doors with ambrosia Fix for ER + OWG in crossed doors Fix for Small Key shuffle --- DoorShuffle.py | 11 ++++++++--- EntranceShuffle.py | 5 +++-- Fill.py | 7 ++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index ecb1b503..380193d5 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -988,11 +988,16 @@ def cross_dungeon(world, player): paths = determine_required_paths(world, player) check_required_paths(paths, world, player) + hc_compass = ItemFactory('Compass (Escape)', player) + at_compass = ItemFactory('Compass (Agahnims Tower)', player) + at_map = ItemFactory('Map (Agahnims Tower)', player) + if world.restrict_boss_items[player] != 'none': + hc_compass.advancement = at_compass.advancement = at_map.advancement = True hc = world.get_dungeon('Hyrule Castle', player) - hc.dungeon_items.append(ItemFactory('Compass (Escape)', player)) + hc.dungeon_items.append(hc_compass) at = world.get_dungeon('Agahnims Tower', player) - at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player)) - at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player)) + at.dungeon_items.append(at_compass) + at.dungeon_items.append(at_map) assign_cross_keys(dungeon_builders, world, player) all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items)) diff --git a/EntranceShuffle.py b/EntranceShuffle.py index 82aa6a75..f67c089b 100644 --- a/EntranceShuffle.py +++ b/EntranceShuffle.py @@ -63,9 +63,10 @@ def link_entrances(world, player): connect_caves(world, lw_entrances, [], hyrule_castle_exits, player) elif world.doorShuffle[player] != 'vanilla': # sanc is in light world, so must all of HC if door shuffle is on - connect_mandatory_exits(world, lw_entrances, - [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')], + hyrule_castle_exits = [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)', 'Hyrule Castle Exit (South)')] + connect_mandatory_exits(world, lw_entrances, hyrule_castle_exits, list(LW_Dungeon_Entrances_Must_Exit), player) + connect_caves(world, lw_entrances, [], hyrule_castle_exits, player) else: connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player) connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player) diff --git a/Fill.py b/Fill.py index e2ea4726..5a414c98 100644 --- a/Fill.py +++ b/Fill.py @@ -48,9 +48,10 @@ def fill_dungeons_restrictive(world, shuffled_locations): bigs, smalls, others = [], [], [] for i in dungeon_items: (bigs if i.bigkey else smalls if i.smallkey else others).append(i) + unplaced_smalls = list(smalls) for i in world.itempool: if i.smallkey and world.keyshuffle[i.player]: - smalls.append(i) + unplaced_smalls.append(i) def fill(base_state, items, key_pool): fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True) @@ -59,12 +60,12 @@ def fill_dungeons_restrictive(world, shuffled_locations): big_state_base = all_state_base.copy() for x in smalls + others: big_state_base.collect(x, True) - fill(big_state_base, bigs, smalls) + fill(big_state_base, bigs, unplaced_smalls) random.shuffle(shuffled_locations) small_state_base = all_state_base.copy() for x in others: small_state_base.collect(x, True) - fill(small_state_base, smalls, list(smalls)) + fill(small_state_base, smalls, unplaced_smalls) random.shuffle(shuffled_locations) fill(all_state_base, others, None) From 258d66fb5cef6c5c477a1eac4e668fba41411f2c Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 31 Aug 2022 14:10:02 -0600 Subject: [PATCH 35/41] Minor standard fix, fix for test suite, made one error less annoying --- DoorShuffle.py | 6 +++++- TestSuite.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/DoorShuffle.py b/DoorShuffle.py index 380193d5..ea287d8f 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -214,7 +214,7 @@ def vanilla_key_logic(world, player): key_layout = build_key_layout(builder, start_regions, doors, world, player) valid = validate_key_layout(key_layout, world, player) if not valid: - logging.getLogger('').warning('Vanilla key layout not valid %s', builder.name) + logging.getLogger('').info('Vanilla key layout not valid %s', builder.name) builder.key_door_proposal = doors if player not in world.key_logic.keys(): world.key_logic[player] = {} @@ -1911,8 +1911,10 @@ def find_inaccessible_regions(world, player): def find_accessible_entrances(world, player, builder): entrances = [region.name for region in (portal.door.entrance.parent_region for portal in world.dungeon_portals[player]) if region.dungeon.name == builder.name] entrances.extend(drop_entrances[builder.name]) + hc_std = False if world.mode[player] == 'standard' and builder.name == 'Hyrule Castle': + hc_std = True start_regions = ['Hyrule Castle Courtyard'] elif world.mode[player] != 'inverted': start_regions = ['Links House', 'Sanctuary'] @@ -1937,6 +1939,8 @@ def find_accessible_entrances(world, player, builder): if connect not in queue and connect not in visited_regions: queue.append(connect) for ext in next_region.exits: + if hc_std and ext.name == 'Hyrule Castle Main Gate (North)': # just skip it + continue connect = ext.connected_region if connect is None or ext.door and ext.door.blocked: continue diff --git a/TestSuite.py b/TestSuite.py index 062cb2eb..ca620b21 100644 --- a/TestSuite.py +++ b/TestSuite.py @@ -45,7 +45,7 @@ def main(args=None): test("Vanilla ", "--shuffle vanilla") test("Retro ", "--retro --shuffle vanilla") - test("Keysanity ", "--shuffle vanilla --keydropshuffle drops_only --keysanity") + test("Keysanity ", "--shuffle vanilla --dropshuffle --keysanity") test("Shopsanity", "--shuffle vanilla --shopsanity") test("Simple ", "--shuffle simple") test("Full ", "--shuffle full") From 2c7817057918e66dc3287397dd7e13e95223ca44 Mon Sep 17 00:00:00 2001 From: aerinon Date: Wed, 31 Aug 2022 16:16:20 -0600 Subject: [PATCH 36/41] Pyinstaller 5.0+ build fix --- source/meta/build-dr.py | 1 - source/meta/build-gui.py | 1 - 2 files changed, 2 deletions(-) diff --git a/source/meta/build-dr.py b/source/meta/build-dr.py index 6f26adb9..a83c9d56 100644 --- a/source/meta/build-dr.py +++ b/source/meta/build-dr.py @@ -22,7 +22,6 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform. subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ", upx_string, "-y ", - "--onefile ", f"--distpath {DEST_DIRECTORY} ", ]), shell=True) diff --git a/source/meta/build-gui.py b/source/meta/build-gui.py index ae284261..4986df67 100644 --- a/source/meta/build-gui.py +++ b/source/meta/build-gui.py @@ -22,7 +22,6 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform. subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ", upx_string, "-y ", - "--onefile ", f"--distpath {DEST_DIRECTORY} ", ]), shell=True) From 43130d05786aed47475fe8453b0f79cb01b5b492 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Fri, 2 Sep 2022 22:41:25 +0200 Subject: [PATCH 37/41] Fix tile swap parity check in Keep Similar --- OverworldShuffle.py | 65 ++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 289bcf20..6cff5c22 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -561,8 +561,8 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): group_parity = {} for group_data in groups: group = group_data[0] - parity = [0, 0, 0, 0, 0] - # vertical land + parity = [0, 0, 0, 0, 0, 0] + # 0: vertical if 0x00 in group: parity[0] += 1 if 0x0f in group: @@ -571,40 +571,45 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): parity[0] -= 1 if 0x81 in group: parity[0] -= 1 - # horizontal land + # 1: horizontal land single if 0x1a in group: parity[1] -= 1 if 0x1b in group: parity[1] += 1 if 0x28 in group: - parity[1] += 1 - if 0x29 in group: parity[1] -= 1 - if 0x30 in group: - parity[1] -= 2 - if 0x3a in group: - parity[1] += 2 - # horizontal water - if 0x2d in group: + if 0x29 in group: + parity[1] += 1 + # 2: horizontal land double + if 0x28 in group: parity[2] += 1 - if 0x80 in group: + if 0x29 in group: parity[2] -= 1 - # whirlpool + if 0x30 in group: + parity[2] -= 1 + if 0x3a in group: + parity[2] += 1 + # 3: horizontal water + if 0x2d in group: + parity[3] += 1 + if 0x80 in group: + parity[3] -= 1 + # 4: whirlpool if 0x0f in group: - parity[3] += 1 + parity[4] += 1 if 0x12 in group: - parity[3] += 1 + parity[4] += 1 if 0x33 in group: - parity[3] += 1 + parity[4] += 1 if 0x35 in group: - parity[3] += 1 - # dropdown exit + parity[4] += 1 + # 5: dropdown exit if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group: - parity[4] += 1 + parity[5] += 1 if 0x1b in group and world.mode[player] != 'standard': - parity[4] += 1 + parity[5] += 1 if 0x1b in group and world.shuffle_ganon: - parity[4] -= 1 + parity[5] -= 1 group_parity[group[0]] = parity attempts = 1000 @@ -629,18 +634,24 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): exist_lw_regions.extend(lw_regions) exist_dw_regions.extend(dw_regions) - parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(5)] - parity[3] %= 2 # actual parity - if (world.owCrossed[player] == 'none' or do_grouped) and parity[:4] != [0, 0, 0, 0]: + parity = [sum(group_parity[group[0][0]][i] for group in groups if group not in removed) for i in range(6)] + if not world.owKeepSimilar[player]: + parity[1] += 2*parity[2] + parity[2] = 0 + # if crossed terrain: + # parity[1] += parity[3] + # parity[3] = 0 + parity[4] %= 2 # actual parity + if (world.owCrossed[player] == 'none' or do_grouped) and parity[:5] != [0, 0, 0, 0, 0]: attempts -= 1 continue # ensure sanc can be placed in LW in certain modes if not do_grouped and world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'lean', 'crossed', 'insanity'] and world.mode[player] != 'inverted' and (world.doorShuffle[player] != 'crossed' or world.intensity[player] < 3 or world.mode[player] == 'standard'): - free_dw_drops = parity[4] + (1 if world.shuffle_ganon else 0) + free_dw_drops = parity[5] + (1 if world.shuffle_ganon else 0) free_drops = 6 + (1 if world.mode[player] != 'standard' else 0) + (1 if world.shuffle_ganon else 0) if free_dw_drops == free_drops: - attempts -= 1 - continue + attempts -= 1 + continue break (exist_owids, exist_lw_regions, exist_dw_regions) = result_list From 2bd89bc367e583bb2910f21e2f9e745c15f5aa1c Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Fri, 2 Sep 2022 22:47:05 +0200 Subject: [PATCH 38/41] Fix for Digging Game screens in Grouped --- OverworldShuffle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 6cff5c22..17a8faf0 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -727,7 +727,7 @@ def define_tile_groups(world, player, do_grouped): if world.owShuffle[player] == 'vanilla' and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x00, 0x2d, 0x80], [0x0f, 0x81], [0x1a, 0x1b], [0x28, 0x29], [0x30, 0x3a]]) - if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and world.owCrossed[player] == 'none': + if world.owShuffle[player] == 'parallel' and world.owKeepSimilar[player] and (world.owCrossed[player] == 'none' or do_grouped): merge_groups([[0x28, 0x29]]) if not world.owWhirlpoolShuffle[player] and (world.owCrossed[player] == 'none' or do_grouped): From d6ae8b9fab6d2f9ade98748f8553ef118a55e9f1 Mon Sep 17 00:00:00 2001 From: Catobat <69204835+Catobat@users.noreply.github.com> Date: Fri, 2 Sep 2022 22:51:59 +0200 Subject: [PATCH 39/41] Counting is hard --- OverworldShuffle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/OverworldShuffle.py b/OverworldShuffle.py index 17a8faf0..fe417281 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -604,8 +604,9 @@ def shuffle_tiles(world, groups, result_list, do_grouped, player): if 0x35 in group: parity[4] += 1 # 5: dropdown exit - if 0x00 in group or 0x02 in group or 0x13 in group or 0x15 in group or 0x18 in group or 0x22 in group: - parity[5] += 1 + for id in [0x00, 0x02, 0x13, 0x15, 0x18, 0x22]: + if id in group: + parity[5] += 1 if 0x1b in group and world.mode[player] != 'standard': parity[5] += 1 if 0x1b in group and world.shuffle_ganon: From a9ed28c59adac2dc86a6c9641ceab43d407fb97a Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 4 Sep 2022 14:09:54 -0500 Subject: [PATCH 40/41] Overhauled how map checks display dungeon prizes --- Rom.py | 55 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/Rom.py b/Rom.py index f028b443..598df466 100644 --- a/Rom.py +++ b/Rom.py @@ -1484,8 +1484,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player] or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']): compass_mode = 0x01 # show on pickup - if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') \ - or (world.owMixed[player] and not (world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default')): + if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') or world.owMixed[player]: compass_mode |= 0x80 # turn on locating dungeons if world.overworld_map[player] == 'compass': compass_mode |= 0x20 # show icon if compass is collected, 0x00 for maps @@ -1500,39 +1499,49 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): for idx, x_map in enumerate(x_map_position_generic): rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map)) rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0)) - elif world.shuffle[player] == 'vanilla': + elif world.overworld_map[player] == 'default': # disable HC/AT/GT icons - # rom.write_bytes(0x53E8A, int16_as_bytes(0xFF00)) # GT - # rom.write_bytes(0x53E8C, int16_as_bytes(0xFF00)) # AT + if not world.owMixed[player]: + rom.write_bytes(0x53E8A, int16_as_bytes(0xFF00)) # GT + rom.write_bytes(0x53E8C, int16_as_bytes(0xFF00)) # AT rom.write_bytes(0x53E8E, int16_as_bytes(0xFF00)) # HC for dungeon, portal_list in dungeon_portals.items(): ow_map_index = dungeon_table[dungeon].map_index - if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default': - if len(portal_list) == 1: - portal_idx = 0 - else: - if world.doorShuffle[player] == 'crossed': - # the random choice excludes sanctuary - portal_idx = next((i for i, elem in enumerate(portal_list) - if world.get_portal(elem, player).chosen), random.choice([1, 2, 3])) - else: - portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}[dungeon] + if world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default': + vanilla_entrances = { 'Hyrule Castle': 'Hyrule Castle Entrance (South)', + 'Desert Palace': 'Desert Palace Entrance (North)', + 'Skull Woods': 'Skull Woods Final Section' + } + entrance_name = vanilla_entrances[dungeon] if dungeon in vanilla_entrances else dungeon + entrance = world.get_entrance(entrance_name, player) else: - if dungeon in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower']: - portal_idx = -1 - elif len(portal_list) == 1: - portal_idx = 0 + if world.shuffle[player] != 'vanilla': + if len(portal_list) == 1: + portal_idx = 0 + else: + if world.doorShuffle[player] == 'crossed': + # the random choice excludes sanctuary + portal_idx = next((i for i, elem in enumerate(portal_list) + if world.get_portal(elem, player).chosen), random.choice([1, 2, 3])) + else: + portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}[dungeon] else: - portal_idx = {'Desert Palace': 1, 'Skull Woods': 3, 'Turtle Rock': 0}[dungeon] - portal = world.get_portal(portal_list[0 if portal_idx == -1 else portal_idx], player) - entrance = portal.find_portal_entrance() + if dungeon in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower']: + portal_idx = -1 + elif len(portal_list) == 1: + portal_idx = 0 + else: + portal_idx = {'Desert Palace': 1, 'Skull Woods': 3, 'Turtle Rock': 0}[dungeon] + portal = world.get_portal(portal_list[0 if portal_idx == -1 else portal_idx], player) + entrance = portal.find_portal_entrance() world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00 coords = ow_prize_table[entrance.name] # figure out compass entrances and what world (light/dark) - if world.shuffle[player] == 'vanilla' or world.overworld_map[player] != 'default': + if world.overworld_map[player] != 'default' or world.owMixed[player]: rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0])) rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1])) rom.write_byte(0x53EA6+ow_map_index, world_indicator) + # in crossed doors - flip the compass exists flags if world.doorShuffle[player] == 'crossed': for dungeon, portal_list in dungeon_portals.items(): From 0030e1bf1d9174e4c1ef03f5d34ca9be2b8c0108 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Sun, 4 Sep 2022 22:23:35 -0500 Subject: [PATCH 41/41] Version bump 0.2.10.0 --- CHANGELOG.md | 14 ++++++++++++++ OverworldShuffle.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1755913e..aac5994d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.2.10.0 +- Merged DR v1.0.1.1-1.0.1.2 + - Removed text color from hint tiles + - Removed Good Bee requirement from Mothula + - Some keylogic/generation fixes + - Fixed a Pottery logic issue in the playthru +- Fixed a generation error in Mixed OWR, resulting in more possible Mixed scenarios (thanks Catobat) +- Added more scenarios where OW Map Checks in Mixed OWR show dungeon prizes in their respective worlds +- Fixed rupee logic to consider Pottery option and lack of early rupees +- Changed Lean ER + Inverted Dark Chapel start is guaranteed to be in DW +- Fixed graphical issue with Hammerpeg Cave +- Fixed logic rule with HC Main Gate to not require mirror if screen is swapped +- Removed Crossed OWR option: "None (Allowed)" + ### 0.2.9.1 - Lite/Lean ER now includes Cave Pot locations with various Pottery options - Changed Unique Boss Shuffle so that GT Bosses are unique amongst themselves diff --git a/OverworldShuffle.py b/OverworldShuffle.py index fe417281..bf1193a4 100644 --- a/OverworldShuffle.py +++ b/OverworldShuffle.py @@ -6,7 +6,7 @@ from Regions import mark_dark_world_regions, mark_light_world_regions from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel from Utils import bidict -version_number = '0.2.9.1' +version_number = '0.2.10.0' # branch indicator is intentionally different across branches version_branch = '-u'