From a563b930cc917c3739a0ffdd46ef6911bacf273c Mon Sep 17 00:00:00 2001 From: codemann8 Date: Tue, 23 Nov 2021 22:32:17 -0600 Subject: [PATCH] Modeled mirror portal re-entry logic with followers --- BaseClasses.py | 154 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 126 insertions(+), 28 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 157dab9a..3bb4f8b3 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1584,16 +1584,16 @@ class Entrance(object): self.temp_path = [] def can_reach(self, state): - # Destination Pickup OW Only No Ledges Can S&Q - multi_step_locations = { 'Pyramid Crack': ('Big Bomb', True, True, False), - 'Missing Smith': ('Frog', True, False, True), - 'Middle Aged Man': ('Dark Blacksmith Ruins', True, False, True) } + # Destination Pickup OW Only No Ledges Can S&Q Allow Mirror + multi_step_locations = { 'Pyramid Crack': ('Big Bomb', True, True, False, True), + 'Missing Smith': ('Frog', True, False, True, True), + 'Middle Aged Man': ('Dark Blacksmith Ruins', True, False, True, True) } if self.name in multi_step_locations: if self not in state.path: - world = self.parent_region.world if self.parent_region else None + world = self.parent_region.world step_location = world.get_location(multi_step_locations[self.name][0], self.player) - if step_location.can_reach(state) and self.can_reach_thru(state, step_location.parent_region, multi_step_locations[self.name][1], multi_step_locations[self.name][2], multi_step_locations[self.name][3]) and self.access_rule(state): + if step_location.can_reach(state) and self.can_reach_thru(state, step_location, multi_step_locations[self.name][1], multi_step_locations[self.name][2], multi_step_locations[self.name][3], multi_step_locations[self.name][4]) and self.access_rule(state): if not self in state.path: path = state.path.get(step_location.parent_region, (step_location.parent_region.name, None)) item_name = step_location.item.name if step_location.item else 'Pick Up Item' @@ -1615,8 +1615,8 @@ class Entrance(object): return False - def can_reach_thru(self, state, start_region, ignore_underworld=False, ignore_ledges=False, allow_save_quit=False): - def explore_region(region, path = []): + def can_reach_thru(self, state, step_location, ignore_underworld=False, ignore_ledges=False, allow_save_quit=False, allow_mirror_reentry=False): + def explore_region(region, destination, path = []): nonlocal found if region not in explored_regions: explored_regions[region] = path @@ -1624,34 +1624,132 @@ class Entrance(object): if exit.connected_region and (not ignore_ledges or exit.spot_type != 'Ledge') \ and exit.connected_region.name not in ['Dig Game Area'] \ and exit.access_rule(state): - if exit.connected_region == self.parent_region: + if exit.connected_region == destination: found = True - explored_regions[self.parent_region] = path + [exit] + explored_regions[destination] = path + [exit] elif not ignore_underworld or region.type == exit.connected_region.type or exit.connected_region.type not in [RegionType.Cave, RegionType.Dungeon]: exits_to_traverse.append(tuple((exit, path))) - def traverse_paths(region, start_path=[]): - explore_region(region, start_path) - while not found and len(exits_to_traverse): - exit, path = exits_to_traverse.pop(0) - explore_region(exit.connected_region, path + [exit]) - if found: - self.temp_path = explored_regions[self.parent_region] - - found = False - explored_regions = {} - exits_to_traverse = list() - traverse_paths(start_region.entrances[0].parent_region) - - if not found and allow_save_quit: + def traverse_paths(region, destination, start_path=[]): + nonlocal explored_regions, exits_to_traverse explored_regions = {} exits_to_traverse = list() - world = self.parent_region.world if self.parent_region else None - exit = world.get_entrance('Links House S&Q', self.player) - traverse_paths(exit.connected_region, [exit]) + explore_region(region, destination, start_path) + while not found and len(exits_to_traverse): + exit, path = exits_to_traverse.pop(0) + explore_region(exit.connected_region, destination, path + [exit]) + if found: + self.temp_path = explored_regions[destination] - #TODO: Implement residual mirror portal placing for the previous leg, to be used for the final destination + start_region = step_location.parent_region + explored_regions = {} + exits_to_traverse = list() + found = False + + if not found and allow_mirror_reentry and state.has('Magic Mirror', self.player): + # check for path using mirror portal re-entry at location of the follower pickup + # this is checked first as this often the shortest path + follower_region = start_region + if follower_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]: + follower_region = start_region.entrances[0].parent_region + if (follower_region.world.mode[self.player] != 'inverted') == (follower_region.type == RegionType.LightWorld): + from OWEdges import OWTileRegions + from OverworldShuffle import ow_connections + owid = OWTileRegions[follower_region.name] + (mirror_map, other_world) = ow_connections[owid % 0x40] + mirror_map.extend(other_world) + mirror_exit = None + while len(mirror_map): + if mirror_map[0][1] == follower_region.name: + mirror_exit = mirror_map[0][0] + break + mirror_map.pop(0) + if mirror_exit: + mirror_region = follower_region.world.get_entrance(mirror_exit, self.player).parent_region + if mirror_region.can_reach(state): + traverse_paths(mirror_region, self.parent_region) + if found: + path = state.path.get(mirror_region, (mirror_region.name, None)) + path = (follower_region.name, (mirror_exit, path)) + item_name = step_location.item.name if step_location.item else 'Pick Up Item' + if start_region.name != follower_region.name: + path = (start_region.name, (start_region.entrances[0].name, path)) + path = (f'{step_location.parent_region.name} Exit', ('Leave Item Area', (item_name, path))) + else: + path = (item_name, path) + path = ('Use Mirror Portal', (follower_region.name, path)) + while len(self.temp_path): + exit = self.temp_path.pop(0) + path = (exit.name, (exit.parent_region.name, path)) + item_name = self.connected_region.locations[0].item.name if self.connected_region.locations[0].item else 'Deliver Item' + path = (self.parent_region.name, path) + state.path[self] = (self.name, path) + + if not found: + # check normal paths + traverse_paths(start_region.entrances[0].parent_region, self.parent_region) + if not found and allow_save_quit: + # check paths involving save and quit + exit = self.parent_region.world.get_entrance('Links House S&Q', self.player) + traverse_paths(exit.connected_region, self.parent_region, [exit]) + + if not found and allow_mirror_reentry and state.has('Magic Mirror', self.player): + # check for paths using mirror portal re-entry at location of final destination + # this is checked last as this is the most complicated/exhaustive check + follower_region = start_region + if follower_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]: + follower_region = start_region.entrances[0].parent_region + if (follower_region.world.mode[self.player] != 'inverted') == (follower_region.type == RegionType.LightWorld): + dest_region = self.parent_region + if dest_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]: + dest_region = start_region.entrances[0].parent_region + if (dest_region.world.mode[self.player] != 'inverted') != (dest_region.type == RegionType.LightWorld): + from OWEdges import OWTileRegions + from OverworldShuffle import ow_connections + owid = OWTileRegions[dest_region.name] + (mirror_map, other_world) = ow_connections[owid % 0x40] + mirror_map.extend(other_world) + mirror_map = [(x, d) for (x, d) in mirror_map if x in [e.name for e in dest_region.exits]] + # loop thru potential places to leave a mirror portal + while len(mirror_map) and not found: + mirror_exit = dest_region.world.get_entrance(mirror_map[0][0], self.player) + if mirror_exit.connected_region.type != dest_region.type: + # find path from placed mirror portal to the follower pickup + from Items import ItemFactory + mirror_item = ItemFactory('Magic Mirror', self.player) + while state.prog_items['Magic Mirror', self.player]: + state.remove(mirror_item) + temp_ignore_ledges = ignore_ledges + ignore_ledges = False + traverse_paths(mirror_exit.connected_region, start_region) + ignore_ledges = temp_ignore_ledges + state.collect(mirror_item, True) + if found: + path_to_pickup = self.temp_path + # find path from follower pickup to placed mirror portal + found = False + state.remove(mirror_item) + traverse_paths(follower_region, mirror_exit.connected_region) + state.collect(mirror_item, True) + mirror_map.pop(0) + if found: + path = state.path.get(self.parent_region, (self.parent_region.name, None)) + path = (mirror_exit.name, path) + + while len(path_to_pickup): + exit = path_to_pickup.pop(0) + path = (exit.name, (exit.parent_region.name, path)) + item_name = step_location.item.name if step_location.item else 'Pick Up Item' + path = (f'{step_location.parent_region.name} Exit', (item_name, path)) + + while len(self.temp_path): + exit = self.temp_path.pop(0) + path = (exit.name, (exit.parent_region.name, path)) + path = ('Use Mirror Portal', (mirror_exit.connected_region.name, path)) + path = (self.parent_region.name, path) + state.path[self] = (self.name, path) + return found def connect(self, region, addresses=None, target=None, vanilla=None):