Factorio: recipe randomization (rocket-part and science-packs only for now)
This commit is contained in:
parent
007f2caecf
commit
9db506ef42
|
@ -1492,6 +1492,12 @@ class Spoiler(object):
|
|||
for dungeon, medallion in self.medallions.items():
|
||||
outfile.write(f'\n{dungeon}: {medallion}')
|
||||
|
||||
if self.world.factorio_player_ids:
|
||||
outfile.write('\n\nRecipes:\n')
|
||||
for player in self.world.factorio_player_ids:
|
||||
for recipe in self.world.worlds[player].custom_recipes.values():
|
||||
outfile.write(f"{recipe.name}: {recipe.ingredients} -> {recipe.products}\n")
|
||||
|
||||
if self.startinventory:
|
||||
outfile.write('\n\nStarting Inventory:\n\n')
|
||||
outfile.write('\n'.join(self.startinventory))
|
||||
|
|
9
Utils.py
9
Utils.py
|
@ -23,6 +23,7 @@ import sys
|
|||
import pickle
|
||||
import functools
|
||||
import io
|
||||
import collections
|
||||
|
||||
from yaml import load, dump, safe_load
|
||||
|
||||
|
@ -53,7 +54,6 @@ def snes_to_pc(value):
|
|||
def parse_player_names(names, players, teams):
|
||||
names = tuple(n for n in (n.strip() for n in names.split(",")) if n)
|
||||
if len(names) != len(set(names)):
|
||||
import collections
|
||||
name_counter = collections.Counter(names)
|
||||
raise ValueError(f"Duplicate Player names is not supported, "
|
||||
f'found multiple "{name_counter.most_common(1)[0][0]}".')
|
||||
|
@ -405,4 +405,9 @@ class RestrictedUnpickler(pickle.Unpickler):
|
|||
|
||||
def restricted_loads(s):
|
||||
"""Helper function analogous to pickle.loads()."""
|
||||
return RestrictedUnpickler(io.BytesIO(s)).load()
|
||||
return RestrictedUnpickler(io.BytesIO(s)).load()
|
||||
|
||||
class KeyedDefaultDict(collections.defaultdict):
|
||||
def __missing__(self, key):
|
||||
self[key] = value = self.default_factory(key)
|
||||
return value
|
|
@ -2,7 +2,9 @@
|
|||
-- this file gets written automatically by the Archipelago Randomizer and is in its raw form a Jinja2 Template
|
||||
require('lib')
|
||||
|
||||
data.raw["recipe"]["rocket-part"].ingredients = {{ dict_to_recipe(rocket_recipe) }}
|
||||
{%- for recipe_name, recipe in custom_recipes.items() %}
|
||||
data.raw["recipe"]["{{recipe_name}}"].ingredients = {{ dict_to_recipe(recipe.ingredients) }}
|
||||
{%- endfor %}
|
||||
|
||||
local technologies = data.raw["technology"]
|
||||
local original_tech
|
||||
|
|
|
@ -70,6 +70,9 @@ Factorio:
|
|||
normal: 0 # 50 % to 200% of original time
|
||||
slow: 0 # 100% to 400% of original time
|
||||
chaos: 0 # 25% to 400% of original time
|
||||
recipe_ingredients:
|
||||
rocket: 1 # only randomize rocket part recipe
|
||||
science_packs: 1 # also randomize science pack ingredients
|
||||
max_science_pack:
|
||||
automation_science_pack: 0
|
||||
logistic_science_pack: 0
|
||||
|
|
|
@ -9,7 +9,6 @@ from worlds.alttp.Dungeons import get_dungeon_item_pool
|
|||
from worlds.alttp.EntranceShuffle import connect_entrance
|
||||
from Fill import FillError, fill_restrictive
|
||||
from worlds.alttp.Items import ItemFactory, GetBeemizerItem
|
||||
from worlds.generic.Rules import forbid_items_for_player
|
||||
|
||||
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||
# Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||
|
@ -349,9 +348,7 @@ def generate_itempool(world, player: int):
|
|||
world.escape_assist[player].append('bombs')
|
||||
|
||||
for (location, item) in placed_items.items():
|
||||
world.push_item(world.get_location(location, player), ItemFactory(item, player), False)
|
||||
world.get_location(location, player).event = True
|
||||
world.get_location(location, player).locked = True
|
||||
world.get_location(location, player).place_locked_item(ItemFactory(item, player))
|
||||
|
||||
items = ItemFactory(pool, player)
|
||||
|
||||
|
|
|
@ -43,8 +43,9 @@ recipe_time_scales = {
|
|||
Options.RecipeTime.option_vanilla: None
|
||||
}
|
||||
|
||||
|
||||
def generate_mod(world: MultiWorld, player: int):
|
||||
def generate_mod(world):
|
||||
player = world.player
|
||||
multiworld = world.world
|
||||
global data_final_template, locale_template, control_template, data_template
|
||||
with template_load_lock:
|
||||
if not data_final_template:
|
||||
|
@ -56,36 +57,36 @@ def generate_mod(world: MultiWorld, player: int):
|
|||
locale_template = template_env.get_template(r"locale/en/locale.cfg")
|
||||
control_template = template_env.get_template("control.lua")
|
||||
# get data for templates
|
||||
player_names = {x: world.player_names[x][0] for x in world.player_ids}
|
||||
player_names = {x: multiworld.player_names[x][0] for x in multiworld.player_ids}
|
||||
locations = []
|
||||
for location in world.get_filled_locations(player):
|
||||
for location in multiworld.get_filled_locations(player):
|
||||
if location.address:
|
||||
locations.append((location.name, location.item.name, location.item.player, location.item.advancement))
|
||||
mod_name = f"AP-{world.seed_name}-P{player}-{world.player_names[player][0]}"
|
||||
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.player_names[player][0]}"
|
||||
tech_cost_scale = {0: 0.1,
|
||||
1: 0.25,
|
||||
2: 0.5,
|
||||
3: 1,
|
||||
4: 2,
|
||||
5: 5,
|
||||
6: 10}[world.tech_cost[player].value]
|
||||
6: 10}[multiworld.tech_cost[player].value]
|
||||
|
||||
template_data = {"locations": locations, "player_names": player_names, "tech_table": tech_table,
|
||||
"base_tech_table": base_tech_table, "tech_to_progressive_lookup": tech_to_progressive_lookup,
|
||||
"mod_name": mod_name, "allowed_science_packs": world.max_science_pack[player].get_allowed_packs(),
|
||||
"tech_cost_scale": tech_cost_scale, "custom_technologies": world.worlds[player].custom_technologies,
|
||||
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites[player],
|
||||
"rocket_recipe": rocket_recipes[world.max_science_pack[player].value],
|
||||
"slot_name": world.player_names[player][0], "seed_name": world.seed_name,
|
||||
"starting_items": world.starting_items[player], "recipes": recipes,
|
||||
"random": world.slot_seeds[player], "static_nodes": world.worlds[player].static_nodes,
|
||||
"recipe_time_scale": recipe_time_scales[world.recipe_time[player].value],
|
||||
"mod_name": mod_name, "allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(),
|
||||
"tech_cost_scale": tech_cost_scale, "custom_technologies": multiworld.worlds[player].custom_technologies,
|
||||
"tech_tree_layout_prerequisites": multiworld.tech_tree_layout_prerequisites[player],
|
||||
"slot_name": multiworld.player_names[player][0], "seed_name": multiworld.seed_name,
|
||||
"starting_items": multiworld.starting_items[player], "recipes": recipes,
|
||||
"random": multiworld.slot_seeds[player], "static_nodes": multiworld.worlds[player].static_nodes,
|
||||
"recipe_time_scale": recipe_time_scales[multiworld.recipe_time[player].value],
|
||||
"free_sample_blacklist": {item : 1 for item in free_sample_blacklist},
|
||||
"progressive_technology_table": {tech.name : tech.progressive for tech in
|
||||
progressive_technology_table.values()}}
|
||||
progressive_technology_table.values()},
|
||||
"custom_recipes": world.custom_recipes}
|
||||
|
||||
for factorio_option in Options.factorio_options:
|
||||
template_data[factorio_option] = getattr(world, factorio_option)[player].value
|
||||
template_data[factorio_option] = getattr(multiworld, factorio_option)[player].value
|
||||
|
||||
control_code = control_template.render(**template_data)
|
||||
data_template_code = data_template.render(**template_data)
|
||||
|
|
|
@ -17,6 +17,12 @@ class MaxSciencePack(Choice):
|
|||
return {option.replace("_", "-") for option, value in self.options.items() if value <= self.value} - \
|
||||
{"space-science-pack"} # with rocket launch being the goal, post-launch techs don't make sense
|
||||
|
||||
@classmethod
|
||||
def get_ordered_science_packs(cls):
|
||||
return [option.replace("_", "-") for option, value in sorted(cls.options.items(), key=lambda pair: pair[1])]
|
||||
|
||||
def get_max_pack(self):
|
||||
return self.get_ordered_science_packs()[self.value].replace("_", "-")
|
||||
|
||||
class TechCost(Choice):
|
||||
option_very_easy = 0
|
||||
|
@ -68,6 +74,19 @@ class RecipeTime(Choice):
|
|||
option_slow = 4
|
||||
option_chaos = 5
|
||||
|
||||
# TODO: implement random
|
||||
class Progressive(Choice):
|
||||
option_off = 0
|
||||
option_random = 1
|
||||
option_on = 2
|
||||
default = 2
|
||||
|
||||
def want_progressives(self, random):
|
||||
return random.choice([True, False]) if self.value == self.option_random else int(self.value)
|
||||
|
||||
class RecipeIngredients(Choice):
|
||||
option_rocket = 0
|
||||
option_science_pack = 1
|
||||
|
||||
class FactorioStartItems(OptionDict):
|
||||
default = {"burner-mining-drill": 19, "stone-furnace": 19}
|
||||
|
@ -95,6 +114,7 @@ factorio_options: typing.Dict[str, type(Option)] = {
|
|||
"tech_tree_information": TechTreeInformation,
|
||||
"starting_items": FactorioStartItems,
|
||||
"recipe_time": RecipeTime,
|
||||
"recipe_ingredients": RecipeIngredients,
|
||||
"imported_blueprints": DefaultOnToggle,
|
||||
"world_gen": FactorioWorldGen,
|
||||
"progressive": DefaultOnToggle
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from __future__ import annotations
|
||||
# Factorio technologies are imported from a .json document in /data
|
||||
from typing import Dict, Set, FrozenSet, Tuple
|
||||
from collections import Counter, defaultdict
|
||||
import os
|
||||
import json
|
||||
import string
|
||||
|
@ -92,15 +93,37 @@ class Recipe(FactorioElement):
|
|||
return f"{self.__class__.__name__}({self.name})"
|
||||
|
||||
@property
|
||||
def crafting_machines(self) -> Set[Machine]:
|
||||
"""crafting machines able to run this recipe"""
|
||||
return machines_per_category[self.category]
|
||||
def crafting_machine(self) -> str:
|
||||
"""cheapest crafting machine name able to run this recipe"""
|
||||
return machine_per_category[self.category]
|
||||
|
||||
@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, ())}
|
||||
|
||||
@property
|
||||
def recursive_unlocking_technologies(self) -> Set[Technology]:
|
||||
base = {technology_table[tech_name] for tech_name in recipe_sources.get(self.name, ())}
|
||||
for ingredient in self.ingredients:
|
||||
base |= required_technologies[ingredient]
|
||||
return base
|
||||
|
||||
@property
|
||||
def rel_cost(self) -> float:
|
||||
ingredients = sum(self.ingredients.values())
|
||||
return min(ingredients/amount for product, amount in self.products.items())
|
||||
|
||||
@property
|
||||
def base_cost(self) -> Dict[str, int]:
|
||||
ingredients = Counter()
|
||||
for ingredient, cost in self.ingredients.items():
|
||||
if ingredient in all_product_sources:
|
||||
for recipe in all_product_sources[ingredient]:
|
||||
ingredients.update({name: amount*cost/recipe.products[ingredient] for name, amount in recipe.base_cost.items()})
|
||||
else:
|
||||
ingredients[ingredient] += cost
|
||||
return ingredients
|
||||
|
||||
class Machine(FactorioElement):
|
||||
def __init__(self, name, categories):
|
||||
|
@ -137,7 +160,9 @@ for recipe_name, recipe_data in raw_recipes.items():
|
|||
recipe = Recipe(recipe_name, recipe_data["category"], recipe_data["ingredients"], recipe_data["products"])
|
||||
recipes[recipe_name] = recipe
|
||||
if set(recipe.products).isdisjoint(
|
||||
set(recipe.ingredients)) and "empty-barrel" not in recipe.products: # prevents loop recipes like uranium centrifuging
|
||||
# prevents loop recipes like uranium centrifuging
|
||||
set(recipe.ingredients)) and ("empty-barrel" not in recipe.products or recipe.name == "empty-barrel") and \
|
||||
not recipe_name.endswith("-reprocessing"):
|
||||
for product_name in recipe.products:
|
||||
all_product_sources.setdefault(product_name, set()).add(recipe)
|
||||
|
||||
|
@ -151,6 +176,8 @@ for name, categories in raw_machines.items():
|
|||
|
||||
# add electric mining drill as a crafting machine to resolve uranium-ore
|
||||
machines["electric-mining-drill"] = Machine("electric-mining-drill", {"mining"})
|
||||
machines["assembling-machine-1"].categories.add("crafting-with-fluid") # mod enables this
|
||||
machines["character"].categories.add("basic-crafting") # somehow this is implied and not exported
|
||||
del (raw_machines)
|
||||
|
||||
# build requirements graph for all technology ingredients
|
||||
|
@ -161,18 +188,17 @@ for technology in technology_table.values():
|
|||
|
||||
|
||||
def unlock_just_tech(recipe: Recipe, _done) -> Set[Technology]:
|
||||
current_technologies = set()
|
||||
current_technologies |= recipe.unlocking_technologies
|
||||
current_technologies = recipe.unlocking_technologies
|
||||
for ingredient_name in recipe.ingredients:
|
||||
current_technologies |= recursively_get_unlocking_technologies(ingredient_name, _done)
|
||||
current_technologies |= recursively_get_unlocking_technologies(ingredient_name, _done,
|
||||
unlock_func=unlock_just_tech)
|
||||
return current_technologies
|
||||
|
||||
|
||||
def unlock(recipe: Recipe, _done) -> Set[Technology]:
|
||||
current_technologies = set()
|
||||
current_technologies |= recipe.unlocking_technologies
|
||||
current_technologies = recipe.unlocking_technologies
|
||||
for ingredient_name in recipe.ingredients:
|
||||
current_technologies |= recursively_get_unlocking_technologies(ingredient_name, _done)
|
||||
current_technologies |= recursively_get_unlocking_technologies(ingredient_name, _done, unlock_func=unlock)
|
||||
current_technologies |= required_category_technologies[recipe.category]
|
||||
|
||||
return current_technologies
|
||||
|
@ -202,48 +228,41 @@ for ingredient_name in machines:
|
|||
required_machine_technologies[ingredient_name] = frozenset(recursively_get_unlocking_technologies(ingredient_name))
|
||||
|
||||
logical_machines = {}
|
||||
machine_tech_cost = {}
|
||||
for machine in machines.values():
|
||||
logically_useful = True
|
||||
for pot_source_machine in machines.values():
|
||||
if machine != pot_source_machine \
|
||||
and machine.categories.issuperset(pot_source_machine.categories) \
|
||||
and required_machine_technologies[machine.name].issuperset(
|
||||
required_machine_technologies[pot_source_machine.name]):
|
||||
logically_useful = False
|
||||
break
|
||||
|
||||
if logically_useful:
|
||||
logical_machines[machine.name] = machine
|
||||
|
||||
del (required_machine_technologies)
|
||||
|
||||
machines_per_category: Dict[str: Set[Machine]] = {}
|
||||
for machine in logical_machines.values():
|
||||
for category in machine.categories:
|
||||
machines_per_category.setdefault(category, set()).add(machine)
|
||||
current_cost, current_machine = machine_tech_cost.get(category, (10000, "character"))
|
||||
machine_cost = len(required_machine_technologies[machine.name])
|
||||
if machine_cost < current_cost:
|
||||
machine_tech_cost[category] = machine_cost, machine.name
|
||||
|
||||
machine_per_category: Dict[str: str] = {}
|
||||
for category, (cost, machine_name) in machine_tech_cost.items():
|
||||
machine_per_category[category] = machine_name
|
||||
|
||||
del (machine_tech_cost)
|
||||
|
||||
# required technologies to be able to craft recipes from a certain category
|
||||
required_category_technologies: Dict[str, FrozenSet[FrozenSet[Technology]]] = {}
|
||||
for category_name, cat_machines in machines_per_category.items():
|
||||
for category_name, machine_name in machine_per_category.items():
|
||||
techs = set()
|
||||
for machine in cat_machines:
|
||||
techs |= recursively_get_unlocking_technologies(machine.name)
|
||||
techs |= recursively_get_unlocking_technologies(machine_name)
|
||||
required_category_technologies[category_name] = frozenset(techs)
|
||||
|
||||
required_technologies: Dict[str, FrozenSet[Technology]] = {}
|
||||
for ingredient_name in all_ingredient_names:
|
||||
required_technologies[ingredient_name] = frozenset(
|
||||
recursively_get_unlocking_technologies(ingredient_name, unlock_func=unlock))
|
||||
required_technologies: Dict[str, FrozenSet[Technology]] = Utils.KeyedDefaultDict(lambda ingredient_name : frozenset(
|
||||
recursively_get_unlocking_technologies(ingredient_name, unlock_func=unlock)))
|
||||
|
||||
|
||||
advancement_technologies: Set[str] = set()
|
||||
for technologies in required_technologies.values():
|
||||
for ingredient_name in all_ingredient_names:
|
||||
technologies = required_technologies[ingredient_name]
|
||||
advancement_technologies |= {technology.name for technology in technologies}
|
||||
|
||||
|
||||
@functools.lru_cache(10)
|
||||
def get_rocket_requirements(ingredients: Set[str]) -> Set[str]:
|
||||
def get_rocket_requirements(recipe: Recipe) -> Set[str]:
|
||||
techs = recursively_get_unlocking_technologies("rocket-silo")
|
||||
for ingredient in ingredients:
|
||||
for ingredient in recipe.ingredients:
|
||||
techs |= recursively_get_unlocking_technologies(ingredient)
|
||||
return {tech.name for tech in techs}
|
||||
|
||||
|
@ -266,9 +285,8 @@ rocket_recipes = {
|
|||
Options.MaxSciencePack.option_automation_science_pack:
|
||||
{"copper-cable": 10, "iron-plate": 10, "wood": 10}
|
||||
}
|
||||
for products in rocket_recipes.values():
|
||||
requirements = get_rocket_requirements(frozenset(products))
|
||||
advancement_technologies |= requirements
|
||||
|
||||
advancement_technologies |= {tech.name for tech in required_technologies["rocket-silo"]}
|
||||
|
||||
# progressive technologies
|
||||
# auto-progressive
|
||||
|
@ -357,4 +375,46 @@ technology_table.update(progressive_technology_table)
|
|||
common_tech_table: Dict[str, int] = {tech_name: tech_id for tech_name, tech_id in base_tech_table.items()
|
||||
if tech_name not in progressive_tech_table}
|
||||
|
||||
lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()}
|
||||
lookup_id_to_name: Dict[int, str] = {item_id: item_name for item_name, item_id in tech_table.items()}
|
||||
|
||||
rel_cost = {
|
||||
"wood" : 10000,
|
||||
"iron-ore": 1,
|
||||
"copper-ore": 1,
|
||||
"stone": 1,
|
||||
"crude-oil": 0.5,
|
||||
"water": 0.001,
|
||||
"coal": 1,
|
||||
"raw-fish": 1000,
|
||||
"steam": 0.01,
|
||||
"used-up-uranium-fuel-cell": 1000
|
||||
}
|
||||
|
||||
# forbid liquids for now, TODO: allow a single liquid per assembler
|
||||
blacklist = all_ingredient_names | {"rocket-part", "crude-oil", "water", "sulfuric-acid", "petroleum-gas", "light-oil",
|
||||
"heavy-oil", "lubricant", "steam"}
|
||||
|
||||
|
||||
def get_estimated_difficulty(recipe: Recipe):
|
||||
base_ingredients = recipe.base_cost
|
||||
cost = 0
|
||||
|
||||
for ingredient_name, amount in base_ingredients.items():
|
||||
if ingredient_name not in rel_cost:
|
||||
raise Exception((recipe.name, ingredient_name))
|
||||
cost += rel_cost.get(ingredient_name, 1) * amount
|
||||
return cost
|
||||
|
||||
|
||||
science_pack_pools = {}
|
||||
already_taken = blacklist.copy()
|
||||
current_difficulty = 5
|
||||
for science_pack in Options.MaxSciencePack.get_ordered_science_packs():
|
||||
current = science_pack_pools[science_pack] = set()
|
||||
for name, recipe in recipes.items():
|
||||
if (science_pack != "automation-science-pack" or not recipe.recursive_unlocking_technologies) \
|
||||
and get_estimated_difficulty(recipe) < current_difficulty:
|
||||
current |= set(recipe.products)
|
||||
current -= already_taken
|
||||
already_taken |= current
|
||||
current_difficulty *= 2
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from ..AutoWorld import World
|
||||
|
||||
from BaseClasses import Region, Entrance, Location, MultiWorld, Item
|
||||
from BaseClasses import Region, Entrance, Location, Item
|
||||
from .Technologies import base_tech_table, recipe_sources, base_technology_table, advancement_technologies, \
|
||||
all_ingredient_names, required_technologies, get_rocket_requirements, rocket_recipes, \
|
||||
progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table
|
||||
progressive_technology_table, common_tech_table, tech_to_progressive_lookup, progressive_tech_table, \
|
||||
science_pack_pools, Recipe, recipes, technology_table
|
||||
from .Shapes import get_shapes
|
||||
from .Mod import generate_mod
|
||||
from .Options import factorio_options
|
||||
|
@ -11,6 +12,8 @@ from .Options import factorio_options
|
|||
class Factorio(World):
|
||||
game: str = "Factorio"
|
||||
static_nodes = {"automation", "logistics", "rocket-silo"}
|
||||
custom_recipes = {}
|
||||
additional_advancement_technologies = set()
|
||||
|
||||
def generate_basic(self):
|
||||
for tech_name, tech_id in base_tech_table.items():
|
||||
|
@ -20,7 +23,8 @@ class Factorio(World):
|
|||
else:
|
||||
item_name = tech_name
|
||||
|
||||
tech_item = Item(item_name, item_name in advancement_technologies,
|
||||
tech_item = Item(item_name, item_name in advancement_technologies or
|
||||
item_name in self.additional_advancement_technologies,
|
||||
tech_id, self.player)
|
||||
tech_item.game = "Factorio"
|
||||
if tech_name in self.static_nodes:
|
||||
|
@ -28,11 +32,10 @@ class Factorio(World):
|
|||
else:
|
||||
self.world.itempool.append(tech_item)
|
||||
world_gen = self.world.world_gen[self.player].value
|
||||
if world_gen.get("seed", None) is None: # allow seed 0
|
||||
if world_gen.get("seed", None) is None: # allow seed 0
|
||||
world_gen["seed"] = self.world.slot_seeds[self.player].randint(0, 2**32-1) # 32 bit uint
|
||||
|
||||
def generate_output(self):
|
||||
generate_mod(self.world, self.player)
|
||||
generate_output = generate_mod
|
||||
|
||||
def create_regions(self):
|
||||
player = self.player
|
||||
|
@ -48,11 +51,14 @@ class Factorio(World):
|
|||
tech.game = "Factorio"
|
||||
location = Location(player, "Rocket Launch", None, nauvis)
|
||||
nauvis.locations.append(location)
|
||||
location.game = "Factorio"
|
||||
event = Item("Victory", True, None, player)
|
||||
event.game = "Factorio"
|
||||
self.world.push_item(location, event, False)
|
||||
location.event = location.locked = True
|
||||
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
location = Location(player, f"Automate {ingredient}", None, nauvis)
|
||||
location.game = "Factorio"
|
||||
nauvis.locations.append(location)
|
||||
event = Item(f"Automated {ingredient}", True, None, player)
|
||||
self.world.push_item(location, event, False)
|
||||
|
@ -63,14 +69,25 @@ class Factorio(World):
|
|||
def set_rules(self):
|
||||
world = self.world
|
||||
player = self.player
|
||||
self.custom_technologies = set_custom_technologies(self.world, self.player)
|
||||
self.custom_technologies = self.set_custom_technologies()
|
||||
self.set_custom_recipes()
|
||||
shapes = get_shapes(self)
|
||||
if world.logic[player] != 'nologic':
|
||||
from worlds.generic import Rules
|
||||
for ingredient in self.world.max_science_pack[self.player].get_allowed_packs():
|
||||
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])
|
||||
|
||||
if self.world.recipe_ingredients[self.player]:
|
||||
custom_recipe = self.custom_recipes[ingredient]
|
||||
|
||||
location.access_rule = lambda state, ingredient=ingredient, custom_recipe = custom_recipe: \
|
||||
(ingredient not in technology_table or state.has(ingredient, player)) and \
|
||||
all(state.has(technology.name, player) for sub_ingredient in custom_recipe.ingredients
|
||||
for technology in required_technologies[sub_ingredient])
|
||||
else:
|
||||
location.access_rule = lambda state, ingredient=ingredient: \
|
||||
all(state.has(technology.name, player) for technology in required_technologies[ingredient])
|
||||
|
||||
for tech_name, technology in self.custom_technologies.items():
|
||||
location = world.get_location(tech_name, player)
|
||||
Rules.set_rule(location, technology.build_rule(player))
|
||||
|
@ -79,8 +96,8 @@ class Factorio(World):
|
|||
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 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]))
|
||||
|
||||
victory_tech_names = get_rocket_requirements(self.custom_recipes["rocket-part"])
|
||||
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player)
|
||||
for technology in
|
||||
victory_tech_names)
|
||||
|
@ -101,9 +118,41 @@ class Factorio(World):
|
|||
|
||||
options = factorio_options
|
||||
|
||||
def set_custom_technologies(world: MultiWorld, player: int):
|
||||
custom_technologies = {}
|
||||
allowed_packs = world.max_science_pack[player].get_allowed_packs()
|
||||
for technology_name, technology in base_technology_table.items():
|
||||
custom_technologies[technology_name] = technology.get_custom(world, allowed_packs, player)
|
||||
return custom_technologies
|
||||
def set_custom_technologies(self):
|
||||
custom_technologies = {}
|
||||
allowed_packs = self.world.max_science_pack[self.player].get_allowed_packs()
|
||||
for technology_name, technology in base_technology_table.items():
|
||||
custom_technologies[technology_name] = technology.get_custom(self.world, allowed_packs, self.player)
|
||||
return custom_technologies
|
||||
|
||||
def set_custom_recipes(self):
|
||||
original_rocket_part = recipes["rocket-part"]
|
||||
valid_pool = sorted(science_pack_pools[self.world.max_science_pack[self.player].get_max_pack()])
|
||||
self.world.random.shuffle(valid_pool)
|
||||
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
|
||||
{valid_pool[x] : 10 for x in range(3)},
|
||||
original_rocket_part.products)}
|
||||
self.additional_advancement_technologies = {tech.name for tech in
|
||||
self.custom_recipes["rocket-part"].recursive_unlocking_technologies}
|
||||
|
||||
if self.world.recipe_ingredients[self.player]:
|
||||
valid_pool = []
|
||||
for pack in self.world.max_science_pack[self.player].get_ordered_science_packs():
|
||||
valid_pool += sorted(science_pack_pools[pack])
|
||||
self.world.random.shuffle(valid_pool)
|
||||
if pack in recipes: # skips over space science pack
|
||||
original = recipes[pack]
|
||||
new_ingredients = {}
|
||||
for _ in original.ingredients:
|
||||
new_ingredients[valid_pool.pop()] = 1
|
||||
new_recipe = Recipe(pack, original.category, new_ingredients, original.products)
|
||||
self.additional_advancement_technologies |= {tech.name for tech in
|
||||
new_recipe.recursive_unlocking_technologies}
|
||||
self.custom_recipes[pack] = new_recipe
|
||||
|
||||
# handle marking progressive techs as advancement
|
||||
prog_add = set()
|
||||
for tech in self.additional_advancement_technologies:
|
||||
if tech in tech_to_progressive_lookup:
|
||||
prog_add.add(tech_to_progressive_lookup[tech])
|
||||
self.additional_advancement_technologies |= prog_add
|
||||
|
|
Loading…
Reference in New Issue