traverse recipe tree for Factorio logic

This commit is contained in:
Fabian Dill 2021-04-09 22:10:04 +02:00
parent c4d6ac50be
commit ceea55e3c6
3 changed files with 84 additions and 36 deletions

View File

@ -196,11 +196,9 @@ retro:
hints:
'on': 50 # Hint tiles sometimes give item location hints
'off': 0 # Hint tiles provide gameplay tips
weapons: # Specifically, swords
randomized: 0 # Swords are placed randomly throughout the world
assured: 50 # Begin with a sword, the rest are placed randomly throughout the world
vanilla: 0 # Swords are placed in vanilla locations in your own game (Uncle, Pyramid Fairy, Smiths, Pedestal)
swordless: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change
swordless:
on: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change
off: 1
item_pool:
easy: 0 # Doubled upgrades, progressives, and etc
normal: 50 # Item availability remains unchanged from vanilla game
@ -259,12 +257,14 @@ beemizer: # Remove items from the global item pool and replace them with single
2: 0 # 50% of rupees, bombs and arrows are replaced with bees, of which 70% are traps and 30% single bees
3: 0 # 75% of rupees, bombs and arrows are replaced with bees, of which 80% are traps and 20% single bees
4: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 90% are traps and 10% single bees
5: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 100% are traps and 0% single bees
### Shop Settings ###
shop_shuffle_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl)
0: 50
5: 0
15: 0
30: 0
random: 0 # 0 to 30 evenly distributed
shop_shuffle:
none: 50
g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops
@ -325,8 +325,8 @@ meta_ignore: # Nullify options specified in the meta.yaml file. Adding an option
- inverted # Never play inverted seeds
retro:
- on # Never play retro seeds
weapons:
- swordless # Never play a swordless seed
swordless:
- on # Never play a swordless seed
linked_options:
- name: crosskeys
options: # These overwrite earlier options if the percentage chance triggers

View File

@ -1,7 +1,8 @@
# Factorio technologies are imported from a .json document in /data
from typing import Dict, Set
from typing import Dict, Set, FrozenSet
import json
import Utils
import logging
factorio_id = 2 ** 17
@ -23,31 +24,43 @@ class Technology(): # maybe make subclass of Location?
factorio_id += 1
self.ingredients = ingredients
def get_required_technologies(self):
requirements = set()
for ingredient in self.ingredients:
if ingredient in recipe_sources: # no source likely means starting item
requirements |= recipe_sources[ingredient] # technically any, not all, need to improve later
return requirements
def build_rule(self):
def build_rule(self, player: int):
logging.debug(f"Building rules for {self.name}")
ingredient_rules = []
for ingredient in self.ingredients:
if ingredient in recipe_sources:
technologies = recipe_sources[ingredient] # technologies that unlock the recipe
ingredient_rules.append(lambda state, technologies=technologies: any(state.has(technology) for technology in technologies))
logging.debug(f"Building rules for ingredient {ingredient}")
if ingredient in required_technologies:
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))
ingredient_rules = frozenset(ingredient_rules)
return lambda state: all(rule(state) for rule in ingredient_rules)
def __hash__(self):
return self.factorio_id
def __repr__(self):
return f"{self.__class__.__name__}({self.name})"
class Recipe():
def __init__(self, name, category, ingredients, products):
self.name = name
self.category = category
self.products = ingredients
self.ingredients = products
self.ingredients = ingredients
self.products = products
def __repr__(self):
return f"{self.__class__.__name__}({self.name})"
@property
def unlocking_technologies(self) -> Set[Technology]:
"""Unlocked by any of the returned technologies. Empty set indicates a starting recipe."""
return {technology_table[tech_name] for tech_name in recipe_sources.get(self.name, ())}
# recipes and technologies can share names in Factorio
for technology_name in sorted(raw):
@ -62,20 +75,55 @@ for technology_name in sorted(raw):
tech_table[technology_name] = technology.factorio_id
technology_table[technology_name] = technology
recipe_sources = {} # recipe_name -> technology source
recipe_sources: Dict[str, str] = {} # recipe_name -> technology source
for technology, data in raw.items():
for recipe in data["unlocks"]:
recipe_sources.setdefault(recipe, set()).add(technology)
for recipe_name in data["unlocks"]:
recipe_sources.setdefault(recipe_name, set()).add(technology)
del (raw)
lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()}
all_recipes = set()
all_recipes: Dict[str, Recipe] = {}
all_product_sources: Dict[str, Recipe] = {}
for recipe_name, recipe_data in raw_recipes.items():
# example:
# "accumulator":{"ingredients":["iron-plate","battery"],"products":["accumulator"],"category":"crafting"}
all_recipes.add(Recipe(recipe_name, recipe_data["category"], set(recipe_data["ingredients"]), set(recipe_data["products"])))
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
# build requirements graph for all technology ingredients
all_ingredient_names: Set[str] = set()
for technology in technology_table.values():
all_ingredient_names |= technology.ingredients
def recursively_get_unlocking_technologies(ingredient_name, _done=None) -> Set[Technology]:
if _done:
if ingredient_name in _done:
return set()
else:
_done.add(ingredient_name)
else:
_done = set(ingredient_name)
recipe = all_product_sources.get(ingredient_name)
if not recipe:
return set()
current_technologies = recipe.unlocking_technologies.copy()
for ingredient_name in recipe.ingredients:
current_technologies |= recursively_get_unlocking_technologies(ingredient_name, _done)
return current_technologies
required_technologies: Dict[str, FrozenSet[Technology]] = {}
for ingredient_name in all_ingredient_names:
required_technologies[ingredient_name] = frozenset(recursively_get_unlocking_technologies(ingredient_name))
advancement_technologies: Set[str] = set()
for technologies in required_technologies.values():
advancement_technologies |= {technology.name for technology in technologies}

View File

@ -2,19 +2,21 @@ import logging
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
from .Technologies import tech_table, recipe_sources, technology_table
from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies, required_technologies
static_nodes = {"automation", "logistics"}
def gen_factorio(world: MultiWorld, player: int):
for tech_name, tech_id in tech_table.items():
# TODO: some techs don't need the advancement marker
tech_item = Item(tech_name, True, tech_id, player)
tech_item.game = "Factorio"
if tech_name in static_nodes:
loc = world.get_location(tech_name, player)
loc.item = tech_item
loc.locked = loc.event = True
loc.locked = True
loc.event = tech_item.advancement
else:
world.itempool.append(tech_item)
set_rules(world, player)
@ -39,11 +41,9 @@ def set_rules(world: MultiWorld, player: int):
from worlds.generic import Rules
for tech_name, technology in technology_table.items():
# loose nodes
rules = technology.get_required_technologies()
if rules:
location = world.get_location(tech_name, player)
Rules.set_rule(location, lambda state, rules=rules: all(state.has(rule, player) for rule in rules))
location = world.get_location(tech_name, player)
Rules.set_rule(location, technology.build_rule(player))
# get all technologies
world.completion_condition[player] = lambda state: all(state.has(technology, player)
for technology in tech_table)
for technology in advancement_technologies)