Factorio: option to randomize silo recipe
This commit is contained in:
		 black-sliver
					black-sliver
				
			
				
					committed by
					
						 Fabian Dill
						Fabian Dill
					
				
			
			
				
	
			
			
			 Fabian Dill
						Fabian Dill
					
				
			
						parent
						
							7df06b87a5
						
					
				
				
					commit
					08beb5fbe6
				
			| @@ -44,6 +44,13 @@ class TechCost(Choice): | ||||
|     default = 3 | ||||
|  | ||||
|  | ||||
| class Silo(Choice): | ||||
|     """Ingredients to craft rocket silo.""" | ||||
|     option_vanilla = 0 | ||||
|     option_randomize_recipe = 1 | ||||
|     default = 0 | ||||
|  | ||||
|  | ||||
| class FreeSamples(Choice): | ||||
|     """Get free items with your technologies.""" | ||||
|     option_none = 0 | ||||
| @@ -242,6 +249,7 @@ factorio_options: typing.Dict[str, type(Option)] = { | ||||
|     "max_science_pack": MaxSciencePack, | ||||
|     "tech_tree_layout": TechTreeLayout, | ||||
|     "tech_cost": TechCost, | ||||
|     "silo": Silo, | ||||
|     "free_samples": FreeSamples, | ||||
|     "tech_tree_information": TechTreeInformation, | ||||
|     "starting_items": FactorioStartItems, | ||||
|   | ||||
| @@ -82,12 +82,14 @@ class Recipe(FactorioElement): | ||||
|     category: str | ||||
|     ingredients: 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.category = category | ||||
|         self.ingredients = ingredients | ||||
|         self.products = products | ||||
|         self.energy = energy | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return f"{self.__class__.__name__}({self.name})" | ||||
| @@ -125,6 +127,19 @@ class Recipe(FactorioElement): | ||||
|                 ingredients[ingredient] += cost | ||||
|         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): | ||||
|     def __init__(self, name, categories): | ||||
|         self.name: str = name | ||||
| @@ -151,13 +166,14 @@ del (raw) | ||||
| 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["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(): | ||||
|     # example: | ||||
|     # "accumulator":{"ingredients":{"iron-plate":2,"battery":5},"products":{"accumulator":1},"category":"crafting"} | ||||
|  | ||||
|     recipe = Recipe(recipe_name, recipe_data["category"], recipe_data["ingredients"], recipe_data["products"]) | ||||
|     # FIXME: add mining? | ||||
|     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 | ||||
|     if set(recipe.products).isdisjoint( | ||||
|             # prevents loop recipes like uranium centrifuging | ||||
| @@ -260,9 +276,11 @@ for ingredient_name in all_ingredient_names: | ||||
|  | ||||
|  | ||||
| @functools.lru_cache(10) | ||||
| def get_rocket_requirements(recipe: Recipe) -> Set[str]: | ||||
|     techs = recursively_get_unlocking_technologies("rocket-silo") | ||||
|     for ingredient in recipe.ingredients: | ||||
| def get_rocket_requirements(silo_recipe: Recipe, part_recipe: Recipe) -> Set[str]: | ||||
|     techs = set() | ||||
|     for ingredient in silo_recipe.ingredients: | ||||
|         techs |= recursively_get_unlocking_technologies(ingredient) | ||||
|     for ingredient in part_recipe.ingredients: | ||||
|         techs |= recursively_get_unlocking_technologies(ingredient) | ||||
|     return {tech.name for tech in techs} | ||||
|  | ||||
| @@ -416,4 +434,4 @@ def get_science_pack_pools() -> Dict[str, Set[str]]: | ||||
|         current -= already_taken | ||||
|         already_taken |= current | ||||
|         current_difficulty *= 2 | ||||
|     return science_pack_pools | ||||
|     return science_pack_pools | ||||
|   | ||||
| @@ -2,13 +2,15 @@ from ..AutoWorld import World | ||||
|  | ||||
| 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, \ | ||||
|     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 | ||||
| from .Shapes import get_shapes | ||||
| from .Mod import generate_mod | ||||
| from .Options import factorio_options | ||||
|  | ||||
| import logging | ||||
|  | ||||
|  | ||||
| class FactorioItem(Item): | ||||
|     game = "Factorio" | ||||
| @@ -99,7 +101,11 @@ class Factorio(World): | ||||
|                     Rules.add_rule(location, lambda state, | ||||
|                                                     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) | ||||
|                                                                                         for technology in | ||||
|                                                                                         victory_tech_names) | ||||
| @@ -120,6 +126,76 @@ class Factorio(World): | ||||
|  | ||||
|     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): | ||||
|         custom_technologies = {} | ||||
|         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.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category, | ||||
|                                                      {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.custom_recipes["rocket-part"].recursive_unlocking_technologies} | ||||
|  | ||||
| @@ -148,11 +225,21 @@ class Factorio(World): | ||||
|                     new_ingredients = {} | ||||
|                     for _ in original.ingredients: | ||||
|                         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 | ||||
|                                                                  new_recipe.recursive_unlocking_technologies} | ||||
|                     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 | ||||
|         prog_add = set() | ||||
|         for tech in self.additional_advancement_technologies: | ||||
| @@ -164,4 +251,4 @@ class Factorio(World): | ||||
|         assert name in tech_table | ||||
|         return FactorioItem(name, name in advancement_technologies or | ||||
|                             name in self.additional_advancement_technologies, | ||||
|                             tech_table[name], self.player) | ||||
|                             tech_table[name], self.player) | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user