Fixed generation with different elevator tiers

This commit is contained in:
Jarno Westhof
2025-04-03 22:50:06 +02:00
parent dcb820b483
commit 3b49aae19e
8 changed files with 116 additions and 70 deletions

View File

@@ -61,7 +61,8 @@ class TestBase(unittest.TestCase):
self.assertFalse(region.can_reach(state))
else:
with self.subTest("Region should be reached", region=region.name):
self.assertTrue(region.can_reach(state))
if not region.can_reach(state):
self.assertTrue(region.can_reach(state))
with self.subTest("Completion Condition"):
self.assertTrue(multiworld.can_beat_game(state))

View File

@@ -19,6 +19,10 @@ class CriticalPathCalculator:
__potential_required_pipes: bool
__potential_required_radioactive: bool
parts_to_exclude: set[str]
recipes_to_exclude: set[str]
buildings_to_exclude: set[str]
def __init__(self, logic: GameLogic, random: Random, options: SatisfactoryOptions):
self.logic = logic
self.random = random
@@ -30,7 +34,6 @@ class CriticalPathCalculator:
self.__potential_required_belt_speed = 1
self.__potential_required_pipes = False
self.__potential_required_radioactive = False
selected_power_infrastructure: dict[int, Recipe] = {}
@@ -57,9 +60,8 @@ class CriticalPathCalculator:
self.select_minimal_required_parts_for_building("Power Storage")
self.select_minimal_required_parts_for_building("Miner Mk.2")
#equipment
self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs)
self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs)
if self.logic.recipes["Uranium"][0].minimal_tier <= options.final_elevator_package:
self.select_minimal_required_parts_for(("Hazmat Suit", "Iodine Infused Filter"))
for i in range(1, self.__potential_required_belt_speed + 1):
self.select_minimal_required_parts_for_building(f"Conveyor Mk.{i}")
@@ -68,9 +70,6 @@ class CriticalPathCalculator:
self.select_minimal_required_parts_for_building("Pipes Mk.2")
self.select_minimal_required_parts_for_building("Pipeline Pump Mk.1")
self.select_minimal_required_parts_for_building("Pipeline Pump Mk.2")
if self.__potential_required_radioactive:
self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs)
self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs)
for i in range(1, self.required_power_level + 1):
power_recipe = random.choice(self.logic.requirement_per_powerlevel[i])
selected_power_infrastructure[i] = power_recipe
@@ -85,6 +84,55 @@ class CriticalPathCalculator:
)
self.required_item_names.update("Building: "+ building for building in self.required_buildings)
self.parts_to_exclude = set()
self.buildings_to_exclude = set()
self.recipes_to_exclude = set(
recipe.name
for part in self.logic.recipes
for recipe in self.logic.recipes[part]
if recipe.minimal_tier > self.options.final_elevator_package
)
excluded_count = len(self.recipes_to_exclude)
while True:
for part in self.logic.recipes:
if part in self.parts_to_exclude:
continue
for recipe in self.logic.recipes[part]:
if recipe.name in self.recipes_to_exclude:
continue
if recipe.inputs and any(input in self.parts_to_exclude for input in recipe.inputs):
self.recipes_to_exclude.add(recipe.name)
if all(r.name in self.recipes_to_exclude for r in self.logic.recipes[part]):
self.parts_to_exclude.add(part)
new_buildings_to_exclude = set(
building_name
for building_name, building in self.logic.buildings.items()
if building_name not in self.buildings_to_exclude
and building.inputs and any(input in self.parts_to_exclude for input in building.inputs)
)
self.recipes_to_exclude.update(
recipe_per_part.name
for building_to_exclude in new_buildings_to_exclude
for recipes_per_part in self.logic.recipes.values()
for recipe_per_part in recipes_per_part
if recipe_per_part.building == building_to_exclude
)
self.buildings_to_exclude.update(new_buildings_to_exclude)
new_length = len(self.recipes_to_exclude)
if new_length == excluded_count:
break
excluded_count = new_length
Debug = True
def select_minimal_required_parts_for_building(self, building: str) -> None:
self.select_minimal_required_parts_for(self.logic.buildings[building].inputs)
self.required_buildings.add(building)

View File

@@ -191,15 +191,15 @@ class GameLogic:
"Crude Oil": (
Recipe("Crude Oil", "Oil Extractor", implicitly_unlocked=True), ),
"Bauxite": (
Recipe("Bauxite", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ),
Recipe("Bauxite", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True, minimal_tier=2), ),
"Nitrogen Gas": (
Recipe("Nitrogen Gas", "Resource Well Pressurizer", implicitly_unlocked=True), ),
Recipe("Nitrogen Gas", "Resource Well Pressurizer", implicitly_unlocked=True, minimal_tier=2), ),
"Uranium": (
Recipe("Uranium", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ),
Recipe("Uranium", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True, minimal_tier=2), ),
# Special Items
"Uranium Waste": (
Recipe("Uranium Waste", "Nuclear Power Plant", ("Uranium Fuel Rod", "Water"), implicitly_unlocked=True), ),
Recipe("Uranium Waste", "Nuclear Power Plant", ("Uranium Fuel Rod", "Water"), implicitly_unlocked=True, minimal_tier=2), ),
#"Plutonium Waste": (
# Recipe("Plutonium Waste", "Nuclear Power Plant", ("Plutonium Fuel Rod", "Water"), implicitly_unlocked=True), ),
@@ -280,7 +280,7 @@ class GameLogic:
Recipe("Wet Concrete", "Refinery", ("Limestone", "Water"), minimal_belt_speed=2)),
"Silica": (
Recipe("Silica", "Constructor", ("Raw Quartz", ), handcraftable=True),
Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Alumina Solution", ), minimal_belt_speed=2),
Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Alumina Solution", ), minimal_belt_speed=2, minimal_tier=2),
Recipe("Cheap Silica", "Assembler", ("Raw Quartz", "Limestone")),
Recipe("Distilled Silica", "Blender", ("Dissolved Silica", "Limestone", "Water"), additional_outputs=("Water", ), minimal_tier=2)),
"Dissolved Silica": (
@@ -356,18 +356,18 @@ class GameLogic:
Recipe("Smart Plating", "Assembler", ("Reinforced Iron Plate", "Rotor")),
Recipe("Plastic Smart Plating", "Manufacturer", ("Reinforced Iron Plate", "Rotor", "Plastic"))),
"Versatile Framework": (
Recipe("Versatile Framework", "Assembler", ("Modular Frame", "Steel Beam")),
Recipe("Flexible Framework", "Manufacturer", ("Modular Frame", "Steel Beam", "Rubber"))),
Recipe("Versatile Framework", "Assembler", ("Modular Frame", "Steel Beam"), minimal_tier=2),
Recipe("Flexible Framework", "Manufacturer", ("Modular Frame", "Steel Beam", "Rubber"), minimal_tier=2)),
"Automated Wiring": (
Recipe("Automated Wiring", "Assembler", ("Stator", "Cable")),
Recipe("Automated Speed Wiring", "Manufacturer", ("Stator", "Wire", "High-Speed Connector"), minimal_belt_speed=2)),
Recipe("Automated Wiring", "Assembler", ("Stator", "Cable"), minimal_tier=2),
Recipe("Automated Speed Wiring", "Manufacturer", ("Stator", "Wire", "High-Speed Connector"), minimal_belt_speed=2, minimal_tier=2)),
"Modular Engine": (
Recipe("Modular Engine", "Manufacturer", ("Motor", "Rubber", "Smart Plating")), ),
Recipe("Modular Engine", "Manufacturer", ("Motor", "Rubber", "Smart Plating"), minimal_tier=3), ),
"Adaptive Control Unit": (
Recipe("Adaptive Control Unit", "Manufacturer", ("Automated Wiring", "Circuit Board", "Heavy Modular Frame", "Computer")), ),
Recipe("Adaptive Control Unit", "Manufacturer", ("Automated Wiring", "Circuit Board", "Heavy Modular Frame", "Computer"), minimal_tier=3), ),
"Portable Miner": (
Recipe("Portable Miner", "Equipment Workshop", ("Iron Rod", "Iron Plate"), handcraftable=True, minimal_belt_speed=0, implicitly_unlocked=True),
Recipe("Automated Miner", "Manufacturer", ("Steel Pipe", "Iron Plate")), ),
Recipe("Automated Miner", "Assembler", ("Steel Pipe", "Iron Plate")), ),
"Alumina Solution": (
Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Silica", ), minimal_belt_speed=2, minimal_tier=2),
Recipe("Sloppy Alumina", "Refinery", ("Bauxite", "Water"), minimal_belt_speed=3, minimal_tier=2)),
@@ -433,19 +433,19 @@ class GameLogic:
"Gas Filter": (
Recipe("Gas Filter", "Manufacturer", ("Coal", "Rubber", "Fabric"), handcraftable=True), ),
"Iodine Infused Filter": (
Recipe("Iodine Infused Filter", "Manufacturer", ("Gas Filter", "Quickwire", "Aluminum Casing"), handcraftable=True), ),
Recipe("Iodine Infused Filter", "Manufacturer", ("Gas Filter", "Quickwire", "Aluminum Casing"), handcraftable=True, minimal_tier=2), ),
"Hazmat Suit": (
Recipe("Hazmat Suit", "Equipment Workshop", ("Rubber", "Plastic", "Fabric", "Alclad Aluminum Sheet"), handcraftable=True, minimal_belt_speed=0), ),
Recipe("Hazmat Suit", "Equipment Workshop", ("Rubber", "Plastic", "Fabric", "Alclad Aluminum Sheet"), handcraftable=True, minimal_tier=2), ),
"Assembly Director System": (
Recipe("Assembly Director System", "Assembler", ("Adaptive Control Unit", "Supercomputer")), ),
Recipe("Assembly Director System", "Assembler", ("Adaptive Control Unit", "Supercomputer"), minimal_tier=4), ),
"Magnetic Field Generator": (
Recipe("Magnetic Field Generator", "Assembler", ("Versatile Framework", "Electromagnetic Control Rod")), ),
Recipe("Magnetic Field Generator", "Assembler", ("Versatile Framework", "Electromagnetic Control Rod"), minimal_tier=4), ),
"Copper Powder": (
Recipe("Copper Powder", "Constructor", ("Copper Ingot", ), handcraftable=True), ),
"Nuclear Pasta": (
Recipe("Nuclear Pasta", "Particle Accelerator", ("Copper Powder", "Pressure Conversion Cube"), minimal_tier=2), ),
"Thermal Propulsion Rocket": (
Recipe("Thermal Propulsion Rocket", "Manufacturer", ("Modular Engine", "Turbo Motor", "Cooling System", "Fused Modular Frame")), ),
Recipe("Thermal Propulsion Rocket", "Manufacturer", ("Modular Engine", "Turbo Motor", "Cooling System", "Fused Modular Frame"), minimal_tier=4), ),
"Alien Protein": (
Recipe("Hatcher Protein", "Constructor", ("Hatcher Remains", ), handcraftable=True),
Recipe("Hog Protein", "Constructor", ("Hog Remains", ), handcraftable=True),
@@ -513,7 +513,7 @@ class GameLogic:
Recipe("Power Shard (1)", "Constructor", ("Blue Power Slug", ), handcraftable=True),
Recipe("Power Shard (2)", "Constructor", ("Yellow Power Slug", ), handcraftable=True),
Recipe("Power Shard (5)", "Constructor", ("Purple Power Slug", ), handcraftable=True),
Recipe("Synthetic Power Shard", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Dark Matter Crystal", "Quartz Crystal"), minimal_tier=2)), # 1.0
Recipe("Synthetic Power Shard", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Dark Matter Crystal", "Quartz Crystal"), minimal_tier=4)), # 1.0
"Object Scanner": (
Recipe("Object Scanner", "Equipment Workshop", ("Reinforced Iron Plate", "Wire", "Screw"), handcraftable=True), ),
"Xeno-Zapper": (
@@ -559,9 +559,9 @@ class GameLogic:
"Singularity Cell": (
Recipe("Singularity Cell", "Manufacturer", ("Nuclear Pasta", "Dark Matter Crystal", "Iron Plate", "Concrete"), minimal_belt_speed=3), ),
"Biochemical Sculptor": (
Recipe("Biochemical Sculptor", "Blender", ("Assembly Director System", "Ficsite Trigon", "Water")), ),
Recipe("Biochemical Sculptor", "Blender", ("Assembly Director System", "Ficsite Trigon", "Water"), minimal_tier=5), ),
"Ballistic Warp Drive": (
Recipe("Ballistic Warp Drive", "Manufacturer", ("Thermal Propulsion Rocket", "Singularity Cell", "Superposition Oscillator", "Dark Matter Crystal")), ),
Recipe("Ballistic Warp Drive", "Manufacturer", ("Thermal Propulsion Rocket", "Singularity Cell", "Superposition Oscillator", "Dark Matter Crystal"), minimal_tier=5), ),
# All Quantum Encoder recipes have `Dark Matter Residue` set as an input, this hack makes the logic make sure you can get rid of it
"Dark Matter Residue": (
@@ -574,7 +574,7 @@ class GameLogic:
"Neural-Quantum Processor": (
Recipe("Neural-Quantum Processor", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Supercomputer", "Ficsite Trigon")), ),
"AI Expansion Server": (
Recipe("AI Expansion Server", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Magnetic Field Generator", "Neural-Quantum Processor", "Superposition Oscillator")), ),
Recipe("AI Expansion Server", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Magnetic Field Generator", "Neural-Quantum Processor", "Superposition Oscillator"), minimal_tier=5), ),
###
#1.0
}

View File

@@ -31,7 +31,7 @@ class ItemGroups(IntFlag):
Vehicles = 1 << 26
Customizer = 1 << 27
ConveyorMk6 = 1 << 28
BasicNeeds = 1 << 29
AlwaysUseful = 1 << 29
class ItemData(NamedTuple):

View File

@@ -1,4 +1,3 @@
import copy
from random import Random
from typing import ClassVar, Dict, Set, List, Tuple, Optional
from BaseClasses import Item, ItemClassification as C, MultiWorld
@@ -194,7 +193,7 @@ class Items:
"Small Inflated Pocket Dimension": ItemData(G.Upgrades, 1338188, C.useful, 11),
"Inflated Pocket Dimension": ItemData(G.Upgrades, 1338189, C.useful, 5),
"Expanded Toolbelt": ItemData(G.Upgrades, 1338190, C.useful, 5),
"Dimensional Depot upload from inventory": ItemData(G.Upgrades | G.BasicNeeds, 1338191, C.useful),
"Dimensional Depot upload from inventory": ItemData(G.Upgrades, 1338191, C.useful),
#1338191 - 1338199 Reserved for future equipment/ammo
@@ -493,17 +492,17 @@ class Items:
"Building: Fuel Generator": ItemData(G.Building, 1338618, C.progression),
"Building: Resource Well Pressurizer": ItemData(G.Building, 1338619, C.progression),
"Building: Equipment Workshop": ItemData(G.Building, 1338620, C.progression),
"Building: AWESOME Sink": ItemData(G.Building | G.BasicNeeds, 1338621, C.progression),
"Building: AWESOME Shop": ItemData(G.Building | G.BasicNeeds, 1338622, C.progression),
"Building: AWESOME Sink": ItemData(G.Building | G.AlwaysUseful, 1338621, C.progression),
"Building: AWESOME Shop": ItemData(G.Building | G.AlwaysUseful, 1338622, C.progression),
"Building: Painted Beams": ItemData(G.Beams, 1338623, C.filler),
"Building: Blueprint Designer": ItemData(G.Building, 1338624, C.filler, 0), # unlocked by default
"Building: Fluid Buffer": ItemData(G.Building, 1338625, C.filler),
"Building: Industrial Fluid Buffer": ItemData(G.Building, 1338626, C.filler),
"Building: Jump Pad": ItemData(G.Building, 1338627, C.filler),
"Building: Ladder": ItemData(G.Building, 1338628, C.filler),
"Building: MAM": ItemData(G.Building | G.BasicNeeds, 1338629, C.progression),
"Building: MAM": ItemData(G.Building | G.AlwaysUseful, 1338629, C.progression),
"Building: Personal Storage Box": ItemData(G.Building, 1338630, C.filler),
"Building: Power Storage": ItemData(G.Building | G.BasicNeeds, 1338631, C.progression),
"Building: Power Storage": ItemData(G.Building | G.AlwaysUseful, 1338631, C.progression),
"Building: U-Jelly Landing Pad": ItemData(G.Building, 1338632, C.useful),
"Building: Power Switch": ItemData(G.Building, 1338633, C.useful),
"Building: Priority Power Switch": ItemData(G.Building, 1338634, C.useful),
@@ -513,8 +512,8 @@ class Items:
"Building: Power Pole Mk.2": ItemData(G.Building, 1338638, C.useful),
"Building: Power Pole Mk.3": ItemData(G.Building, 1338639, C.useful),
"Building: Industrial Storage Container": ItemData(G.Building, 1338640, C.filler),
"Building: Conveyor Merger": ItemData(G.Building | G.BasicNeeds, 1338641, C.progression),
"Building: Conveyor Splitter": ItemData(G.Building | G.BasicNeeds, 1338642, C.progression),
"Building: Conveyor Merger": ItemData(G.Building | G.AlwaysUseful, 1338641, C.progression),
"Building: Conveyor Splitter": ItemData(G.Building | G.AlwaysUseful, 1338642, C.progression),
"Building: Conveyor Mk.1": ItemData(G.Building | G.ConveyorMk1, 1338643, C.progression), # unlocked by default
"Building: Conveyor Mk.2": ItemData(G.Building | G.ConveyorMk2, 1338644, C.progression),
"Building: Conveyor Mk.3": ItemData(G.Building | G.ConveyorMk3, 1338645, C.progression),
@@ -564,7 +563,7 @@ class Items:
#"Building: Beam Support": ItemData(G.Beams, 1338689, C.filler, 0),
#"Building: Beam Connector": ItemData(G.Beams, 1338690, C.filler, 0),
#"Building: Beam Connector Double": ItemData(G.Beams, 1338691, C.filler, 0),
"Building: Foundation": ItemData(G.Building | G.Foundations | G.BasicNeeds, 1338692, C.progression),
"Building: Foundation": ItemData(G.Building | G.Foundations | G.AlwaysUseful, 1338692, C.progression),
"Building: Half Foundation": ItemData(G.Foundations, 1338693, C.filler, 0),
"Building: Corner Ramp Pack": ItemData(G.Foundations, 1338694, C.filler, 0),
"Building: Inverted Ramp Pack": ItemData(G.Foundations, 1338695, C.filler, 0),
@@ -725,15 +724,14 @@ class Items:
if type == C.progression \
and instance and instance.critical_path.required_item_names \
and (data.category & (G.Recipe | G.Building)) and not (data.category & G.BasicNeeds) \
and (data.category & (G.Recipe | G.Building)) and not (data.category & G.AlwaysUseful) \
and name not in instance.critical_path.required_item_names:
type = C.filler
logging.info(f"Dropping... {name}")
type = C.useful
return Item(name, type, data.code, player)
def get_filler_item_name(self, random: Random, options: SatisfactoryOptions) -> str:
def get_filler_item_name(self, filler_items: Tuple[str, ...], random: Random, options: SatisfactoryOptions) -> str:
trap_chance: int = options.trap_chance.value
enabled_traps: List[str] = options.trap_selection_override.value
@@ -745,6 +743,9 @@ class Items:
def get_excluded_items(self, multiworld: MultiWorld, options: SatisfactoryOptions) -> Set[str]:
excluded_items: Set[str] = set()
excluded_items.update("Bundle: "+ part for part in self.critical_path.parts_to_exclude)
excluded_items.update(recipe for recipe in self.critical_path.recipes_to_exclude)
excluded_items.update("Building: "+ building for building in self.critical_path.buildings_to_exclude)
for item in multiworld.precollected_items[self.player]:
if item.name in self.item_data \
@@ -775,9 +776,12 @@ class Items:
filler_pool_size: int = number_of_locations - len(pool)
if (filler_pool_size < 0):
raise Exception(f"Location pool starved, trying to add {len(pool)} items to {number_of_locations} locations")
logging.warning(f"Itempool size: {len(pool)}, number of locations: {number_of_locations}, spare: {filler_pool_size}")
filtered_filler_items = tuple(item for item in self.filler_items if item not in excluded_from_pool)
for _ in range(filler_pool_size):
item = self.create_item(self, self.get_filler_item_name(random, options), self.player)
filler_item_name = self.get_filler_item_name(filtered_filler_items, random, options)
item = self.create_item(self, filler_item_name, self.player)
pool.append(item)
return pool

View File

@@ -9,6 +9,7 @@ from math import ceil, floor
class LocationData():
__slots__ = ("region", "name", "event_name", "code", "non_progression", "rule")
region: str
name: str
event_name: str
@@ -28,7 +29,7 @@ class LocationData():
class Part(LocationData):
@staticmethod
def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, items: Items,
def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str,
final_elevator_tier: int) -> List[LocationData]:
recipes_per_region: Dict[str, List[Recipe]] = {}
@@ -38,15 +39,15 @@ class Part(LocationData):
recipes_per_region.setdefault(recipe.building or "Overworld", []).append(recipe)
return [Part(state_logic, region, recipes_for_region, name, items)
return [Part(state_logic, region, recipes_for_region, name)
for region, recipes_for_region in recipes_per_region.items()]
def __init__(self, state_logic: StateLogic, region: str, recipes: Iterable[Recipe], name: str, items: Items):
super().__init__(region, part_event_prefix + name + region, EventId, part_event_prefix + name,
rule = self.can_produce_any_recipe_for_part(state_logic, recipes, name, items))
def __init__(self, state_logic: StateLogic, region: str, recipes: Iterable[Recipe], name: str):
super().__init__(region, part_event_prefix + name + " in " + region, EventId, part_event_prefix + name,
rule = self.can_produce_any_recipe_for_part(state_logic, recipes))
def can_produce_any_recipe_for_part(self, state_logic: StateLogic, recipes: Iterable[Recipe],
name: str, items: Items) -> Callable[[CollectionState], bool]:
def can_produce_any_recipe_for_part(self, state_logic: StateLogic, recipes: Iterable[Recipe]) \
-> Callable[[CollectionState], bool]:
def can_build_by_any_recipe(state: CollectionState) -> bool:
return any(state_logic.can_produce_specific_recipe_for_part(state, recipe) for recipe in recipes)
@@ -374,10 +375,10 @@ class Locations():
for index, parts in enumerate(self.game_logic.space_elevator_tiers)
if index < self.options.final_elevator_package)
location_table.extend(
part
part
for part_name, recipes in self.game_logic.recipes.items()
if part_name in self.critical_path.required_parts
for part in Part.get_parts(self.state_logic, recipes, part_name, self.items, final_elevator_tier))
for part in Part.get_parts(self.state_logic, recipes, part_name, final_elevator_tier))
location_table.extend(
EventBuilding(self.game_logic, self.state_logic, name, building)
for name, building in self.game_logic.buildings.items()

View File

@@ -3,6 +3,7 @@ from typing import Dict, List, Any, Tuple, ClassVar, cast
from enum import IntEnum
from Options import PerGameCommonOptions, DeathLink, AssembleOptions, Visibility
from Options import Range, Toggle, OptionSet, StartInventoryPool, NamedRange, Choice
from schema import Schema, And, Use
class Placement(IntEnum):
starting_inventory = 0
@@ -41,7 +42,6 @@ class ChoiceMap(Choice, metaclass=ChoiceMapMeta):
if index == self.value:
return self.choices[choice]
class ElevatorTier(NamedRange):
"""
Put these Shipments to Space Elevator packages in logic.
@@ -347,6 +347,8 @@ class GoalSelection(OptionSet):
# "FICSMAS Tree",
}
default = {"Space Elevator Tier"}
schema = Schema(And(set, len),
error = "yaml does not specify a goal, the Satisfactory option `goal_selection` is empty")
class GoalRequirement(Choice):
"""

View File

@@ -1,5 +1,5 @@
from typing import Dict, List, Set, TextIO, ClassVar, Tuple
from BaseClasses import Item, MultiWorld, ItemClassification, CollectionState
from typing import Dict, List, Set, TextIO, ClassVar
from BaseClasses import Item, ItemClassification, CollectionState
from .GameLogic import GameLogic
from .Items import Items
from .Locations import Locations, LocationData
@@ -21,7 +21,6 @@ class SatisfactoryWorld(World):
options_dataclass = SatisfactoryOptions
options: SatisfactoryOptions
topology_present = False
data_version = 0
web = SatisfactoryWebWorld()
origin_region_name = "Overworld"
@@ -34,20 +33,11 @@ class SatisfactoryWorld(World):
items: Items
critical_path: CriticalPathCalculator
def __init__(self, multiworld: "MultiWorld", player: int):
super().__init__(multiworld, player)
self.items = None
def generate_early(self) -> None:
self.state_logic = StateLogic(self.player, self.options)
self.critical_path = CriticalPathCalculator(self.game_logic, self.random, self.options)
self.items = Items(self.player, self.game_logic, self.random, self.options, self.critical_path)
if not self.options.goal_selection.value:
raise Exception("""Satisfactory: player {} needs to choose a goal, the option goal_selection is empty"""
.format(self.multiworld.player_name[self.player]))
if self.options.mam_logic_placement.value == Placement.starting_inventory:
self.push_precollected("Building: MAM")
if self.options.awesome_logic_placement.value == Placement.starting_inventory:
@@ -144,15 +134,15 @@ class SatisfactoryWorld(World):
}
def write_spoiler(self, spoiler_handle: TextIO):
def write_spoiler(self, spoiler_handle: TextIO) -> None:
pass
def get_filler_item_name(self) -> str:
return self.items.get_filler_item_name(self.random, self.options)
return self.items.get_filler_item_name(self.items.filler_items, self.random, self.options)
def setup_events(self):
def setup_events(self) -> None:
location: SatisfactoryLocation
for location in self.multiworld.get_locations(self.player):
if location.address == EventId: