From 569e0e30048bada32a17890c741e5da02dc8b806 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 24 Apr 2021 01:16:49 +0200 Subject: [PATCH] Factorio: add option: random tech ingredients --- BaseClasses.py | 4 +- Options.py | 3 +- data/factorio/mod/lib.lua | 4 +- .../mod_template/data-final-fixes.lua | 13 ++++-- playerSettings.yaml | 3 ++ worlds/factorio/Mod.py | 2 +- worlds/factorio/Shapes.py | 17 ++++---- worlds/factorio/Technologies.py | 43 +++++++++++++------ worlds/factorio/__init__.py | 23 +++++++--- 9 files changed, 75 insertions(+), 37 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index aef638d5..fb0a6882 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -143,7 +143,9 @@ class MultiWorld(): import Options for hk_option in Options.hollow_knight_options: set_player_attr(hk_option, False) - + self.custom_data = {} + for player in range(1, players+1): + self.custom_data[player] = {} # self.worlds = [] # for i in range(players): # self.worlds.append(worlds.alttp.ALTTPWorld({}, i)) diff --git a/Options.py b/Options.py index 70a438d7..7958fea3 100644 --- a/Options.py +++ b/Options.py @@ -293,7 +293,8 @@ factorio_options: typing.Dict[str, type(Option)] = {"max_science_pack": MaxScien "tech_tree_layout": TechTreeLayout, "tech_cost": TechCost, "free_samples": FreeSamples, - "visibility": Visibility} + "visibility": Visibility, + "random_tech_ingredients": Toggle} minecraft_options: typing.Dict[str, type(Option)] = {} diff --git a/data/factorio/mod/lib.lua b/data/factorio/mod/lib.lua index db2b1661..2d77bc95 100644 --- a/data/factorio/mod/lib.lua +++ b/data/factorio/mod/lib.lua @@ -1,7 +1,7 @@ -function filter_ingredients(ingredients) +function filter_ingredients(ingredients, ingredient_filter) local new_ingredient_list = {} for _, ingredient_table in pairs(ingredients) do - if allowed_ingredients[ingredient_table[1]] then -- name of ingredient_table + if ingredient_filter[ingredient_table[1]] then -- name of ingredient_table table.insert(new_ingredient_list, ingredient_table) end end diff --git a/data/factorio/mod_template/data-final-fixes.lua b/data/factorio/mod_template/data-final-fixes.lua index 68e43332..6508aedc 100644 --- a/data/factorio/mod_template/data-final-fixes.lua +++ b/data/factorio/mod_template/data-final-fixes.lua @@ -5,8 +5,12 @@ local technologies = data.raw["technology"] local original_tech local new_tree_copy allowed_ingredients = {} -{%- for ingredient in allowed_science_packs %} -allowed_ingredients["{{ingredient}}"]= 1 +{%- for tech_name, technology in custom_data["custom_technologies"].items() %} +allowed_ingredients["{{ tech_name }}"] = { +{%- for ingredient in technology.ingredients %} +["{{ingredient}}"] = 1, +{%- endfor %} +} {% endfor %} local template_tech = table.deepcopy(technologies["automation"]) {#- ensure the copy unlocks nothing #} @@ -18,7 +22,10 @@ template_tech.prerequisites = {} function prep_copy(new_copy, old_tech) old_tech.enabled = false new_copy.unit = table.deepcopy(old_tech.unit) - new_copy.unit.ingredients = filter_ingredients(new_copy.unit.ingredients) + local ingredient_filter = allowed_ingredients[old_tech.name] + if ingredient_filter ~= nil then + new_copy.unit.ingredients = filter_ingredients(new_copy.unit.ingredients, ingredient_filter) + end end diff --git a/playerSettings.yaml b/playerSettings.yaml index c30271e6..08b45ce1 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -67,6 +67,9 @@ free_samples: visibility: none: 0 sending: 1 +random_tech_ingredients: + on: 1 + off: 0 # A Link to the Past options: ### Logic Section ### # Warning: overworld_glitches is not available and minor_glitches is only partially implemented on the door-rando version diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index d31e23a5..9f280ff6 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -51,7 +51,7 @@ def generate_mod(world: MultiWorld, player: int, seedname: str): 6: 10}[world.tech_cost[player].value] template_data = {"locations": locations, "player_names" : player_names, "tech_table": tech_table, "mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(), - "tech_cost_scale": tech_cost, + "tech_cost_scale": tech_cost, "custom_data": world.custom_data[player], "tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites[player]} for factorio_option in Options.factorio_options: template_data[factorio_option] = getattr(world, factorio_option)[player].value diff --git a/worlds/factorio/Shapes.py b/worlds/factorio/Shapes.py index 12f250e8..d85cdd5b 100644 --- a/worlds/factorio/Shapes.py +++ b/worlds/factorio/Shapes.py @@ -7,15 +7,16 @@ from worlds.factorio.Technologies import technology_table def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]: prerequisites: Dict[str, Set[str]] = {} layout = world.tech_tree_layout[player].value + custom_technologies = world.custom_data[player]["custom_technologies"] if layout == TechTreeLayout.option_small_diamonds: slice_size = 4 - tech_names: List[str] = list(set(technology_table) - world._static_nodes) + tech_names: List[str] = list(set(custom_technologies) - world._static_nodes) tech_names.sort() world.random.shuffle(tech_names) while len(tech_names) > slice_size: slice = tech_names[:slice_size] tech_names = tech_names[slice_size:] - slice.sort(key=lambda tech_name: len(technology_table[tech_name].ingredients)) + slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].ingredients)) diamond_0, diamond_1, diamond_2, diamond_3 = slice # 0 | @@ -25,13 +26,13 @@ def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]: prerequisites[diamond_2] = prerequisites[diamond_1] = {diamond_0} elif layout == TechTreeLayout.option_medium_diamonds: slice_size = 9 - tech_names: List[str] = list(set(technology_table) - world._static_nodes) + tech_names: List[str] = list(set(custom_technologies) - world._static_nodes) tech_names.sort() world.random.shuffle(tech_names) while len(tech_names) > slice_size: slice = tech_names[:slice_size] tech_names = tech_names[slice_size:] - slice.sort(key=lambda tech_name: len(technology_table[tech_name].ingredients)) + slice.sort(key=lambda tech_name: len(custom_technologies[tech_name].ingredients)) # 0 | # 1 2 | @@ -53,10 +54,10 @@ def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]: elif layout == TechTreeLayout.option_pyramid: slice_size = 1 - tech_names: List[str] = list(set(technology_table) - world._static_nodes) + tech_names: List[str] = list(set(custom_technologies) - world._static_nodes) tech_names.sort() world.random.shuffle(tech_names) - tech_names.sort(key=lambda tech_name: len(technology_table[tech_name].ingredients)) + tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].ingredients)) previous_slice = [] while len(tech_names) > slice_size: slice = tech_names[:slice_size] @@ -71,14 +72,14 @@ def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]: elif layout == TechTreeLayout.option_funnel: - tech_names: List[str] = list(set(technology_table) - world._static_nodes) + tech_names: List[str] = list(set(custom_technologies) - world._static_nodes) # find largest inverse pyramid # https://www.wolframalpha.com/input/?i=x+=+1/2+(n++++1)+(2++++n)+solve+for+n import math slice_size = int(0.5*(math.sqrt(8*len(tech_names)+1)-3)) tech_names.sort() world.random.shuffle(tech_names) - tech_names.sort(key=lambda tech_name: len(technology_table[tech_name].ingredients)) + tech_names.sort(key=lambda tech_name: len(custom_technologies[tech_name].ingredients)) previous_slice = [] while slice_size: slice = tech_names[:slice_size] diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index b9edadf0..dcb68372 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -13,31 +13,28 @@ with open(source_file) as f: with open(recipe_source_file) as f: raw_recipes = json.load(f) tech_table = {} -technology_table:Dict[str, Technology] = {} +technology_table: Dict[str, Technology] = {} always = lambda state: True class Technology(): # maybe make subclass of Location? - def __init__(self, name, ingredients): + def __init__(self, name, ingredients, factorio_id): self.name = name - global factorio_id self.factorio_id = factorio_id - factorio_id += 1 self.ingredients = ingredients - def build_rule(self, allowed_packs, player: int): + def build_rule(self, player: int): logging.debug(f"Building rules for {self.name}") ingredient_rules = [] for ingredient in self.ingredients: - if ingredient in allowed_packs: - logging.debug(f"Building rules for ingredient {ingredient}") - technologies = required_technologies[ingredient] # technologies that unlock the recipes - if technologies: - logging.debug(f"Required Technologies: {technologies}") - ingredient_rules.append( - lambda state, technologies=technologies: all(state.has(technology.name, player) - for technology in technologies)) + logging.debug(f"Building rules for ingredient {ingredient}") + technologies = required_technologies[ingredient] # technologies that unlock the recipes + if technologies: + logging.debug(f"Required Technologies: {technologies}") + ingredient_rules.append( + lambda state, technologies=technologies: all(state.has(technology.name, player) + for technology in technologies)) if ingredient_rules: ingredient_rules = frozenset(ingredient_rules) return lambda state: all(rule(state) for rule in ingredient_rules) @@ -58,6 +55,23 @@ class Technology(): # maybe make subclass of Location? def __repr__(self): return f"{self.__class__.__name__}({self.name})" + def get_custom(self, world, allowed_packs: Set[str], player: int) -> CustomTechnology: + return CustomTechnology(self, world, allowed_packs, player) + + +class CustomTechnology(Technology): + """A particularly configured Technology for a world.""" + + def __init__(self, origin: Technology, world, allowed_packs: Set[str], player: int): + ingredients = origin.ingredients & allowed_packs + self.player = player + if world.random_tech_ingredients[player]: + ingredients = list(ingredients) + ingredients.sort() # deterministic sample + ingredients = world.random.sample(ingredients, world.random.randint(1, len(ingredients))) + super(CustomTechnology, self).__init__(origin.name, ingredients, origin.factorio_id) + + class Recipe(): def __init__(self, name, category, ingredients, products): @@ -80,7 +94,8 @@ for technology_name in sorted(raw): data = raw[technology_name] factorio_id += 1 current_ingredients = set(data["ingredients"]) - technology = Technology(technology_name, current_ingredients) + technology = Technology(technology_name, current_ingredients, factorio_id) + factorio_id += 1 tech_table[technology_name] = technology.factorio_id technology_table[technology_name] = technology diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 50102e01..f0ded67d 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -15,7 +15,8 @@ def gen_factorio(world: MultiWorld, player: int): loc.event = tech_item.advancement else: world.itempool.append(tech_item) - set_rules(world, player) + world.custom_data[player]["custom_technologies"] = custom_technologies = set_custom_technologies(world, player) + set_rules(world, player, custom_technologies) def factorio_create_regions(world: MultiWorld, player: int): @@ -31,22 +32,30 @@ def factorio_create_regions(world: MultiWorld, player: int): crash.connect(nauvis) world.regions += [menu, nauvis] +def set_custom_technologies(world: MultiWorld, player: int): + custom_technologies = {} + world_custom = getattr(world, "_custom_technologies", {}) + world_custom[player] = custom_technologies + world._custom_technologies = world_custom + allowed_packs = world.max_science_pack[player].get_allowed_packs() + for technology_name, technology in technology_table.items(): + custom_technologies[technology_name] = technology.get_custom(world, allowed_packs, player) + return custom_technologies -def set_rules(world: MultiWorld, player: int): +def set_rules(world: MultiWorld, player: int, custom_technologies): shapes = get_shapes(world, player) if world.logic[player] != 'nologic': from worlds.generic import Rules - allowed_packs = world.max_science_pack[player].get_allowed_packs() - for tech_name, technology in technology_table.items(): - # loose nodes + + for tech_name, technology in custom_technologies.items(): location = world.get_location(tech_name, player) - Rules.set_rule(location, technology.build_rule(allowed_packs, player)) + Rules.set_rule(location, technology.build_rule(player)) prequisites = shapes.get(tech_name) if prequisites: locations = {world.get_location(requisite, player) for requisite in prequisites} Rules.add_rule(location, lambda state, locations=locations: all(state.can_reach(loc) for loc in locations)) - # get all technologies + # get all science pack technologies (but not the ability to craft them) world.completion_condition[player] = lambda state: all(state.has(technology, player) for technology in advancement_technologies)