From 38eef5ac002b36e575c85a986c3ea881e61a608b Mon Sep 17 00:00:00 2001 From: CaitSith2 Date: Sun, 25 Sep 2022 04:01:00 -0700 Subject: [PATCH] Remaining changes * Process burnt results. * Add fluidbox info to machine definition * Recipe base cost now uses cheapest product source. * free sample exclusions no longer hard-coded * science pack pool exclusions no longer hard-coded * burnt result is forced to have cost of machine used to obtain the results factored into the cost of crafting. * name of machine is no longer assumed to be name of item. machine definition now list item sources specifically. * science pack difficulty is now the minimum of average estimated difficulty of unlocked recipes and 8. * Actually exclude archipelago-extractor from mod list. --- worlds/factorio/Technologies.py | 111 +++++++-- worlds/factorio/data/machines.json | 222 +----------------- .../data/mod_template/data-final-fixes.lua | 4 +- 3 files changed, 92 insertions(+), 245 deletions(-) diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index ad18f5556e..8a98aa3249 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -121,15 +121,17 @@ class Recipe(FactorioElement): products: Dict[str, int] energy: float mining: bool + burning: bool unlocked_at_start: bool - def __init__(self, name: str, category: str, ingredients: Dict[str, int], products: Dict[str, int], energy: float, mining: bool, unlocked_at_start: bool): + def __init__(self, name: str, category: str, ingredients: Dict[str, int], products: Dict[str, int], energy: float, mining: bool = False, unlocked_at_start: bool = False, burning: bool = False): self.name = name self.category = category self.ingredients = ingredients self.products = products self.energy = energy self.mining = mining + self.burning = burning self.unlocked_at_start = unlocked_at_start def __repr__(self): @@ -165,12 +167,22 @@ class Recipe(FactorioElement): ingredients = Counter() for ingredient, cost in self.ingredients.items(): if ingredient in all_product_sources: + recipe_counters: Dict[str, (Recipe, Counter)] = {} for recipe in all_product_sources[ingredient]: + recipe_ingredients = Counter() if recipe.ingredients: - ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in + recipe_ingredients.update({name: amount * cost / recipe.products[ingredient] for name, amount in recipe.base_cost.items()}) else: - ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient] + recipe_ingredients[ingredient] += recipe.energy * cost / recipe.products[ingredient] + recipe_counters[recipe.name] = (recipe, recipe_ingredients) + selected_recipe_ingredients = None + for recipe_name, (recipe, recipe_ingredients) in recipe_counters.items(): + if not selected_recipe_ingredients or ( + sum([rel_cost.get(name, high_cost_item) * value for name, value in recipe_ingredients.items()]) + < sum([rel_cost.get(name, high_cost_item) * value for name, value in selected_recipe_ingredients.items()])): + selected_recipe_ingredients = recipe_ingredients + ingredients.update(selected_recipe_ingredients) else: ingredients[ingredient] += cost return ingredients @@ -209,11 +221,14 @@ class Recipe(FactorioElement): class Machine(FactorioElement): - def __init__(self, name, categories, machine_type, speed): + def __init__(self, name, categories, machine_type, speed, item_sources, input_fluids, output_fluids): self.name: str = name self.categories: set = categories self.machine_type: str = machine_type self.speed: float = speed + self.item_sources: set = item_sources + self.input_fluids: int = int(input_fluids) + self.output_fluids: int = int(output_fluids) class Lab(FactorioElement): def __init__(self, name, inputs): @@ -306,8 +321,12 @@ del resources_future machines: Dict[str, Machine] = {} labs: Dict[str, Lab] = {} +rel_cost = {} +high_cost_item = 10000 for name, prototype in machines_future.result().items(): + machine_prototype = prototype.get("common", {}).get("type", None) + machine_item_sources = set(prototype.get("common", {}).get("placeable_by", {})) for machine_type, machine_data in prototype.items(): if machine_type == "lab": lab = Lab(name, machine_data.get("inputs", set())) @@ -326,7 +345,7 @@ for name, prototype in machines_future.result().items(): "mining": True, "unlocked_at_start": True } - machine = Machine(name, {category}, machine_data.get("type"), 1) + machine = Machine(name, {category}, machine_prototype, 1, machine_item_sources, 0, 1) machines[name] = machine if machine_type == "crafting": categories = machine_data.get("categories", set()) @@ -336,7 +355,7 @@ for name, prototype in machines_future.result().items(): speed = machine_data.get("speed", 1) input_fluid_box = machine_data.get("input_fluid_box", 0) output_fluid_box = machine_data.get("output_fluid_box", 0) - machine = Machine(name, set(categories), machine_data.get("type"), speed) + machine = Machine(name, set(categories), machine_prototype, speed, machine_item_sources, input_fluid_box, output_fluid_box) machines[name] = machine if machine_type == "mining": categories = machine_data.get("categories", set()) @@ -345,7 +364,7 @@ for name, prototype in machines_future.result().items(): speed = machine_data.get("speed", 1) input_fluid_box = machine_data.get("input_fluid_box", False) # Can this machine mine resources with required fluids? output_fluid_box = machine_data.get("output_fluid_box", False) # Can this machine mine fluid resources? - machine = machines.setdefault(name, Machine(name, set(categories), machine_data.get("type"), speed)) + machine = machines.setdefault(name, Machine(name, set(categories), machine_prototype, speed, machine_item_sources, input_fluid_box, output_fluid_box)) machine.categories |= set(categories) # character has both crafting and basic-solid machine.speed = (machine.speed + speed) / 2 machines[name] = machine @@ -354,7 +373,7 @@ for name, prototype in machines_future.result().items(): output_fluid = machine_data.get("output_fluid") target_temperature = machine_data.get("target_temperature") energy_usage = machine_data.get("energy_usage") - amount = energy_usage / (target_temperature - fluids[input_fluid].get("default_temperature", 15)) / fluids[input_fluid].get("heat_capacity", 1) + amount = energy_usage / (target_temperature - fluids[input_fluid].default_temperature) / fluids[input_fluid].heat_capacity amount *= 60 amount = int(amount) category = f"boiling-{amount}-{input_fluid}-to-{output_fluid}-at-{target_temperature}-degrees-centigrade" @@ -363,25 +382,48 @@ for name, prototype in machines_future.result().items(): "products": {output_fluid: amount}, "energy": 1, "category": category, - "mining": False, + "mining": True, "unlocked_at_start": True } - machine = Machine(name, {category}, machine_data.get("type"), 1) + machine = Machine(name, {category}, machine_prototype, 1, machine_item_sources, 1, 1) machines[name] = machine + if machine_type == "fuel_burner": + categories = set(machine_data.get("categories")) + fuel_items = {name: item for name, item in items.items() if item.burnt_result and item.fuel_category in categories} + if not fuel_items: + continue + energy_usage = machine_data.get("energy_usage") + for item_name, item in fuel_items.items(): + recipe_name = f"burning-{item_name}-for-{item.burnt_result}" + raw_recipes[recipe_name] = { + "ingredients": {item_name: 1}, + "products": {item.burnt_result: 1}, + "energy": item.fuel_value, + "category": item.fuel_category, + "burning": True, + "unlocked_at_start": True + } + machine = machines.get(name, Machine(name, categories, machine_prototype, energy_usage * 60, machine_item_sources, 0, 0)) + machines[name] = machine + # TODO: set up machine/recipe pairs for burners in order to retrieve the burnt_result from items. # TODO: set up machine/recipe pairs for retrieving rocket_launch_products from items. - - del machines_future for recipe_name, recipe_data in raw_recipes.items(): # example: # "accumulator":{"ingredients":{"iron-plate":2,"battery":5},"products":{"accumulator":1},"category":"crafting"} # FIXME: add mining? - recipe = Recipe(recipe_name, recipe_data["category"], recipe_data["ingredients"], - recipe_data["products"], recipe_data.get("energy", 0), recipe_data.get("mining", False), recipe_data.get("unlocked_at_start", False)) + recipe = Recipe(recipe_name, + recipe_data["category"], + recipe_data["ingredients"], + recipe_data["products"], + recipe_data.get("energy", 0), + recipe_data.get("mining", False), + recipe_data.get("unlocked_at_start", False), + recipe_data.get("burning", False)) recipes[recipe_name] = recipe if set(recipe.products).isdisjoint(set(recipe.ingredients)): for product_name in [product_name for product_name, amount in recipe.products.items() if amount > 0]: @@ -399,6 +441,7 @@ for recipe_name, recipe_data in raw_recipes.items(): machines["assembling-machine-1"].categories |= machines["assembling-machine-3"].categories # mod enables this machines["assembling-machine-2"].categories |= machines["assembling-machine-3"].categories +machines["rocket-silo"].input_fluids = 3 # machines["character"].categories.add("basic-crafting") # charter only knows the categories of "crafting" and "basic-solid" by default. @@ -406,7 +449,7 @@ machines["assembling-machine-2"].categories |= machines["assembling-machine-3"]. mods: Dict[str, Mod] = {} for name, version in mods_future.result().items(): - if name in ["base"]: + if name in ["base", "archipelago-extractor"]: continue mod = Mod(name, version) mods[name] = mod @@ -463,12 +506,21 @@ def recursively_get_unlocking_technologies(ingredient_name, _done=None, unlock_f return current_technologies +for item_name, item in items.items(): + if item.place_result and machines.get(item.place_result, None): + machines[item.place_result].item_sources |= {item_name} + + required_machine_technologies: Dict[str, FrozenSet[Technology]] = {} -for ingredient_name in machines: +for ingredient_name, machine in machines.items(): if ingredient_name == "character": required_machine_technologies[ingredient_name] = frozenset() continue - required_machine_technologies[ingredient_name] = frozenset(recursively_get_unlocking_technologies(ingredient_name)) + techs = recursively_get_unlocking_technologies(ingredient_name) + for item_name in machine.item_sources: + techs |= recursively_get_unlocking_technologies(item_name) + required_machine_technologies[ingredient_name] = frozenset(techs) + for ingredient_name in labs: required_machine_technologies[ingredient_name] = frozenset(recursively_get_unlocking_technologies(ingredient_name)) @@ -527,7 +579,10 @@ def get_rocket_requirements(silo_recipe: Recipe, part_recipe: Recipe, satellite_ return {tech.name for tech in techs} -free_sample_exclusions: Set[str] = all_ingredient_names | {"rocket-part"} +free_sample_exclusions: Set[str] = all_ingredient_names.copy() # Rocket-silo crafting recipe results to be added here. +for name, recipe in recipes.items(): + if machines[recipe.crafting_machine].machine_type == "rocket-silo": + free_sample_exclusions |= set(recipe.products) # progressive technologies # auto-progressive @@ -641,7 +696,6 @@ useless_technologies: Set[str] = {tech_name for tech_name in common_tech_table lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()} -rel_cost = {} for name, recipe in {name: recipe for name, recipe in recipes.items() if recipe.mining and not recipe.ingredients}.items(): machine = machines[machine_per_category[recipe.category]] cost = recipe.energy / machine.speed @@ -654,7 +708,15 @@ def get_estimated_difficulty(recipe: Recipe): cost = 0 for ingredient_name, amount in base_ingredients.items(): - cost += rel_cost.get(ingredient_name, 1000) * amount + cost += rel_cost.get(ingredient_name, high_cost_item) * amount + if recipe.burning: + for item in machines[recipe.crafting_machine].item_sources: + if items[item].product_sources: + cost += min([get_estimated_difficulty(recipe) for recipe in items[item].product_sources]) + else: + cost += high_cost_item + + # print(f"{recipe.name}: {cost} ({({ingredient_name: rel_cost.get(ingredient_name, high_cost_item) * amount for ingredient_name, amount in base_ingredients.items()})})") return cost @@ -664,14 +726,19 @@ for name, recipe in {name: recipe for name, recipe in recipes.items() if recipe. for product_name, amount in recipe.products.items(): rel_cost[product_name] = cost / amount -exclusion_list: Set[str] = all_ingredient_names | {"rocket-part", "used-up-uranium-fuel-cell"} +exclusion_list: Set[str] = free_sample_exclusions.copy() # Also exclude the burnt results. @Utils.cache_argsless def get_science_pack_pools() -> Dict[str, Set[str]]: science_pack_pools: Dict[str, Set[str]] = {} already_taken = exclusion_list.copy() - current_difficulty = 5 + unlocked_recipes = {name: recipe for name, recipe in recipes.items() + if recipe.unlocked_at_start + and not recipe.recursive_unlocking_technologies + and get_estimated_difficulty(recipe) < high_cost_item} # wood in recipe is expensive. + average_starting_difficulty = sum([get_estimated_difficulty(recipe) for name, recipe in unlocked_recipes.items()]) / len(unlocked_recipes) + current_difficulty = min(average_starting_difficulty, 8) for science_pack in Options.MaxSciencePack.get_ordered_science_packs(): current = science_pack_pools[science_pack] = set() for name, recipe in recipes.items(): diff --git a/worlds/factorio/data/machines.json b/worlds/factorio/data/machines.json index cf18564bb1..fc8cced5c0 100644 --- a/worlds/factorio/data/machines.json +++ b/worlds/factorio/data/machines.json @@ -1,221 +1 @@ -{ - "boiler":{ - "boiler":{ - "type":"boiler", - "input_fluid":"water", - "output_fluid":"steam", - "target_temperature":165, - "energy_usage":30000 - } - }, - "nuclear-reactor":{ - "fuel_burner":{ - "type":"reactor", - "categories":{ - "nuclear":true - }, - "energy_usage":666666.666666666627861559391021728515625 - } - }, - "heat-exchanger":{ - "boiler":{ - "type":"boiler", - "input_fluid":"water", - "output_fluid":"steam", - "target_temperature":500, - "energy_usage":166666.66666666665696538984775543212890625 - } - }, - "burner-mining-drill":{ - "mining":{ - "type":"mining-drill", - "categories":{ - "basic-solid":true - }, - "speed":0.25, - "input_fluid_box":false, - "output_fluid_box":false - } - }, - "electric-mining-drill":{ - "mining":{ - "type":"mining-drill", - "categories":{ - "basic-solid":true - }, - "speed":0.5, - "input_fluid_box":true, - "output_fluid_box":false - } - }, - "offshore-pump":{ - "offshore-pump":{ - "type":"offshore-pump", - "fluid":"water", - "speed":20 - } - }, - "pumpjack":{ - "mining":{ - "type":"mining-drill", - "categories":{ - "basic-fluid":true - }, - "speed":1, - "input_fluid_box":false, - "output_fluid_box":true - } - }, - "stone-furnace":{ - "crafting":{ - "type":"furnace", - "speed":1, - "categories":{ - "smelting":true - }, - "input_fluid_box":0, - "output_fluid_box":0 - } - }, - "steel-furnace":{ - "crafting":{ - "type":"furnace", - "speed":2, - "categories":{ - "smelting":true - }, - "input_fluid_box":0, - "output_fluid_box":0 - } - }, - "electric-furnace":{ - "crafting":{ - "type":"furnace", - "speed":2, - "categories":{ - "smelting":true - }, - "input_fluid_box":0, - "output_fluid_box":0 - } - }, - "assembling-machine-1":{ - "crafting":{ - "type":"assembling-machine", - "speed":0.5, - "categories":{ - "crafting":true, - "basic-crafting":true, - "advanced-crafting":true - }, - "input_fluid_box":0, - "output_fluid_box":0 - } - }, - "assembling-machine-2":{ - "crafting":{ - "type":"assembling-machine", - "speed":0.75, - "categories":{ - "basic-crafting":true, - "crafting":true, - "advanced-crafting":true, - "crafting-with-fluid":true - }, - "input_fluid_box":1, - "output_fluid_box":1 - } - }, - "assembling-machine-3":{ - "crafting":{ - "type":"assembling-machine", - "speed":1.25, - "categories":{ - "basic-crafting":true, - "crafting":true, - "advanced-crafting":true, - "crafting-with-fluid":true - }, - "input_fluid_box":1, - "output_fluid_box":1 - } - }, - "oil-refinery":{ - "crafting":{ - "type":"assembling-machine", - "speed":1, - "categories":{ - "oil-processing":true - }, - "input_fluid_box":2, - "output_fluid_box":3 - } - }, - "chemical-plant":{ - "crafting":{ - "type":"assembling-machine", - "speed":1, - "categories":{ - "chemistry":true - }, - "input_fluid_box":2, - "output_fluid_box":2 - } - }, - "centrifuge":{ - "crafting":{ - "type":"assembling-machine", - "speed":1, - "categories":{ - "centrifuging":true - }, - "input_fluid_box":0, - "output_fluid_box":0 - } - }, - "lab":{ - "lab":{ - "inputs":[ - "automation-science-pack", - "logistic-science-pack", - "military-science-pack", - "chemical-science-pack", - "production-science-pack", - "utility-science-pack", - "space-science-pack" - ] - } - }, - "rocket-silo":{ - "crafting":{ - "type":"rocket-silo", - "speed":1, - "categories":{ - "rocket-building":true - }, - "fixed_recipe":"rocket-part", - "input_fluid_box":0, - "output_fluid_box":0 - } - }, - "character":{ - "crafting":{ - "type":"character", - "speed":1, - "categories":{ - "crafting":true - }, - "input_fluid_box":0, - "output_fluid_box":0 - }, - "mining":{ - "type":"character", - "categories":{ - "basic-solid":true - }, - "speed":0.5, - "input_fluid_box":false, - "output_fluid_box":false - } - } -} \ No newline at end of file +{"boiler":{"boiler":{"input_fluid":"water","output_fluid":"steam","target_temperature":165,"energy_usage":30000},"common":{"type":"boiler","placeable_by":{"boiler":true}}},"nuclear-reactor":{"fuel_burner":{"categories":{"nuclear":true},"energy_usage":666666.666666666627861559391021728515625},"common":{"type":"reactor","placeable_by":{"nuclear-reactor":true}}},"heat-exchanger":{"boiler":{"input_fluid":"water","output_fluid":"steam","target_temperature":500,"energy_usage":166666.66666666665696538984775543212890625},"common":{"type":"boiler","placeable_by":{"heat-exchanger":true}}},"burner-mining-drill":{"mining":{"categories":{"basic-solid":true},"speed":0.25,"input_fluid_box":false,"output_fluid_box":false},"common":{"type":"mining-drill","placeable_by":{"burner-mining-drill":true}}},"electric-mining-drill":{"mining":{"categories":{"basic-solid":true},"speed":0.5,"input_fluid_box":true,"output_fluid_box":false},"common":{"type":"mining-drill","placeable_by":{"electric-mining-drill":true}}},"offshore-pump":{"offshore-pump":{"fluid":"water","speed":20},"common":{"type":"offshore-pump","placeable_by":{"offshore-pump":true}}},"pumpjack":{"mining":{"categories":{"basic-fluid":true},"speed":1,"input_fluid_box":false,"output_fluid_box":true},"common":{"type":"mining-drill","placeable_by":{"pumpjack":true}}},"stone-furnace":{"crafting":{"speed":1,"categories":{"smelting":true},"input_fluid_box":0,"output_fluid_box":0},"common":{"type":"furnace","placeable_by":{"stone-furnace":true}}},"steel-furnace":{"crafting":{"speed":2,"categories":{"smelting":true},"input_fluid_box":0,"output_fluid_box":0},"common":{"type":"furnace","placeable_by":{"steel-furnace":true}}},"electric-furnace":{"crafting":{"speed":2,"categories":{"smelting":true},"input_fluid_box":0,"output_fluid_box":0},"common":{"type":"furnace","placeable_by":{"electric-furnace":true}}},"assembling-machine-1":{"crafting":{"speed":0.5,"categories":{"crafting":true,"basic-crafting":true,"advanced-crafting":true},"input_fluid_box":0,"output_fluid_box":0},"common":{"type":"assembling-machine","placeable_by":{"assembling-machine-1":true}}},"assembling-machine-2":{"crafting":{"speed":0.75,"categories":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true},"input_fluid_box":1,"output_fluid_box":1},"common":{"type":"assembling-machine","placeable_by":{"assembling-machine-2":true}}},"assembling-machine-3":{"crafting":{"speed":1.25,"categories":{"basic-crafting":true,"crafting":true,"advanced-crafting":true,"crafting-with-fluid":true},"input_fluid_box":1,"output_fluid_box":1},"common":{"type":"assembling-machine","placeable_by":{"assembling-machine-3":true}}},"oil-refinery":{"crafting":{"speed":1,"categories":{"oil-processing":true},"input_fluid_box":2,"output_fluid_box":3},"common":{"type":"assembling-machine","placeable_by":{"oil-refinery":true}}},"chemical-plant":{"crafting":{"speed":1,"categories":{"chemistry":true},"input_fluid_box":2,"output_fluid_box":2},"common":{"type":"assembling-machine","placeable_by":{"chemical-plant":true}}},"centrifuge":{"crafting":{"speed":1,"categories":{"centrifuging":true},"input_fluid_box":0,"output_fluid_box":0},"common":{"type":"assembling-machine","placeable_by":{"centrifuge":true}}},"lab":{"lab":{"inputs":["automation-science-pack","logistic-science-pack","military-science-pack","chemical-science-pack","production-science-pack","utility-science-pack","space-science-pack"]},"common":{"type":"lab","placeable_by":{"lab":true}}},"rocket-silo":{"crafting":{"speed":1,"categories":{"rocket-building":true},"fixed_recipe":"rocket-part","input_fluid_box":0,"output_fluid_box":0},"common":{"type":"rocket-silo","placeable_by":{"rocket-silo":true}}},"character":{"crafting":{"speed":1,"categories":{"crafting":true},"input_fluid_box":0,"output_fluid_box":0},"mining":{"categories":{"basic-solid":true},"speed":0.5,"input_fluid_box":false,"output_fluid_box":false},"common":{"type":"character"}}} \ No newline at end of file diff --git a/worlds/factorio/data/mod_template/data-final-fixes.lua b/worlds/factorio/data/mod_template/data-final-fixes.lua index b934d690cb..87902b58da 100644 --- a/worlds/factorio/data/mod_template/data-final-fixes.lua +++ b/worlds/factorio/data/mod_template/data-final-fixes.lua @@ -241,13 +241,13 @@ data:extend{new_tree_copy} {% endfor %} {% if recipe_time_scale %} {%- for recipe_name, recipe in recipes.items() %} -{%- if not recipe.mining %} +{%- if not recipe.mining and not recipe.burning %} adjust_energy("{{ recipe_name }}", {{ flop_random(*recipe_time_scale) }}) {%- endif %} {%- endfor -%} {% elif recipe_time_range %} {%- for recipe_name, recipe in recipes.items() %} -{%- if not recipe.mining %} +{%- if not recipe.mining and not recipe.burning %} set_energy("{{ recipe_name }}", {{ flop_random(*recipe_time_range) }}) {%- endif %} {%- endfor -%}