Factorio: option to randomize silo recipe
This commit is contained in:
		
							parent
							
								
									7df06b87a5
								
							
						
					
					
						commit
						08beb5fbe6
					
				|  | @ -94,6 +94,9 @@ Factorio: | ||||||
|     hard : 0 |     hard : 0 | ||||||
|     very_hard : 0 |     very_hard : 0 | ||||||
|     insane : 0 |     insane : 0 | ||||||
|  |   silo: | ||||||
|  |     vanilla: 1 | ||||||
|  |     randomize_recipe: 0 | ||||||
|   free_samples: |   free_samples: | ||||||
|     none: 1 |     none: 1 | ||||||
|     single_craft: 0 |     single_craft: 0 | ||||||
|  |  | ||||||
|  | @ -44,6 +44,13 @@ class TechCost(Choice): | ||||||
|     default = 3 |     default = 3 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class Silo(Choice): | ||||||
|  |     """Ingredients to craft rocket silo.""" | ||||||
|  |     option_vanilla = 0 | ||||||
|  |     option_randomize_recipe = 1 | ||||||
|  |     default = 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class FreeSamples(Choice): | class FreeSamples(Choice): | ||||||
|     """Get free items with your technologies.""" |     """Get free items with your technologies.""" | ||||||
|     option_none = 0 |     option_none = 0 | ||||||
|  | @ -242,6 +249,7 @@ factorio_options: typing.Dict[str, type(Option)] = { | ||||||
|     "max_science_pack": MaxSciencePack, |     "max_science_pack": MaxSciencePack, | ||||||
|     "tech_tree_layout": TechTreeLayout, |     "tech_tree_layout": TechTreeLayout, | ||||||
|     "tech_cost": TechCost, |     "tech_cost": TechCost, | ||||||
|  |     "silo": Silo, | ||||||
|     "free_samples": FreeSamples, |     "free_samples": FreeSamples, | ||||||
|     "tech_tree_information": TechTreeInformation, |     "tech_tree_information": TechTreeInformation, | ||||||
|     "starting_items": FactorioStartItems, |     "starting_items": FactorioStartItems, | ||||||
|  |  | ||||||
|  | @ -82,12 +82,14 @@ class Recipe(FactorioElement): | ||||||
|     category: str |     category: str | ||||||
|     ingredients: Dict[str, int] |     ingredients: Dict[str, int] | ||||||
|     products: Dict[str, int] |     products: Dict[str, int] | ||||||
|  |     energy: float | ||||||
| 
 | 
 | ||||||
|     def __init__(self, name: str, category: str, ingredients: Dict[str, int], products: Dict[str, int]): |     def __init__(self, name: str, category: str, ingredients: Dict[str, int], products: Dict[str, int], energy: float): | ||||||
|         self.name = name |         self.name = name | ||||||
|         self.category = category |         self.category = category | ||||||
|         self.ingredients = ingredients |         self.ingredients = ingredients | ||||||
|         self.products = products |         self.products = products | ||||||
|  |         self.energy = energy | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return f"{self.__class__.__name__}({self.name})" |         return f"{self.__class__.__name__}({self.name})" | ||||||
|  | @ -125,6 +127,19 @@ class Recipe(FactorioElement): | ||||||
|                 ingredients[ingredient] += cost |                 ingredients[ingredient] += cost | ||||||
|         return ingredients |         return ingredients | ||||||
| 
 | 
 | ||||||
|  |     @property | ||||||
|  |     def total_energy(self) -> float: | ||||||
|  |         """Total required energy (crafting time) for single craft""" | ||||||
|  |         # TODO: multiply mining energy by 2 since drill has 0.5 speed | ||||||
|  |         total_energy = self.energy | ||||||
|  |         for ingredient, cost in self.ingredients.items(): | ||||||
|  |             if ingredient in all_product_sources: | ||||||
|  |                 for ingredient_recipe in all_product_sources[ingredient]: # FIXME: this may select the wrong recipe | ||||||
|  |                     craft_count = max((n for name, n in ingredient_recipe.products.items() if name == ingredient)) | ||||||
|  |                     total_energy += ingredient_recipe.total_energy / craft_count * cost | ||||||
|  |                     break | ||||||
|  |         return total_energy | ||||||
|  | 
 | ||||||
| class Machine(FactorioElement): | class Machine(FactorioElement): | ||||||
|     def __init__(self, name, categories): |     def __init__(self, name, categories): | ||||||
|         self.name: str = name |         self.name: str = name | ||||||
|  | @ -151,13 +166,14 @@ del (raw) | ||||||
| recipes = {} | recipes = {} | ||||||
| all_product_sources: Dict[str, Set[Recipe]] = {"character": set()} | all_product_sources: Dict[str, Set[Recipe]] = {"character": set()} | ||||||
| # add uranium mining to logic graph. TODO: add to automatic extractor for mod support | # add uranium mining to logic graph. TODO: add to automatic extractor for mod support | ||||||
| raw_recipes["uranium-ore"] = {"ingredients": {"sulfuric-acid": 1}, "products": {"uranium-ore": 1}, "category": "mining"} | raw_recipes["uranium-ore"] = {"ingredients": {"sulfuric-acid": 1}, "products": {"uranium-ore": 1}, "category": "mining", "energy": 2} | ||||||
| 
 | 
 | ||||||
| for recipe_name, recipe_data in raw_recipes.items(): | for recipe_name, recipe_data in raw_recipes.items(): | ||||||
|     # example: |     # example: | ||||||
|     # "accumulator":{"ingredients":{"iron-plate":2,"battery":5},"products":{"accumulator":1},"category":"crafting"} |     # "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 = Recipe(recipe_name, recipe_data["category"], recipe_data["ingredients"], | ||||||
|  |                     recipe_data["products"], recipe_data["energy"] if "energy" in recipe_data else 0) | ||||||
|     recipes[recipe_name] = recipe |     recipes[recipe_name] = recipe | ||||||
|     if set(recipe.products).isdisjoint( |     if set(recipe.products).isdisjoint( | ||||||
|             # prevents loop recipes like uranium centrifuging |             # prevents loop recipes like uranium centrifuging | ||||||
|  | @ -260,9 +276,11 @@ for ingredient_name in all_ingredient_names: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @functools.lru_cache(10) | @functools.lru_cache(10) | ||||||
| def get_rocket_requirements(recipe: Recipe) -> Set[str]: | def get_rocket_requirements(silo_recipe: Recipe, part_recipe: Recipe) -> Set[str]: | ||||||
|     techs = recursively_get_unlocking_technologies("rocket-silo") |     techs = set() | ||||||
|     for ingredient in recipe.ingredients: |     for ingredient in silo_recipe.ingredients: | ||||||
|  |         techs |= recursively_get_unlocking_technologies(ingredient) | ||||||
|  |     for ingredient in part_recipe.ingredients: | ||||||
|         techs |= recursively_get_unlocking_technologies(ingredient) |         techs |= recursively_get_unlocking_technologies(ingredient) | ||||||
|     return {tech.name for tech in techs} |     return {tech.name for tech in techs} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,13 +2,15 @@ from ..AutoWorld import World | ||||||
| 
 | 
 | ||||||
| from BaseClasses import Region, Entrance, Location, Item | from BaseClasses import Region, Entrance, Location, Item | ||||||
| from .Technologies import base_tech_table, recipe_sources, base_technology_table, advancement_technologies, \ | from .Technologies import base_tech_table, recipe_sources, base_technology_table, advancement_technologies, \ | ||||||
|     all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes, \ |     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, \ |     progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \ | ||||||
|     get_science_pack_pools, Recipe, recipes, technology_table, tech_table |     get_science_pack_pools, Recipe, recipes, technology_table, tech_table | ||||||
| from .Shapes import get_shapes | from .Shapes import get_shapes | ||||||
| from .Mod import generate_mod | from .Mod import generate_mod | ||||||
| from .Options import factorio_options | from .Options import factorio_options | ||||||
| 
 | 
 | ||||||
|  | import logging | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class FactorioItem(Item): | class FactorioItem(Item): | ||||||
|     game = "Factorio" |     game = "Factorio" | ||||||
|  | @ -99,7 +101,11 @@ class Factorio(World): | ||||||
|                     Rules.add_rule(location, lambda state, |                     Rules.add_rule(location, lambda state, | ||||||
|                                                     locations=locations: all(state.can_reach(loc) for loc in locations)) |                                                     locations=locations: all(state.can_reach(loc) for loc in locations)) | ||||||
| 
 | 
 | ||||||
|             victory_tech_names = get_rocket_requirements(self.custom_recipes["rocket-part"]) |             silo_recipe = self.custom_recipes["rocket-silo"] \ | ||||||
|  |                           if "rocket-silo" in self.custom_recipes \ | ||||||
|  |                           else next(iter(all_product_sources.get("rocket-silo"))) | ||||||
|  |             part_recipe = self.custom_recipes["rocket-part"] | ||||||
|  |             victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe) | ||||||
|             world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player) |             world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player) | ||||||
|                                                                                         for technology in |                                                                                         for technology in | ||||||
|                                                                                         victory_tech_names) |                                                                                         victory_tech_names) | ||||||
|  | @ -120,6 +126,76 @@ class Factorio(World): | ||||||
| 
 | 
 | ||||||
|     options = factorio_options |     options = factorio_options | ||||||
| 
 | 
 | ||||||
|  |     def make_balanced_recipe(self, original: Recipe, pool: list, factor: float = 1) -> Recipe: | ||||||
|  |         """Generate a recipe from pool with time and cost similar to original * factor""" | ||||||
|  |         new_ingredients = {} | ||||||
|  |         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) | ||||||
|  |         remaining_raw = target_raw | ||||||
|  |         remaining_energy = target_energy | ||||||
|  |         remaining_num_ingredients = target_num_ingredients | ||||||
|  |         fallback_pool = [] | ||||||
|  | 
 | ||||||
|  |         # fill all but one slot with random ingredients, last with a good match | ||||||
|  |         while remaining_num_ingredients > 0 and len(pool) > 0: | ||||||
|  |             if remaining_num_ingredients == 1: | ||||||
|  |                 max_raw = 1.1 * remaining_raw | ||||||
|  |                 min_raw = 0.9 * remaining_raw | ||||||
|  |                 max_energy = 1.1 * remaining_energy | ||||||
|  |                 min_energy = 1.1 * remaining_energy | ||||||
|  |             else: | ||||||
|  |                 max_raw = remaining_raw * 0.75 | ||||||
|  |                 min_raw = (remaining_raw - max_raw) / remaining_num_ingredients | ||||||
|  |                 max_energy = remaining_energy * 0.75 | ||||||
|  |                 min_energy = (remaining_energy - max_energy) / remaining_num_ingredients | ||||||
|  |             ingredient = pool.pop() | ||||||
|  |             if ingredient not in recipes: | ||||||
|  |                 logging.warning(f"missing recipe for {ingredient}") | ||||||
|  |                 continue | ||||||
|  |             ingredient_recipe = recipes[ingredient] | ||||||
|  |             ingredient_raw = sum((count for ingredient, count in ingredient_recipe.base_cost.items())) | ||||||
|  |             ingredient_energy = ingredient_recipe.total_energy | ||||||
|  |             min_num_raw = min_raw/ingredient_raw | ||||||
|  |             max_num_raw = max_raw/ingredient_raw | ||||||
|  |             min_num_energy = min_energy/ingredient_energy | ||||||
|  |             max_num_energy = max_energy/ingredient_energy | ||||||
|  |             min_num = int(max(1, min_num_raw, min_num_energy)) | ||||||
|  |             max_num = int(min(1000, max_num_raw, max_num_energy)) | ||||||
|  |             if min_num > max_num: | ||||||
|  |                 fallback_pool.append(ingredient) | ||||||
|  |                 continue # can't use that ingredient | ||||||
|  |             num = self.world.random.randint(min_num,max_num) | ||||||
|  |             new_ingredients[ingredient] = num | ||||||
|  |             remaining_raw -= num * ingredient_raw | ||||||
|  |             remaining_energy -= num * ingredient_energy | ||||||
|  |             remaining_num_ingredients -= 1 | ||||||
|  | 
 | ||||||
|  |         # fill failed slots with whatever we got | ||||||
|  |         pool = fallback_pool | ||||||
|  |         while remaining_num_ingredients > 0 and len(pool) > 0: | ||||||
|  |             ingredient = pool.pop() | ||||||
|  |             if ingredient not in recipes: | ||||||
|  |                 logging.warning(f"missing recipe for {ingredient}") | ||||||
|  |                 continue | ||||||
|  |             ingredient_recipe = recipes[ingredient] | ||||||
|  |             ingredient_raw = sum((count for ingredient, count in ingredient_recipe.base_cost.items())) | ||||||
|  |             ingredient_energy = ingredient_recipe.total_energy | ||||||
|  |             num_raw = remaining_raw/ingredient_raw/remaining_num_ingredients | ||||||
|  |             num_energy = remaining_energy/ingredient_energy/remaining_num_ingredients | ||||||
|  |             num = int(min(num_raw, num_energy)) | ||||||
|  |             if num < 1: continue | ||||||
|  |             new_ingredients[ingredient] = num | ||||||
|  |             remaining_raw -= num * ingredient_raw | ||||||
|  |             remaining_energy -= num * ingredient_energy | ||||||
|  |             remaining_num_ingredients -= 1 | ||||||
|  | 
 | ||||||
|  |         if remaining_num_ingredients > 1: | ||||||
|  |             logging.warning("could not randomize recipe") | ||||||
|  | 
 | ||||||
|  |         return Recipe(original.name, original.category, new_ingredients, original.products, original.energy) | ||||||
|  | 
 | ||||||
|     def set_custom_technologies(self): |     def set_custom_technologies(self): | ||||||
|         custom_technologies = {} |         custom_technologies = {} | ||||||
|         allowed_packs = self.world.max_science_pack[self.player].get_allowed_packs() |         allowed_packs = self.world.max_science_pack[self.player].get_allowed_packs() | ||||||
|  | @ -134,7 +210,8 @@ class Factorio(World): | ||||||
|         self.world.random.shuffle(valid_pool) |         self.world.random.shuffle(valid_pool) | ||||||
|         self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category, |         self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category, | ||||||
|                                                      {valid_pool[x] : 10 for x in range(3)}, |                                                      {valid_pool[x] : 10 for x in range(3)}, | ||||||
|                                                      original_rocket_part.products)} |                                                      original_rocket_part.products, | ||||||
|  |                                                      original_rocket_part.energy)} | ||||||
|         self.additional_advancement_technologies = {tech.name for tech in |         self.additional_advancement_technologies = {tech.name for tech in | ||||||
|                                                     self.custom_recipes["rocket-part"].recursive_unlocking_technologies} |                                                     self.custom_recipes["rocket-part"].recursive_unlocking_technologies} | ||||||
| 
 | 
 | ||||||
|  | @ -148,11 +225,21 @@ class Factorio(World): | ||||||
|                     new_ingredients = {} |                     new_ingredients = {} | ||||||
|                     for _ in original.ingredients: |                     for _ in original.ingredients: | ||||||
|                         new_ingredients[valid_pool.pop()] = 1 |                         new_ingredients[valid_pool.pop()] = 1 | ||||||
|                     new_recipe = Recipe(pack, original.category, new_ingredients, original.products) |                     new_recipe = Recipe(pack, original.category, new_ingredients, original.products, original.energy) | ||||||
|                     self.additional_advancement_technologies |= {tech.name for tech in |                     self.additional_advancement_technologies |= {tech.name for tech in | ||||||
|                                                                  new_recipe.recursive_unlocking_technologies} |                                                                  new_recipe.recursive_unlocking_technologies} | ||||||
|                     self.custom_recipes[pack] = new_recipe |                     self.custom_recipes[pack] = new_recipe | ||||||
| 
 | 
 | ||||||
|  |         if self.world.silo[self.player]: | ||||||
|  |             valid_pool = [] | ||||||
|  |             for pack in self.world.max_science_pack[self.player].get_allowed_packs(): | ||||||
|  |                 valid_pool += sorted(science_pack_pools[pack]) | ||||||
|  |             new_recipe = self.make_balanced_recipe(recipes["rocket-silo"], valid_pool, | ||||||
|  |                                                    factor = (self.world.max_science_pack[self.player].value+1)/7) | ||||||
|  |             self.additional_advancement_technologies |= {tech.name for tech in | ||||||
|  |                                                          new_recipe.recursive_unlocking_technologies} | ||||||
|  |             self.custom_recipes["rocket-silo"] = new_recipe | ||||||
|  | 
 | ||||||
|         # handle marking progressive techs as advancement |         # handle marking progressive techs as advancement | ||||||
|         prog_add = set() |         prog_add = set() | ||||||
|         for tech in self.additional_advancement_technologies: |         for tech in self.additional_advancement_technologies: | ||||||
|  |  | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
		Reference in New Issue