mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-22 15:45:04 -07:00
Added options groups and presets
This commit is contained in:
@@ -16,13 +16,14 @@ class CriticalPathCalculator:
|
||||
required_power_level: int
|
||||
|
||||
__potential_required_belt_speed: int
|
||||
__potential_required_pipes: bool
|
||||
__potential_required_radioactive: bool
|
||||
|
||||
parts_to_exclude: set[str]
|
||||
recipes_to_exclude: set[str]
|
||||
buildings_to_exclude: set[str]
|
||||
|
||||
implicitly_unlocked: set[str]
|
||||
handcraftable_parts: dict[str, list[Recipe]]
|
||||
|
||||
def __init__(self, logic: GameLogic, random: Random, options: SatisfactoryOptions):
|
||||
self.logic = logic
|
||||
self.random = random
|
||||
@@ -33,9 +34,24 @@ class CriticalPathCalculator:
|
||||
self.required_power_level: int = 1
|
||||
|
||||
self.__potential_required_belt_speed = 1
|
||||
self.__potential_required_pipes = False
|
||||
|
||||
selected_power_infrastructure: dict[int, Recipe] = {}
|
||||
|
||||
self.implicitly_unlocked: set[str] = {
|
||||
recipe.name
|
||||
for recipes_per_part in logic.recipes.values()
|
||||
for recipe in recipes_per_part if recipe.implicitly_unlocked
|
||||
}
|
||||
self.implicitly_unlocked.update({
|
||||
building.name
|
||||
for building in logic.buildings.values() if building.implicitly_unlocked
|
||||
})
|
||||
|
||||
self.handcraftable_parts: dict[str, list[Recipe]] = {}
|
||||
for part, recipes_per_part in logic.recipes.items():
|
||||
for recipe in recipes_per_part:
|
||||
if recipe.handcraftable:
|
||||
self.handcraftable_parts.setdefault(part, list()).append(recipe)
|
||||
|
||||
self.select_minimal_required_parts_for(self.logic.space_elevator_tiers[options.final_elevator_package-1].keys())
|
||||
|
||||
@@ -59,39 +75,39 @@ class CriticalPathCalculator:
|
||||
self.select_minimal_required_parts_for_building("Walls Orange")
|
||||
self.select_minimal_required_parts_for_building("Power Storage")
|
||||
self.select_minimal_required_parts_for_building("Miner Mk.2")
|
||||
self.select_minimal_required_parts_for_building("Pipes Mk.1")
|
||||
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.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}")
|
||||
if self.__potential_required_pipes:
|
||||
self.select_minimal_required_parts_for_building("Pipes Mk.1")
|
||||
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")
|
||||
|
||||
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
|
||||
self.select_minimal_required_parts_for(power_recipe.inputs)
|
||||
self.select_minimal_required_parts_for_building(power_recipe.building)
|
||||
|
||||
self.required_item_names = set(
|
||||
self.required_item_names = {
|
||||
recipe.name
|
||||
for part in self.required_parts
|
||||
for recipe in self.logic.recipes[part]
|
||||
if recipe.minimal_tier <= self.options.final_elevator_package
|
||||
)
|
||||
self.required_item_names.update("Building: "+ building for building in self.required_buildings)
|
||||
}
|
||||
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(
|
||||
self.recipes_to_exclude = {
|
||||
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:
|
||||
@@ -109,20 +125,20 @@ class CriticalPathCalculator:
|
||||
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(
|
||||
new_buildings_to_exclude = {
|
||||
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(
|
||||
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)
|
||||
|
||||
@@ -131,10 +147,12 @@ class CriticalPathCalculator:
|
||||
break
|
||||
excluded_count = new_length
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def select_minimal_required_parts_for(self, parts: Optional[Iterable[str]]) -> None:
|
||||
if parts is None:
|
||||
return
|
||||
@@ -154,11 +172,6 @@ class CriticalPathCalculator:
|
||||
|
||||
self.select_minimal_required_parts_for(recipe.inputs)
|
||||
|
||||
if recipe.needs_pipes:
|
||||
self.__potential_required_pipes = True
|
||||
if recipe.is_radio_active:
|
||||
self.__potential_required_radioactive = True
|
||||
|
||||
if recipe.building:
|
||||
self.select_minimal_required_parts_for(self.logic.buildings[recipe.building].inputs)
|
||||
self.required_buildings.add(recipe.building)
|
||||
|
||||
@@ -632,22 +632,6 @@ class GameLogic:
|
||||
#1.0
|
||||
}
|
||||
|
||||
handcraftable_recipes: dict[str, list[Recipe]] = {}
|
||||
for part, recipes_per_part in recipes.items():
|
||||
for recipe in recipes_per_part:
|
||||
if recipe.handcraftable:
|
||||
handcraftable_recipes.setdefault(part, list()).append(recipe)
|
||||
|
||||
implicitly_unlocked_recipes: dict[str, Recipe] = {
|
||||
recipe.name: recipe
|
||||
for recipes_per_part in recipes.values()
|
||||
for recipe in recipes_per_part if recipe.implicitly_unlocked
|
||||
}
|
||||
implicitly_unlocked_recipes.update({
|
||||
building.name: building
|
||||
for building in buildings.values() if building.implicitly_unlocked
|
||||
})
|
||||
|
||||
requirement_per_powerlevel: dict[PowerInfrastructureLevel, tuple[Recipe, ...]] = {
|
||||
# no need to polute the logic by including higher level recipes based on previus recipes
|
||||
PowerInfrastructureLevel.Basic: (
|
||||
|
||||
@@ -929,7 +929,7 @@ class Items:
|
||||
def build_item_pool(self, random: Random, multiworld: MultiWorld,
|
||||
options: SatisfactoryOptions, number_of_locations: int) -> list[Item]:
|
||||
excluded_from_pool: set[str] = self.get_excluded_items(multiworld, options) \
|
||||
.union(self.logic.implicitly_unlocked_recipes.keys())
|
||||
.union(self.critical_path.implicitly_unlocked)
|
||||
pool: list[Item] = []
|
||||
|
||||
for name, data in self.item_data.items():
|
||||
|
||||
@@ -63,7 +63,7 @@ class EventBuilding(LocationData):
|
||||
) -> Callable[[CollectionState], bool]:
|
||||
|
||||
def can_build(state: CollectionState) -> bool:
|
||||
return (building.implicitly_unlocked or state_logic.has_recipe(state, building)) \
|
||||
return state_logic.has_recipe(state, building) \
|
||||
and state_logic.can_power(state, building.power_requirement) \
|
||||
and state_logic.can_produce_all_allowing_handcrafting(state, game_logic, building.inputs)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar, Any, cast
|
||||
from enum import IntEnum
|
||||
from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions, Visibility
|
||||
from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions, Visibility, OptionGroup
|
||||
from Options import Range, NamedRange, Toggle, DefaultOnToggle, OptionSet, StartInventoryPool, Choice
|
||||
from schema import Schema, And
|
||||
|
||||
@@ -30,11 +30,13 @@ class ChoiceMapMeta(AssembleOptions):
|
||||
option_name = "option_" + choice.replace(' ', '_')
|
||||
attrs[option_name] = index
|
||||
|
||||
if "default" in attrs and attrs["default"] == choice:
|
||||
attrs["default"] = index
|
||||
|
||||
cls = super(ChoiceMapMeta, mcs).__new__(mcs, name, bases, attrs)
|
||||
return cast(ChoiceMapMeta, cls)
|
||||
|
||||
class ChoiceMap(Choice, metaclass=ChoiceMapMeta):
|
||||
# TODO `default` doesn't do anything, default is always the first `choices` value. if uncommented it messes up the template file generation (caps mismatch)
|
||||
choices: ClassVar[dict[str, list[str]]]
|
||||
|
||||
def get_selected_list(self) -> list[str]:
|
||||
@@ -47,7 +49,7 @@ class ElevatorTier(NamedRange):
|
||||
Put these Shipments to Space Elevator packages in logic.
|
||||
if your goal selection contains *Space Elevator Tier* then the goal will be to complete these shipments.
|
||||
"""
|
||||
display_name = "Goal: Space Elevator shipment"
|
||||
display_name = "Space Elevator shipments in logic"
|
||||
default = 2
|
||||
range_start = 1
|
||||
range_end = 5
|
||||
@@ -73,7 +75,7 @@ class ResourceSinkPointsTotal(NamedRange):
|
||||
If you have *Free Samples* enabled, consider setting this higher so that you can't reach the goal just by sinking your Free Samples.
|
||||
"""
|
||||
# Coupon data for above comment from https://satisfactory.wiki.gg/wiki/AWESOME_Shop
|
||||
display_name = "Goal: AWESOME Sink points total"
|
||||
display_name = "AWESOME Sink points total"
|
||||
default = 2166000
|
||||
range_start = 2166000
|
||||
range_end = 18436379500
|
||||
@@ -110,7 +112,7 @@ class ResourceSinkPointsPerMinute(NamedRange):
|
||||
Use the **TFIT - Ficsit Information Tool** mod or the Satisfactory wiki to find out how many points items are worth.
|
||||
"""
|
||||
# Coupon data for above comment from https://satisfactory.wiki.gg/wiki/AWESOME_Shop
|
||||
display_name = "Goal: AWESOME Sink points per minute"
|
||||
display_name = "AWESOME Sink points per minute"
|
||||
default = 50000
|
||||
range_start = 1000
|
||||
range_end = 10000000
|
||||
@@ -124,7 +126,7 @@ class ResourceSinkPointsPerMinute(NamedRange):
|
||||
"~50 motor/min": 76000,
|
||||
"~10 heavy modular frame/min": 100000,
|
||||
"~10 radio control unit": 300000,
|
||||
"~10 heavy modular frame/min": 625000,
|
||||
"~10 fused modular frame/min": 625000,
|
||||
"~10 supercomputer/min": 1000000,
|
||||
"~10 pressure conversion cube/min": 2500000,
|
||||
"~10 nuclear pasta/min": 5000000,
|
||||
@@ -253,8 +255,8 @@ class TrapSelectionPreset(ChoiceMap):
|
||||
"""
|
||||
display_name = "Trap Presets"
|
||||
choices = {
|
||||
"Normal": ["Trap: Doggo with Pulse Nobelisk", "Trap: Doggo with Gas Nobelisk", "Trap: Hog", "Trap: Alpha Hog", "Trap: Hatcher", "Trap: Elite Hatcher", "Trap: Small Stinger", "Trap: Stinger", "Trap: Spitter", "Trap: Alpha Spitter", "Trap: Not the Bees", "Trap: Nuclear Waste Drop", "Bundle: Uranium", "Bundle: Non-fissile Uranium", "Trap: Can of Beans", "Trap: Fart Cloud"],
|
||||
"Gentle": ["Trap: Doggo with Pulse Nobelisk", "Trap: Hog", "Trap: Spitter", "Trap: Can of Beans"],
|
||||
"Normal": ["Trap: Doggo with Pulse Nobelisk", "Trap: Doggo with Gas Nobelisk", "Trap: Hog", "Trap: Alpha Hog", "Trap: Hatcher", "Trap: Elite Hatcher", "Trap: Small Stinger", "Trap: Stinger", "Trap: Spitter", "Trap: Alpha Spitter", "Trap: Not the Bees", "Trap: Nuclear Waste Drop", "Bundle: Uranium", "Bundle: Non-fissile Uranium", "Trap: Can of Beans", "Trap: Fart Cloud"],
|
||||
"Harder": ["Trap: Doggo with Pulse Nobelisk", "Trap: Doggo with Nuke Nobelisk", "Trap: Doggo with Gas Nobelisk", "Trap: Alpha Hog", "Trap: Cliff Hog", "Trap: Spore Flower", "Trap: Hatcher", "Trap: Elite Hatcher", "Trap: Stinger", "Trap: Alpha Spitter", "Trap: Not the Bees", "Trap: Fart Cloud", "Trap: Nuclear Waste Drop", "Trap: Plutonium Waste Drop", "Bundle: Uranium", "Bundle: Uranium Fuel Rod", "Bundle: Uranium Waste", "Bundle: Plutonium Fuel Rod", "Bundle: Plutonium Pellet", "Bundle: Plutonium Waste", "Bundle: Non-fissile Uranium"],
|
||||
"All": list(_trap_types),
|
||||
"Ruthless": ["Trap: Doggo with Nuke Nobelisk", "Trap: Nuclear Hog", "Trap: Cliff Hog", "Trap: Elite Hatcher", "Trap: Spore Flower", "Trap: Gas Stinger", "Trap: Nuclear Waste Drop", "Trap: Plutonium Waste Drop", "Bundle: Uranium Fuel Rod", "Bundle: Uranium Waste", "Bundle: Plutonium Fuel Rod", "Bundle: Plutonium Pellet", "Bundle: Plutonium Waste", "Bundle: Non-fissile Uranium", "Bundle: Ficsonium", "Bundle: Ficsonium Fuel Rod"],
|
||||
@@ -263,7 +265,7 @@ class TrapSelectionPreset(ChoiceMap):
|
||||
"Nicholas Cage": ["Trap: Hatcher", "Trap: Elite Hatcher", "Trap: Not the Bees"],
|
||||
"Fallout": ["Trap: Doggo with Nuke Nobelisk", "Trap: Nuclear Hog", "Trap: Nuclear Waste Drop", "Trap: Plutonium Waste Drop", "Bundle: Uranium", "Bundle: Uranium Fuel Rod", "Bundle: Uranium Waste", "Bundle: Plutonium Fuel Rod", "Bundle: Plutonium Waste", "Bundle: Ficsonium", "Bundle: Ficsonium Fuel Rod"],
|
||||
}
|
||||
# default="Normal" # TODO `default` doesn't do anything, default is always the first `choices` value. if uncommented it messes up the template file generation (caps mismatch)
|
||||
default="Normal"
|
||||
|
||||
class TrapSelectionOverride(OptionSet):
|
||||
"""
|
||||
@@ -358,13 +360,13 @@ class StartingInventoryPreset(ChoiceMap):
|
||||
"""
|
||||
display_name = "Starting Goodies Presets"
|
||||
choices = {
|
||||
"Archipelago": _default_starting_items,
|
||||
"Barebones": [], # Nothing but the xeno zapper
|
||||
"Skip Tutorial Inspired": _skip_tutorial_starting_items,
|
||||
"Archipelago": _default_starting_items,
|
||||
"Foundations": _default_plus_foundations_starting_items,
|
||||
"Foundation Lover": _foundation_lover_starting_items
|
||||
}
|
||||
# default = "Archipelago" # TODO `default` doesn't do anything, default is always the first `choices` value. if uncommented it messes up the template file generation (caps mismatch)
|
||||
default = "Archipelago"
|
||||
|
||||
class ExplorationCollectableCount(Range):
|
||||
"""
|
||||
@@ -372,7 +374,7 @@ class ExplorationCollectableCount(Range):
|
||||
|
||||
Collect this amount of Mercer Spheres and Summer Sloops each to finish.
|
||||
"""
|
||||
display_name = "Goal: Exploration Collectables"
|
||||
display_name = "Exploration Collectables"
|
||||
default = 20
|
||||
range_start = 20
|
||||
range_end = 100
|
||||
@@ -417,22 +419,20 @@ class GoalRequirement(Choice):
|
||||
option_require_all_goals = 1
|
||||
default = 0
|
||||
|
||||
class ExperimentalGeneration(Toggle):
|
||||
class RandomizeTier0(DefaultOnToggle):
|
||||
"""
|
||||
Attempts to only mark recipes as progression if they are on your path to victory.
|
||||
WARNING: has a very high change of generation failure and should therefore only be used in single player games.
|
||||
Randomizer the way you obtain basic parts such as ingots and wire
|
||||
"""
|
||||
display_name = "Experimental Generation"
|
||||
visibility = Visibility.none
|
||||
display_name = "Randomize tier 0 recipes"
|
||||
|
||||
@dataclass
|
||||
class SatisfactoryOptions(PerGameCommonOptions, DeathLinkMixin):
|
||||
goal_selection: GoalSelection
|
||||
goal_requirement: GoalRequirement
|
||||
final_elevator_package: ElevatorTier
|
||||
final_awesome_sink_points_total: ResourceSinkPointsTotal
|
||||
final_awesome_sink_points_per_minute: ResourceSinkPointsPerMinute
|
||||
final_exploration_collectables_amount: ExplorationCollectableCount
|
||||
goal_awesome_sink_points_total: ResourceSinkPointsTotal
|
||||
goal_awesome_sink_points_per_minute: ResourceSinkPointsPerMinute
|
||||
goal_exploration_collectables_amount: ExplorationCollectableCount
|
||||
hard_drive_progression_limit: HardDriveProgressionLimit
|
||||
free_sample_equipment: FreeSampleEquipment
|
||||
free_sample_buildings: FreeSampleBuildings
|
||||
@@ -449,4 +449,80 @@ class SatisfactoryOptions(PerGameCommonOptions, DeathLinkMixin):
|
||||
trap_selection_override: TrapSelectionOverride
|
||||
energy_link: EnergyLink
|
||||
start_inventory_from_pool: StartInventoryPool
|
||||
experimental_generation: ExperimentalGeneration
|
||||
randomize_tier_0: RandomizeTier0
|
||||
|
||||
option_groups = [
|
||||
OptionGroup("Game Scope", [
|
||||
ElevatorTier,
|
||||
HardDriveProgressionLimit
|
||||
]),
|
||||
OptionGroup("Goal Selection", [
|
||||
GoalSelection,
|
||||
GoalRequirement,
|
||||
ResourceSinkPointsTotal,
|
||||
ResourceSinkPointsPerMinute,
|
||||
ExplorationCollectableCount
|
||||
]),
|
||||
OptionGroup("Placement logic", [
|
||||
StartingInventoryPreset,
|
||||
RandomizeTier0,
|
||||
MamLogic,
|
||||
AwesomeLogic,
|
||||
SplitterLogic,
|
||||
EnergyLinkLogic
|
||||
], start_collapsed = True),
|
||||
OptionGroup("Free Samples", [
|
||||
FreeSampleEquipment,
|
||||
FreeSampleBuildings,
|
||||
FreeSampleParts,
|
||||
FreeSampleRadioactive
|
||||
], start_collapsed = True),
|
||||
OptionGroup("Traps", [
|
||||
TrapChance,
|
||||
TrapSelectionPreset,
|
||||
TrapSelectionOverride
|
||||
], start_collapsed = True)
|
||||
]
|
||||
|
||||
option_presets: dict[str, dict[str, Any]] = {
|
||||
"Short": {
|
||||
"final_elevator_package": 1,
|
||||
"goal_selection": {"Space Elevator Tier", "AWESOME Sink Points (total)"},
|
||||
"goal_requirement": GoalRequirement.option_require_any_one_goal,
|
||||
"goal_awesome_sink_points_total": 17804500, # 100 coupons
|
||||
"hard_drive_progression_limit": 20,
|
||||
"starting_inventory_preset": 3, # "Foundations"
|
||||
"randomize_tier_0": False,
|
||||
"mam_logic_placement": int(Placement.starting_inventory),
|
||||
"awesome_logic_placement": int(Placement.starting_inventory),
|
||||
"energy_link_logic_placement": int(Placement.starting_inventory),
|
||||
"splitter_placement": int(Placement.starting_inventory),
|
||||
"milestone_cost_multiplier": 50,
|
||||
"trap_selection_preset": 1 # Gentle
|
||||
},
|
||||
"Long": {
|
||||
"final_elevator_package": 3,
|
||||
"goal_selection": {"Space Elevator Tier", "AWESOME Sink Points (per minute)"},
|
||||
"goal_requirement": GoalRequirement.option_require_all_goals,
|
||||
"goal_awesome_sink_points_per_minute": 100000, # ~10 heavy modular frame/min
|
||||
"hard_drive_progression_limit": 60,
|
||||
"mam_logic_placement": int(Placement.somewhere),
|
||||
"awesome_logic_placement": int(Placement.somewhere),
|
||||
"energy_link_logic_placement": int(Placement.somewhere),
|
||||
"splitter_placement": int(Placement.somewhere),
|
||||
"trap_selection_preset": 3 # Harder
|
||||
},
|
||||
"Extra long": {
|
||||
"final_elevator_package": 5,
|
||||
"goal_selection": {"Space Elevator Tier", "AWESOME Sink Points (per minute)"},
|
||||
"goal_requirement": GoalRequirement.option_require_all_goals,
|
||||
"goal_awesome_sink_points_per_minute": 625000, # ~10 fused modular frame/min
|
||||
"hard_drive_progression_limit": 100,
|
||||
"mam_logic_placement": int(Placement.somewhere),
|
||||
"awesome_logic_placement": int(Placement.somewhere),
|
||||
"energy_link_logic_placement": int(Placement.somewhere),
|
||||
"splitter_placement": int(Placement.somewhere),
|
||||
"milestone_cost_multiplier": 300,
|
||||
"trap_selection_preset": 4 # All
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ from collections.abc import Iterable
|
||||
from BaseClasses import CollectionState
|
||||
from .GameLogic import GameLogic, Recipe, PowerInfrastructureLevel
|
||||
from .Options import SatisfactoryOptions
|
||||
from .CriticalPathCalculator import CriticalPathCalculator
|
||||
|
||||
EventId: Optional[int] = None
|
||||
|
||||
@@ -12,14 +13,16 @@ building_event_prefix = "Can Build: "
|
||||
class StateLogic:
|
||||
player: int
|
||||
options: SatisfactoryOptions
|
||||
critical_path: CriticalPathCalculator
|
||||
initial_unlocked_items: set[str]
|
||||
|
||||
def __init__(self, player: int, options: SatisfactoryOptions):
|
||||
def __init__(self, player: int, options: SatisfactoryOptions, critical_path: CriticalPathCalculator):
|
||||
self.player = player
|
||||
self.options = options
|
||||
self.critical_path = critical_path
|
||||
|
||||
def has_recipe(self, state: CollectionState, recipe: Recipe):
|
||||
return recipe.implicitly_unlocked or state.has(recipe.name, self.player)
|
||||
return state.has(recipe.name, self.player) or recipe.name in self.critical_path.implicitly_unlocked
|
||||
|
||||
def can_build(self, state: CollectionState, building_name: Optional[str]) -> bool:
|
||||
return building_name is None or state.has(building_event_prefix + building_name, self.player)
|
||||
@@ -48,11 +51,10 @@ class StateLogic:
|
||||
def can_handcraft_part(part: str) -> bool:
|
||||
if self.can_produce(state, part):
|
||||
return True
|
||||
elif part not in logic.handcraftable_recipes:
|
||||
elif part not in self.critical_path.handcraftable_parts:
|
||||
return False
|
||||
|
||||
recipes: list[Recipe] = logic.handcraftable_recipes[part]
|
||||
|
||||
recipes: list[Recipe] = self.critical_path.handcraftable_parts[part]
|
||||
return any(
|
||||
self.has_recipe(state, recipe)
|
||||
and (not recipe.inputs or self.can_produce_all_allowing_handcrafting(state, logic, recipe.inputs))
|
||||
@@ -69,7 +71,7 @@ class StateLogic:
|
||||
if recipe.is_radio_active and not self.can_produce_all(state, ("Hazmat Suit", "Iodine-Infused Filter")):
|
||||
return False
|
||||
|
||||
if not self.options.experimental_generation and recipe.minimal_belt_speed and \
|
||||
if recipe.minimal_belt_speed and \
|
||||
not self.can_build_any(state, map(self.to_belt_name, range(recipe.minimal_belt_speed, 6))):
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from BaseClasses import Tutorial
|
||||
from .Options import option_groups, option_presets
|
||||
from ..AutoWorld import WebWorld
|
||||
|
||||
class SatisfactoryWebWorld(WebWorld):
|
||||
@@ -13,3 +14,6 @@ class SatisfactoryWebWorld(WebWorld):
|
||||
)
|
||||
tutorials = [setup]
|
||||
rich_text_options_doc = True
|
||||
|
||||
option_groups = option_groups
|
||||
options_presets = option_presets
|
||||
|
||||
@@ -34,8 +34,8 @@ class SatisfactoryWorld(World):
|
||||
critical_path: CriticalPathCalculator
|
||||
|
||||
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.state_logic = StateLogic(self.player, self.options, self.critical_path)
|
||||
self.items = Items(self.player, self.game_logic, self.random, self.options, self.critical_path)
|
||||
|
||||
if self.options.mam_logic_placement.value == Placement.starting_inventory:
|
||||
|
||||
Reference in New Issue
Block a user