diff --git a/Main.py b/Main.py index 2fdcb2b5..70857341 100644 --- a/Main.py +++ b/Main.py @@ -672,11 +672,12 @@ def create_playthrough(world): pathpairs = zip_longest(pathsiter, pathsiter) return list(pathpairs) - world.spoiler.paths = dict() - for player in range(1, world.players + 1): + world.spoiler.paths = {} + topology_worlds = (player for player in world.player_ids if world.worlds[player].topology_present) + for player in topology_worlds: 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 and world.worlds[player].topology_present}) + sphere if location.player == player}) if player in world.alttp_player_ids: for path in dict(world.spoiler.paths).values(): if any(exit == 'Pyramid Fairy' for (_, exit) in path): diff --git a/Utils.py b/Utils.py index 644d5369..f182a1a5 100644 --- a/Utils.py +++ b/Utils.py @@ -69,6 +69,21 @@ def parse_player_names(names, players, teams): return ret +def cache_argsless(function): + if function.__code__.co_argcount: + raise Exception("Can only cache 0 argument functions with this cache.") + + result = sentinel = object() + + def _wrap(): + nonlocal result + if result is sentinel: + result = function() + return result + + return _wrap + + def is_bundled() -> bool: return getattr(sys, 'frozen', False) @@ -132,7 +147,7 @@ def close_console(): parse_yaml = safe_load unsafe_parse_yaml = functools.partial(load, Loader=Loader) - +@cache_argsless def get_public_ipv4() -> str: import socket import urllib.request @@ -148,7 +163,7 @@ def get_public_ipv4() -> str: pass # we could be offline, in a local game, so no point in erroring out return ip - +@cache_argsless def get_public_ipv6() -> str: import socket import urllib.request @@ -161,70 +176,68 @@ def get_public_ipv6() -> str: pass # we could be offline, in a local game, or ipv6 may not be available return ip - +@cache_argsless def get_default_options() -> dict: - if not hasattr(get_default_options, "options"): - # Refer to host.yaml for comments as to what all these options mean. - options = { - "general_options": { - "output_path": "output", - }, - "factorio_options": { - "executable": "factorio\\bin\\x64\\factorio", - }, - "lttp_options": { - "rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc", - "sni": "SNI", - "rom_start": True, + # Refer to host.yaml for comments as to what all these options mean. + options = { + "general_options": { + "output_path": "output", + }, + "factorio_options": { + "executable": "factorio\\bin\\x64\\factorio", + }, + "lttp_options": { + "rom_file": "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc", + "sni": "SNI", + "rom_start": True, - }, - "server_options": { - "host": None, - "port": 38281, - "password": None, - "multidata": None, - "savefile": None, - "disable_save": False, - "loglevel": "info", - "server_password": None, - "disable_item_cheat": False, - "location_check_points": 1, - "hint_cost": 10, - "forfeit_mode": "goal", - "remaining_mode": "goal", - "auto_shutdown": 0, - "compatibility": 2, - "log_network": 0 - }, - "multi_mystery_options": { - "teams": 1, - "enemizer_path": "EnemizerCLI/EnemizerCLI.Core.exe", - "player_files_path": "Players", - "players": 0, - "weights_file_path": "weights.yaml", - "meta_file_path": "meta.yaml", - "pre_roll": False, - "create_spoiler": 1, - "zip_roms": 0, - "zip_diffs": 2, - "zip_apmcs": 1, - "zip_spoiler": 0, - "zip_multidata": 1, - "zip_format": 1, - "glitch_triforce_room": 1, - "race": 0, - "cpu_threads": 0, - "max_attempts": 0, - "take_first_working": False, - "keep_all_seeds": False, - "log_output_path": "Output Logs", - "log_level": None, - "plando_options": "bosses", - } + }, + "server_options": { + "host": None, + "port": 38281, + "password": None, + "multidata": None, + "savefile": None, + "disable_save": False, + "loglevel": "info", + "server_password": None, + "disable_item_cheat": False, + "location_check_points": 1, + "hint_cost": 10, + "forfeit_mode": "goal", + "remaining_mode": "goal", + "auto_shutdown": 0, + "compatibility": 2, + "log_network": 0 + }, + "multi_mystery_options": { + "teams": 1, + "enemizer_path": "EnemizerCLI/EnemizerCLI.Core.exe", + "player_files_path": "Players", + "players": 0, + "weights_file_path": "weights.yaml", + "meta_file_path": "meta.yaml", + "pre_roll": False, + "create_spoiler": 1, + "zip_roms": 0, + "zip_diffs": 2, + "zip_apmcs": 1, + "zip_spoiler": 0, + "zip_multidata": 1, + "zip_format": 1, + "glitch_triforce_room": 1, + "race": 0, + "cpu_threads": 0, + "max_attempts": 0, + "take_first_working": False, + "keep_all_seeds": False, + "log_output_path": "Output Logs", + "log_level": None, + "plando_options": "bosses", } + } - get_default_options.options = options - return get_default_options.options + return options blacklisted_options = {"multi_mystery_options.cpu_threads", @@ -254,7 +267,7 @@ def update_options(src: dict, dest: dict, filename: str, keys: list) -> dict: dest[key] = update_options(value, dest[key], filename, new_keys) return dest - +@cache_argsless def get_options() -> dict: if not hasattr(get_options, "options"): locations = ("options.yaml", "host.yaml", @@ -368,7 +381,7 @@ def get_adjuster_settings(romfile: str) -> typing.Tuple[str, bool]: return romfile, adjusted return romfile, False - +@cache_argsless def get_unique_identifier(): uuid = persistent_load().get("client", {}).get("uuid", None) if uuid: @@ -407,6 +420,7 @@ def restricted_loads(s): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() + class KeyedDefaultDict(collections.defaultdict): def __missing__(self, key): self[key] = value = self.default_factory(key) diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index 51a87561..f5356536 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -393,27 +393,27 @@ rel_cost = { blacklist = all_ingredient_names | {"rocket-part", "crude-oil", "water", "sulfuric-acid", "petroleum-gas", "light-oil", "heavy-oil", "lubricant", "steam"} +@Utils.cache_argsless +def get_science_pack_pools() -> Dict[str, Set[str]]: + def get_estimated_difficulty(recipe: Recipe): + base_ingredients = recipe.base_cost + cost = 0 -def get_estimated_difficulty(recipe: Recipe): - base_ingredients = recipe.base_cost - cost = 0 - - for ingredient_name, amount in base_ingredients.items(): - if ingredient_name not in rel_cost: - raise Exception((recipe.name, ingredient_name)) - cost += rel_cost.get(ingredient_name, 1) * amount - return cost + for ingredient_name, amount in base_ingredients.items(): + cost += rel_cost.get(ingredient_name, 1) * amount + return cost -science_pack_pools = {} -already_taken = blacklist.copy() -current_difficulty = 5 -for science_pack in Options.MaxSciencePack.get_ordered_science_packs(): - current = science_pack_pools[science_pack] = set() - for name, recipe in recipes.items(): - if (science_pack != "automation-science-pack" or not recipe.recursive_unlocking_technologies) \ - and get_estimated_difficulty(recipe) < current_difficulty: - current |= set(recipe.products) - current -= already_taken - already_taken |= current - current_difficulty *= 2 + science_pack_pools = {} + already_taken = blacklist.copy() + current_difficulty = 5 + for science_pack in Options.MaxSciencePack.get_ordered_science_packs(): + current = science_pack_pools[science_pack] = set() + for name, recipe in recipes.items(): + if (science_pack != "automation-science-pack" or not recipe.recursive_unlocking_technologies) \ + and get_estimated_difficulty(recipe) < current_difficulty: + current |= set(recipe.products) + current -= already_taken + already_taken |= current + current_difficulty *= 2 + return science_pack_pools \ No newline at end of file diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 0e8b666d..14e18246 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -4,7 +4,7 @@ from BaseClasses import Region, Entrance, Location, Item from .Technologies import base_tech_table, recipe_sources, base_technology_table, advancement_technologies, \ all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes, \ progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \ - science_pack_pools, Recipe, recipes, technology_table + get_science_pack_pools, Recipe, recipes, technology_table from .Shapes import get_shapes from .Mod import generate_mod from .Options import factorio_options @@ -127,6 +127,7 @@ class Factorio(World): def set_custom_recipes(self): original_rocket_part = recipes["rocket-part"] + science_pack_pools = get_science_pack_pools() valid_pool = sorted(science_pack_pools[self.world.max_science_pack[self.player].get_max_pack()]) self.world.random.shuffle(valid_pool) self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,