diff --git a/BaseClasses.py b/BaseClasses.py index fe0f9c06..3f31a536 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -60,6 +60,7 @@ class MultiWorld(): for player in range(1, players + 1): def set_player_attr(attr, val): self.__dict__.setdefault(attr, {})[player] = val + set_player_attr('tech_tree_layout_prerequisites', {}) set_player_attr('_region_cache', {}) set_player_attr('shuffle', "vanilla") set_player_attr('logic', "noglitches") diff --git a/Options.py b/Options.py index 667c2ac6..5f4009b7 100644 --- a/Options.py +++ b/Options.py @@ -273,6 +273,7 @@ class FreeSamples(Choice): class TechTreeLayout(Choice): option_single = 0 + option_small_diamonds = 1 default = 0 class Visibility(Choice): diff --git a/data/factorio/mod_template/data-final-fixes.lua b/data/factorio/mod_template/data-final-fixes.lua index 66704239..68e43332 100644 --- a/data/factorio/mod_template/data-final-fixes.lua +++ b/data/factorio/mod_template/data-final-fixes.lua @@ -44,9 +44,14 @@ new_tree_copy.icon_size = table.deepcopy(technologies["{{ item_name }}"].icon_si new_tree_copy.icon = "__{{ mod_name }}__/graphics/icons/ap.png" new_tree_copy.icons = nil new_tree_copy.icon_size = 512 - {% endif %} -{#- add new technology to game #} +{#- connect Technology #} +{%- if original_tech_name in tech_tree_layout_prerequisites %} +{%- for prerequesite in tech_tree_layout_prerequisites[original_tech_name] %} +table.insert(new_tree_copy.prerequisites, "ap-{{ tech_table[prerequesite] }}-") +{% endfor %} +{% endif -%} +{#- add new Technology to game #} data:extend{new_tree_copy} {% endfor %} \ No newline at end of file diff --git a/playerSettings.yaml b/playerSettings.yaml index 380cdaa4..308ab07f 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -38,6 +38,7 @@ progression_balancing: # Factorio options: tech_tree_layout: single: 1 + small_diamonds: 1 max_science_pack: automation_science_pack: 0 logistic_science_pack: 0 diff --git a/worlds/__init__.py b/worlds/__init__.py index 5b983ac7..941158b1 100644 --- a/worlds/__init__.py +++ b/worlds/__init__.py @@ -8,28 +8,27 @@ __all__ = {"lookup_any_item_id_to_name", from .alttp.Items import lookup_id_to_name as alttp from .hk.Items import lookup_id_to_name as hk from .factorio import Technologies + lookup_any_item_id_to_name = {**alttp, **hk, **Technologies.lookup_id_to_name} assert len(alttp) + len(hk) + len(Technologies.lookup_id_to_name) == len(lookup_any_item_id_to_name) lookup_any_item_name_to_id = {name: id for id, name in lookup_any_item_id_to_name.items()} - from .alttp import Regions from .hk import Locations + lookup_any_location_id_to_name = {**Regions.lookup_id_to_name, **Locations.lookup_id_to_name, **Technologies.lookup_id_to_name} assert len(Regions.lookup_id_to_name) + len(Locations.lookup_id_to_name) + len(Technologies.lookup_id_to_name) == \ len(lookup_any_location_id_to_name) lookup_any_location_name_to_id = {name: id for id, name in lookup_any_location_id_to_name.items()} - - network_data_package = {"lookup_any_location_id_to_name": lookup_any_location_id_to_name, "lookup_any_item_id_to_name": lookup_any_item_id_to_name, - "version": 3} + "version": 4} + @enum.unique class Games(str, enum.Enum): HK = "Hollow Knight" LTTP = "A Link to the Past" Factorio = "Factorio" - diff --git a/worlds/factorio/Mod.py b/worlds/factorio/Mod.py index 174dd97d..b0eda59e 100644 --- a/worlds/factorio/Mod.py +++ b/worlds/factorio/Mod.py @@ -51,7 +51,8 @@ def generate_mod(world: MultiWorld, player: int): 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, + "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 control_code = control_template.render(**template_data) diff --git a/worlds/factorio/Technologies.py b/worlds/factorio/Technologies.py index a8aa904d..87e01a06 100644 --- a/worlds/factorio/Technologies.py +++ b/worlds/factorio/Technologies.py @@ -1,3 +1,4 @@ +from __future__ import annotations # Factorio technologies are imported from a .json document in /data from typing import Dict, Set, FrozenSet import json @@ -16,6 +17,7 @@ technology_table = {} always = lambda state: True + class Technology(): # maybe make subclass of Location? def __init__(self, name, ingredients): self.name = name @@ -42,6 +44,14 @@ class Technology(): # maybe make subclass of Location? return always + def get_prior_technologies(self, allowed_packs) -> Set[Technology]: + """Get Technologies that have to precede this one to resolve tree connections.""" + technologies = set() + for ingredient in self.ingredients: + if ingredient in allowed_packs: + technologies |= required_technologies[ingredient] # technologies that unlock the recipes + return technologies + def __hash__(self): return self.factorio_id @@ -68,11 +78,7 @@ class Recipe(): # recipes and technologies can share names in Factorio for technology_name in sorted(raw): data = raw[technology_name] - factorio_id += 1 - # not used yet - # if data["requires"]: - # requirements[technology] = set(data["requires"]) current_ingredients = set(data["ingredients"]) technology = Technology(technology_name, current_ingredients) tech_table[technology_name] = technology.factorio_id @@ -87,7 +93,6 @@ for technology, data in raw.items(): del (raw) lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()} -all_recipes: Dict[str, Recipe] = {} all_product_sources: Dict[str, Recipe] = {} for recipe_name, recipe_data in raw_recipes.items(): # example: @@ -95,7 +100,6 @@ for recipe_name, recipe_data in raw_recipes.items(): recipe = Recipe(recipe_name, recipe_data["category"], set(recipe_data["ingredients"]), set(recipe_data["products"])) if recipe.products != recipe.ingredients: # prevents loop recipes like uranium centrifuging - all_recipes[recipe_name] = recipe for product_name in recipe.products: all_product_sources[product_name] = recipe diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 0b37be12..772029d7 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -1,7 +1,8 @@ import logging +from typing import List, Dict from BaseClasses import Region, Entrance, Location, MultiWorld, Item - +from Options import TechTreeLayout from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies, required_technologies static_nodes = {"automation", "logistics"} @@ -35,7 +36,26 @@ def factorio_create_regions(world: MultiWorld, player: int): world.regions += [menu, nauvis] +def get_shapes(world: MultiWorld, player: int) -> Dict[str, List[str]]: + prerequisites = {} + if world.tech_tree_layout[player].value == TechTreeLayout.option_small_diamonds: + tech_names: List[str] = list(set(technology_table)-static_nodes) + tech_names.sort() + world.random.shuffle(tech_names) + while len(tech_names) > 4: + diamond_0, diamond_1, diamond_2, diamond_3 = tech_names[:4] + tech_names = tech_names[4:] + # 0 | + # 1 2 | + # 3 V + prerequisites[diamond_3] = [diamond_1, diamond_2] + prerequisites[diamond_2] = prerequisites[diamond_1] = [diamond_0] + world.tech_tree_layout_prerequisites[player] = prerequisites + return prerequisites + + def set_rules(world: MultiWorld, player: int): + 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() @@ -43,6 +63,12 @@ def set_rules(world: MultiWorld, player: int): # loose nodes location = world.get_location(tech_name, player) Rules.set_rule(location, technology.build_rule(allowed_packs, 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 world.completion_condition[player] = lambda state: all(state.has(technology, player)