Files
Archipelago/worlds/satisfactory/Options.py
2025-04-06 00:56:27 +02:00

453 lines
18 KiB
Python

from dataclasses import dataclass
from typing import ClassVar, Any, cast
from enum import IntEnum
from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions, Visibility
from Options import Range, NamedRange, Toggle, DefaultOnToggle, OptionSet, StartInventoryPool, Choice
from schema import Schema, And
class Placement(IntEnum):
starting_inventory = 0
early = 1
somewhere = 2
class PlacementLogicMeta(AssembleOptions):
def __new__(mcs, name: str, bases: tuple[type], attrs: dict[Any, Any]) -> "PlacementLogicMeta":
if "default" in attrs and isinstance(attrs["default"], Placement):
attrs["default"] = int(attrs["default"])
cls = super(PlacementLogicMeta, mcs).__new__(mcs, name, bases, attrs)
return cast(PlacementLogicMeta, cls)
class PlacementLogic(Choice, metaclass=PlacementLogicMeta):
option_unlocked_from_start = Placement.starting_inventory
option_early_game = Placement.early
option_somewhere = Placement.somewhere
class ChoiceMapMeta(AssembleOptions):
def __new__(mcs, name: str, bases: tuple[type], attrs: dict[Any, Any]) -> "ChoiceMapMeta":
if "choices" in attrs:
for index, choice in enumerate(attrs["choices"].keys()):
option_name = "option_" + choice.replace(' ', '_')
attrs[option_name] = 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]:
for index, choice in enumerate(self.choices.keys()):
if index == self.value:
return self.choices[choice]
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"
default = 2
range_start = 1
range_end = 5
special_range_names = {
"one package (tiers 1-2)": 1,
"two packages (tiers 1-4)": 2,
"three packages (tiers 1-6)": 3,
"four packages (tiers 1-8)": 4,
"five packages (tiers 1-9)": 5,
}
class ResourceSinkPointsTotal(NamedRange):
"""
Does nothing if *AWESOME Sink Points (total)* goal is not enabled.
Sink an amount of items totalling this amount of points to finish.
This setting is a *point count*, not a *coupon* count!
In the base game, it takes 347 coupons to unlock every non-repeatable purchase, or 1895 coupons to purchase every non-producible item.
Use the **TFIT - Ficsit Information Tool** mod or the Satisfactory wiki to find out how many points items are worth.
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"
default = 2166000
range_start = 2166000
range_end = 18436379500
special_range_names = {
"50 coupons (~2m points)": 2166000,
"100 coupons (~18m points)": 17804500,
"150 coupons (~61m points)": 60787500,
"200 coupons (~145m points)": 145053500,
"250 coupons (~284m points)": 284442000,
"300 coupons (~493m points)": 492825000,
"350 coupons (~784m points)": 784191000,
"400 coupons (~1,2b points)": 1172329500,
"450 coupons (~1,7b points)": 1671112500,
"500 coupons (~2b points)": 2294578500,
"550 coupons (~3b points)": 3056467000,
"600 coupons (~4b points)": 3970650000,
"650 coupons (~5b points)": 5051216000,
"700 coupons (~6b points)": 6311854500,
"750 coupons (~8b points)": 7766437500,
"800 coupons (~9b points)": 9429103500,
"850 coupons (~11b points)": 11313492000,
"900 coupons (~13b points)": 13433475000,
"950 coupons (~16b points)": 15803241000,
"1000 coupons (~18b points)": 18436379500
}
class ResourceSinkPointsPerMinute(NamedRange):
"""
Does nothing if *AWESOME Sink Points (per minute)* goal is not enabled.
Continuously Sink an amount of items to maintain a sink points per minute of this amount of points for 10 minutes to finish.
This setting is in *points per minute* on the orange track so no DNA Capsules!
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"
default = 50000
range_start = 1000
range_end = 10000000
special_range_names = {
"~500 screw/min": 1000,
"~100 reinforced iron plate/min": 12000,
"~100 stator/min": 24000,
"~100 modular frame/min": 40000,
"~100 smart plating/min": 50000,
"~20 crystal oscillator/min": 60000,
"~50 motor/min": 76000,
"~10 heavy modular frame/min": 100000,
"~10 radio control unit": 300000,
"~10 heavy modular frame/min": 625000,
"~10 supercomputer/min": 1000000,
"~10 pressure conversion cube/min": 2500000,
"~10 nuclear pasta/min": 5000000,
"~4 ballistic warp drive/min": 10000000,
}
class HardDriveProgressionLimit(Range):
"""
How many Hard Drives can contain progression items.
Hard Drives above this count cannot contain progression, but can still be Useful.
There are 118 total hard drives in the world and the current implementation supports up to 100 progression hard drives.
"""
display_name = "Hard Drive Progression Items"
default = 20
range_start = 0
range_end = 100
class FreeSampleEquipment(Range):
"""
How many free sample items of Equipment items should be given when they are unlocked.
(ex. Jetpack, Rifle)
"""
display_name = "Free Samples: Equipment"
default = 1
range_start = 0
range_end = 10
class FreeSampleBuildings(Range):
"""
How many copies of a Building's construction cost to give as a free sample when they are unlocked.
Space Elevator is always excluded.
(ex. Packager, Constructor, Smelter)
"""
display_name = "Free Samples: Buildings"
default = 5
range_start = 0
range_end = 10
class FreeSampleParts(NamedRange):
"""
How free sample items of general crafting components should be given when a recipe for them is unlocked.
Space Elevator Project Parts are always excluded.
Negative numbers mean that fraction of a full stack.
(ex. Iron Plate, Packaged Turbofuel, Reinforced Modular Frame)
"""
display_name = "Free Samples: Parts"
default = -2
range_start = -5
range_end = 500
special_range_names = {
"disabled": 0,
"half_stack": -2,
"one_stack": -1,
"1": 1,
"50": 50,
"100": 100,
"200": 200,
"500": 500,
}
class FreeSampleRadioactive(Toggle):
"""
Allow free samples to include radioactive parts.
Remember, they are delivered directly to your player inventory.
"""
display_name = "Free Samples: Radioactive"
class TrapChance(Range):
"""
Chance of traps in the item pool.
Traps will only replace filler items such as parts and resources.
- **0:** No traps will be present
- **100:** Every filler item will be a trap.
"""
display_name = "Trap Chance"
range_start = 0
range_end = 100
default = 10
_trap_types = {
"Trap: Doggo with Pulse Nobelisk",
"Trap: Doggo with Nuke Nobelisk",
"Trap: Doggo with Gas Nobelisk",
"Trap: Hog",
"Trap: Alpha Hog",
"Trap: Cliff Hog",
"Trap: Nuclear Hog",
"Trap: Johnny",
"Trap: Hatcher",
"Trap: Elite Hatcher",
"Trap: Small Stinger",
"Trap: Stinger",
"Trap: Gas Stinger",
"Trap: Spore Flower",
"Trap: Spitter",
"Trap: Alpha Spitter",
"Trap: Not the Bees",
"Trap: Nuclear Waste Drop",
"Trap: Plutonium Waste Drop",
"Trap: Can of Beans",
"Trap: Fart Cloud",
# Radioactive parts delivered via portal
"Bundle: Uranium",
"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"
}
class TrapSelectionPreset(ChoiceMap):
"""
Themed presets of trap types to enable.
If you want more control, use *Trap Override* or visit the Weighted Options page.
"""
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"],
"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"],
"All Arachnids All the Time": ["Trap: Small Stinger", "Trap: Stinger", "Trap: Gas Stinger"],
"Whole Hog": ["Trap: Hog", "Trap: Alpha Hog", "Trap: Cliff Hog", "Trap: Nuclear Hog", "Trap: Johnny"],
"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)
class TrapSelectionOverride(OptionSet):
"""
Precise list of traps that may be in the item pool to find.
If you select anything with this option it will be used instead of the *Trap Presets* setting.
"""
display_name = "Trap Override"
valid_keys = _trap_types
default = {}
class EnergyLink(DefaultOnToggle):
"""
Allow transferring energy to and from other worlds using the Power Storage building.
No energy is lost in the transfer on Satisfactory's side, but other worlds may have other settings.
"""
display_name = "EnergyLink"
class MamLogic(PlacementLogic):
"""
Where to place the MAM building in logic.
Earlier means it will be more likely you need to interact with it for progression purposes.
"""
display_name = "MAM Placement"
default = Placement.early
class AwesomeLogic(PlacementLogic):
"""
Where to place the AWESOME Shop and Sink buildings in logic.
Earlier means it will be more likely you need to interact with it for progression purposes.
"""
display_name = "AWESOME Stuff Placement"
default = Placement.early
class EnergyLinkLogic(PlacementLogic):
"""
Where to place the EnergyLink building (or Power Storage if EnergyLink is disabled) in logic.
Earlier means it will be more likely to get access to it early into your game.
"""
display_name = "EnergyLink Placement"
default = Placement.early
class SplitterLogic(PlacementLogic):
"""
Where to place the Conveyor Splitter and Merger buildings in logic.
Earlier means it will be more likely to get access to it early into your game.
"""
display_name = "Splitter and Merger Placement"
default = Placement.starting_inventory
_skip_tutorial_starting_items = [
# https://satisfactory.wiki.gg/wiki/Onboarding
"Bundle: Portable Miner",
"Bundle: Iron Plate",
"Bundle: Concrete",
"Bundle: Iron Rod",
"Bundle: Wire",
"Bundle: Reinforced Iron Plate",
"Bundle: Cable"
]
_default_starting_items = _skip_tutorial_starting_items + [
"Bundle: Iron Ingot",
"Bundle: Copper Ingot",
"Bundle: Concrete",
"Bundle: Solid Biofuel", # user's choice if they want to hold onto it for chainsaw or burn it right away
"Building: Blueprint Designer",
"Expanded Toolbelt",
"Inflated Pocket Dimension",
"Building: Personal Storage Box"
]
_default_plus_foundations_starting_items = _default_starting_items + [
"Building: Foundation",
"Building: Half Foundation"
]
_foundation_lover_starting_items = _default_plus_foundations_starting_items + [
"Bundle: Iron Plate", "Bundle: Iron Plate", "Bundle: Iron Plate",
"Bundle: Concrete", "Bundle: Concrete", "Bundle: Concrete"
]
class StartingInventoryPreset(ChoiceMap):
"""
What resources (and buildings) the player should start with in their inventory.
If you want more control, visit the Weighted Options page or edit the YAML directly.
- **Barebones**: Nothing but the default xeno zapper and buildings.
- **Skip Tutorial Inspired**: Inspired by the items you would have if you skipped the base game's tutorial.
- **Archipelago**: The starting items we think will lead to a fun experience.
- **Foundations**: 'Archipelago' option, but also guaranteeing that you have foundations unlocked at the start.
- **Foundation Lover**: You really like foundations.
"""
display_name = "Starting Goodies Presets"
choices = {
"Archipelago": _default_starting_items,
"Barebones": [], # Nothing but the xeno zapper
"Skip Tutorial Inspired": _skip_tutorial_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)
class ExplorationCollectableCount(Range):
"""
Does nothing if *Exploration Collectables* goal is not enabled.
Collect this amount of Mercer Spheres and Summer Sloops each to finish.
"""
display_name = "Goal: Exploration Collectables"
default = 20
range_start = 20
range_end = 100
class MilestoneCostMultiplier(Range):
"""
Multiplies the amount of resources needed to unlock a milestone by this factor
The value is in percentage:
50 = half cost
100 = normal milestone cost
200 = double the cost
"""
display_name = "Milestone cost multiplier %"
default = 100
range_start = 1
range_end = 500
class GoalSelection(OptionSet):
"""
What will be your goal(s)?
Configure them further with other options.
"""
display_name = "Select your Goals"
valid_keys = {
"Space Elevator Tier",
"AWESOME Sink Points (total)",
"AWESOME Sink Points (per minute)",
"Exploration Collectables",
# "Erect a 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):
"""
Of the goals selected in *Select your Goals*, how many must be reached to complete your slot?
"""
display_name = "Goal Requirements"
option_require_any_one_goal = 0
option_require_all_goals = 1
default = 0
class ExperimentalGeneration(Toggle):
"""
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.
"""
display_name = "Experimental Generation"
visibility = Visibility.none
@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
hard_drive_progression_limit: HardDriveProgressionLimit
free_sample_equipment: FreeSampleEquipment
free_sample_buildings: FreeSampleBuildings
free_sample_parts: FreeSampleParts
free_sample_radioactive: FreeSampleRadioactive
starting_inventory_preset: StartingInventoryPreset
mam_logic_placement: MamLogic
awesome_logic_placement: AwesomeLogic
energy_link_logic_placement: EnergyLinkLogic
splitter_placement: SplitterLogic
milestone_cost_multiplier: MilestoneCostMultiplier
trap_chance: TrapChance
trap_selection_preset: TrapSelectionPreset
trap_selection_override: TrapSelectionOverride
energy_link: EnergyLink
start_inventory_from_pool: StartInventoryPool
experimental_generation: ExperimentalGeneration