Factorio: add option: random tech ingredients
This commit is contained in:
		
							parent
							
								
									73ed18c11d
								
							
						
					
					
						commit
						569e0e3004
					
				| 
						 | 
				
			
			@ -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))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)] = {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue