Factorio: Build logic for rocket launch, allow beatable only to work correctly

Convert Science requirements to Event of "automate <pack>"
This commit is contained in:
Fabian Dill 2021-05-22 10:06:21 +02:00
parent 14b430a168
commit 80b7e2e188
6 changed files with 78 additions and 45 deletions

View File

@ -1,10 +1,5 @@
{% macro dict_to_lua(dict) -%} {% from "macros.lua" import dict_to_lua %}
{ -- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template
{% for key, value in dict.items() %}
["{{ key }}"] = {{ value | safe }}{% if not loop.last %},{% endif %}
{% endfor %}
}
{%- endmacro %}
require "lib" require "lib"
require "util" require "util"

View File

@ -1,7 +1,8 @@
{% from "macros.lua" import dict_to_recipe %}
-- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template -- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template
require('lib') require('lib')
data.raw["recipe"]["rocket-part"].ingredients = {{ rocket_recipe | safe }} data.raw["recipe"]["rocket-part"].ingredients = {{ dict_to_recipe(rocket_recipe) }}
local technologies = data.raw["technology"] local technologies = data.raw["technology"]
local original_tech local original_tech

View File

@ -0,0 +1,14 @@
{% macro dict_to_lua(dict) -%}
{
{%- for key, value in dict.items() -%}
["{{ key }}"] = {{ value | safe }}{% if not loop.last %},{% endif %}
{% endfor -%}
}
{%- endmacro %}
{% macro dict_to_recipe(dict) -%}
{
{%- for key, value in dict.items() -%}
{"{{ key }}", {{ value | safe }}}{% if not loop.last %},{% endif %}
{% endfor -%}
}
{%- endmacro %}

View File

@ -11,7 +11,9 @@ import Utils
import shutil import shutil
import Options import Options
from BaseClasses import MultiWorld from BaseClasses import MultiWorld
from .Technologies import tech_table from .Technologies import tech_table, rocket_recipes
template_env: Optional[jinja2.Environment] = None
template: Optional[jinja2.Template] = None template: Optional[jinja2.Template] = None
locale_template: Optional[jinja2.Template] = None locale_template: Optional[jinja2.Template] = None
@ -28,32 +30,18 @@ base_info = {
"factorio_version": "1.1" "factorio_version": "1.1"
} }
# TODO: clean this up, probably as a jinja macro; then add logic for the recipes in completion condition
rocket_recipes = {
Options.MaxSciencePack.option_space_science_pack:
'{{"rocket-control-unit", 10}, {"low-density-structure", 10}, {"rocket-fuel", 10}}',
Options.MaxSciencePack.option_utility_science_pack:
'{{"speed-module", 10}, {"steel-plate", 10}, {"solid-fuel", 10}}',
Options.MaxSciencePack.option_production_science_pack:
'{{"speed-module", 10}, {"steel-plate", 10}, {"solid-fuel", 10}}',
Options.MaxSciencePack.option_chemical_science_pack:
'{{"advanced-circuit", 10}, {"steel-plate", 10}, {"solid-fuel", 10}}',
Options.MaxSciencePack.option_military_science_pack:
'{{"defender-capsule", 10}, {"stone-wall", 10}, {"coal", 10}}',
Options.MaxSciencePack.option_logistic_science_pack:
'{{"electronic-circuit", 10}, {"stone-brick", 10}, {"coal", 10}}',
Options.MaxSciencePack.option_automation_science_pack:
'{{"copper-cable", 10}, {"iron-plate", 10}, {"wood", 10}}'
}
def generate_mod(world: MultiWorld, player: int): def generate_mod(world: MultiWorld, player: int):
global template, locale_template, control_template global template, locale_template, control_template
with template_load_lock: with template_load_lock:
if not template: if not template:
mod_template_folder = Utils.local_path("data", "factorio", "mod_template") mod_template_folder = Utils.local_path("data", "factorio", "mod_template")
template = jinja2.Template(open(os.path.join(mod_template_folder, "data-final-fixes.lua")).read()) template_env: Optional[jinja2.Environment] = \
locale_template = jinja2.Template(open(os.path.join(mod_template_folder, "locale", "en", "locale.cfg")).read()) jinja2.Environment(loader=jinja2.FileSystemLoader([mod_template_folder]))
control_template = jinja2.Template(open(os.path.join(mod_template_folder, "control.lua")).read())
template = template_env.get_template("data-final-fixes.lua")
locale_template = template_env.get_template(r"locale/en/locale.cfg")
control_template = template_env.get_template("control.lua")
# get data for templates # get data for templates
player_names = {x: world.player_names[x][0] for x in world.player_ids} player_names = {x: world.player_names[x][0] for x in world.player_ids}
locations = [] locations = []

View File

@ -3,8 +3,11 @@ from __future__ import annotations
from typing import Dict, Set, FrozenSet from typing import Dict, Set, FrozenSet
import os import os
import json import json
import Options
import Utils import Utils
import logging import logging
import functools
factorio_id = 2 ** 17 factorio_id = 2 ** 17
source_folder = Utils.local_path("data", "factorio") source_folder = Utils.local_path("data", "factorio")
@ -39,19 +42,9 @@ class Technology(FactorioElement): # maybe make subclass of Location?
def build_rule(self, player: int): def build_rule(self, player: int):
logging.debug(f"Building rules for {self.name}") logging.debug(f"Building rules for {self.name}")
ingredient_rules = []
technologies = self.get_prior_technologies() # 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: return lambda state, technologies=technologies: all(state.has(f"Automated {ingredient}", player)
ingredient_rules = frozenset(ingredient_rules) for ingredient in self.ingredients)
return lambda state: all(rule(state) for rule in ingredient_rules)
return always
def get_prior_technologies(self) -> Set[Technology]: def get_prior_technologies(self) -> Set[Technology]:
"""Get Technologies that have to precede this one to resolve tree connections.""" """Get Technologies that have to precede this one to resolve tree connections."""
@ -229,3 +222,28 @@ for ingredient_name in all_ingredient_names:
advancement_technologies: Set[str] = set() advancement_technologies: Set[str] = set()
for technologies in required_technologies.values(): for technologies in required_technologies.values():
advancement_technologies |= {technology.name for technology in technologies} advancement_technologies |= {technology.name for technology in technologies}
@functools.lru_cache(10)
def get_rocket_requirements(ingredients: Set[str]) -> Set[str]:
techs = set()
for ingredient in ingredients:
techs |= recursively_get_unlocking_technologies(ingredient)
return {tech.name for tech in techs}
rocket_recipes = {
Options.MaxSciencePack.option_space_science_pack:
{"rocket-control-unit": 10, "low-density-structure": 10, "rocket-fuel": 10},
Options.MaxSciencePack.option_utility_science_pack:
{"speed-module": 10, "steel-plate": 10, "solid-fuel": 10},
Options.MaxSciencePack.option_production_science_pack:
{"speed-module": 10, "steel-plate": 10, "solid-fuel": 10},
Options.MaxSciencePack.option_chemical_science_pack:
{"advanced-circuit": 10, "steel-plate": 10, "solid-fuel": 10},
Options.MaxSciencePack.option_military_science_pack:
{"defender-capsule": 10, "stone-wall": 10, "coal": 10},
Options.MaxSciencePack.option_logistic_science_pack:
{"electronic-circuit": 10, "stone-brick": 10, "coal": 10},
Options.MaxSciencePack.option_automation_science_pack:
{"copper-cable": 10, "iron-plate": 10, "wood": 10}
}

View File

@ -1,12 +1,15 @@
from BaseClasses import Region, Entrance, Location, MultiWorld, Item from BaseClasses import Region, Entrance, Location, MultiWorld, Item
from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies from .Technologies import tech_table, recipe_sources, technology_table, advancement_technologies, \
all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes
from .Shapes import get_shapes from .Shapes import get_shapes
def gen_factorio(world: MultiWorld, player: int): def gen_factorio(world: MultiWorld, player: int):
static_nodes = world._static_nodes = {"automation", "logistics"} # turn dynamic/option? static_nodes = world._static_nodes = {"automation", "logistics"} # turn dynamic/option?
victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value]))
for tech_name, tech_id in tech_table.items(): for tech_name, tech_id in tech_table.items():
tech_item = Item(tech_name, tech_name in advancement_technologies, tech_id, player) tech_item = Item(tech_name, tech_name in advancement_technologies or tech_name in victory_tech_names,
tech_id, player)
tech_item.game = "Factorio" tech_item.game = "Factorio"
if tech_name in static_nodes: if tech_name in static_nodes:
loc = world.get_location(tech_name, player) loc = world.get_location(tech_name, player)
@ -25,6 +28,7 @@ def factorio_create_regions(world: MultiWorld, player: int):
menu.exits.append(crash) menu.exits.append(crash)
nauvis = Region("Nauvis", None, "Nauvis", player) nauvis = Region("Nauvis", None, "Nauvis", player)
nauvis.world = menu.world = world nauvis.world = menu.world = world
for tech_name, tech_id in tech_table.items(): for tech_name, tech_id in tech_table.items():
tech = Location(player, tech_name, tech_id, nauvis) tech = Location(player, tech_name, tech_id, nauvis)
nauvis.locations.append(tech) nauvis.locations.append(tech)
@ -34,9 +38,16 @@ def factorio_create_regions(world: MultiWorld, player: int):
event = Item("Victory", True, None, player) event = Item("Victory", True, None, player)
world.push_item(location, event, False) world.push_item(location, event, False)
location.event = location.locked = True location.event = location.locked = True
for ingredient in all_ingredient_names:
location = Location(player, f"Automate {ingredient}", None, nauvis)
nauvis.locations.append(location)
event = Item(f"Automated {ingredient}", True, None, player)
world.push_item(location, event, False)
location.event = location.locked = True
crash.connect(nauvis) crash.connect(nauvis)
world.regions += [menu, nauvis] world.regions += [menu, nauvis]
def set_custom_technologies(world: MultiWorld, player: int): def set_custom_technologies(world: MultiWorld, player: int):
custom_technologies = {} custom_technologies = {}
world_custom = getattr(world, "_custom_technologies", {}) world_custom = getattr(world, "_custom_technologies", {})
@ -47,11 +58,15 @@ def set_custom_technologies(world: MultiWorld, player: int):
custom_technologies[technology_name] = technology.get_custom(world, allowed_packs, player) custom_technologies[technology_name] = technology.get_custom(world, allowed_packs, player)
return custom_technologies return custom_technologies
def set_rules(world: MultiWorld, player: int, custom_technologies): def set_rules(world: MultiWorld, player: int, custom_technologies):
shapes = get_shapes(world, player) shapes = get_shapes(world, player)
if world.logic[player] != 'nologic': if world.logic[player] != 'nologic':
from worlds.generic import Rules from worlds.generic import Rules
for ingredient in all_ingredient_names:
location = world.get_location(f"Automate {ingredient}", player)
location.access_rule = lambda state, ingredient=ingredient: \
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
for tech_name, technology in custom_technologies.items(): for tech_name, technology in custom_technologies.items():
location = world.get_location(tech_name, player) location = world.get_location(tech_name, player)
Rules.set_rule(location, technology.build_rule(player)) Rules.set_rule(location, technology.build_rule(player))
@ -61,7 +76,9 @@ def set_rules(world: MultiWorld, player: int, custom_technologies):
Rules.add_rule(location, lambda state, Rules.add_rule(location, lambda state,
locations=locations: all(state.can_reach(loc) for loc in locations)) locations=locations: all(state.can_reach(loc) for loc in locations))
# get all science pack technologies (but not the ability to craft them) # get all science pack technologies (but not the ability to craft them)
victory_tech_names = get_rocket_requirements(frozenset(rocket_recipes[world.max_science_pack[player].value]))
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player) world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
for technology in advancement_technologies) for technology in
victory_tech_names)
world.completion_condition[player] = lambda state: state.has('Victory', player) world.completion_condition[player] = lambda state: state.has('Victory', player)