Factorio: add option: random tech ingredients

This commit is contained in:
Fabian Dill
2021-04-24 01:16:49 +02:00
parent 73ed18c11d
commit 569e0e3004
9 changed files with 75 additions and 37 deletions

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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)