From 01ef29568ad5b57de070855f4bcf715c70681b2c Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Wed, 17 Mar 2021 10:53:40 +0100 Subject: [PATCH] Temporarily destroy the world, instead of copying it. Not pretty, but faster. Unfortunately can't be threaded alongside rom/multidata creation, as otherwise locations may be empty at certain times. --- BaseClasses.py | 4 ---- Main.py | 34 ++++++++++++++++++---------------- worlds/alttp/ItemPool.py | 6 +++--- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 62fdacdd..9b52a2bc 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -141,10 +141,6 @@ class MultiWorld(): #for i in range(players): # self.worlds.append(worlds.alttp.ALTTPWorld({}, i)) - def copy(self) -> MultiWorld: - import copy - return copy.deepcopy(self) - def secure(self): self.random = secrets.SystemRandom() diff --git a/Main.py b/Main.py index eed8551a..12556900 100644 --- a/Main.py +++ b/Main.py @@ -512,12 +512,12 @@ def main(args, seed=None): raise Exception("Game appears as unbeatable. Aborting.") else: logger.warning("Location Accessibility requirements not fulfilled.") - if not args.skip_playthrough: - logger.info('Calculating playthrough.') - create_playthrough(world) if multidata_task: multidata_task.result() # retrieve exception if one exists pool.shutdown() # wait for all queued tasks to complete + if not args.skip_playthrough: + logger.info('Calculating playthrough.') + create_playthrough(world) if args.create_spoiler: # needs spoiler.hashes to be filled, that depend on rom_futures being done world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) @@ -673,10 +673,7 @@ def copy_dynamic_regions_and_locations(world, ret): def create_playthrough(world): - # create a copy as we will modify it - old_world = world - world = world.copy() - + """Destructive to the world it is run on.""" # get locations containing progress items prog_locations = {location for location in world.get_filled_locations() if location.item.advancement} state_cache = [None] @@ -708,9 +705,9 @@ def create_playthrough(world): raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). ' f'Something went terribly wrong here.') else: - old_world.spoiler.unreachables = sphere_candidates.copy() + world.spoiler.unreachables = sphere_candidates break - + restore_later = {} # in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it for num, sphere in reversed(tuple(enumerate(collection_spheres))): to_delete = set() @@ -721,6 +718,7 @@ def create_playthrough(world): location.item = None if world.can_beat_game(state_cache[num]): to_delete.add(location) + restore_later[location] = old_item else: # still required, got to keep it around location.item = old_item @@ -774,19 +772,23 @@ def create_playthrough(world): pathpairs = zip_longest(pathsiter, pathsiter) return list(pathpairs) - old_world.spoiler.paths = dict() + world.spoiler.paths = dict() for player in range(1, world.players + 1): - old_world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) + world.spoiler.paths.update({ str(location) : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) if player in world.alttp_player_ids: - for path in dict(old_world.spoiler.paths).values(): + for path in dict(world.spoiler.paths).values(): if any(exit == 'Pyramid Fairy' for (_, exit) in path): if world.mode[player] != 'inverted': - old_world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) + world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = get_path(state, world.get_region('Big Bomb Shop', player)) else: - old_world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player)) + world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = get_path(state, world.get_region('Inverted Big Bomb Shop', player)) # we can finally output our playthrough - old_world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])} + world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])} for i, sphere in enumerate(collection_spheres): - old_world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)} + world.spoiler.playthrough[str(i + 1)] = {str(location): str(location.item) for location in sorted(sphere)} + + # repair the world again + for location, item in restore_later.items(): + location.item = item diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 638c8824..5429b58f 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -228,11 +228,11 @@ def generate_itempool(world, player: int): raise NotImplementedError(f"Diffulty {world.difficulty[player]}") if world.goal[player] not in {'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'icerodhunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}: - raise NotImplementedError(f"Goal {world.goal[player]}") + raise NotImplementedError(f"Goal {world.goal[player]} for player {player}") if world.mode[player] not in {'open', 'standard', 'inverted'}: - raise NotImplementedError(f"Mode {world.mode[player]}") + raise NotImplementedError(f"Mode {world.mode[player]} for player {player}") if world.timer[player] not in {False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'}: - raise NotImplementedError(f"Timer {world.mode[player]}") + raise NotImplementedError(f"Timer {world.mode[player]} for player {player}") if world.timer[player] in ['ohko', 'timed-ohko']: world.can_take_damage[player] = False