diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index d7d1b88b..e541bf25 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -1,25 +1,33 @@ from __future__ import annotations -# Factorio technologies are imported from a .json document in /data -from typing import Dict, Set, FrozenSet, Tuple, Union, List -from collections import Counter -import os + import json +import logging +import os import string +from collections import Counter +from concurrent.futures import ThreadPoolExecutor +from typing import Dict, Set, FrozenSet, Tuple, Union, List import Utils -import logging - from . import Options factorio_id = factorio_base_id = 2 ** 17 +# Factorio technologies are imported from a .json document in /data source_folder = os.path.join(os.path.dirname(__file__), "data") -with open(os.path.join(source_folder, "techs.json")) as f: - raw = json.load(f) -with open(os.path.join(source_folder, "recipes.json")) as f: - raw_recipes = json.load(f) -with open(os.path.join(source_folder, "machines.json")) as f: - raw_machines = json.load(f) +pool = ThreadPoolExecutor(1) + + +def load_json_data(data_name: str) -> dict: + with open(os.path.join(source_folder, f"{data_name}.json")) as f: + return json.load(f) + + +techs_future = pool.submit(load_json_data, "techs") +recipes_future = pool.submit(load_json_data, "recipes") +machines_future = pool.submit(load_json_data, "machines") +items_future = pool.submit(load_json_data, "items") + tech_table: Dict[str, int] = {} technology_table: Dict[str, Technology] = {} @@ -177,8 +185,7 @@ class Machine(FactorioElement): recipe_sources: Dict[str, Set[str]] = {} # recipe_name -> technology source # recipes and technologies can share names in Factorio -for technology_name in sorted(raw): - data = raw[technology_name] +for technology_name, data in sorted(techs_future.result().items()): current_ingredients = set(data["ingredients"]) technology = Technology(technology_name, current_ingredients, factorio_id, has_modifier=data["has_modifier"], unlocks=set(data["unlocks"])) @@ -188,11 +195,14 @@ for technology_name in sorted(raw): for recipe_name in technology.unlocks: recipe_sources.setdefault(recipe_name, set()).add(technology_name) -del (raw) +del techs_future recipes = {} all_product_sources: Dict[str, Set[Recipe]] = {"character": set()} # add uranium mining to logic graph. TODO: add to automatic extractor for mod support +raw_recipes = recipes_future.result() +del recipes_future + raw_recipes["uranium-ore"] = { "ingredients": {"sulfuric-acid": 1}, "products": {"uranium-ore": 1}, @@ -225,11 +235,10 @@ for recipe_name, recipe_data in raw_recipes.items(): for product_name in recipe.products: all_product_sources.setdefault(product_name, set()).add(recipe) -del (raw_recipes) machines: Dict[str, Machine] = {} -for name, categories in raw_machines.items(): +for name, categories in machines_future.result().items(): machine = Machine(name, set(categories)) machines[name] = machine @@ -238,7 +247,8 @@ machines["electric-mining-drill"] = Machine("electric-mining-drill", {"mining"}) machines["pumpjack"] = Machine("pumpjack", {"basic-fluid"}) machines["assembling-machine-1"].categories.add("crafting-with-fluid") # mod enables this machines["character"].categories.add("basic-crafting") # somehow this is implied and not exported -del (raw_machines) + +del machines_future # build requirements graph for all technology ingredients @@ -504,3 +514,12 @@ def get_science_pack_pools() -> Dict[str, Set[str]]: already_taken |= current current_difficulty *= 2 return science_pack_pools + + +item_stack_sizes: Dict[str, int] = items_future.result() +non_stacking_items: Set[str] = {item for item, stack in item_stack_sizes.items() if stack == 1} +stacking_items: Set[str] = set(item_stack_sizes) - non_stacking_items + +# cleanup async helpers +pool.shutdown() +del pool diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 9a86a05f..dbeaab6f 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -8,7 +8,7 @@ from .Technologies import base_tech_table, recipe_sources, base_technology_table all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, rocket_recipes, \ progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \ get_science_pack_pools, Recipe, recipes, technology_table, tech_table, factorio_base_id, useless_technologies, \ - liquids + liquids, stacking_items from .Shapes import get_shapes from .Mod import generate_mod from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal @@ -226,11 +226,14 @@ class Factorio(World): return Recipe(original.name, self.get_category(original.category, liquids_used), new_ingredients, original.products, original.energy) - def make_balanced_recipe(self, original: Recipe, pool: list, factor: float = 1, allow_liquids: int = 2) -> \ - Recipe: + def make_balanced_recipe(self, original: Recipe, pool: typing.Set[str], factor: float = 1, + allow_liquids: int = 2) -> Recipe: """Generate a recipe from pool with time and cost similar to original * factor""" new_ingredients = {} - pool = sorted(pool, key=lambda x: self.world.random.random()) + # have to first sort for determinism, while filtering out non-stacking items + pool: typing.List[str] = sorted(pool & stacking_items) + # then sort with random data to shuffle + self.world.random.shuffle(pool) target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor) target_energy = original.total_energy * factor target_num_ingredients = len(original.ingredients) @@ -346,9 +349,9 @@ class Factorio(World): if self.world.silo[self.player].value == Silo.option_randomize_recipe \ or self.world.satellite[self.player].value == Satellite.option_randomize_recipe: - valid_pool = [] + valid_pool = set() for pack in sorted(self.world.max_science_pack[self.player].get_allowed_packs()): - valid_pool += sorted(science_pack_pools[pack]) + valid_pool |= science_pack_pools[pack] if self.world.silo[self.player].value == Silo.option_randomize_recipe: new_recipe = self.make_balanced_recipe(recipes["rocket-silo"], valid_pool, diff --git a/worlds/factorio/data/items.json b/worlds/factorio/data/items.json new file mode 100644 index 00000000..fa34430f --- /dev/null +++ b/worlds/factorio/data/items.json @@ -0,0 +1 @@ +{"wooden-chest":50,"iron-chest":50,"steel-chest":50,"storage-tank":50,"transport-belt":100,"fast-transport-belt":100,"express-transport-belt":100,"underground-belt":50,"fast-underground-belt":50,"express-underground-belt":50,"splitter":50,"fast-splitter":50,"express-splitter":50,"loader":50,"fast-loader":50,"express-loader":50,"burner-inserter":50,"inserter":50,"long-handed-inserter":50,"fast-inserter":50,"filter-inserter":50,"stack-inserter":50,"stack-filter-inserter":50,"small-electric-pole":50,"medium-electric-pole":50,"big-electric-pole":50,"substation":50,"pipe":100,"pipe-to-ground":50,"pump":50,"rail":100,"train-stop":10,"rail-signal":50,"rail-chain-signal":50,"locomotive":5,"cargo-wagon":5,"fluid-wagon":5,"artillery-wagon":5,"car":1,"tank":1,"spidertron":1,"spidertron-remote":1,"logistic-robot":50,"construction-robot":50,"logistic-chest-active-provider":50,"logistic-chest-passive-provider":50,"logistic-chest-storage":50,"logistic-chest-buffer":50,"logistic-chest-requester":50,"roboport":10,"small-lamp":50,"red-wire":200,"green-wire":200,"arithmetic-combinator":50,"decider-combinator":50,"constant-combinator":50,"power-switch":50,"programmable-speaker":50,"stone-brick":100,"concrete":100,"hazard-concrete":100,"refined-concrete":100,"refined-hazard-concrete":100,"landfill":100,"cliff-explosives":20,"dummy-steel-axe":1,"repair-pack":100,"blueprint":1,"deconstruction-planner":1,"upgrade-planner":1,"blueprint-book":1,"copy-paste-tool":1,"cut-paste-tool":1,"boiler":50,"steam-engine":10,"solar-panel":50,"accumulator":50,"nuclear-reactor":10,"heat-pipe":50,"heat-exchanger":50,"steam-turbine":10,"burner-mining-drill":50,"electric-mining-drill":50,"offshore-pump":20,"pumpjack":20,"stone-furnace":50,"steel-furnace":50,"electric-furnace":50,"assembling-machine-1":50,"assembling-machine-2":50,"assembling-machine-3":50,"oil-refinery":10,"chemical-plant":10,"centrifuge":50,"lab":10,"beacon":10,"speed-module":50,"speed-module-2":50,"speed-module-3":50,"effectivity-module":50,"effectivity-module-2":50,"effectivity-module-3":50,"productivity-module":50,"productivity-module-2":50,"productivity-module-3":50,"rocket-silo":1,"satellite":1,"wood":100,"coal":50,"stone":50,"iron-ore":50,"copper-ore":50,"uranium-ore":50,"raw-fish":100,"iron-plate":100,"copper-plate":100,"solid-fuel":50,"steel-plate":100,"plastic-bar":100,"sulfur":50,"battery":200,"explosives":50,"crude-oil-barrel":10,"heavy-oil-barrel":10,"light-oil-barrel":10,"lubricant-barrel":10,"petroleum-gas-barrel":10,"sulfuric-acid-barrel":10,"water-barrel":10,"copper-cable":200,"iron-stick":100,"iron-gear-wheel":100,"empty-barrel":10,"electronic-circuit":200,"advanced-circuit":200,"processing-unit":100,"engine-unit":50,"electric-engine-unit":50,"flying-robot-frame":50,"rocket-control-unit":10,"low-density-structure":10,"rocket-fuel":10,"rocket-part":5,"nuclear-fuel":1,"uranium-235":100,"uranium-238":100,"uranium-fuel-cell":50,"used-up-uranium-fuel-cell":50,"automation-science-pack":200,"logistic-science-pack":200,"military-science-pack":200,"chemical-science-pack":200,"production-science-pack":200,"utility-science-pack":200,"space-science-pack":2000,"coin":100000,"pistol":5,"submachine-gun":5,"tank-machine-gun":1,"vehicle-machine-gun":1,"tank-flamethrower":1,"shotgun":5,"combat-shotgun":5,"rocket-launcher":5,"flamethrower":5,"land-mine":100,"artillery-wagon-cannon":1,"spidertron-rocket-launcher-1":1,"spidertron-rocket-launcher-2":1,"spidertron-rocket-launcher-3":1,"spidertron-rocket-launcher-4":1,"tank-cannon":1,"firearm-magazine":200,"piercing-rounds-magazine":200,"uranium-rounds-magazine":200,"shotgun-shell":200,"piercing-shotgun-shell":200,"cannon-shell":200,"explosive-cannon-shell":200,"uranium-cannon-shell":200,"explosive-uranium-cannon-shell":200,"artillery-shell":1,"rocket":200,"explosive-rocket":200,"atomic-bomb":10,"flamethrower-ammo":100,"grenade":100,"cluster-grenade":100,"poison-capsule":100,"slowdown-capsule":100,"defender-capsule":100,"distractor-capsule":100,"destroyer-capsule":100,"light-armor":1,"heavy-armor":1,"modular-armor":1,"power-armor":1,"power-armor-mk2":1,"solar-panel-equipment":20,"fusion-reactor-equipment":20,"battery-equipment":20,"battery-mk2-equipment":20,"belt-immunity-equipment":20,"exoskeleton-equipment":20,"personal-roboport-equipment":20,"personal-roboport-mk2-equipment":20,"night-vision-equipment":20,"energy-shield-equipment":20,"energy-shield-mk2-equipment":20,"personal-laser-defense-equipment":20,"discharge-defense-equipment":20,"discharge-defense-remote":1,"stone-wall":100,"gate":50,"gun-turret":50,"laser-turret":50,"flamethrower-turret":50,"artillery-turret":10,"artillery-targeting-remote":1,"radar":50,"player-port":50,"item-unknown":1,"electric-energy-interface":50,"linked-chest":10,"heat-interface":20,"linked-belt":10,"infinity-chest":10,"infinity-pipe":10,"selection-tool":1,"item-with-inventory":1,"item-with-label":1,"item-with-tags":1,"simple-entity-with-force":50,"simple-entity-with-owner":50,"burner-generator":10} \ No newline at end of file diff --git a/worlds/factorio/data/resources.json b/worlds/factorio/data/resources.json new file mode 100644 index 00000000..10279db3 --- /dev/null +++ b/worlds/factorio/data/resources.json @@ -0,0 +1 @@ +{"iron-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"iron-ore":{"name":"iron-ore","amount":1}}},"copper-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"copper-ore":{"name":"copper-ore","amount":1}}},"stone":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"stone":{"name":"stone","amount":1}}},"coal":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":1,"products":{"coal":{"name":"coal","amount":1}}},"uranium-ore":{"minable":true,"infinite":false,"category":"basic-solid","mining_time":2,"required_fluid":"sulfuric-acid","fluid_amount":10,"products":{"uranium-ore":{"name":"uranium-ore","amount":1}}},"crude-oil":{"minable":true,"infinite":true,"infinite_depletion":10,"category":"basic-fluid","mining_time":1,"products":{"crude-oil":{"name":"crude-oil","amount":10}}}} \ No newline at end of file