From ddba1cd8139d6d7f1fefbaa47abdeaa0c0080936 Mon Sep 17 00:00:00 2001 From: codemann8 Date: Mon, 1 Aug 2022 20:54:29 -0500 Subject: [PATCH] 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]: