Factorio: update API use (#3760)

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Fabian Dill 2024-09-18 00:18:17 +02:00 committed by GitHub
parent debb936618
commit 6fac83b84c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 136 additions and 149 deletions

View File

@ -1,5 +1,6 @@
"""Outputs a Factorio Mod to facilitate integration with Archipelago""" """Outputs a Factorio Mod to facilitate integration with Archipelago"""
import dataclasses
import json import json
import os import os
import shutil import shutil
@ -88,6 +89,8 @@ class FactorioModFile(worlds.Files.APContainer):
def generate_mod(world: "Factorio", output_directory: str): def generate_mod(world: "Factorio", output_directory: str):
player = world.player player = world.player
multiworld = world.multiworld multiworld = world.multiworld
random = world.random
global data_final_template, locale_template, control_template, data_template, settings_template global data_final_template, locale_template, control_template, data_template, settings_template
with template_load_lock: with template_load_lock:
if not data_final_template: if not data_final_template:
@ -110,8 +113,6 @@ def generate_mod(world: "Factorio", output_directory: str):
mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}" mod_name = f"AP-{multiworld.seed_name}-P{player}-{multiworld.get_file_safe_player_name(player)}"
versioned_mod_name = mod_name + "_" + Utils.__version__ versioned_mod_name = mod_name + "_" + Utils.__version__
random = multiworld.per_slot_randoms[player]
def flop_random(low, high, base=None): def flop_random(low, high, base=None):
"""Guarantees 50% below base and 50% above base, uniform distribution in each direction.""" """Guarantees 50% below base and 50% above base, uniform distribution in each direction."""
if base: if base:
@ -129,43 +130,43 @@ def generate_mod(world: "Factorio", output_directory: str):
"base_tech_table": base_tech_table, "base_tech_table": base_tech_table,
"tech_to_progressive_lookup": tech_to_progressive_lookup, "tech_to_progressive_lookup": tech_to_progressive_lookup,
"mod_name": mod_name, "mod_name": mod_name,
"allowed_science_packs": multiworld.max_science_pack[player].get_allowed_packs(), "allowed_science_packs": world.options.max_science_pack.get_allowed_packs(),
"custom_technologies": multiworld.worlds[player].custom_technologies, "custom_technologies": world.custom_technologies,
"tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites, "tech_tree_layout_prerequisites": world.tech_tree_layout_prerequisites,
"slot_name": multiworld.player_name[player], "seed_name": multiworld.seed_name, "slot_name": world.player_name, "seed_name": multiworld.seed_name,
"slot_player": player, "slot_player": player,
"starting_items": multiworld.starting_items[player], "recipes": recipes, "starting_items": world.options.starting_items, "recipes": recipes,
"random": random, "flop_random": flop_random, "random": random, "flop_random": flop_random,
"recipe_time_scale": recipe_time_scales.get(multiworld.recipe_time[player].value, None), "recipe_time_scale": recipe_time_scales.get(world.options.recipe_time.value, None),
"recipe_time_range": recipe_time_ranges.get(multiworld.recipe_time[player].value, None), "recipe_time_range": recipe_time_ranges.get(world.options.recipe_time.value, None),
"free_sample_blacklist": {item: 1 for item in free_sample_exclusions}, "free_sample_blacklist": {item: 1 for item in free_sample_exclusions},
"progressive_technology_table": {tech.name: tech.progressive for tech in "progressive_technology_table": {tech.name: tech.progressive for tech in
progressive_technology_table.values()}, progressive_technology_table.values()},
"custom_recipes": world.custom_recipes, "custom_recipes": world.custom_recipes,
"max_science_pack": multiworld.max_science_pack[player].value, "max_science_pack": world.options.max_science_pack.value,
"liquids": fluids, "liquids": fluids,
"goal": multiworld.goal[player].value, "goal": world.options.goal.value,
"energy_link": multiworld.energy_link[player].value, "energy_link": world.options.energy_link.value,
"useless_technologies": useless_technologies, "useless_technologies": useless_technologies,
"chunk_shuffle": multiworld.chunk_shuffle[player].value if hasattr(multiworld, "chunk_shuffle") else 0, "chunk_shuffle": 0,
} }
for factorio_option in Options.factorio_options: for factorio_option, factorio_option_instance in dataclasses.asdict(world.options).items():
if factorio_option in ["free_sample_blacklist", "free_sample_whitelist"]: if factorio_option in ["free_sample_blacklist", "free_sample_whitelist"]:
continue continue
template_data[factorio_option] = getattr(multiworld, factorio_option)[player].value template_data[factorio_option] = factorio_option_instance.value
if getattr(multiworld, "silo")[player].value == Options.Silo.option_randomize_recipe: if world.options.silo == Options.Silo.option_randomize_recipe:
template_data["free_sample_blacklist"]["rocket-silo"] = 1 template_data["free_sample_blacklist"]["rocket-silo"] = 1
if getattr(multiworld, "satellite")[player].value == Options.Satellite.option_randomize_recipe: if world.options.satellite == Options.Satellite.option_randomize_recipe:
template_data["free_sample_blacklist"]["satellite"] = 1 template_data["free_sample_blacklist"]["satellite"] = 1
template_data["free_sample_blacklist"].update({item: 1 for item in multiworld.free_sample_blacklist[player].value}) template_data["free_sample_blacklist"].update({item: 1 for item in world.options.free_sample_blacklist.value})
template_data["free_sample_blacklist"].update({item: 0 for item in multiworld.free_sample_whitelist[player].value}) template_data["free_sample_blacklist"].update({item: 0 for item in world.options.free_sample_whitelist.value})
zf_path = os.path.join(output_directory, versioned_mod_name + ".zip") zf_path = os.path.join(output_directory, versioned_mod_name + ".zip")
mod = FactorioModFile(zf_path, player=player, player_name=multiworld.player_name[player]) mod = FactorioModFile(zf_path, player=player, player_name=world.player_name)
if world.zip_path: if world.zip_path:
with zipfile.ZipFile(world.zip_path) as zf: with zipfile.ZipFile(world.zip_path) as zf:

View File

@ -1,10 +1,13 @@
from __future__ import annotations from __future__ import annotations
import typing
from dataclasses import dataclass
import datetime import datetime
import typing
from schema import Schema, Optional, And, Or
from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \ from Options import Choice, OptionDict, OptionSet, Option, DefaultOnToggle, Range, DeathLink, Toggle, \
StartInventoryPool StartInventoryPool, PerGameCommonOptions
from schema import Schema, Optional, And, Or
# schema helpers # schema helpers
FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high) FloatRange = lambda low, high: And(Or(int, float), lambda f: low <= f <= high)
@ -422,50 +425,37 @@ class EnergyLink(Toggle):
display_name = "EnergyLink" display_name = "EnergyLink"
factorio_options: typing.Dict[str, type(Option)] = { @dataclass
"max_science_pack": MaxSciencePack, class FactorioOptions(PerGameCommonOptions):
"goal": Goal, max_science_pack: MaxSciencePack
"tech_tree_layout": TechTreeLayout, goal: Goal
"min_tech_cost": MinTechCost, tech_tree_layout: TechTreeLayout
"max_tech_cost": MaxTechCost, min_tech_cost: MinTechCost
"tech_cost_distribution": TechCostDistribution, max_tech_cost: MaxTechCost
"tech_cost_mix": TechCostMix, tech_cost_distribution: TechCostDistribution
"ramping_tech_costs": RampingTechCosts, tech_cost_mix: TechCostMix
"silo": Silo, ramping_tech_costs: RampingTechCosts
"satellite": Satellite, silo: Silo
"free_samples": FreeSamples, satellite: Satellite
"tech_tree_information": TechTreeInformation, free_samples: FreeSamples
"starting_items": FactorioStartItems, tech_tree_information: TechTreeInformation
"free_sample_blacklist": FactorioFreeSampleBlacklist, starting_items: FactorioStartItems
"free_sample_whitelist": FactorioFreeSampleWhitelist, free_sample_blacklist: FactorioFreeSampleBlacklist
"recipe_time": RecipeTime, free_sample_whitelist: FactorioFreeSampleWhitelist
"recipe_ingredients": RecipeIngredients, recipe_time: RecipeTime
"recipe_ingredients_offset": RecipeIngredientsOffset, recipe_ingredients: RecipeIngredients
"imported_blueprints": ImportedBlueprint, recipe_ingredients_offset: RecipeIngredientsOffset
"world_gen": FactorioWorldGen, imported_blueprints: ImportedBlueprint
"progressive": Progressive, world_gen: FactorioWorldGen
"teleport_traps": TeleportTrapCount, progressive: Progressive
"grenade_traps": GrenadeTrapCount, teleport_traps: TeleportTrapCount
"cluster_grenade_traps": ClusterGrenadeTrapCount, grenade_traps: GrenadeTrapCount
"artillery_traps": ArtilleryTrapCount, cluster_grenade_traps: ClusterGrenadeTrapCount
"atomic_rocket_traps": AtomicRocketTrapCount, artillery_traps: ArtilleryTrapCount
"attack_traps": AttackTrapCount, atomic_rocket_traps: AtomicRocketTrapCount
"evolution_traps": EvolutionTrapCount, attack_traps: AttackTrapCount
"evolution_trap_increase": EvolutionTrapIncrease, evolution_traps: EvolutionTrapCount
"death_link": DeathLink, evolution_trap_increase: EvolutionTrapIncrease
"energy_link": EnergyLink, death_link: DeathLink
"start_inventory_from_pool": StartInventoryPool, energy_link: EnergyLink
} start_inventory_from_pool: StartInventoryPool
# spoilers below. If you spoil it for yourself, please at least don't spoil it for anyone else.
if datetime.datetime.today().month == 4:
class ChunkShuffle(Toggle):
"""Entrance Randomizer."""
display_name = "Chunk Shuffle"
if datetime.datetime.today().day > 1:
ChunkShuffle.__doc__ += """
2023 April Fool's option. Shuffles chunk border transitions."""
factorio_options["chunk_shuffle"] = ChunkShuffle

View File

@ -19,12 +19,10 @@ def _sorter(location: "FactorioScienceLocation"):
return location.complexity, location.rel_cost return location.complexity, location.rel_cost
def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]: def get_shapes(world: "Factorio") -> Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]]:
world = factorio_world.multiworld
player = factorio_world.player
prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {} prerequisites: Dict["FactorioScienceLocation", Set["FactorioScienceLocation"]] = {}
layout = world.tech_tree_layout[player].value layout = world.options.tech_tree_layout.value
locations: List["FactorioScienceLocation"] = sorted(factorio_world.science_locations, key=lambda loc: loc.name) locations: List["FactorioScienceLocation"] = sorted(world.science_locations, key=lambda loc: loc.name)
world.random.shuffle(locations) world.random.shuffle(locations)
if layout == TechTreeLayout.option_single: if layout == TechTreeLayout.option_single:
@ -247,5 +245,5 @@ def get_shapes(factorio_world: "Factorio") -> Dict["FactorioScienceLocation", Se
else: else:
raise NotImplementedError(f"Layout {layout} is not implemented.") raise NotImplementedError(f"Layout {layout} is not implemented.")
factorio_world.tech_tree_layout_prerequisites = prerequisites world.tech_tree_layout_prerequisites = prerequisites
return prerequisites return prerequisites

View File

@ -13,12 +13,11 @@ import Utils
from . import Options from . import Options
factorio_tech_id = factorio_base_id = 2 ** 17 factorio_tech_id = factorio_base_id = 2 ** 17
# Factorio technologies are imported from a .json document in /data
source_folder = os.path.join(os.path.dirname(__file__), "data")
pool = ThreadPoolExecutor(1) pool = ThreadPoolExecutor(1)
# Factorio technologies are imported from a .json document in /data
def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]: def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]:
return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json")) return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json"))
@ -99,7 +98,7 @@ class CustomTechnology(Technology):
and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"}) and ((ingredients & {"chemical-science-pack", "production-science-pack", "utility-science-pack"})
or origin.name == "rocket-silo") or origin.name == "rocket-silo")
self.player = player self.player = player
if origin.name not in world.worlds[player].special_nodes: if origin.name not in world.special_nodes:
if military_allowed: if military_allowed:
ingredients.add("military-science-pack") ingredients.add("military-science-pack")
ingredients = list(ingredients) ingredients = list(ingredients)

View File

@ -11,7 +11,7 @@ from worlds.LauncherComponents import Component, components, Type, launch_subpro
from worlds.generic import Rules from worlds.generic import Rules
from .Locations import location_pools, location_table from .Locations import location_pools, location_table
from .Mod import generate_mod from .Mod import generate_mod
from .Options import factorio_options, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution from .Options import FactorioOptions, MaxSciencePack, Silo, Satellite, TechTreeInformation, Goal, TechCostDistribution
from .Shapes import get_shapes from .Shapes import get_shapes
from .Technologies import base_tech_table, recipe_sources, base_technology_table, \ from .Technologies import base_tech_table, recipe_sources, base_technology_table, \
all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \ all_ingredient_names, all_product_sources, required_technologies, get_rocket_requirements, \
@ -89,13 +89,15 @@ class Factorio(World):
advancement_technologies: typing.Set[str] advancement_technologies: typing.Set[str]
web = FactorioWeb() web = FactorioWeb()
options_dataclass = FactorioOptions
options: FactorioOptions
item_name_to_id = all_items item_name_to_id = all_items
location_name_to_id = location_table location_name_to_id = location_table
item_name_groups = { item_name_groups = {
"Progressive": set(progressive_tech_table.keys()), "Progressive": set(progressive_tech_table.keys()),
} }
required_client_version = (0, 4, 2) required_client_version = (0, 5, 0)
ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs() ordered_science_packs: typing.List[str] = MaxSciencePack.get_ordered_science_packs()
tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]] tech_tree_layout_prerequisites: typing.Dict[FactorioScienceLocation, typing.Set[FactorioScienceLocation]]
@ -117,32 +119,32 @@ class Factorio(World):
def generate_early(self) -> None: def generate_early(self) -> None:
# if max < min, then swap max and min # if max < min, then swap max and min
if self.multiworld.max_tech_cost[self.player] < self.multiworld.min_tech_cost[self.player]: if self.options.max_tech_cost < self.options.min_tech_cost:
self.multiworld.min_tech_cost[self.player].value, self.multiworld.max_tech_cost[self.player].value = \ self.options.min_tech_cost.value, self.options.max_tech_cost.value = \
self.multiworld.max_tech_cost[self.player].value, self.multiworld.min_tech_cost[self.player].value self.options.max_tech_cost.value, self.options.min_tech_cost.value
self.tech_mix = self.multiworld.tech_cost_mix[self.player] self.tech_mix = self.options.tech_cost_mix.value
self.skip_silo = self.multiworld.silo[self.player].value == Silo.option_spawn self.skip_silo = self.options.silo.value == Silo.option_spawn
def create_regions(self): def create_regions(self):
player = self.player player = self.player
random = self.multiworld.random random = self.random
nauvis = Region("Nauvis", player, self.multiworld) nauvis = Region("Nauvis", player, self.multiworld)
location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \ location_count = len(base_tech_table) - len(useless_technologies) - self.skip_silo + \
self.multiworld.evolution_traps[player] + \ self.options.evolution_traps + \
self.multiworld.attack_traps[player] + \ self.options.attack_traps + \
self.multiworld.teleport_traps[player] + \ self.options.teleport_traps + \
self.multiworld.grenade_traps[player] + \ self.options.grenade_traps + \
self.multiworld.cluster_grenade_traps[player] + \ self.options.cluster_grenade_traps + \
self.multiworld.atomic_rocket_traps[player] + \ self.options.atomic_rocket_traps + \
self.multiworld.artillery_traps[player] self.options.artillery_traps
location_pool = [] location_pool = []
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()): for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
location_pool.extend(location_pools[pack]) location_pool.extend(location_pools[pack])
try: try:
location_names = self.multiworld.random.sample(location_pool, location_count) location_names = random.sample(location_pool, location_count)
except ValueError as e: except ValueError as e:
# should be "ValueError: Sample larger than population or is negative" # should be "ValueError: Sample larger than population or is negative"
raise Exception("Too many traps for too few locations. Either decrease the trap count, " raise Exception("Too many traps for too few locations. Either decrease the trap count, "
@ -150,9 +152,9 @@ class Factorio(World):
self.science_locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis) self.science_locations = [FactorioScienceLocation(player, loc_name, self.location_name_to_id[loc_name], nauvis)
for loc_name in location_names] for loc_name in location_names]
distribution: TechCostDistribution = self.multiworld.tech_cost_distribution[self.player] distribution: TechCostDistribution = self.options.tech_cost_distribution
min_cost = self.multiworld.min_tech_cost[self.player] min_cost = self.options.min_tech_cost.value
max_cost = self.multiworld.max_tech_cost[self.player] max_cost = self.options.max_tech_cost.value
if distribution == distribution.option_even: if distribution == distribution.option_even:
rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations) rand_values = (random.randint(min_cost, max_cost) for _ in self.science_locations)
else: else:
@ -161,7 +163,7 @@ class Factorio(World):
distribution.option_high: max_cost}[distribution.value] distribution.option_high: max_cost}[distribution.value]
rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations) rand_values = (random.triangular(min_cost, max_cost, mode) for _ in self.science_locations)
rand_values = sorted(rand_values) rand_values = sorted(rand_values)
if self.multiworld.ramping_tech_costs[self.player]: if self.options.ramping_tech_costs:
def sorter(loc: FactorioScienceLocation): def sorter(loc: FactorioScienceLocation):
return loc.complexity, loc.rel_cost return loc.complexity, loc.rel_cost
else: else:
@ -176,7 +178,7 @@ class Factorio(World):
event = FactorioItem("Victory", ItemClassification.progression, None, player) event = FactorioItem("Victory", ItemClassification.progression, None, player)
location.place_locked_item(event) location.place_locked_item(event)
for ingredient in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()): for ingredient in sorted(self.options.max_science_pack.get_allowed_packs()):
location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis) location = FactorioLocation(player, f"Automate {ingredient}", None, nauvis)
nauvis.locations.append(location) nauvis.locations.append(location)
event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player) event = FactorioItem(f"Automated {ingredient}", ItemClassification.progression, None, player)
@ -185,24 +187,23 @@ class Factorio(World):
self.multiworld.regions.append(nauvis) self.multiworld.regions.append(nauvis)
def create_items(self) -> None: def create_items(self) -> None:
player = self.player
self.custom_technologies = self.set_custom_technologies() self.custom_technologies = self.set_custom_technologies()
self.set_custom_recipes() self.set_custom_recipes()
traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket") traps = ("Evolution", "Attack", "Teleport", "Grenade", "Cluster Grenade", "Artillery", "Atomic Rocket")
for trap_name in traps: for trap_name in traps:
self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in self.multiworld.itempool.extend(self.create_item(f"{trap_name} Trap") for _ in
range(getattr(self.multiworld, range(getattr(self.options,
f"{trap_name.lower().replace(' ', '_')}_traps")[player])) f"{trap_name.lower().replace(' ', '_')}_traps")))
want_progressives = collections.defaultdict(lambda: self.multiworld.progressive[player]. want_progressives = collections.defaultdict(lambda: self.options.progressive.
want_progressives(self.multiworld.random)) want_progressives(self.random))
cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name) cost_sorted_locations = sorted(self.science_locations, key=lambda location: location.name)
special_index = {"automation": 0, special_index = {"automation": 0,
"logistics": 1, "logistics": 1,
"rocket-silo": -1} "rocket-silo": -1}
loc: FactorioScienceLocation loc: FactorioScienceLocation
if self.multiworld.tech_tree_information[player] == TechTreeInformation.option_full: if self.options.tech_tree_information == TechTreeInformation.option_full:
# mark all locations as pre-hinted # mark all locations as pre-hinted
for loc in self.science_locations: for loc in self.science_locations:
loc.revealed = True loc.revealed = True
@ -229,14 +230,13 @@ class Factorio(World):
loc.revealed = True loc.revealed = True
def set_rules(self): def set_rules(self):
world = self.multiworld
player = self.player player = self.player
shapes = get_shapes(self) shapes = get_shapes(self)
for ingredient in self.multiworld.max_science_pack[self.player].get_allowed_packs(): for ingredient in self.options.max_science_pack.get_allowed_packs():
location = world.get_location(f"Automate {ingredient}", player) location = self.get_location(f"Automate {ingredient}")
if self.multiworld.recipe_ingredients[self.player]: if self.options.recipe_ingredients:
custom_recipe = self.custom_recipes[ingredient] custom_recipe = self.custom_recipes[ingredient]
location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \ location.access_rule = lambda state, ingredient=ingredient, custom_recipe=custom_recipe: \
@ -257,30 +257,30 @@ class Factorio(World):
prerequisites: all(state.can_reach(loc) for loc in locations)) prerequisites: all(state.can_reach(loc) for loc in locations))
silo_recipe = None silo_recipe = None
if self.multiworld.silo[self.player] == Silo.option_spawn: if self.options.silo == Silo.option_spawn:
silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \ silo_recipe = self.custom_recipes["rocket-silo"] if "rocket-silo" in self.custom_recipes \
else next(iter(all_product_sources.get("rocket-silo"))) else next(iter(all_product_sources.get("rocket-silo")))
part_recipe = self.custom_recipes["rocket-part"] part_recipe = self.custom_recipes["rocket-part"]
satellite_recipe = None satellite_recipe = None
if self.multiworld.goal[self.player] == Goal.option_satellite: if self.options.goal == Goal.option_satellite:
satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \ satellite_recipe = self.custom_recipes["satellite"] if "satellite" in self.custom_recipes \
else next(iter(all_product_sources.get("satellite"))) else next(iter(all_product_sources.get("satellite")))
victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe) victory_tech_names = get_rocket_requirements(silo_recipe, part_recipe, satellite_recipe)
if self.multiworld.silo[self.player] != Silo.option_spawn: if self.options.silo != Silo.option_spawn:
victory_tech_names.add("rocket-silo") victory_tech_names.add("rocket-silo")
world.get_location("Rocket Launch", player).access_rule = lambda state: all(state.has(technology, player) self.get_location("Rocket Launch").access_rule = lambda state: all(state.has(technology, player)
for technology in for technology in
victory_tech_names) victory_tech_names)
world.completion_condition[player] = lambda state: state.has('Victory', player) self.multiworld.completion_condition[player] = lambda state: state.has('Victory', player)
def generate_basic(self): def generate_basic(self):
map_basic_settings = self.multiworld.world_gen[self.player].value["basic"] map_basic_settings = self.options.world_gen.value["basic"]
if map_basic_settings.get("seed", None) is None: # allow seed 0 if map_basic_settings.get("seed", None) is None: # allow seed 0
# 32 bit uint # 32 bit uint
map_basic_settings["seed"] = self.multiworld.per_slot_randoms[self.player].randint(0, 2 ** 32 - 1) map_basic_settings["seed"] = self.random.randint(0, 2 ** 32 - 1)
start_location_hints: typing.Set[str] = self.multiworld.start_location_hints[self.player].value start_location_hints: typing.Set[str] = self.options.start_location_hints.value
for loc in self.science_locations: for loc in self.science_locations:
# show start_location_hints ingame # show start_location_hints ingame
@ -304,8 +304,6 @@ class Factorio(World):
return super(Factorio, self).collect_item(state, item, remove) return super(Factorio, self).collect_item(state, item, remove)
option_definitions = factorio_options
@classmethod @classmethod
def stage_write_spoiler(cls, world, spoiler_handle): def stage_write_spoiler(cls, world, spoiler_handle):
factorio_players = world.get_game_players(cls.game) factorio_players = world.get_game_players(cls.game)
@ -345,7 +343,7 @@ class Factorio(World):
# have to first sort for determinism, while filtering out non-stacking items # have to first sort for determinism, while filtering out non-stacking items
pool: typing.List[str] = sorted(pool & valid_ingredients) pool: typing.List[str] = sorted(pool & valid_ingredients)
# then sort with random data to shuffle # then sort with random data to shuffle
self.multiworld.random.shuffle(pool) self.random.shuffle(pool)
target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor) target_raw = int(sum((count for ingredient, count in original.base_cost.items())) * factor)
target_energy = original.total_energy * factor target_energy = original.total_energy * factor
target_num_ingredients = len(original.ingredients) + ingredients_offset target_num_ingredients = len(original.ingredients) + ingredients_offset
@ -389,7 +387,7 @@ class Factorio(World):
if min_num > max_num: if min_num > max_num:
fallback_pool.append(ingredient) fallback_pool.append(ingredient)
continue # can't use that ingredient continue # can't use that ingredient
num = self.multiworld.random.randint(min_num, max_num) num = self.random.randint(min_num, max_num)
new_ingredients[ingredient] = num new_ingredients[ingredient] = num
remaining_raw -= num * ingredient_raw remaining_raw -= num * ingredient_raw
remaining_energy -= num * ingredient_energy remaining_energy -= num * ingredient_energy
@ -433,66 +431,66 @@ class Factorio(World):
def set_custom_technologies(self): def set_custom_technologies(self):
custom_technologies = {} custom_technologies = {}
allowed_packs = self.multiworld.max_science_pack[self.player].get_allowed_packs() allowed_packs = self.options.max_science_pack.get_allowed_packs()
for technology_name, technology in base_technology_table.items(): for technology_name, technology in base_technology_table.items():
custom_technologies[technology_name] = technology.get_custom(self.multiworld, allowed_packs, self.player) custom_technologies[technology_name] = technology.get_custom(self, allowed_packs, self.player)
return custom_technologies return custom_technologies
def set_custom_recipes(self): def set_custom_recipes(self):
ingredients_offset = self.multiworld.recipe_ingredients_offset[self.player] ingredients_offset = self.options.recipe_ingredients_offset
original_rocket_part = recipes["rocket-part"] original_rocket_part = recipes["rocket-part"]
science_pack_pools = get_science_pack_pools() science_pack_pools = get_science_pack_pools()
valid_pool = sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_max_pack()] & valid_ingredients) valid_pool = sorted(science_pack_pools[self.options.max_science_pack.get_max_pack()] & valid_ingredients)
self.multiworld.random.shuffle(valid_pool) self.random.shuffle(valid_pool)
self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category, self.custom_recipes = {"rocket-part": Recipe("rocket-part", original_rocket_part.category,
{valid_pool[x]: 10 for x in range(3 + ingredients_offset)}, {valid_pool[x]: 10 for x in range(3 + ingredients_offset)},
original_rocket_part.products, original_rocket_part.products,
original_rocket_part.energy)} original_rocket_part.energy)}
if self.multiworld.recipe_ingredients[self.player]: if self.options.recipe_ingredients:
valid_pool = [] valid_pool = []
for pack in self.multiworld.max_science_pack[self.player].get_ordered_science_packs(): for pack in self.options.max_science_pack.get_ordered_science_packs():
valid_pool += sorted(science_pack_pools[pack]) valid_pool += sorted(science_pack_pools[pack])
self.multiworld.random.shuffle(valid_pool) self.random.shuffle(valid_pool)
if pack in recipes: # skips over space science pack if pack in recipes: # skips over space science pack
new_recipe = self.make_quick_recipe(recipes[pack], valid_pool, ingredients_offset= new_recipe = self.make_quick_recipe(recipes[pack], valid_pool, ingredients_offset=
ingredients_offset) ingredients_offset.value)
self.custom_recipes[pack] = new_recipe self.custom_recipes[pack] = new_recipe
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe \ if self.options.silo.value == Silo.option_randomize_recipe \
or self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe: or self.options.satellite.value == Satellite.option_randomize_recipe:
valid_pool = set() valid_pool = set()
for pack in sorted(self.multiworld.max_science_pack[self.player].get_allowed_packs()): for pack in sorted(self.options.max_science_pack.get_allowed_packs()):
valid_pool |= science_pack_pools[pack] valid_pool |= science_pack_pools[pack]
if self.multiworld.silo[self.player].value == Silo.option_randomize_recipe: if self.options.silo.value == Silo.option_randomize_recipe:
new_recipe = self.make_balanced_recipe( new_recipe = self.make_balanced_recipe(
recipes["rocket-silo"], valid_pool, recipes["rocket-silo"], valid_pool,
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7, factor=(self.options.max_science_pack.value + 1) / 7,
ingredients_offset=ingredients_offset) ingredients_offset=ingredients_offset.value)
self.custom_recipes["rocket-silo"] = new_recipe self.custom_recipes["rocket-silo"] = new_recipe
if self.multiworld.satellite[self.player].value == Satellite.option_randomize_recipe: if self.options.satellite.value == Satellite.option_randomize_recipe:
new_recipe = self.make_balanced_recipe( new_recipe = self.make_balanced_recipe(
recipes["satellite"], valid_pool, recipes["satellite"], valid_pool,
factor=(self.multiworld.max_science_pack[self.player].value + 1) / 7, factor=(self.options.max_science_pack.value + 1) / 7,
ingredients_offset=ingredients_offset) ingredients_offset=ingredients_offset.value)
self.custom_recipes["satellite"] = new_recipe self.custom_recipes["satellite"] = new_recipe
bridge = "ap-energy-bridge" bridge = "ap-energy-bridge"
new_recipe = self.make_quick_recipe( new_recipe = self.make_quick_recipe(
Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1, Recipe(bridge, "crafting", {"replace_1": 1, "replace_2": 1, "replace_3": 1,
"replace_4": 1, "replace_5": 1, "replace_6": 1}, "replace_4": 1, "replace_5": 1, "replace_6": 1},
{bridge: 1}, 10), {bridge: 1}, 10),
sorted(science_pack_pools[self.multiworld.max_science_pack[self.player].get_ordered_science_packs()[0]]), sorted(science_pack_pools[self.options.max_science_pack.get_ordered_science_packs()[0]]),
ingredients_offset=ingredients_offset) ingredients_offset=ingredients_offset.value)
for ingredient_name in new_recipe.ingredients: for ingredient_name in new_recipe.ingredients:
new_recipe.ingredients[ingredient_name] = self.multiworld.random.randint(50, 500) new_recipe.ingredients[ingredient_name] = self.random.randint(50, 500)
self.custom_recipes[bridge] = new_recipe self.custom_recipes[bridge] = new_recipe
needed_recipes = self.multiworld.max_science_pack[self.player].get_allowed_packs() | {"rocket-part"} needed_recipes = self.options.max_science_pack.get_allowed_packs() | {"rocket-part"}
if self.multiworld.silo[self.player] != Silo.option_spawn: if self.options.silo != Silo.option_spawn:
needed_recipes |= {"rocket-silo"} needed_recipes |= {"rocket-silo"}
if self.multiworld.goal[self.player].value == Goal.option_satellite: if self.options.goal.value == Goal.option_satellite:
needed_recipes |= {"satellite"} needed_recipes |= {"satellite"}
for recipe in needed_recipes: for recipe in needed_recipes:
@ -542,7 +540,8 @@ class FactorioScienceLocation(FactorioLocation):
self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1} self.ingredients = {Factorio.ordered_science_packs[self.complexity]: 1}
for complexity in range(self.complexity): for complexity in range(self.complexity):
if parent.multiworld.tech_cost_mix[self.player] > parent.multiworld.random.randint(0, 99): if (parent.multiworld.worlds[self.player].options.tech_cost_mix >
parent.multiworld.worlds[self.player].random.randint(0, 99)):
self.ingredients[Factorio.ordered_science_packs[complexity]] = 1 self.ingredients[Factorio.ordered_science_packs[complexity]] = 1
@property @property