diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py new file mode 100644 index 0000000000..b7b71392d4 --- /dev/null +++ b/worlds/satisfactory/GameLogic.py @@ -0,0 +1,980 @@ +from typing import Tuple, Optional, Dict, Set, List +from dataclasses import dataclass +from enum import IntEnum + +class PowerInfrastructureLevel(IntEnum): + Basic = 1 + Automated = 2 + Advanced = 3 + Complex = 4 + + def to_name(self): + return "Power level: " + self.name + +liquids: Set[str] = { + "Water", + "Liquid Biofuel", + "Crude Oil", + "Fuel", + "Heavy Oil Residue", + "Turbofuel", + "Alumina Solution", + "Sulfuric Acid", + "Nitrogen Gas", + "Nitric Acid", + "Dissolved Silica", + "Rocket Fuel", + "Ionized Fuel", + "Excited Photonic Matter", + "Dark Matter Residue" +} + +radio_actives: Set[str] = { + "Uranium", + "Encased Uranium Cell", + "Uranium Fuel Rod" + "Uranium Waste", + "Non-fissile Uranium", + "Plutonium Pellet", + "Encased Plutonium Cell", + "Plutonium Fuel Rod", + "Plutonium Waste", + "Ficsonium", + "Ficsonium Fuel Rod" +} + +class Recipe(): + """ + Relationship between components and what is required to produce them (input ingredients, production building, etc.) + Not all recipes are Satisfactory FGRecipes - for example, Water has a Recipe, but it's not an FGRecipe + """ + name: str + building: str + inputs: Tuple[str, ...] + minimal_belt_speed: int + handcraftable: bool + implicitly_unlocked: bool + """No explicit location/item is needed to unlock this recipe, you have access as soon as dependencies are met (ex. Water, Leaves, tutorial starting items)""" + additional_outputs: Tuple[str, ...] + + needs_pipes: bool + is_radio_active: bool + + def __init__(self, name: str, building: Optional[str] = None, inputs: Optional[Tuple[str, ...]] = None, + minimal_belt_speed: int = 1, handcraftable: bool = False, implicitly_unlocked: bool = False, + additional_outputs: Optional[Tuple[str, ...]] = None): + self.name = "Recipe: " + name + self.building = building + self.inputs = inputs + self.minimal_belt_speed = minimal_belt_speed + self.handcraftable = handcraftable + self.implicitly_unlocked = implicitly_unlocked + self.additional_outputs = additional_outputs + + all_parts: List[str] = [name] + if inputs: + all_parts += inputs + if additional_outputs: + all_parts += additional_outputs + + self.needs_pipes = not liquids.isdisjoint(all_parts) + self.is_radio_active = not radio_actives.isdisjoint(all_parts) + +class Building(Recipe): + power_requirement: Optional[PowerInfrastructureLevel] + can_produce: bool + + def __init__(self, name: str, inputs: Optional[Tuple[str, ...]] = None, + power_requirement: Optional[PowerInfrastructureLevel] = None, can_produce: bool = True, + implicitly_unlocked: bool = False): + super().__init__(name, None, inputs, handcraftable=True, implicitly_unlocked=implicitly_unlocked) + self.name = "Building: " + name + self.power_requirement = power_requirement + self.can_produce = can_produce + self.implicitly_unlocked = implicitly_unlocked + + +class MamNode(): + name: str + unlock_cost: Dict[str, int] + """All game items must be submitted to purchase this MamNode""" + depends_on: Tuple[str, ...] + """At least one of these prerequisite MamNodes must be unlocked to purchase this MamNode""" + + def __init__(self, name: str, unlock_cost: Dict[str, int], depends_on: Tuple[str, ...]): + self.name = name + self.unlock_cost = unlock_cost + self.depends_on = depends_on + + +class MamTree(): + access_items: Tuple[str, ...] + """At least one of these game items must enter the player inventory for this MamTree to be available""" + nodes: Tuple[MamNode, ...] + + def __init__(self, access_items: Tuple[str, ...], nodes: Tuple[MamNode, ...]): + self.access_items = access_items + self.nodes = nodes + + +@dataclass +class DropPodData: + x: int + y: int + z: int + item: Optional[str] + power: int + gassed: Optional[bool] = None + radioactive: Optional[bool] = None + + +class GameLogic: + recipes: Dict[str, Tuple[Recipe, ...]] = { + # This Dict should only contain items that are used somewhere in a logic chain + + # Exploration Items + "Leaves": ( + Recipe("Leaves", handcraftable=True, implicitly_unlocked=True), ), + "Wood": ( + Recipe("Wood", handcraftable=True, implicitly_unlocked=True), ), + "Hatcher Remains": ( + Recipe("Hatcher Remains", handcraftable=True, implicitly_unlocked=True), ), + "Hog Remains": ( + Recipe("Hog Remains", handcraftable=True, implicitly_unlocked=True), ), + "Plasma Spitter Remains": ( + Recipe("Plasma Spitter Remains", handcraftable=True, implicitly_unlocked=True), ), + "Stinger Remains": ( + Recipe("Stinger Remains", handcraftable=True, implicitly_unlocked=True), ), + "Mycelia": ( + Recipe("Mycelia", handcraftable=True, implicitly_unlocked=True), ), + "Beryl Nut": ( + Recipe("Beryl Nut", handcraftable=True, implicitly_unlocked=True), ), + "Paleberry": ( + Recipe("Paleberry", handcraftable=True, implicitly_unlocked=True), ), + "Bacon Agaric": ( + Recipe("Bacon Agaric", handcraftable=True, implicitly_unlocked=True), ), + "Blue Power Slug": ( + Recipe("Blue Power Slug", handcraftable=True, implicitly_unlocked=True), ), + "Yellow Power Slug": ( + Recipe("Yellow Power Slug", handcraftable=True, implicitly_unlocked=True), ), + "Purple Power Slug": ( + Recipe("Purple Power Slug", handcraftable=True, implicitly_unlocked=True), ), + "Hard Drive": ( + Recipe("Hard Drive", handcraftable=True, implicitly_unlocked=True), ), + "Mercer Sphere": ( + Recipe("Mercer Sphere", handcraftable=True, implicitly_unlocked=True), ), + "Somersloop": ( + Recipe("Somersloop", handcraftable=True, implicitly_unlocked=True), ), + + # Raw Resources + "Water": ( + Recipe("Water", "Water Extractor", implicitly_unlocked=True), ), + "Limestone": ( + Recipe("Limestone", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Raw Quartz": ( + Recipe("Raw Quartz", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Iron Ore": ( + Recipe("Iron Ore", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Copper Ore": ( + Recipe("Copper Ore", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Coal": ( + Recipe("Coal", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Sulfur": ( + Recipe("Sulfur", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Caterium Ore": ( + Recipe("Caterium Ore", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Crude Oil": ( + Recipe("Crude Oil", "Oil Extractor", implicitly_unlocked=True), ), + "Bauxite": ( + Recipe("Bauxite", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Nitrogen Gas": ( + Recipe("Nitrogen Gas", "Resource Well Pressurizer", implicitly_unlocked=True), ), + "Uranium": ( + Recipe("Uranium", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + + # Special Items + "Uranium Waste": ( + Recipe("Uranium Waste", "Nuclear Power Plant", ("Uranium Fuel Rod", "Water"), implicitly_unlocked=True), ), + #"Plutonium Waste": ( + # Recipe("Plutonium Waste", "Nuclear Power Plant", ("Plutonium Fuel Rod", "Water"), implicitly_unlocked=True), ), + + # Recipes + "Reinforced Iron Plate": ( + Recipe("Reinforced Iron Plate", "Assembler", ("Iron Plate", "Screw"), handcraftable=True, implicitly_unlocked=True), + Recipe("Adhered Iron Plate", "Assembler", ("Iron Plate", "Rubber")), + Recipe("Bolted Iron Plate", "Assembler", ("Iron Plate", "Screw"), minimal_belt_speed=3), + Recipe("Stitched Iron Plate", "Assembler", ("Iron Plate", "Wire"))), + "Rotor": ( + Recipe("Rotor", "Assembler", ("Iron Rod", "Screw"), minimal_belt_speed=2, handcraftable=True), + Recipe("Copper Rotor", "Assembler", ("Copper Sheet", "Screw"), minimal_belt_speed=3), + Recipe("Steel Rotor", "Assembler", ("Steel Pipe", "Wire"))), + "Stator": ( + Recipe("Stator", "Assembler", ("Steel Pipe", "Wire"), handcraftable=True), + Recipe("Quickwire Stator", "Assembler", ("Steel Pipe", "Quickwire"))), + "Plastic": ( + Recipe("Plastic", "Refinery", ("Crude Oil", ), additional_outputs=("Heavy Oil Residue", )), + Recipe("Residual Plastic", "Refinery", ("Polymer Resin", "Water")), + Recipe("Recycled Plastic", "Refinery", ("Rubber", "Fuel"))), + "Rubber": ( + Recipe("Rubber", "Refinery", ("Crude Oil", ), additional_outputs=("Heavy Oil Residue", )), + Recipe("Residual Rubber", "Refinery", ("Polymer Resin", "Water")), + Recipe("Recycled Rubber", "Refinery", ("Plastic", "Fuel"))), + "Iron Plate": ( + Recipe("Iron Plate", "Constructor", ("Iron Ingot", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Coated Iron Plate", "Assembler", ("Iron Ingot", "Plastic"), minimal_belt_speed=2), + Recipe("Steel Cast Plate", "Foundry", ("Iron Ingot", "Steel Ingot"))), + "Iron Rod": ( + Recipe("Iron Rod", "Constructor", ("Iron Ingot", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Steel Rod", "Constructor", ("Steel Ingot", )), + Recipe("Aluminum Rod", "Constructor", ("Aluminum Ingot", ))), + "Screw": ( + Recipe("Screw", "Constructor", ("Iron Rod", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Cast Screw", "Constructor", ("Iron Ingot", )), + Recipe("Steel Screw", "Constructor", ("Steel Beam", ), minimal_belt_speed=3)), + "Wire": ( + Recipe("Wire", "Constructor", ("Copper Ingot", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Fused Wire", "Assembler", ("Copper Ingot", "Caterium Ingot"), minimal_belt_speed=2), + Recipe("Iron Wire", "Constructor", ("Iron Ingot", )), + Recipe("Caterium Wire", "Constructor", ("Caterium Ingot", ), minimal_belt_speed=2)), + "Cable": ( + Recipe("Cable", "Constructor", ("Wire", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Coated Cable", "Refinery", ("Wire", "Heavy Oil Residue"), minimal_belt_speed=2), + Recipe("Insulated Cable", "Assembler", ("Wire", "Rubber"), minimal_belt_speed=2), + Recipe("Quickwire Cable", "Assembler", ("Quickwire", "Rubber"))), + "Quickwire": ( + Recipe("Quickwire", "Constructor", ("Caterium Ingot", ), handcraftable=True), + Recipe("Fused Quickwire", "Assembler", ("Caterium Ingot", "Copper Ingot"), minimal_belt_speed=2)), + "Copper Sheet": ( + Recipe("Copper Sheet", "Constructor", ("Copper Ingot", ), handcraftable=True), + Recipe("Steamed Copper Sheet", "Refinery", ("Copper Ingot", "Water"))), + "Steel Pipe": ( + Recipe("Steel Pipe", "Constructor", ("Steel Ingot", ), handcraftable=True), + Recipe("Iron Pipe", "Constructor", ("Iron Ingot", ), minimal_belt_speed=2), + Recipe("Molded Steel Pipe", "Foundry", ("Steel Ingot", "Concrete"))), + "Steel Beam": ( + Recipe("Steel Beam", "Constructor", ("Steel Ingot", ), handcraftable=True), + Recipe("Aluminum Beam", "Constructor", ("Aluminum Ingot", )), + Recipe("Molded Beam", "Foundry", ("Steel Ingot", "Concrete"), minimal_belt_speed=2)), + "Heavy Oil Residue": ( + Recipe("Heavy Oil Residue", "Refinery", ("Crude Oil", ), additional_outputs=("Polymer Resin", )), + Recipe("Plastic", "Refinery", ("Crude Oil", ), additional_outputs=("Plastic", )), + Recipe("Rubber", "Refinery", ("Crude Oil", ), additional_outputs=("Rubber", )), + Recipe("Polymer Resin", "Refinery", ("Crude Oil", ), additional_outputs=("Polymer Resin", ), minimal_belt_speed=3)), + "Polymer Resin": ( + Recipe("Polymer Resin", "Refinery", ("Crude Oil", ), additional_outputs=("Heavy Oil Residue", ), minimal_belt_speed=2), + Recipe("Fuel", "Refinery", ("Crude Oil", ), additional_outputs=("Fuel", )), + Recipe("Heavy Oil Residue", "Refinery", ("Crude Oil", ), additional_outputs=("Heavy Oil Residue", ), minimal_belt_speed=3)), + "Fuel": ( + Recipe("Fuel", "Refinery", ("Crude Oil", ), additional_outputs=("Polymer Resin", )), + Recipe("Diluted Fuel", "Blender", ("Heavy Oil Residue", "Water")), + Recipe("Residual Fuel", "Refinery", ("Heavy Oil Residue", ))), + "Concrete": ( + Recipe("Concrete", "Constructor", ("Limestone", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Fine Concrete", "Assembler", ("Limestone", "Silica")), + Recipe("Rubber Concrete", "Assembler", ("Limestone", "Rubber")), + 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("Cheap Silica", "Assembler", ("Raw Quartz", "Limestone")), + Recipe("Distilled Silica", "Blender", ("Dissolved Silica", "Limestone", "Water"), additional_outputs=("Water", ))), + "Dissolved Silica": ( + Recipe("Quartz Purification", "Refinery", ("Raw Quartz", "Nitric Acid"), additional_outputs=("Quartz Crystal", ), minimal_belt_speed=2), ), + "Quartz Crystal": ( + Recipe("Quartz Crystal", "Constructor", ("Raw Quartz", ), handcraftable=True), + Recipe("Pure Quartz Crystal", "Refinery", ("Raw Quartz", "Water"), minimal_belt_speed=2), + Recipe("Fused Quartz Crystal", "Foundry", ("Raw Quartz", "Coal"), minimal_belt_speed=2), + Recipe("Quartz Purification", "Refinery", ("Raw Quartz", "Nitric Acid"), additional_outputs=("Dissolved Silica", ), minimal_belt_speed=2)), + "Iron Ingot": ( + Recipe("Iron Ingot", "Smelter", ("Iron Ore", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Pure Iron Ingot", "Refinery", ("Iron Ore", "Water"), minimal_belt_speed=2), + Recipe("Iron Alloy Ingot", "Foundry", ("Iron Ore", "Copper Ore")), + Recipe("Basic Iron Ingot", "Foundry", ("Iron Ore", "Limestone")), + Recipe("Leached Iron ingot", "Refinery", ("Iron Ore", "Sulfuric Acid"), minimal_belt_speed=2)), + "Steel Ingot": ( + Recipe("Steel Ingot", "Foundry", ("Iron Ore", "Coal"), handcraftable=True), + Recipe("Coke Steel Ingot", "Foundry", ("Iron Ore", "Petroleum Coke"), minimal_belt_speed=2), + Recipe("Compacted Steel Ingot", "Foundry", ("Iron Ore", "Compacted Coal")), + Recipe("Solid Steel Ingot", "Foundry", ("Iron Ingot", "Coal"))), + "Copper Ingot": ( + Recipe("Copper Ingot", "Smelter", ("Copper Ore", ), handcraftable=True, implicitly_unlocked=True), + Recipe("Copper Alloy Ingot", "Foundry", ("Copper Ore", "Iron Ore"), minimal_belt_speed=2), + Recipe("Pure Copper Ingot", "Refinery", ("Copper Ore", "Water")), + Recipe("Leached Copper Ingot", "Refinery", ("Copper Ore", "Sulfuric Acid"), minimal_belt_speed=2), + Recipe("Tempered Copper Ingot", "Foundry", ("Copper Ore", "Petroleum Coke"))), + "Caterium Ingot": ( + Recipe("Caterium Ingot", "Smelter", ("Caterium Ore", ), handcraftable=True), + Recipe("Pure Caterium Ingot", "Refinery", ("Caterium Ore", "Water")), + Recipe("Leached Caterium Ingot", "Refinery", ("Caterium Ore", "Sulfuric Acid")), + Recipe("Tempered Caterium Ingot", "Foundry", ("Caterium Ore", "Petroleum Coke"))), + "Petroleum Coke": ( + Recipe("Petroleum Coke", "Refinery", ("Heavy Oil Residue", ), minimal_belt_speed=2), ), + "Compacted Coal": ( + Recipe("Compacted Coal", "Assembler", ("Coal", "Sulfur")), ), + "Motor": ( + Recipe("Motor", "Assembler", ("Rotor", "Stator"), handcraftable=True), + Recipe("Rigor Motor", "Manufacturer", ("Rotor", "Stator", "Crystal Oscillator")), + Recipe("Electric Motor", "Assembler", ("Electromagnetic Control Rod", "Rotor"))), + "Modular Frame": ( + Recipe("Modular Frame", "Assembler", ("Reinforced Iron Plate", "Iron Rod"), handcraftable=True), + Recipe("Bolted Frame", "Assembler", ("Reinforced Iron Plate", "Screw"), minimal_belt_speed=3), + Recipe("Steeled Frame", "Assembler", ("Reinforced Iron Plate", "Steel Pipe"))), + "Heavy Modular Frame": ( + Recipe("Heavy Modular Frame", "Manufacturer", ("Modular Frame", "Steel Pipe", "Encased Industrial Beam", "Screw"), minimal_belt_speed=3, handcraftable=True), + Recipe("Heavy Flexible Frame", "Manufacturer", ("Modular Frame", "Encased Industrial Beam", "Rubber", "Screw"), minimal_belt_speed=4), + Recipe("Heavy Encased Frame", "Manufacturer", ("Modular Frame", "Encased Industrial Beam", "Steel Pipe", "Concrete"))), + "Encased Industrial Beam": ( + Recipe("Encased Industrial Beam", "Assembler", ("Steel Beam", "Concrete"), handcraftable=True), + Recipe("Encased Industrial Pipe", "Assembler", ("Steel Pipe", "Concrete"))), + "Computer": ( + Recipe("Computer", "Manufacturer", ("Circuit Board", "Cable", "Plastic"), minimal_belt_speed=3, handcraftable=True), + Recipe("Crystal Computer", "Assembler", ("Circuit Board", "Crystal Oscillator")), + Recipe("Caterium Computer", "Manufacturer", ("Circuit Board", "Quickwire", "Rubber"), minimal_belt_speed=2)), + "Circuit Board": ( + Recipe("Circuit Board", "Assembler", ("Copper Sheet", "Plastic"), handcraftable=True), + Recipe("Electrode Circuit Board", "Assembler", ("Rubber", "Petroleum Coke")), + Recipe("Silicon Circuit Board", "Assembler", ("Copper Sheet", "Silica")), + Recipe("Caterium Circuit Board", "Assembler", ("Plastic", "Quickwire"))), + "Crystal Oscillator": ( + Recipe("Crystal Oscillator", "Manufacturer", ("Quartz Crystal", "Cable", "Reinforced Iron Plate"), handcraftable=True), + Recipe("Insulated Crystal Oscillator", "Manufacturer", ("Quartz Crystal", "Rubber", "AI Limiter"))), + "AI Limiter": ( + Recipe("AI Limiter", "Assembler", ("Copper Sheet", "Quickwire"), minimal_belt_speed=2, handcraftable=True), + Recipe("Plastic AI Limiter", "Assembler", ("Quickwire", "Plastic"), minimal_belt_speed=2)), + "Electromagnetic Control Rod": ( + Recipe("Electromagnetic Control Rod", "Assembler", ("Stator", "AI Limiter"), handcraftable=True), + Recipe("Electromagnetic Connection Rod", "Assembler", ("Stator", "High-Speed Connector"))), + "High-Speed Connector": ( + Recipe("High-Speed Connector", "Manufacturer", ("Quickwire", "Cable", "Circuit Board"), minimal_belt_speed=3, handcraftable=True), + Recipe("Silicon High-Speed Connector", "Manufacturer", ("Quickwire", "Silica", "Circuit Board"), minimal_belt_speed=2)), + "Smart Plating": ( + 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"))), + "Automated Wiring": ( + Recipe("Automated Wiring", "Assembler", ("Stator", "Cable")), + Recipe("Automated Speed Wiring", "Manufacturer", ("Stator", "Wire", "High-Speed Connector"), minimal_belt_speed=2)), + "Modular Engine": ( + Recipe("Modular Engine", "Manufacturer", ("Motor", "Rubber", "Smart Plating")), ), + "Adaptive Control Unit": ( + Recipe("Adaptive Control Unit", "Manufacturer", ("Automated Wiring", "Circuit Board", "Heavy Modular Frame", "Computer")), ), + "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")), ), + "Alumina Solution": ( + Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Silica", ), minimal_belt_speed=2), + Recipe("Sloppy Alumina", "Refinery", ("Bauxite", "Water"), minimal_belt_speed=3)), + "Aluminum Scrap": ( + Recipe("Aluminum Scrap", "Refinery", ("Alumina Solution", "Coal"), additional_outputs=("Water", ), minimal_belt_speed=4), + Recipe("Electrode Aluminum Scrap", "Refinery", ("Alumina Solution", "Petroleum Coke"), additional_outputs=("Water", ), minimal_belt_speed=4), + Recipe("Instant Scrap", "Blender", ("Bauxite", "Coal", "Sulfuric Acid", "Water"), additional_outputs=("Water", ), minimal_belt_speed=3)), + "Aluminum Ingot": ( + Recipe("Aluminum Ingot", "Foundry", ("Aluminum Scrap", "Silica"), minimal_belt_speed=2, handcraftable=True), + Recipe("Pure Aluminum Ingot", "Smelter", ("Aluminum Scrap", ))), + "Alclad Aluminum Sheet": ( + Recipe("Alclad Aluminum Sheet", "Assembler", ("Aluminum Ingot", "Copper Ingot"), handcraftable=True), ), + "Aluminum Casing": ( + Recipe("Aluminum Casing", "Constructor", ("Alclad Aluminum Sheet", ), handcraftable=True), + Recipe("Alclad Casing", "Assembler", ("Aluminum Ingot", "Copper Ingot"))), + "Heat Sink": ( + Recipe("Heat Sink", "Assembler", ("Alclad Aluminum Sheet", "Silica"), minimal_belt_speed=2, handcraftable=True), + Recipe("Heat Exchanger", "Assembler", ("Aluminum Casing", "Rubber"), minimal_belt_speed=3)), + "Nitric Acid": ( + Recipe("Nitric Acid", "Blender", ("Nitrogen Gas", "Water", "Iron Plate")), ), + "Fused Modular Frame": ( + Recipe("Fused Modular Frame", "Blender", ("Heavy Modular Frame", "Aluminum Casing", "Nitrogen Gas"), minimal_belt_speed=2), + Recipe("Heat-Fused Frame", "Blender", ("Heavy Modular Frame", "Aluminum Ingot", "Nitric Acid", "Fuel"), minimal_belt_speed=3)), + "Radio Control Unit": ( + Recipe("Radio Control Unit", "Manufacturer", ("Aluminum Casing", "Crystal Oscillator", "Computer"), handcraftable=True), + Recipe("Radio Connection Unit", "Manufacturer", ("Heat Sink", "High-Speed Connector", "Quartz Crystal")), + Recipe("Radio Control System", "Manufacturer", ("Crystal Oscillator", "Circuit Board", "Aluminum Casing", "Rubber"), minimal_belt_speed=2)), + "Pressure Conversion Cube": ( + Recipe("Pressure Conversion Cube", "Assembler", ("Fused Modular Frame", "Radio Control Unit"), handcraftable=True), ), + "Cooling System": ( + Recipe("Cooling System", "Blender", ("Heat Sink", "Rubber", "Water", "Nitrogen Gas")), + Recipe("Cooling Device", "Blender", ("Heat Sink", "Motor", "Nitrogen Gas"))), + "Turbo Motor": ( + Recipe("Turbo Motor", "Manufacturer", ("Cooling System", "Radio Control Unit", "Motor", "Rubber"), handcraftable=True), + Recipe("Turbo Electric Motor", "Manufacturer", ("Motor", "Radio Control Unit", "Electromagnetic Control Rod", "Rotor")), + Recipe("Turbo Pressure Motor", "Manufacturer", ("Motor", "Pressure Conversion Cube", "Packaged Nitrogen Gas", "Stator"))), + "Battery": ( + Recipe("Battery", "Blender", ("Sulfuric Acid", "Alumina Solution", "Aluminum Casing"), additional_outputs=("Water", )), + Recipe("Classic Battery", "Manufacturer", ("Sulfur", "Alclad Aluminum Sheet", "Plastic", "Wire"), minimal_belt_speed=2)), + "Supercomputer": ( + Recipe("Supercomputer", "Manufacturer", ("Computer", "AI Limiter", "High-Speed Connector", "Plastic"), handcraftable=True), + Recipe("OC Supercomputer", "Assembler", ("Radio Control Unit", "Cooling System")), + Recipe("Super-State Computer", "Manufacturer", ("Computer", "Electromagnetic Control Rod", "Battery", "Wire"))), + "Sulfuric Acid": ( + Recipe("Sulfuric Acid", "Refinery", ("Sulfur", "Water")), ), + "Encased Uranium Cell": ( + Recipe("Encased Uranium Cell", "Blender", ("Uranium", "Concrete", "Sulfuric Acid"), additional_outputs=("Sulfuric Acid", )), + Recipe("Infused Uranium Cell", "Manufacturer", ("Uranium", "Silica", "Sulfur", "Quickwire"), minimal_belt_speed=2)), + "Uranium Fuel Rod": ( + Recipe("Uranium Fuel Rod", "Manufacturer", ("Encased Uranium Cell", "Encased Industrial Beam", "Electromagnetic Control Rod")), + Recipe("Uranium Fuel Unit", "Manufacturer", ("Encased Uranium Cell", "Electromagnetic Control Rod", "Crystal Oscillator", "Rotor"))), + #"Non-fissile Uranium": ( + # Recipe("Non-fissile Uranium", "Blender", ("Uranium Waste", "Silica", "Nitric Acid", "Sulfuric Acid"), additional_outputs=("Water", )), + # Recipe("Fertile Uranium", "Blender", ("Uranium", "Uranium Waste", "Nitric Acid", "Sulfuric Acid"), additional_outputs=("Water", ), minimal_belt_speed=2)), + #"Plutonium Pellet": ( + # Recipe("Plutonium Pellet", "Particle Accelerator", ("Non-fissile Uranium", "Uranium Waste"), minimal_belt_speed=2), ), + #"Encased Plutonium Cell": ( + # Recipe("Encased Plutonium Cell", "Assembler", ("Plutonium Pellet", "Concrete")), + # Recipe("Instant Plutonium Cell", "Particle Accelerator", ("Non-fissile Uranium", "Aluminum Casing"), minimal_belt_speed=2)), + #"Plutonium Fuel Rod": ( + # Recipe("Plutonium Fuel Rod", "Manufacturer", ("Encased Plutonium Cell", "Steel Beam", "Electromagnetic Control Rod", "Heat Sink")), + # Recipe("Plutonium Fuel Unit", "Assembler", ("Encased Plutonium Cell", "Pressure Conversion Cube"))), + "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), ), + "Hazmat Suit": ( + Recipe("Hazmat Suit", "Equipment Workshop", ("Rubber", "Plastic", "Fabric", "Alclad Aluminum Sheet"), handcraftable=True, minimal_belt_speed=0), ), + "Assembly Director System": ( + Recipe("Assembly Director System", "Assembler", ("Adaptive Control Unit", "Supercomputer")), ), + "Magnetic Field Generator": ( + Recipe("Magnetic Field Generator", "Assembler", ("Versatile Framework", "Electromagnetic Control Rod")), ), + "Copper Powder": ( + Recipe("Copper Powder", "Constructor", ("Copper Ingot", ), handcraftable=True), ), + "Nuclear Pasta": ( + Recipe("Nuclear Pasta", "Particle Accelerator", ("Copper Powder", "Pressure Conversion Cube")), ), + "Thermal Propulsion Rocket": ( + Recipe("Thermal Propulsion Rocket", "Manufacturer", ("Modular Engine", "Turbo Motor", "Cooling System", "Fused Modular Frame")), ), + "Alien Protein": ( + Recipe("Hatcher Protein", "Constructor", ("Hatcher Remains", ), handcraftable=True), + Recipe("Hog Protein", "Constructor", ("Hog Remains", ), handcraftable=True), + Recipe("Spitter Protein", "Constructor", ("Plasma Spitter Remains", ), handcraftable=True), + Recipe("Stinger Protein", "Constructor", ("Stinger Remains", ), handcraftable=True)), + "Biomass": ( + Recipe("Biomass (Leaves)", "Constructor", ("Leaves", ), minimal_belt_speed=2, handcraftable=True, implicitly_unlocked=True), + Recipe("Biomass (Wood)", "Constructor", ("Wood", ), minimal_belt_speed=4, handcraftable=True, implicitly_unlocked=True), + Recipe("Biomass (Mycelia)", "Constructor", ("Mycelia", ), minimal_belt_speed=3, handcraftable=True), + Recipe("Biomass (Alien Protein)", "Constructor", ("Alien Protein", ), minimal_belt_speed=5, handcraftable=True)), + "Fabric": ( + Recipe("Fabric", "Assembler", ("Biomass", "Mycelia"), handcraftable=True, minimal_belt_speed=2), + Recipe("Polyester Fabric", "Refinery", ("Polymer Resin", "Water"))), + "Solid Biofuel": ( + Recipe("Solid Biofuel", "Constructor", ("Biomass", ), minimal_belt_speed=2, handcraftable=True), ), + "Liquid Biofuel": ( + Recipe("Liquid Biofuel", "Refinery", ("Solid Biofuel", "Water"), minimal_belt_speed=2), ), + "Empty Canister": ( + Recipe("Empty Canister", "Constructor", ("Plastic", ), handcraftable=True), + Recipe("Coated Iron Canister", "Assembler", ("Iron Plate", "Copper Sheet")), + Recipe("Steel Canister", "Constructor", ("Steel Ingot", ))), + "Empty Fluid Tank": ( + Recipe("Empty Fluid Tank", "Constructor", ("Aluminum Ingot", ), handcraftable=True), ), + "Packaged Alumina Solution": ( + Recipe("Packaged Alumina Solution", "Packager", ("Alumina Solution", "Empty Canister"), minimal_belt_speed=2), ), + "Packaged Fuel": ( + Recipe("Packaged Fuel", "Packager", ("Fuel", "Empty Canister")), + Recipe("Diluted Packaged Fuel", "Refinery", ("Heavy Oil Residue", "Packaged Water"))), + "Packaged Heavy Oil Residue": ( + Recipe("Packaged Heavy Oil Residue", "Packager", ("Heavy Oil Residue", "Empty Canister")), ), + "Packaged Liquid Biofuel": ( + Recipe("Packaged Liquid Biofuel", "Packager", ("Liquid Biofuel", "Empty Canister")), ), + "Packaged Nitric Acid": ( + Recipe("Packaged Nitric Acid", "Packager", ("Nitric Acid", "Empty Fluid Tank")), ), + "Packaged Nitrogen Gas": ( + Recipe("Packaged Nitrogen Gas", "Packager", ("Nitrogen Gas", "Empty Fluid Tank")), ), + "Packaged Oil": ( + Recipe("Packaged Oil", "Packager", ("Crude Oil", "Empty Fluid Tank")), ), + "Packaged Sulfuric Acid": ( + Recipe("Packaged Sulfuric Acid", "Packager", ("Sulfuric Acid", "Empty Fluid Tank")), ), + "Packaged Turbofuel": ( + Recipe("Packaged Turbofuel", "Packager", ("Turbofuel", "Empty Fluid Tank")), ), + "Packaged Water": ( + Recipe("Packaged Water", "Packager", ("Water", "Empty Fluid Tank")), ), + "Turbofuel": ( + Recipe("Turbofuel", "Refinery", ("Fuel", "Compacted Coal")), + Recipe("Turbo Heavy Fuel", "Refinery", ("Heavy Oil Residue", "Compacted Coal")), + Recipe("Turbo Blend Fuel", "Blender", ("Fuel", "Heavy Oil Residue", "Sulfur", "Petroleum Coke"))), + "Gas Mask": ( + Recipe("Gas Mask", "Equipment Workshop", ("Rubber", "Plastic", "Fabric"), handcraftable=True, minimal_belt_speed=0), ), + "Alien DNA Capsule": ( + Recipe("Alien DNA Capsule", "Constructor", ("Alien Protein", ), handcraftable=True), ), + "Black Powder": ( + Recipe("Black Powder", "Assembler", ("Coal", "Sulfur"), handcraftable=True), + Recipe("Fine Black Powder", "Assembler", ("Sulfur", "Compacted Coal"))), + "Smokeless Powder": ( + Recipe("Smokeless Powder", "Refinery", ("Black Powder", "Heavy Oil Residue")), ), + "Rifle Ammo": ( + Recipe("Rifle Ammo", "Assembler", ("Copper Sheet", "Smokeless Powder"), handcraftable=True, minimal_belt_speed=2), ), + "Iron Rebar": ( + Recipe("Iron Rebar", "Constructor", ("Iron Rod", ), handcraftable=True), ), + "Nobelisk": ( + Recipe("Nobelisk", "Assembler", ("Black Powder", "Steel Pipe"), handcraftable=True), ), + "Power Shard": ( + 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"))), # 1.0 + "Object Scanner": ( + Recipe("Object Scanner", "Equipment Workshop", ("Reinforced Iron Plate", "Wire", "Screw"), handcraftable=True), ), + "Xeno-Zapper": ( + Recipe("Xeno-Zapper", "Equipment Workshop", ("Iron Rod", "Reinforced Iron Plate", "Cable", "Wire"), handcraftable=True, implicitly_unlocked=True), ), + +#1.0 + "Rocket Fuel": ( + Recipe("Rocket Fuel", "Blender", ("Turbofuel", "Nitric Acid"), additional_outputs=("Compacted Coal", )), + Recipe("Nitro Rocket Fuel", "Blender", ("Fuel", "Nitrogen Gas", "Sulfur", "Coal"), minimal_belt_speed=2, additional_outputs=("Compacted Coal", ))), + #"Ionized Fuel": ( + # Recipe("Ionized Fuel", "Refinery", ("Rocket Fuel", "Power Shard"), additional_outputs=("Compacted Coal", )), ), + "Packaged Rocket Fuel": ( + Recipe("Packaged Rocket Fuel", "Packager", ("Rocket Fuel", "Empty Fluid Tank")), ), + #"Packaged Ionized Fuel": ( + # Recipe("Packaged Ionized Fuel", "Packager", ("Ionized Fuel", "Empty Fluid Tank")), ), + "Diamonds": ( + Recipe("Diamonds", "Particle Accelerator", ("Coal", ), minimal_belt_speed=5), + Recipe("Cloudy Diamonds", "Particle Accelerator", ("Coal", "Limestone"), minimal_belt_speed=4), + Recipe("Oil-Based Diamonds", "Particle Accelerator", ("Crude Oil", )), + Recipe("Petroleum Diamonds", "Particle Accelerator", ("Petroleum Coke", ), minimal_belt_speed=5), + Recipe("Pink Diamonds", "Converter", ("Coal", "Quartz Crystal"), minimal_belt_speed=2), + Recipe("Turbo Diamonds", "Particle Accelerator", ("Coal", "Packaged Turbofuel"), minimal_belt_speed=5)), + "Time Crystal": ( + Recipe("Time Crystal", "Converter", ("Diamonds", )), ), + "Ficsite Ingot": ( + Recipe("Ficsite Ingot (Aluminum)", "Converter", ("Reanimated SAM", "Aluminum Ingot"), minimal_belt_speed=2), + Recipe("Ficsite Ingot (Caterium)", "Converter", ("Reanimated SAM", "Caterium Ingot")), + Recipe("Ficsite Ingot (Iron)", "Converter", ("Reanimated SAM", "Iron Ingot"), minimal_belt_speed=3)), + "Ficsite Trigon": ( + Recipe("Ficsite Trigon", "Constructor", ("Ficsite Ingot", ), handcraftable=True), ), + "SAM": ( + Recipe("SAM", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + "Reanimated SAM": ( + Recipe("Reanimated SAM", "Constructor", ("SAM", ), handcraftable=True, minimal_belt_speed=2), ), + "SAM Fluctuator": ( + Recipe("SAM Fluctuator", "Manufacturer", ("Reanimated SAM", "Steel Pipe", "Wire"), handcraftable=True), ), + "Excited Photonic Matter": ( + Recipe("Excited Photonic Matter", "Converter", implicitly_unlocked=True), ), + "Dark Matter Crystal": ( + Recipe("Dark Matter Crystal", "Particle Accelerator", ("Diamonds", ), additional_outputs=("Dark Matter Residue", )), + Recipe("Dark Matter Crystallization", "Particle Accelerator", additional_outputs=("Dark Matter Residue", )), + Recipe("Dark Matter Trap", "Particle Accelerator", ("Time Crystal", ), additional_outputs=("Dark Matter Residue", ))), + "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")), ), + "Ballistic Warp Drive": ( + Recipe("Ballistic Warp Drive", "Manufacturer", ("Thermal Propulsion Rocket", "Singularity Cell", "Superposition Oscillator", "Dark Matter Crystal")), ), + + # 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": ( + #Recipe("Ficsonium", "Particle Accelerator", ("Plutonium Waste", "Singularity Cell"), additional_outputs=("Ficsonium", )), + Recipe("Dark Matter Crystal", "Particle Accelerator", ("Diamonds", ), additional_outputs=("Dark Matter Crystal", )), + Recipe("Dark Matter Crystallization", "Particle Accelerator", additional_outputs=("Dark Matter Crystal", )), + Recipe("Dark Matter Trap", "Particle Accelerator", ("Time Crystal", ), additional_outputs=("Dark Matter Crystal", ))), + "Superposition Oscillator": ( + Recipe("Superposition Oscillator", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Dark Matter Crystal", "Crystal Oscillator", "Alclad Aluminum Sheet")), ), + "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")), ), + ### +#1.0 + + # TODO transport types aren't currently in logic + } + + buildings: Dict[str, Building] = { + "Constructor": Building("Constructor", ("Reinforced Iron Plate", "Cable"), PowerInfrastructureLevel.Basic, implicitly_unlocked=True), + "Assembler": Building("Assembler", ("Reinforced Iron Plate", "Rotor", "Cable"), PowerInfrastructureLevel.Basic), + "Manufacturer": Building("Manufacturer", ("Motor", "Heavy Modular Frame", "Cable", "Plastic"), PowerInfrastructureLevel.Advanced), + "Packager": Building("Packager", ("Steel Beam", "Rubber", "Plastic"), PowerInfrastructureLevel.Basic), + "Refinery": Building("Refinery", ("Motor", "Encased Industrial Beam", "Steel Pipe", "Copper Sheet"), PowerInfrastructureLevel.Automated), + "Blender": Building("Blender", ("Motor", "Heavy Modular Frame", "Aluminum Casing", "Radio Control Unit"), PowerInfrastructureLevel.Complex), + "Particle Accelerator": Building("Particle Accelerator", ("Radio Control Unit", "Electromagnetic Control Rod", "Supercomputer", "Cooling System", "Fused Modular Frame", "Turbo Motor"), PowerInfrastructureLevel.Complex), + "Biomass Burner": Building("Biomass Burner", ("Iron Plate", "Iron Rod", "Wire"), implicitly_unlocked=True), + "Coal Generator": Building("Coal Generator", ("Reinforced Iron Plate", "Rotor", "Cable")), + "Fuel Generator": Building("Fuel Generator", ("Computer", "Heavy Modular Frame", "Motor", "Rubber", "Quickwire")), + "Geothermal Generator": Building("Geothermal Generator", ("Motor", "Modular Frame", "High-Speed Connector", "Copper Sheet", "Wire")), + "Nuclear Power Plant": Building("Nuclear Power Plant", ("Concrete", "Heavy Modular Frame", "Supercomputer", "Cable", "Alclad Aluminum Sheet")), + "Miner Mk.1": Building("Miner Mk.1", ("Iron Plate", "Concrete"), PowerInfrastructureLevel.Basic, implicitly_unlocked=True), + "Miner Mk.2": Building("Miner Mk.2", ("Encased Industrial Beam", "Steel Pipe", "Modular Frame"), PowerInfrastructureLevel.Automated), + "Miner Mk.3": Building("Miner Mk.3", ("Steel Pipe", "Supercomputer", "Fused Modular Frame", "Turbo Motor"), PowerInfrastructureLevel.Advanced), + "Oil Extractor": Building("Oil Extractor", ("Motor", "Encased Industrial Beam", "Cable")), + "Water Extractor": Building("Water Extractor", ("Copper Sheet", "Reinforced Iron Plate", "Rotor")), + "Smelter": Building("Smelter", ("Iron Rod", "Wire"), PowerInfrastructureLevel.Basic, implicitly_unlocked=True), + "Foundry": Building("Foundry", ("Modular Frame", "Rotor", "Concrete"), PowerInfrastructureLevel.Basic), + "Resource Well Pressurizer": Building("Resource Well Pressurizer", ("Wire", "Rubber", "Encased Industrial Beam", "Motor", "Steel Beam", "Plastic"), PowerInfrastructureLevel.Advanced), + "Equipment Workshop": Building("Equipment Workshop", ("Iron Plate", "Iron Rod"), implicitly_unlocked=True), + "AWESOME Sink": Building("AWESOME Sink", ("Reinforced Iron Plate", "Cable", "Concrete"), can_produce=False), + "AWESOME Shop": Building("AWESOME Shop", ("Screw", "Iron Plate", "Cable"), can_produce=False), + "MAM": Building("MAM", ("Reinforced Iron Plate", "Wire", "Cable"), can_produce=False), + "Pipes Mk.1": Building("Pipes Mk.1", ("Copper Sheet", "Iron Plate", "Concrete"), can_produce=False), + "Pipes Mk.2": Building("Pipes Mk.2", ("Copper Sheet", "Plastic", "Iron Plate", "Concrete"), can_produce=False), + "Pipeline Pump Mk.1": Building("Pipeline Pump Mk.1", ("Copper Sheet", "Rotor"), can_produce=False), + "Pipeline Pump Mk.2": Building("Pipeline Pump Mk.2", ("Motor", "Encased Industrial Beam", "Plastic"), can_produce=False), + "Conveyor Merger": Building("Conveyor Merger", ("Iron Plate", "Iron Rod"), can_produce=False), + "Conveyor Splitter": Building("Conveyor Splitter", ("Iron Plate", "Cable"), can_produce=False), + "Conveyor Mk.1": Building("Conveyor Mk.1", ("Iron Plate", "Iron Rod", "Concrete"), can_produce=False, implicitly_unlocked=True), + "Conveyor Mk.2": Building("Conveyor Mk.2", ("Reinforced Iron Plate", "Iron Plate", "Iron Rod", "Concrete"), can_produce=False), + "Conveyor Mk.3": Building("Conveyor Mk.3", ("Steel Beam", "Iron Plate", "Iron Rod", "Concrete"), can_produce=False), + "Conveyor Mk.4": Building("Conveyor Mk.4", ("Encased Industrial Beam", "Iron Plate", "Iron Rod", "Concrete"), can_produce=False), + "Conveyor Mk.5": Building("Conveyor Mk.5", ("Alclad Aluminum Sheet", "Iron Plate", "Iron Rod", "Concrete"), can_produce=False), + "Conveyor Mk.6": Building("Conveyor Mk.6", ("Ficsite Trigon", "Time Crystal", "Iron Plate", "Iron Rod", "Concrete"), can_produce=False), + "Power Pole Mk.1": Building("Power Pole Mk.1", ("Iron Plate", "Iron Rod", "Concrete"), can_produce=False, implicitly_unlocked=True), + # higher level power poles arent in logic (yet) + #"Power Pole Mk.2": Building("Power Pole Mk.2", ("Quickwire", "Iron Rod", "Concrete"), False), + #"Power Pole Mk.3": Building("Power Pole Mk.3", ("High-Speed Connector", "Steel Pipe", "Rubber"), False), + "Power Storage": Building("Power Storage", ("Wire", "Modular Frame", "Stator"), can_produce=False), + "Foundation": Building("Foundation", ("Iron Plate", "Concrete"), can_produce=False), + "Walls Orange": Building("Walls Orange", ("Iron Plate", "Concrete"), can_produce=False), + "Space Elevator": Building("Space Elevator", ("Concrete", "Iron Plate", "Iron Rod", "Wire"), can_produce=False, implicitly_unlocked=True), + +#1.0 + "Converter": Building("Converter", ("Fused Modular Frame", "Cooling System", "Radio Control Unit", "SAM Fluctuator"), PowerInfrastructureLevel.Complex), + "Quantum Encoder": Building("Quantum Encoder", ("Turbo Motor", "Supercomputer", "Cooling System", "Time Crystal", "Ficsite Trigon"), PowerInfrastructureLevel.Complex), + "Alien Power Augmenter": Building("Alien Power Augmenter", ("SAM Fluctuator", "Cable", "Encased Industrial Beam", "Motor", "Computer")), +#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: ( + Recipe("Biomass Power (Biomass)", "Biomass Burner", ("Biomass", ), implicitly_unlocked=True), + ), + PowerInfrastructureLevel.Automated: ( + Recipe("Biomass Power (Solid Biofuel)", "Biomass Burner", ("Solid Biofuel", ), implicitly_unlocked=True), + #Recipe("Coal Generator Power (Petroleum Coke)", "Coal Generator", ("Petroleum Coke", "Water"), implicitly_unlocked=True), + Recipe("Coal Generator Power (Coal)", "Coal Generator", ("Coal", "Water"), implicitly_unlocked=True), + ), + PowerInfrastructureLevel.Advanced: ( + Recipe("Coal Generator Power (Compacted Coal)", "Coal Generator", ("Compacted Coal", "Water"), implicitly_unlocked=True), + Recipe("Geothermal Generator Power", "Geothermal Generator", implicitly_unlocked=True), + Recipe("Fuel Generator Power (Liquid Biofuel)","Fuel Generator", ("Liquid Biofuel", ), implicitly_unlocked=True), + Recipe("Fuel Generator Power (Fuel)","Fuel Generator", ("Fuel", ), implicitly_unlocked=True), + Recipe("Alien Power Augmenter Power","Alien Power Augmenter", implicitly_unlocked=True), + ), + PowerInfrastructureLevel.Complex: ( + Recipe("Fuel Generator Power (Turbofuel)","Fuel Generator", ("Turbofuel", ), implicitly_unlocked=True), + #Recipe("Fuel Generator Power (Rocket Fuel)","Fuel Generator", ("Rocket Fuel", ), implicitly_unlocked=True), + #Recipe("Fuel Generator Power (Ionized Fuel)","Fuel Generator", ("Ionized Fuel", ), implicitly_unlocked=True), + Recipe("Nuclear Power Plant Power (Uranium)","Nuclear Power Plant", ("Uranium Fuel Rod", "Water"), implicitly_unlocked=True), + #Recipe("Nuclear Power Plant Power (Plutonium)","Nuclear Power Plant", ("Plutonium Fuel Rod", "Water"), implicitly_unlocked=True), + #Recipe("Nuclear Power Plant Power (Ficsonium)","Nuclear Power Plant", ("Ficsonium Fuel Rod", "Water"), implicitly_unlocked=True), + #Recipe("Alien Power Augmenter Power (Alien Power Matrix)","Alien Power Augmenter", ("Alien Power Matrix"), implicitly_unlocked=True), + ) + } + + slots_per_milestone: int = 8 + + hub_layout: Tuple[Tuple[Dict[str, int], ...], ...] = ( + # Regenerate via /Script/Engine.Blueprint'/Archipelago/Debug/CC_BuildHubData.CC_BuildHubData' + ( # Tier 1 + {"Concrete":200, "Iron Plate":100, "Iron Rod":100, }, # Schematic: Base Building (Schematic_1-1_C) + {"Iron Plate":150, "Iron Rod":150, "Wire":300, }, # Schematic: Logistics (Schematic_1-2_C) + {"Wire":300, "Screw":300, "Iron Plate":100, }, # Schematic: Field Research (Schematic_1-3_C) + {"Wire":100, "Screw":200, "Concrete":200, }, # Schematic: Archipelago Additional Tier1 (Schem_ApExtraTier1_C) + ), + ( # Tier 2 + {"Cable":200, "Iron Rod":200, "Screw":500, "Iron Plate":300, }, # Schematic: Part Assembly (Schematic_2-1_C) + {"Screw":500, "Cable":100, "Concrete":100, }, # Schematic: Obstacle Clearing (Schematic_2-2_C) + {"Rotor":50, "Iron Plate":300, "Cable":150, }, # Schematic: Jump Pads (Schematic_2-3_C) + {"Concrete":400, "Wire":500, "Iron Rod":200, "Iron Plate":200, }, # Schematic: Resource Sink Bonus Program (Schematic_2-5_C) + {"Reinforced Iron Plate":50, "Concrete":200, "Iron Rod":300, "Iron Plate":300, }, # Schematic: Logistics Mk.2 (Schematic_3-2_C) + ), + ( # Tier 3 + {"Reinforced Iron Plate":150, "Rotor":50, "Cable":500, }, # Schematic: Coal Power (Schematic_3-1_C) + {"Modular Frame":25, "Rotor":100, "Cable":100, "Iron Plate":400, }, # Schematic: Vehicular Transport (Schematic_3-3_C) + {"Modular Frame":50, "Rotor":150, "Concrete":500, "Wire":1000, }, # Schematic: Basic Steel Production (Schematic_3-4_C) + {"Reinforced Iron Plate":100, "Iron Rod":600, "Wire":1500, }, # Schematic: Improved Melee Combat (Schematic_4-2_C) + ), + ( # Tier 4 + {"Modular Frame":100, "Steel Beam":200, "Cable":500, "Concrete":1000, }, # Schematic: FICSIT Blueprints (Schematic_4-5_C) + {"Steel Beam":200, "Steel Pipe":200, "Reinforced Iron Plate":400, }, # Schematic: Logistics Mk.3 (Schematic_5-3_C) + {"Steel Pipe":100, "Modular Frame":100, "Rotor":200, "Concrete":500, }, # Schematic: Advanced Steel Production (Schematic_4-1_C) + {"Encased Industrial Beam":50, "Steel Beam":100, "Modular Frame":200, "Wire":2000 }, # Schematic: Expanded Power Infrastructure (Schematic_4-3_C) + {"Copper Sheet":500, "Steel Pipe":300, "Encased Industrial Beam":50, }, # Schematic: Hypertubes (Schematic_4-4_C) + ), + ( # Tier 5 + {"Motor":50, "Cable":100, "Iron Plate":500 }, # Something jetpack + {"Motor":50, "Encased Industrial Beam":100, "Steel Pipe":500, "Copper Sheet":500, }, # Schematic: Oil Processing (Schematic_5-1_C) + {"Rubber":200, "Encased Industrial Beam":300, "Modular Frame":400, }, + {"Plastic":200, "Steel Beam":400, "Copper Sheet":1000, }, # Schematic: Alternative Fluid Transport (Schematic_5-4_C) + {"Motor":100, "Encased Industrial Beam":100, "Plastic":200, "Rubber":200, }, # Schematic: Industrial Manufacturing (Schematic_5-2_C) + ), + ( # Tier 6 + {"Motor":200, "Modular Frame":200, "Plastic":400, "Cable":1000, }, # Schematic: Industrial Manufacturing (Schematic_5-2_C) + {"Motor":250, "Encased Industrial Beam":500, "Steel Pipe":1000, "Steel Beam":1000, }, # Schematic: Monorail Train Technology (Schematic_6-3_C) + {"Computer":50, "Steel Pipe":4000, "Copper Sheet":1000, }, + {"Heavy Modular Frame":50, "Plastic":1000, "Rubber":1000, }, # Schematic: Pipeline Engineering Mk.2 (Schematic_6-5_C) + {"Heavy Modular Frame":50, "Computer":100, "Rubber":400, "Concrete": 1000, }, + ), + ( # Tier 7 + {"Computer":100, "Heavy Modular Frame":100, "Motor":250, "Rubber":500, }, # Schematic: Bauxite Refinement (Schematic_7-1_C) + {"Alclad Aluminum Sheet":100, "Heavy Modular Frame":100, "Computer":100, "Motor":250, }, # Schematic: Hover Pack (Schematic_8-3_C) + {"Alclad Aluminum Sheet":200, "Encased Industrial Beam":400, "Reinforced Iron Plate":600, }, # Schematic: Logistics Mk.5 (Schematic_7-2_C) + {"Gas Filter":50, "Aluminum Casing":100, "Quickwire":500, }, # Schematic: Hazmat Suit (Schematic_7-3_C) + {"Alclad Aluminum Sheet":200, "Aluminum Casing":400, "Computer":200, "Plastic": 1000, }, # Schematic: Aeronautical Engineering (Schematic_7-4_C) + ), + ( # Tier 8 + {"Radio Control Unit": 50, "Alclad Aluminum Sheet":100, "Aluminum Casing":200, "Motor": 300, }, # Schematic: Aeronautical Engineering (Schematic_7-4_C) + {"Supercomputer":50, "Heavy Modular Frame":200, "Cable":1000, "Concrete":2000, }, # Schematic: Nuclear Power (Schematic_8-1_C) + {"Radio Control Unit":50, "Aluminum Casing":200, "Alclad Aluminum Sheet":400, "Wire":3000, }, # Schematic: Advanced Aluminum Production (Schematic_8-2_C) + {"Fused Modular Frame":50, "Supercomputer":100, "Steel Pipe":1000, }, # Schematic: Leading-edge Production (Schematic_8-4_C) + {"Turbo Motor":50, "Fused Modular Frame":100, "Cooling System":200, "Quickwire":2500, }, # Schematic: Particle Enrichment (Schematic_8-5_C) + ), + ( # Tier 9 + {"Fused Modular Frame":100, "Radio Control Unit":250, "Cooling System":500, }, + {"Time Crystal":50, "Ficsite Trigon":100, "Turbo Motor":200, "Supercomputer":400, }, + {"Neural-Quantum Processor":100, "Time Crystal":250, "Ficsite Trigon":500, "Fused Modular Frame":500, }, + {"Superposition Oscillator":100, "Turbo Motor":250, "Radio Control Unit":500, "SAM Fluctuator":1000, }, + {"Time Crystal":250, "Ficsite Trigon":250, "Alclad Aluminum Sheet":500, "Iron Plate":10000, }, + ), + ) + + # Values from /Game/FactoryGame/Schematics/Progression/BP_GamePhaseManager.BP_GamePhaseManager + space_elevator_tiers: Tuple[Dict[str, int], ...] = ( + { "Smart Plating": 50 }, + { "Smart Plating": 500, "Versatile Framework": 500, "Automated Wiring": 100 }, + { "Versatile Framework": 2500, "Modular Engine": 500, "Adaptive Control Unit": 100 }, + { "Assembly Director System": 4000, "Magnetic Field Generator": 4000, "Nuclear Pasta": 1000, "Thermal Propulsion Rocket": 1000 }, + { "Nuclear Pasta": 1000, "Biochemical Sculptor": 1000, "AI Expansion Server": 256, "Ballistic Warp Drive": 200 } + ) + + # Do not regenerate as format got changed + # Regenerate via /Script/Engine.Blueprint'/Archipelago/Debug/CC_BuildMamData.CC_BuildMamData' + man_trees: Dict[str, MamTree] = { + "Alien Organisms": MamTree(("Hog Remains", "Plasma Spitter Remains", "Stinger Remains", "Hatcher Remains"), ( # Alien Organisms (BPD_ResearchTree_AlienOrganisms_C) + MamNode("Inflated Pocket Dimension", {"Alien Protein":3,"Cable":1000,}, depends_on=("Bio-Organic Properties", )), #(Research_AOrgans_3_C) + MamNode("Hostile Organism Detection", {"Alien DNA Capsule":10,"Crystal Oscillator":5,"High-Speed Connector":5,}, depends_on=("Bio-Organic Properties", )), #(Research_AOrganisms_2_C) + MamNode("Expanded Toolbelt", {"Alien DNA Capsule":5,"Steel Beam":500,}, depends_on=("Inflated Pocket Dimension", )), #(Research_ACarapace_3_C) + MamNode("Bio-Organic Properties", {"Alien Protein":5,}, depends_on=("Spitter Research", "Hog Research", "Hatcher Research", "Stinger Research")), #(Research_AO_DNACapsule_C) + MamNode("Stinger Research", {"Stinger Remains":1,}, depends_on=()), #(Research_AO_Stinger_C) + MamNode("Hatcher Research", {"Hatcher Remains":1,}, depends_on=()), #(Research_AO_Hatcher_C) + MamNode("Hog Research", {"Hog Remains":1,}, depends_on=()), #(Research_ACarapace_0_C) + MamNode("Spitter Research", {"Plasma Spitter Remains":1,}, depends_on=()), #(Research_AOrgans_0_C) + MamNode("Structural Analysis", {"Alien DNA Capsule":5,"Iron Rod":100,}, depends_on=("Bio-Organic Properties", )), #(Research_AO_Pre_Rebar_C) + MamNode("Protein Inhaler", {"Alien Protein":2,"Beryl Nut":20,"Rotor":50,}, depends_on=("Bio-Organic Properties", )), #(Research_AOrgans_2_C) + MamNode("The Rebar Gun", {"Rotor":25,"Reinforced Iron Plate":50,"Screw":500,}, depends_on=("Structural Analysis", )), #(Research_ACarapace_2_C) + )), + # 1.0 + "Alien Technology": MamTree(("SAM", "Mercer Sphere", "Somersloop"), ( + MamNode("SAM Analysis", {"SAM":10,}, depends_on=()), + MamNode("SAM Reanimation", {"SAM":20,}, depends_on=("SAM Analysis",)), + MamNode("SAM Fluctuator", {"Reanimated SAM":10,"Steel Pipe":100,"Wire":200,}, depends_on=("SAM Reanimation",)), + MamNode("Mercer Sphere Analysis", {"Mercer Sphere":1,}, depends_on=()), + MamNode("Dimensional Depot", {"Mercer Sphere":1,"SAM Fluctuator":11,}, depends_on=("Mercer Sphere Analysis", "SAM Fluctuator")), + MamNode("Manual Depot Uploader", {"Mercer Sphere":3,"Computer":17,"SAM Fluctuator":19,}, depends_on=("Dimensional Depot",)), + MamNode("Depot Expansion (200%)", {"Mercer Sphere":3,"SAM Fluctuator":47,}, depends_on=("Dimensional Depot",)), + MamNode("Depot Expansion (300%)", {"Mercer Sphere":7,"SAM Fluctuator":103,}, depends_on=("Depot Expansion (200%)",)), + MamNode("Depot Expansion (400%)", {"Mercer Sphere":13,"SAM Fluctuator":151,}, depends_on=("Depot Expansion (300%)",)), + MamNode("Depot Expansion (500%)", {"Mercer Sphere":23,"SAM Fluctuator":199,}, depends_on=("Depot Expansion (400%)",)), + MamNode("Upload Upgrade: 30/min", {"Mercer Sphere":3,"SAM Fluctuator":47,}, depends_on=("Dimensional Depot",)), + MamNode("Upload Upgrade: 60/min", {"Mercer Sphere":7,"SAM Fluctuator":103,}, depends_on=("Upload Upgrade: 30/min",)), + MamNode("Upload Upgrade: 120/min", {"Mercer Sphere":13,"SAM Fluctuator":151,}, depends_on=("Upload Upgrade: 60/min",)), + MamNode("Upload Upgrade: 240/min", {"Mercer Sphere":23,"SAM Fluctuator":199,}, depends_on=("Upload Upgrade: 120/min",)), + MamNode("Somersloop Analysis", {"Somersloop":1,}, depends_on=()), + MamNode("Alien Energy Harvesting", {"SAM Fluctuator":10,}, depends_on=("Somersloop Analysis", "SAM Fluctuator")), + MamNode("Production Amplifier", {"Somersloop":1,"SAM Fluctuator":100,"Circuit Board":50,}, depends_on=("Alien Energy Harvesting",)), + MamNode("Power Augmenter", {"Somersloop":1,"SAM Fluctuator":100,"Computer":50,}, depends_on=("Alien Energy Harvesting",)), + MamNode("Alien Power Matrix", {"Singularity Cell":50,"Power Shard":100,"SAM Fluctuator":500}, depends_on=("Power Augmenter",)), + )), + # 1.0 + "Caterium": MamTree(("Caterium Ore", ), ( # Caterium (BPD_ResearchTree_Caterium_C) + MamNode("Caterium Electronics", {"Quickwire":100,}, depends_on=("Quickwire", )), #(Research_Caterium_3_C) + MamNode("Bullet Guidance System", {"High-Speed Connector":10,"Rifle Ammo":500,}, depends_on=("High-Speed Connector", )), #(Research_Caterium_6_3_C) + MamNode("High-Speed Connector", {"Quickwire":500,"Plastic":50,}, depends_on=("Caterium Electronics", )), #(Research_Caterium_5_C) + MamNode("Caterium", {"Caterium Ore":10,}, depends_on=()), #(Research_Caterium_0_C) + MamNode("Caterium Ingots", {"Caterium Ore":50,}, depends_on=("Caterium", )), #(Research_Caterium_1_C) + MamNode("Quickwire", {"Caterium Ingot":50,}, depends_on=("Caterium Ingots", )), #(Research_Caterium_2_C) + MamNode("Power Switch", {"Steel Beam":100,"AI Limiter":50,}, depends_on=("AI Limiter", )), #(Research_Caterium_4_1_2_C) + MamNode("Priority Power Switch", {"High-Speed Connector":25,"Quickwire":500,}, depends_on=("High-Speed Connector", )), # 1.0 + MamNode("Power Poles Mk.2", {"Quickwire":300,}, depends_on=("Caterium Electronics", )), #(Research_Caterium_4_2_C) + MamNode("AI Limiter", {"Quickwire":200,"Copper Sheet":50,}, depends_on=("Caterium Electronics", )), #(Research_Caterium_4_1_C) + MamNode("Smart Splitter", {"AI Limiter":10,"Reinforced Iron Plate":50,}, depends_on=("AI Limiter", )), #(Research_Caterium_4_1_1_C) + MamNode("Programmable Splitter", {"AI Limiter":100, "Computer":50,"Heavy Modular Frame":50,}, depends_on=("AI Limiter", "High-Speed Connector")), #(Research_Caterium_7_1_C) # 1.0 + MamNode("Zipline", {"Quickwire":100,"Cable":50,}, depends_on=("Quickwire", )), #(Research_Caterium_2_1_C) + MamNode("Geothermal Generator", {"Supercomputer":50,"Heavy Modular Frame":50,"Rubber":300,}, depends_on=("AI Limiter", "High-Speed Connector")), #(Research_Caterium_7_2_C) # 1.0 + MamNode("Stun Rebar", {"Quickwire":50,"Iron Rebar":10,}, depends_on=("Quickwire", )), #(Research_Caterium_3_2_C) + MamNode("Power Poles Mk.3", {"High-Speed Connector":50,"Steel Pipe":200,}, depends_on=("Power Poles Mk.2", )), #(Research_Caterium_6_2_C) # 1.0 + )), + "Mycelia": MamTree(("Mycelia", ), ( # Mycelia (BPD_ResearchTree_Mycelia_C) + MamNode("Therapeutic Inhaler", {"Mycelia":15,"Bacon Agaric":1,"Alien Protein":1,}, depends_on=("Medical Properties", )), #(Research_Mycelia_6_C) + MamNode("Expanded Toolbelt", {"Fabric":50,"Rotor":100,}, depends_on=("Fabric", )), #(Research_Mycelia_7_C) + MamNode("Mycelia", {"Mycelia":5,}, depends_on=tuple()), #(Research_Mycelia_1_C) + MamNode("Fabric", {"Mycelia":25,"Biomass":100,}, depends_on=("Mycelia", )), #(Research_Mycelia_2_C) + MamNode("Medical Properties", {"Mycelia":25,"Stator":10,}, depends_on=("Mycelia", )), #(Research_Mycelia_4_C) + MamNode("Toxic Cellular Modification", {"Nobelisk":10,"Mycelia":100,"Biomass":200,}, depends_on=("Mycelia", )), #(Research_Mycelia_8_C) + MamNode("Vitamin Inhaler", {"Mycelia":10,"Paleberry":5,}, depends_on=("Medical Properties", )), #(Research_Mycelia_5_C) + MamNode("Parachute", {"Fabric":10,"Cable":50,}, depends_on=("Fabric", )), #(Research_Mycelia_3_C) + MamNode("Synthethic Polyester Fabric", {"Fabric":25,"Polymer Resin":100,}, depends_on=("Fabric", )), #(Research_Mycelia_2_1_C) + MamNode("Gas Mask", {"Coal":10,"Fabric":50,"Steel Pipe":50}, depends_on=("Fabric", )), # 1.0 + )), + "Nutrients": MamTree(("Paleberry", "Beryl Nut", "Bacon Agaric"), ( # Nutrients (BPD_ResearchTree_Nutrients_C) + MamNode("Bacon Agaric", {"Bacon Agaric":1,}, depends_on=()), #(Research_Nutrients_2_C) + MamNode("Beryl Nut", {"Beryl Nut":5,}, depends_on=()), #(Research_Nutrients_1_C) + MamNode("Paleberry", {"Paleberry":2,}, depends_on=()), #(Research_Nutrients_0_C) + MamNode("Nutritional Processor", {"Modular Frame":25,"Steel Pipe":50,"Wire":500,}, depends_on=("Beryl Nut", "Bacon Agaric", "Paleberry")), #(Research_Nutrients_3_C) + MamNode("Nutritional Inhaler", {"Bacon Agaric":2,"Paleberry":4,"Beryl Nut":10,}, depends_on=("Nutritional Processor", )), #(Research_Nutrients_4_C) + )), + "Power Slugs": MamTree(("Blue Power Slug", ), ( # Power Slugs (BPD_ResearchTree_PowerSlugs_C) + MamNode("Slug Scanning", {"Iron Rod":50,"Wire":100,"Screw":200,}, depends_on=("Blue Power Slugs", )), #(Research_PowerSlugs_3_C) + MamNode("Blue Power Slugs", {"Blue Power Slug":1,}, depends_on=()), #(Research_PowerSlugs_1_C) + MamNode("Yellow Power Shards", {"Yellow Power Slug":1,"Rotor":25,"Cable":100,}, depends_on=("Blue Power Slugs", )), #(Research_PowerSlugs_4_C) + MamNode("Purple Power Shards", {"Purple Power Slug":1,"Modular Frame":25,"Copper Sheet":100,}, depends_on=("Yellow Power Shards", )), #(Research_PowerSlugs_5_C) + MamNode("Overclock Production", {"Power Shard":1,"Iron Plate":50,"Wire":50,}, depends_on=("Blue Power Slugs", )), #(Research_PowerSlugs_2_C) + MamNode("Synthetic Power Shards", {"Power Shard":10,"Time Crystal":100,"Quartz Crystal":200,}, depends_on=("Purple Power Shards", )), # 1.0 + )), + "Quartz": MamTree(("Raw Quartz", ), ( # Quartz (BPD_ResearchTree_Quartz_C) + MamNode("Crystal Oscillator", {"Quartz Crystal":100,"Reinforced Iron Plate":50,}, depends_on=("Quartz Crystals", )), #(Research_Quartz_2_C) + MamNode("Quartz Crystals", {"Raw Quartz":20,}, depends_on=("Quartz", )), #(Research_Quartz_1_1_C) + MamNode("Quartz", {"Raw Quartz":10,}, depends_on=()), #(Research_Quartz_0_C) + MamNode("Shatter Rebar", {"Quartz Crystal":30,"Iron Rebar":150,}, depends_on=("Quartz Crystals", )), #(Research_Quartz_2_1_C) + MamNode("Silica", {"Raw Quartz":20,}, depends_on=("Quartz", )), #(Research_Quartz_1_2_C) + MamNode("Explosive Resonance Application", {"Crystal Oscillator":5,"Nobelisk":100,}, depends_on=("Crystal Oscillator", )), #(Research_Quartz_3_4_C) + MamNode("Blade Runners", {"Silica":50,"Modular Frame":10,}, depends_on=("Silica", )), #(Research_Caterium_4_3_C) + MamNode("The Explorer", {"Crystal Oscillator":10,"Modular Frame":100,}, depends_on=("Crystal Oscillator", )), #(Research_Quartz_3_1_C) + MamNode("Radio Signal Scanning", {"Crystal Oscillator":100,"Motor":100,"Object Scanner":1,}, depends_on=("Crystal Oscillator", )), #(Research_Quartz_4_1_C) + MamNode("Inflated Pocket Dimension", {"Silica":200,}, depends_on=("Silica", )), #(Research_Caterium_3_1_C) + MamNode("Radar Technology", {"Crystal Oscillator":50,"Heavy Modular Frame":50,"Computer":50,}, depends_on=("Crystal Oscillator", )), #(Research_Quartz_4_C) # 1.0 + )), + "Sulfur": MamTree(("Sulfur", ), ( # Sulfur (BPD_ResearchTree_Sulfur_C) + MamNode("The Nobelisk Detonator", {"Black Powder":50,"Steel Pipe":100,"Cable":200,}, depends_on=("Black Powder", )), #(Research_Sulfur_3_1_C) + MamNode("Smokeless Powder", {"Black Powder":100,"Plastic":50,}, depends_on=("Black Powder", )), #(Research_Sulfur_3_C) + MamNode("Sulfur", {"Sulfur":10,}, depends_on=()), #(Research_Sulfur_0_C) + MamNode("Inflated Pocket Dimension", {"Smokeless Powder":50,"Computer":50,}, depends_on=("Nuclear Deterrent Development", "Turbo Rifle Ammo", "Cluster Nobelisk", "The Rifle")), #(Research_Sulfur_6_C) + MamNode("The Rifle", {"Smokeless Powder":50,"Motor":100,"Rubber":200,}, depends_on=("Smokeless Powder", )), #(Research_Sulfur_4_1_C) + MamNode("Compacted Coal", {"Hard Drive":1,"Sulfur":25,"Coal":25,}, depends_on=("Experimental Power Generation", )), #(Research_Sulfur_CompactedCoal_C) + MamNode("Black Powder", {"Sulfur":50,"Coal":25,}, depends_on=("Sulfur", )), #(Research_Sulfur_1_C) + MamNode("Explosive Rebar", {"Smokeless Powder":200,"Iron Rebar":200,"Steel Beam":200,}, depends_on=("Smokeless Powder", )), #(Research_Sulfur_4_2_C) + MamNode("Cluster Nobelisk", {"Smokeless Powder":100,"Nobelisk":200,}, depends_on=("Smokeless Powder", )), #(Research_Sulfur_4_C) + MamNode("Experimental Power Generation", {"Sulfur":25,"Modular Frame":50,"Rotor":100,}, depends_on=("Sulfur", )), #(Research_Sulfur_ExperimentalPower_C) + MamNode("Turbo Rifle Ammo", {"Rifle Ammo":1000,"Packaged Turbofuel":50,"Aluminum Casing":100,}, depends_on=("The Rifle", )), #(Research_Sulfur_5_2_C) # 1.0 + MamNode("Turbo Fuel", {"Hard Drive":1,"Compacted Coal":15,"Packaged Fuel":50,}, depends_on=("Experimental Power Generation", )), #(Research_Sulfur_TurboFuel_C) + MamNode("Expanded Toolbelt", {"Black Powder":100,"Encased Industrial Beam":50,}, depends_on=("Black Powder", )), #(Research_Sulfur_5_C) + MamNode("Nuclear Deterrent Development", {"Nobelisk":500,"Encased Uranium Cell":10,"AI Limiter":100,}, depends_on=("Cluster Nobelisk", )), #(Research_Sulfur_5_1_C) # 1.0 + MamNode("Rocket Fuel", {"Hard Drive":1,"Empty Fluid Tank":10,"Packaged Turbofuel":100,}, depends_on=("Turbo Fuel", )), # 1.0 + MamNode("Ionized Fuel", {"Hard Drive":1,"Power Shard":100,"Packaged Rocket Fuel":200,}, depends_on=("Turbo Fuel", )), # 1.0 + )) + } + + drop_pods: List[DropPodData] = [ + # Regenerate via /Script/Engine.Blueprint'/Archipelago/Debug/CC_BuildDropPodLocations.CC_BuildDropPodLocations' + DropPodData(-29068, -22640, 17384, "Encased Industrial Beam", 0), # Unlocks with: 4 x Desc_SteelPlateReinforced_C + DropPodData(-33340, 5176, 23519, "Crystal Oscillator", 0), # Unlocks with: 5 x Desc_CrystalOscillator_C + DropPodData(8680, -41777, 13053, "Steel Pipe", 0), # Unlocks with: 7 x Desc_SteelPipe_C + DropPodData(35082, 16211, 22759, "Supercomputer", 0), # Unlocks with: 7 x Desc_ComputerSuper_C + #DropPodData(-3511, 62314, 22109, "Quantum Computer", 0), # Unlocks with: 1 x Desc_ComputerQuantum_C + DropPodData(66652, -13642, 13420, "Encased Industrial Beam", 50), # Unlocks with: 3 x Desc_SteelPlateReinforced_C + DropPodData(55247, -51316, 14363, None, 25), # Unlocks with: (No Item) + DropPodData(-4706, -76301, 13618, "Black Powder", 0), # Unlocks with: 10 x Desc_Gunpowder_C + DropPodData(-40194,62956, 26261, "Superposition Oscillator", 138), # Unlocks with: 2 x Desc_QuantumOscillator_C + DropPodData(80980, -44100, 8303, "Rotor", 0), # Unlocks with: 3 x Desc_Rotor_C + DropPodData(-56144, -72864, 27668, "Quartz Crystal", 0), # Unlocks with: 2 x Desc_QuartzCrystal_C + DropPodData(-95228, 6970, 25142, "High-Speed Connector", 112), # Unlocks with: 11 x Desc_HighSpeedConnector_C + DropPodData(-89284, -50630, 16019, None, 50), # Unlocks with: (No Item) + DropPodData(-94708, 40337, 19832, "Heat Sink", 138), # Unlocks with: 2 x Desc_AluminumPlateReinforced_C + DropPodData(94267, 47237, 9435, "Motor", 0), # Unlocks with: 1 x Desc_Motor_C + DropPodData(87739, -62975, 13444, None, 30), # Unlocks with: (No Item) + DropPodData(12249, 114177, 26721, "AI Limiter", 267), # Unlocks with: 9 x Desc_CircuitBoardHighSpeed_C + DropPodData(115978, 21424, 15519, None, 0), # Unlocks with: (No Item) + DropPodData(-78236, 90857, 20305, "Radio Control Unit", 0), # Unlocks with: 6 x Desc_ModularFrameLightweight_C + DropPodData(-35359, 116594, 21827, "Turbo Motor", 0), # Unlocks with: 6 x Desc_MotorLightweight_C + DropPodData(111479, -54515, 17081, "Stator", 20), # Unlocks with: 1 x Desc_Stator_C + DropPodData(121061, 45324, 17373, None, 0), # Unlocks with: (No Item) + DropPodData(125497, -34949, 8220, None, 50), # Unlocks with: (No Item) + DropPodData(-26327, -129047,7780, "Modular Frame", 0), # Unlocks with: 1 x Desc_ModularFrame_C + DropPodData(21373, 132336, 2510, "Motor", 20), # Unlocks with: 2 x Desc_Motor_C + DropPodData(17807, -136922,13621, "Rotor", 0), # Unlocks with: 1 x Desc_Rotor_C + DropPodData(-118480,74929, 16995, None, 420), # Unlocks with: (No Item) + DropPodData(94940, 105482, 9860, "Heavy Modular Frame", 0), # Unlocks with: 1 x Desc_ModularFrameHeavy_C + DropPodData(-129115,60165, 4800, None, 53), # Unlocks with: (No Item) + DropPodData(-142000,23970, 32660, None, 0), # Unlocks with: (No Item) + DropPodData(46048, 141933, 13064, None, 40), # Unlocks with: (No Item) + DropPodData(144456, 36294, 17301, "Circuit Board", 48), # Unlocks with: 20 x Desc_CircuitBoard_C + DropPodData(-43144, 145820, 7472, "Modular Frame", 0), # Unlocks with: 5 x Desc_ModularFrame_C + DropPodData(-108774,107811, 10154, "Crystal Oscillator", 0), # Unlocks with: 1 x Desc_CrystalOscillator_C + DropPodData(-56987, -144603,2072, "Rotor", 10), # Unlocks with: 1 x Desc_Rotor_C + DropPodData(-152676,33864, 19283, None, 256), # Unlocks with: (No Item) + DropPodData(90313, 129583, 9112, "Crystal Oscillator", 20), # Unlocks with: 2 x Desc_CrystalOscillator_C + DropPodData(111212, -113040,12036, "Screw", 10), # Unlocks with: 15 x Desc_IronScrew_C + DropPodData(-157077,-6312, 25128, "Turbo Motor", 0), # Unlocks with: 8 x Desc_MotorLightweight_C + DropPodData(157249, -40206, 13694, "High-Speed Connector", 0), # Unlocks with: 2 x Desc_HighSpeedConnector_C + DropPodData(-151842,72468, 9945, "Encased Industrial Beam", 0), # Unlocks with: 3 x Desc_SteelPlateReinforced_C + DropPodData(64696, 156038, 14067, "Modular Frame", 0), # Unlocks with: 6 x Desc_ModularFrame_C + DropPodData(-157080,-67028, 11766, "Rotor", 0), # Unlocks with: 4 x Desc_Rotor_C + DropPodData(170057, -10579, 18823, None, 50), # Unlocks with: (No Item) + DropPodData(143671, 92573, 24990, "Crystal Oscillator", 20), # Unlocks with: 2 x Desc_CrystalOscillator_C + DropPodData(127215, -116866,-1397, "Rubber", 0), # Unlocks with: 10 x Desc_Rubber_C + DropPodData(163999, 61333, 21481, "AI Limiter", 0), # Unlocks with: 3 x Desc_CircuitBoardHighSpeed_C + DropPodData(98306, -149781,2552, None, 40), # Unlocks with: (No Item) + DropPodData(5302, -187090,-1608, None, 0), # Unlocks with: (No Item) + DropPodData(188304, 17059, 12949, None, 0), # Unlocks with: (No Item) + DropPodData(84256, -171122,-290, None, 0), # Unlocks with: (No Item) + DropPodData(191366, 37694, 5676, "Computer", 0), # Unlocks with: 4 x Desc_Computer_C + DropPodData(28695, 193441, 17459, "Quickwire", 0), # Unlocks with: 9 x Desc_HighSpeedWire_C + DropPodData(-146044,-137047,2357, "Modular Frame", 0), # Unlocks with: 9 x Desc_ModularFrame_C + DropPodData(-200203,-17766, 12193, "Solid Biofuel", 0), # Unlocks with: 10 x Desc_Biofuel_C + DropPodData(47834, 195703, 2943, "Black Powder", 0), # Unlocks with: 4 x Desc_Gunpowder_C + DropPodData(198418, -41186, 13786, None, 0), # Unlocks with: (No Item) + DropPodData(-195756,-59210, -84, None, 30), # Unlocks with: (No Item) + DropPodData(-121994,166916, -49, "Steel Beam", 20), # Unlocks with: 4 x Desc_SteelPlate_C + DropPodData(88323, 188913, 1420, None, 30), # Unlocks with: (No Item) + DropPodData(-123677,-167107,29710, "Motor", 0), # Unlocks with: 4 x Desc_Motor_C + DropPodData(150633, 146698, 7727, "Crystal Oscillator", 20), # Unlocks with: 2 x Desc_CrystalOscillator_C + DropPodData(-55111, -204857,7844, "Motor", 0), # Unlocks with: 30 x Desc_Motor_C + DropPodData(216096, -268, -1592, "Heat Sink", 0), # Unlocks with: 7 x Desc_AluminumPlateReinforced_C + DropPodData(159088, -145116,23164, "Motor", 0), # Unlocks with: 30 x Desc_Motor_C + DropPodData(207683, -68352, 3927, "Encased Industrial Beam", 20), # Unlocks with: 27 x Desc_SteelPlateReinforced_C + DropPodData(-189258,116331, -1764, None, 0), # Unlocks with: (No Item) + DropPodData(46951, 221859, 5917, None, 20), # Unlocks with: (No Item) + DropPodData(-9988, 227625, -1017, None, 40), # Unlocks with: (No Item) + DropPodData(232515, -20519, 8979, "Crystal Oscillator", 15), # Unlocks with: 2 x Desc_CrystalOscillator_C + DropPodData(232138,27191, -1629, "Supercomputer", 0), # Unlocks with: 5 x Desc_ComputerSuper_C + DropPodData(-135, -237257,-1760, None, 0), # Unlocks with: (No Item) + DropPodData(-232498,-51432, -386, "Rotor", 0), # Unlocks with: 21 x Desc_Rotor_C + DropPodData(-238333,17321, 19741, "Heat Sink", 0), # Unlocks with: 3 x Desc_AluminumPlateReinforced_C + DropPodData(200510, 131912, 6341, "Motor", 0), # Unlocks with: 30 x Desc_Motor_C + DropPodData(-108812,214051, 3200, "Quickwire", 0), # Unlocks with: 1 x Desc_HighSpeedWire_C + DropPodData(232255, 79925, -1275, "Turbo Motor", 67), # Unlocks with: 2 x Desc_MotorLightweight_C + DropPodData(226418, 98109, 7339, None, 200), # Unlocks with: (No Item) + DropPodData(156569, 191767, -9312, "Rubber", 0), # Unlocks with: 4 x Desc_Rubber_C + DropPodData(44579, -244343,-874, None, 0), # Unlocks with: (No Item) + DropPodData(118349, 221905, -7063, "Encased Industrial Beam", 0), # Unlocks with: 6 x Desc_SteelPlateReinforced_C + #DropPodData(249919,59534, 2430, "Quantum Computer", 0), # Unlocks with: 1 x Desc_ComputerQuantum_C + DropPodData(188233, 177201, 9608, "Quickwire", 0), # Unlocks with: 12 x Desc_HighSpeedWire_C + DropPodData(-174494,-197134,-1538, None, 30), # Unlocks with: (No Item) + DropPodData(-50655, -259272,-1667, None, 0), # Unlocks with: (No Item) + DropPodData(30383, 266975, -987, "Screw", 0), # Unlocks with: 12 x Desc_IronScrew_C + DropPodData(272715, 28087, -1586, "Supercomputer", 0), # Unlocks with: 2 x Desc_ComputerSuper_C + DropPodData(-152279,229520,1052, "Modular Frame", 0), # Unlocks with: 5 x Desc_ModularFrame_C + DropPodData(241532, 131343, 17157, None, 0), # Unlocks with: (No Item) + DropPodData(-259577,105048, -1548, None, 0), # Unlocks with: (No Item) + DropPodData(275070, -52585, 5980, None, 0), # Unlocks with: (No Item) + DropPodData(-247303,-142348,4524, "Rotor", 0), # Unlocks with: 4 x Desc_Rotor_C + DropPodData(261797, 124616, -2597, "AI Limiter", 73), # Unlocks with: 3 x Desc_CircuitBoardHighSpeed_C + DropPodData(187056, 223656, -3215, None, 42), # Unlocks with: (No Item) + DropPodData(293299, 51, 522, "Crystal Oscillator", 42), # Unlocks with: 8 x Desc_CrystalOscillator_C + DropPodData(219146, -199880,6503, "Rotor", 0), # Unlocks with: 10 x Desc_Rotor_C + DropPodData(176423, 243273, -9780, "Motor", 19), # Unlocks with: 3 x Desc_Motor_C + DropPodData(291821, 74782, -1574, "Superposition Oscillator", 0), # Unlocks with: 5 x Desc_QuantumOscillator_C + DropPodData(-78884, 292640, -4763, "Modular Frame", 0), # Unlocks with: 5 x Desc_ModularFrame_C + DropPodData(174948, -276436,21151, "Motor", 0), # Unlocks with: 30 x Desc_Motor_C + DropPodData(295166, -173139,8083, None, 0), # Unlocks with: (No Item) + DropPodData(349295, -38831, -1485, "Motor", 0), # Unlocks with: 10 x Desc_Motor_C + DropPodData(360114, -106614,11815, "Motor", 0), # Unlocks with: 35 x Desc_Motor_C + DropPodData(303169, -246169,5487, None, 50), # Unlocks with: (No Item) + DropPodData(236508, -312236,9971, "Motor", 0), # Unlocks with: 30 x Desc_Motor_C + DropPodData(360285, -217558,3900, None, 70), # Unlocks with: (No Item) + DropPodData(366637, -303548,-7288, None, 0), # Unlocks with: (No Item) + ] + + diff --git a/worlds/satisfactory/ItemData.py b/worlds/satisfactory/ItemData.py new file mode 100644 index 0000000000..2f8dd991e2 --- /dev/null +++ b/worlds/satisfactory/ItemData.py @@ -0,0 +1,41 @@ +from enum import Enum +from typing import NamedTuple, Set +from BaseClasses import ItemClassification + +class ItemGroups(str, Enum): + Parts = 1 + Equipment = 2 + Ammo = 3 + Recipe = 4 + Building = 5 + Trap = 6 + Lights = 7 + Foundations = 8 + Transport = 9 + Trains = 10 + ConveyorMk1 = 11 + ConveyorMk2 = 12 + ConveyorMk3 = 13 + ConveyorMk4 = 14 + ConveyorMk5 = 15 + ConveyorSupports = 16 + PipesMk1 = 17 + PipesMk2 = 18 + PipelineSupports = 19 + HyperTubes = 20 + Signs = 21 + Pilars = 22 + Beams = 23 + Walls = 24 + Upgrades = 25 + Vehicles = 26 + Customizer = 27 + ConveyorMk6 = 28 + +class ItemData(NamedTuple): + """Represents an item in the pool, it could be a resource bundle, production recipe, trap, etc.""" + category: Set[ItemGroups] + code: int + type: ItemClassification = ItemClassification.filler + count: int = 1 + """How many of this item exists in the pool. 0 means none, but still defines the item so it can be added in the starting inventory for example""" diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py new file mode 100644 index 0000000000..735b06e0a1 --- /dev/null +++ b/worlds/satisfactory/Items.py @@ -0,0 +1,890 @@ +import copy +from random import Random +from typing import ClassVar, Dict, Set, List, TextIO, Tuple, Optional +from BaseClasses import Item, ItemClassification as C, MultiWorld +from .GameLogic import GameLogic, Recipe +from .Options import SatisfactoryOptions +from .ItemData import ItemData, ItemGroups as G +from .Options import SatisfactoryOptions +import logging + + +class Items: + item_data: ClassVar[Dict[str, ItemData]] = { + # Resource Bundles + "Bundle: Adaptive Control Unit": ItemData(frozenset({G.Parts}), 1338000), + "Bundle: AI Limiter": ItemData(frozenset({G.Parts}), 1338001), + "Bundle: Alclad Aluminum Sheet": ItemData(frozenset({G.Parts}), 1338002), + "Bundle: Blue Power Slug": ItemData(frozenset({G.Parts}), 1338003), + "Bundle: Yellow Power Slug": ItemData(frozenset({G.Parts}), 1338004), + "Bundle: Alien Protein": ItemData(frozenset({G.Parts}), 1338005), + "Bundle: Purple Power Slug": ItemData(frozenset({G.Parts}), 1338006), + "Bundle: Aluminum Casing": ItemData(frozenset({G.Parts}), 1338007), + "Bundle: Aluminum Ingot": ItemData(frozenset({G.Parts}), 1338008), + "Bundle: Aluminum Scrap": ItemData(frozenset({G.Parts}), 1338009), + "Bundle: Assembly Director System": ItemData(frozenset({G.Parts}), 1338010), + "Bundle: Automated Wiring": ItemData(frozenset({G.Parts}), 1338011), + "Bundle: Battery": ItemData(frozenset({G.Parts}), 1338012), + "Bundle: Bauxite": ItemData(frozenset({G.Parts}), 1338013), + "Bundle: Neural-Quantum Processor": ItemData(frozenset({G.Parts}), 1338014), #1.0 + "Bundle: Biomass": ItemData(frozenset({G.Parts}), 1338015), + "Bundle: Black Powder": ItemData(frozenset({G.Parts}), 1338016), + "Bundle: Cable": ItemData(frozenset({G.Parts}), 1338017), + "Bundle: Caterium Ingot": ItemData(frozenset({G.Parts}), 1338018), + "Bundle: Caterium Ore": ItemData(frozenset({G.Parts}), 1338019), + "Bundle: Circuit Board": ItemData(frozenset({G.Parts}), 1338020), + "Bundle: Coal": ItemData(frozenset({G.Parts}), 1338021), + "Bundle: Singularity Cell": ItemData(frozenset({G.Parts}), 1338022), #1.0 + "Bundle: Compacted Coal": ItemData(frozenset({G.Parts}), 1338023), + "Bundle: Computer": ItemData(frozenset({G.Parts}), 1338024), + "Bundle: Concrete": ItemData(frozenset({G.Parts}), 1338025), + "Bundle: Cooling System": ItemData(frozenset({G.Parts}), 1338026), + "Bundle: Copper Ingot": ItemData(frozenset({G.Parts}), 1338027), + "Bundle: Copper Ore": ItemData(frozenset({G.Parts}), 1338028), + "Bundle: Copper Powder": ItemData(frozenset({G.Parts}), 1338029), + "Bundle: Copper Sheet": ItemData(frozenset({G.Parts}), 1338030), + "Bundle: Adequate Pioneering Statue": ItemData(frozenset({G.Parts}), 1338031), + "Bundle: Crystal Oscillator": ItemData(frozenset({G.Parts}), 1338032), + "Bundle: Electromagnetic Control Rod": ItemData(frozenset({G.Parts}), 1338033), + "Bundle: Empty Canister": ItemData(frozenset({G.Parts}), 1338034), + "Bundle: Empty Fluid Tank": ItemData(frozenset({G.Parts}), 1338035), + "Bundle: Encased Industrial Beam": ItemData(frozenset({G.Parts}), 1338036), + "Bundle: Encased Plutonium Cell": ItemData(frozenset({G.Trap}), 1338037, C.trap), + "Bundle: Encased Uranium Cell": ItemData(frozenset({G.Trap}), 1338038, C.trap), + "Bundle: Fabric": ItemData(frozenset({G.Parts}), 1338039), + "Bundle: FICSIT Coupon": ItemData(frozenset({G.Parts}), 1338040), + "Bundle: AI Expansion Server": ItemData(frozenset({G.Parts}), 1338041), #1.0 + "Bundle: Fused Modular Frame": ItemData(frozenset({G.Parts}), 1338042), + "Bundle: Hard Drive": ItemData(frozenset({G.Parts}), 1338043), + "Bundle: Heat Sink": ItemData(frozenset({G.Parts}), 1338044), + "Bundle: Heavy Modular Frame": ItemData(frozenset({G.Parts}), 1338045), + "Bundle: High-Speed Connector": ItemData(frozenset({G.Parts}), 1338046), + "Bundle: Satisfactory Pioneering Statue": ItemData(frozenset({G.Parts}), 1338047), + "Bundle: Pretty Good Pioneering Statue": ItemData(frozenset({G.Parts}), 1338048), + "Bundle: Iron Ingot": ItemData(frozenset({G.Parts}), 1338049), + "Bundle: Iron Ore": ItemData(frozenset({G.Parts}), 1338050), + "Bundle: Iron Plate": ItemData(frozenset({G.Parts}), 1338051), + "Bundle: Iron Rod": ItemData(frozenset({G.Parts}), 1338052), + "Bundle: Golden Nut Statue": ItemData(frozenset({G.Parts}), 1338053), + "Bundle: Leaves": ItemData(frozenset({G.Parts}), 1338054), + "Bundle: Limestone": ItemData(frozenset({G.Parts}), 1338055), + "Bundle: Magnetic Field Generator": ItemData(frozenset({G.Parts}), 1338056), + "Bundle: Mercer Sphere": ItemData(frozenset({G.Parts}), 1338057), + "Bundle: Modular Engine": ItemData(frozenset({G.Parts}), 1338058), + "Bundle: Modular Frame": ItemData(frozenset({G.Parts}), 1338059), + "Bundle: Motor": ItemData(frozenset({G.Parts}), 1338060), + "Bundle: Mycelia": ItemData(frozenset({G.Parts}), 1338061), + "Bundle: Non-fissile Uranium": ItemData(frozenset({G.Trap}), 1338062, C.trap), + "Bundle: Nuclear Pasta": ItemData(frozenset({G.Parts}), 1338063), + "Bundle: Lizard Doggo Statue": ItemData(frozenset({G.Parts}), 1338064), + "Bundle: Organic Data Capsule": ItemData(frozenset({G.Parts}), 1338065), + "Bundle: Packaged Alumina Solution": ItemData(frozenset({G.Parts}), 1338066), + "Bundle: Packaged Fuel": ItemData(frozenset({G.Parts}), 1338067), + "Bundle: Packaged Heavy Oil Residue": ItemData(frozenset({G.Parts}), 1338068), + "Bundle: Packaged Liquid Biofuel": ItemData(frozenset({G.Parts}), 1338069), + "Bundle: Packaged Nitric Acid": ItemData(frozenset({G.Parts}), 1338070), + "Bundle: Packaged Nitrogen Gas": ItemData(frozenset({G.Parts}), 1338071), + "Bundle: Packaged Oil": ItemData(frozenset({G.Parts}), 1338072), + "Bundle: Packaged Sulfuric Acid": ItemData(frozenset({G.Parts}), 1338073), + "Bundle: Packaged Turbofuel": ItemData(frozenset({G.Parts}), 1338074), + "Bundle: Packaged Water": ItemData(frozenset({G.Parts}), 1338075), + "Bundle: Petroleum Coke": ItemData(frozenset({G.Parts}), 1338076), + "Bundle: Plastic": ItemData(frozenset({G.Parts}), 1338077), + "Bundle: Plutonium Fuel Rod": ItemData(frozenset({G.Trap}), 1338078, C.trap), + "Bundle: Plutonium Pellet": ItemData(frozenset({G.Trap}), 1338079, C.trap), + "Bundle: Plutonium Waste": ItemData(frozenset({G.Trap}), 1338080, C.trap), + "Bundle: Polymer Resin": ItemData(frozenset({G.Parts}), 1338081), + "Bundle: Power Shard": ItemData(frozenset({G.Parts}), 1338082), + "Bundle: Confusing Creature Statue": ItemData(frozenset({G.Parts}), 1338083), + "Bundle: Pressure Conversion Cube": ItemData(frozenset({G.Parts}), 1338084), + "Bundle: Alien Power Matrix": ItemData(frozenset({G.Parts}), 1338085), #1.0 + "Bundle: Quartz Crystal": ItemData(frozenset({G.Parts}), 1338086), + "Bundle: Quickwire": ItemData(frozenset({G.Parts}), 1338087), + "Bundle: Radio Control Unit": ItemData(frozenset({G.Parts}), 1338088), + "Bundle: Raw Quartz": ItemData(frozenset({G.Parts}), 1338089), + "Bundle: Reinforced Iron Plate": ItemData(frozenset({G.Parts}), 1338090), + "Bundle: Rotor": ItemData(frozenset({G.Parts}), 1338091), + "Bundle: Rubber": ItemData(frozenset({G.Parts}), 1338092), + "Bundle: SAM": ItemData(frozenset({G.Parts}), 1338093), # 1.0 + "Bundle: Screw": ItemData(frozenset({G.Parts}), 1338094), + "Bundle: Silica": ItemData(frozenset({G.Parts}), 1338095), + "Bundle: Smart Plating": ItemData(frozenset({G.Parts}), 1338096), + "Bundle: Smokeless Powder": ItemData(frozenset({G.Parts}), 1338097), + "Bundle: Solid Biofuel": ItemData(frozenset({G.Parts}), 1338098), + "Bundle: Somersloop": ItemData(frozenset({G.Parts}), 1338099), + "Bundle: Stator": ItemData(frozenset({G.Parts}), 1338100), + "Bundle: Silver Hog Statue": ItemData(frozenset({G.Parts}), 1338101), + "Bundle: Steel Beam": ItemData(frozenset({G.Parts}), 1338102), + "Bundle: Steel Ingot": ItemData(frozenset({G.Parts}), 1338103), + "Bundle: Steel Pipe": ItemData(frozenset({G.Parts}), 1338104), + "Bundle: Sulfur": ItemData(frozenset({G.Parts}), 1338105), + "Bundle: Supercomputer": ItemData(frozenset({G.Parts}), 1338106), + "Bundle: Superposition Oscillator": ItemData(frozenset({G.Parts}), 1338107), + "Bundle: Thermal Propulsion Rocket": ItemData(frozenset({G.Parts}), 1338108), + "Bundle: Turbo Motor": ItemData(frozenset({G.Parts}), 1338109), + "Bundle: Hog Remains": ItemData(frozenset({G.Parts}), 1338110), + "Bundle: Uranium": ItemData(frozenset({G.Trap}), 1338111, C.trap), + "Bundle: Uranium Fuel Rod": ItemData(frozenset({G.Trap}), 1338112, C.trap), + "Bundle: Uranium Waste": ItemData(frozenset({G.Trap}), 1338113, C.trap), + "Bundle: Versatile Framework": ItemData(frozenset({G.Parts}), 1338114), + "Bundle: Wire": ItemData(frozenset({G.Parts}), 1338115), + "Bundle: Wood": ItemData(frozenset({G.Parts}), 1338116), + "Bundle: Plasma Spitter Remains": ItemData(frozenset({G.Parts}), 1338117), + "Bundle: Stinger Remains": ItemData(frozenset({G.Parts}), 1338118), + "Bundle: Hatcher Remains": ItemData(frozenset({G.Parts}), 1338119), + "Bundle: Alien DNA Capsule": ItemData(frozenset({G.Parts}), 1338120), +# 1.0 + "Bundle: Diamonds": ItemData(frozenset({G.Parts}), 1338121), + "Bundle: Time Crystal": ItemData(frozenset({G.Parts}), 1338122), + "Bundle: Ficsite Ingot": ItemData(frozenset({G.Parts}), 1338123), + "Bundle: Ficsite Trigon": ItemData(frozenset({G.Parts}), 1338124), + "Bundle: Reanimated SAM": ItemData(frozenset({G.Parts}), 1338125), + "Bundle: SAM Fluctuator": ItemData(frozenset({G.Parts}), 1338126), + "Bundle: Biochemical Sculptor": ItemData(frozenset({G.Parts}), 1338127), + "Bundle: Ballistic Warp Drive": ItemData(frozenset({G.Parts}), 1338128), + "Bundle: Ficsonium": ItemData(frozenset({G.Trap}), 1338129, C.trap), + "Bundle: Ficsonium Fuel Rod": ItemData(frozenset({G.Trap}), 1338130, C.trap), + "Bundle: Packaged Rocket Fuel": ItemData(frozenset({G.Parts}), 1338131), + "Bundle: Packaged Ionized Fuel": ItemData(frozenset({G.Parts}), 1338132), +# 1.0 + + #1338131 - 1338149 Reserved for future parts + + # Equipment / Ammo + "Bundle: Bacon Agaric": ItemData(frozenset({G.Ammo}), 1338150), + "Bundle: Beryl Nut": ItemData(frozenset({G.Ammo}), 1338151), + "Bundle: Blade Runners": ItemData(frozenset({G.Equipment}), 1338152), + "Bundle: Boom Box": ItemData(frozenset({G.Equipment}), 1338153), + "Bundle: Chainsaw": ItemData(frozenset({G.Equipment}), 1338154), + "Bundle: Cluster Nobelisk": ItemData(frozenset({G.Ammo}), 1338155), + #"Bundle: Color Gun": ItemData(frozenset({G.Equipment}), 1338156), Removed in U8 + "Bundle: Cup": ItemData(frozenset({G.Equipment}), 1338157), + "Bundle: Cup (gold)": ItemData(frozenset({G.Equipment}), 1338158, count=0), + "Bundle: Explosive Rebar": ItemData(frozenset({G.Ammo}), 1338159), + "Bundle: Factory Cart": ItemData(frozenset({G.Equipment}), 1338160), + "Bundle: Factory Cart (golden)": ItemData(frozenset({G.Equipment}), 1338161, count=0), + "Bundle: Gas Mask": ItemData(frozenset({G.Equipment}), 1338162), + "Bundle: Gas Nobelisk": ItemData(frozenset({G.Ammo}), 1338163), + "Bundle: Hazmat Suit": ItemData(frozenset({G.Equipment}), 1338164), + "Bundle: Homing Rifle Ammo": ItemData(frozenset({G.Ammo}), 1338165), + "Bundle: Hover Pack": ItemData(frozenset({G.Equipment}), 1338166), + "Bundle: Iron Rebar": ItemData(frozenset({G.Ammo}), 1338167), + "Bundle: Jetpack": ItemData(frozenset({G.Equipment}), 1338168), + "Bundle: Medicinal Inhaler": ItemData(frozenset({G.Ammo}), 1338169), + "Bundle: Nobelisk": ItemData(frozenset({G.Ammo}), 1338170), + "Bundle: Nobelisk Detonator": ItemData(frozenset({G.Equipment}), 1338171), + "Bundle: Nuke Nobelisk": ItemData(frozenset({G.Ammo}), 1338172), + "Bundle: Object Scanner": ItemData(frozenset({G.Equipment}), 1338173), + "Bundle: Paleberry": ItemData(frozenset({G.Ammo}), 1338174), + "Bundle: Parachute": ItemData(frozenset({G.Equipment}), 1338175), + "Bundle: Pulse Nobelisk": ItemData(frozenset({G.Ammo}), 1338176), + "Bundle: Rebar Gun": ItemData(frozenset({G.Equipment}), 1338177), + "Bundle: Rifle": ItemData(frozenset({G.Equipment}), 1338178), + "Bundle: Rifle Ammo": ItemData(frozenset({G.Ammo}), 1338179), + "Bundle: Shatter Rebar": ItemData(frozenset({G.Ammo}), 1338180), + "Bundle: Stun Rebar": ItemData(frozenset({G.Ammo}), 1338181), + "Bundle: Turbo Rifle Ammo": ItemData(frozenset({G.Ammo}), 1338182), + "Bundle: Xeno-Basher": ItemData(frozenset({G.Equipment}), 1338183), + "Bundle: Xeno-Zapper": ItemData(frozenset({G.Equipment}), 1338184), + "Bundle: Zipline": ItemData(frozenset({G.Equipment}), 1338185), + "Bundle: Portable Miner": ItemData(frozenset({G.Equipment}), 1338186), + "Bundle: Gas Filter": ItemData(frozenset({G.Ammo}), 1338187), + # Other + "Small Inflated Pocket Dimension": ItemData(frozenset({G.Upgrades}), 1338188, C.useful, 11), + "Inflated Pocket Dimension": ItemData(frozenset({G.Upgrades}), 1338189, C.useful, 5), + "Expanded Toolbelt": ItemData(frozenset({G.Upgrades}), 1338190, C.useful, 5), + "Dimensional Depot upload from inventory": ItemData(frozenset({G.Upgrades}), 1338191, C.useful), + + #1338191 - 1338199 Reserved for future equipment/ammo + + #1338200+ Recipes / buildings / schematics + "Recipe: Reinforced Iron Plate": ItemData(frozenset({G.Recipe}), 1338200, C.progression), + "Recipe: Adhered Iron Plate": ItemData(frozenset({G.Recipe}), 1338201, C.progression), + "Recipe: Bolted Iron Plate": ItemData(frozenset({G.Recipe}), 1338202, C.progression), + "Recipe: Stitched Iron Plate": ItemData(frozenset({G.Recipe}), 1338203, C.progression), + "Recipe: Rotor": ItemData(frozenset({G.Recipe}), 1338204, C.progression), + "Recipe: Copper Rotor": ItemData(frozenset({G.Recipe}), 1338205, C.progression), + "Recipe: Steel Rotor": ItemData(frozenset({G.Recipe}), 1338206, C.progression), + "Recipe: Stator": ItemData(frozenset({G.Recipe}), 1338207, C.progression), + "Recipe: Quickwire Stator": ItemData(frozenset({G.Recipe}), 1338208, C.progression), + "Recipe: Plastic": ItemData(frozenset({G.Recipe}), 1338209, C.progression), + "Recipe: Residual Plastic": ItemData(frozenset({G.Recipe}), 1338210, C.progression), + "Recipe: Recycled Plastic": ItemData(frozenset({G.Recipe}), 1338211, C.progression), + "Recipe: Rubber": ItemData(frozenset({G.Recipe}), 1338212, C.progression), + "Recipe: Residual Rubber": ItemData(frozenset({G.Recipe}), 1338213, C.progression), + "Recipe: Recycled Rubber": ItemData(frozenset({G.Recipe}), 1338214, C.progression), + "Recipe: Iron Plate": ItemData(frozenset({G.Recipe}), 1338215, C.progression), + "Recipe: Coated Iron Plate": ItemData(frozenset({G.Recipe}), 1338216, C.progression), + "Recipe: Steel Cast Plate": ItemData(frozenset({G.Recipe}), 1338217, C.progression), # 1.0 + "Recipe: Iron Rod": ItemData(frozenset({G.Recipe}), 1338218, C.progression), + "Recipe: Steel Rod": ItemData(frozenset({G.Recipe}), 1338219, C.progression), + "Recipe: Screw": ItemData(frozenset({G.Recipe}), 1338220, C.progression), + "Recipe: Cast Screw": ItemData(frozenset({G.Recipe}), 1338221, C.progression), + "Recipe: Steel Screw": ItemData(frozenset({G.Recipe}), 1338222, C.progression), + "Recipe: Wire": ItemData(frozenset({G.Recipe}), 1338223, C.progression), + "Recipe: Fused Wire": ItemData(frozenset({G.Recipe}), 1338224, C.progression), + "Recipe: Iron Wire": ItemData(frozenset({G.Recipe}), 1338225, C.progression), + "Recipe: Caterium Wire": ItemData(frozenset({G.Recipe}), 1338226, C.progression), + "Recipe: Cable": ItemData(frozenset({G.Recipe}), 1338227, C.progression), + "Recipe: Coated Cable": ItemData(frozenset({G.Recipe}), 1338228, C.progression), + "Recipe: Insulated Cable": ItemData(frozenset({G.Recipe}), 1338229, C.progression), + "Recipe: Quickwire Cable": ItemData(frozenset({G.Recipe}), 1338230, C.progression), + "Recipe: Quickwire": ItemData(frozenset({G.Recipe}), 1338231, C.progression), + "Recipe: Fused Quickwire": ItemData(frozenset({G.Recipe}), 1338232, C.progression), + "Recipe: Copper Sheet": ItemData(frozenset({G.Recipe}), 1338233, C.progression), + "Recipe: Steamed Copper Sheet": ItemData(frozenset({G.Recipe}), 1338234, C.progression), + "Recipe: Steel Pipe": ItemData(frozenset({G.Recipe}), 1338235, C.progression), + "Recipe: Steel Beam": ItemData(frozenset({G.Recipe}), 1338236, C.progression), + "Recipe: Neural-Quantum Processor": ItemData(frozenset({G.Recipe}), 1338237, C.progression), # 1.0 + "Recipe: Heavy Oil Residue": ItemData(frozenset({G.Recipe}), 1338238, C.progression), + "Recipe: Polymer Resin": ItemData(frozenset({G.Recipe}), 1338239, C.progression), + "Recipe: Fuel": ItemData(frozenset({G.Recipe}), 1338240, C.progression), + "Recipe: Residual Fuel": ItemData(frozenset({G.Recipe}), 1338241, C.progression), + "Recipe: Diluted Fuel (refinery)": ItemData(frozenset({G.Recipe}), 1338242, C.progression), + "Recipe: AI Expansion Server": ItemData(frozenset({G.Recipe}), 1338243, C.progression), # 1.0 + "Recipe: Concrete": ItemData(frozenset({G.Recipe}), 1338244, C.progression), + "Recipe: Rubber Concrete": ItemData(frozenset({G.Recipe}), 1338245, C.progression), + "Recipe: Wet Concrete": ItemData(frozenset({G.Recipe}), 1338246, C.progression), + "Recipe: Fine Concrete": ItemData(frozenset({G.Recipe}), 1338247, C.progression), + "Recipe: Silica": ItemData(frozenset({G.Recipe}), 1338248, C.progression), + "Recipe: Cheap Silica": ItemData(frozenset({G.Recipe}), 1338249, C.progression), + "Recipe: Quartz Crystal": ItemData(frozenset({G.Recipe}), 1338250, C.progression), + "Recipe: Pure Quartz Crystal": ItemData(frozenset({G.Recipe}), 1338251, C.progression), + "Recipe: Iron Ingot": ItemData(frozenset({G.Recipe}), 1338252, C.progression), + "Recipe: Pure Iron Ingot": ItemData(frozenset({G.Recipe}), 1338253, C.progression), + "Recipe: Iron Alloy Ingot": ItemData(frozenset({G.Recipe}), 1338254, C.progression), + "Recipe: Steel Ingot": ItemData(frozenset({G.Recipe}), 1338255, C.progression), + "Recipe: Coke Steel Ingot": ItemData(frozenset({G.Recipe}), 1338256, C.progression), + "Recipe: Compacted Steel Ingot": ItemData(frozenset({G.Recipe}), 1338257, C.progression), + "Recipe: Solid Steel Ingot": ItemData(frozenset({G.Recipe}), 1338258, C.progression), + "Recipe: Copper Ingot": ItemData(frozenset({G.Recipe}), 1338259, C.progression), + "Recipe: Copper Alloy Ingot": ItemData(frozenset({G.Recipe}), 1338260, C.progression), + "Recipe: Pure Copper Ingot": ItemData(frozenset({G.Recipe}), 1338261, C.progression), + "Recipe: Caterium Ingot": ItemData(frozenset({G.Recipe}), 1338262, C.progression), + "Recipe: Pure Caterium Ingot": ItemData(frozenset({G.Recipe}), 1338263, C.progression), + "Recipe: Alien Power Matrix": ItemData(frozenset({G.Recipe}), 1338264), # 1.0 + "Recipe: Ficsite Ingot (Aluminum)": ItemData(frozenset({G.Recipe}), 1338265, C.progression), # 1.0 + "Recipe: Ficsite Ingot (Caterium)": ItemData(frozenset({G.Recipe}), 1338266, C.progression), # 1.0 + "Recipe: Ficsite Ingot (Iron)": ItemData(frozenset({G.Recipe}), 1338267, C.progression), # 1.0 + "Recipe: Ficsite Trigon": ItemData(frozenset({G.Recipe}), 1338268, C.progression), # 1.0 + "Recipe: Reanimated SAM": ItemData(frozenset({G.Recipe}), 1338269, C.progression), # 1.0 + "Recipe: SAM Fluctuator": ItemData(frozenset({G.Recipe}), 1338270, C.progression), # 1.0 + "Recipe: Petroleum Coke": ItemData(frozenset({G.Recipe}), 1338271, C.progression), + "Recipe: Compacted Coal": ItemData(frozenset({G.Recipe}), 1338272, C.progression), + "Recipe: Motor": ItemData(frozenset({G.Recipe}), 1338273, C.progression), + "Recipe: Rigor Motor": ItemData(frozenset({G.Recipe}), 1338274, C.progression), + "Recipe: Electric Motor": ItemData(frozenset({G.Recipe}), 1338275, C.progression), + "Recipe: Modular Frame": ItemData(frozenset({G.Recipe}), 1338276, C.progression), + "Recipe: Bolted Frame": ItemData(frozenset({G.Recipe}), 1338277, C.progression), + "Recipe: Steeled Frame": ItemData(frozenset({G.Recipe}), 1338278, C.progression), + "Recipe: Heavy Modular Frame": ItemData(frozenset({G.Recipe}), 1338279, C.progression), + "Recipe: Heavy Flexible Frame": ItemData(frozenset({G.Recipe}), 1338280, C.progression), + "Recipe: Heavy Encased Frame": ItemData(frozenset({G.Recipe}), 1338281, C.progression), + "Recipe: Encased Industrial Beam": ItemData(frozenset({G.Recipe}), 1338282, C.progression), + "Recipe: Encased Industrial Pipe": ItemData(frozenset({G.Recipe}), 1338283, C.progression), + "Recipe: Computer": ItemData(frozenset({G.Recipe}), 1338284, C.progression), + "Recipe: Crystal Computer": ItemData(frozenset({G.Recipe}), 1338285, C.progression), + "Recipe: Caterium Computer": ItemData(frozenset({G.Recipe}), 1338286, C.progression), + "Recipe: Circuit Board": ItemData(frozenset({G.Recipe}), 1338287, C.progression), + "Recipe: Electrode Circuit Board": ItemData(frozenset({G.Recipe}), 1338288, C.progression), + "Recipe: Silicon Circuit Board": ItemData(frozenset({G.Recipe}), 1338289, C.progression), + "Recipe: Caterium Circuit Board": ItemData(frozenset({G.Recipe}), 1338290, C.progression), + "Recipe: Crystal Oscillator": ItemData(frozenset({G.Recipe}), 1338291, C.progression), + "Recipe: Insulated Crystal Oscillator": ItemData(frozenset({G.Recipe}), 1338292, C.progression), + "Recipe: AI Limiter": ItemData(frozenset({G.Recipe}), 1338293, C.progression), + "Recipe: Electromagnetic Control Rod": ItemData(frozenset({G.Recipe}), 1338294, C.progression), + "Recipe: Electromagnetic Connection Rod": ItemData(frozenset({G.Recipe}), 1338295, C.progression), + "Recipe: High-Speed Connector": ItemData(frozenset({G.Recipe}), 1338296, C.progression), + "Recipe: Silicon High-Speed Connector": ItemData(frozenset({G.Recipe}), 1338297, C.progression), + "Recipe: Smart Plating": ItemData(frozenset({G.Recipe}), 1338298, C.progression), + "Recipe: Plastic Smart Plating": ItemData(frozenset({G.Recipe}), 1338299, C.progression), + "Recipe: Versatile Framework": ItemData(frozenset({G.Recipe}), 1338300, C.progression), + "Recipe: Flexible Framework": ItemData(frozenset({G.Recipe}), 1338301, C.progression), + "Recipe: Automated Wiring": ItemData(frozenset({G.Recipe}), 1338302, C.progression), + "Recipe: Automated Speed Wiring": ItemData(frozenset({G.Recipe}), 1338303, C.progression), + "Recipe: Modular Engine": ItemData(frozenset({G.Recipe}), 1338304, C.progression), + "Recipe: Adaptive Control Unit": ItemData(frozenset({G.Recipe}), 1338305, C.progression), + "Recipe: Diluted Fuel": ItemData(frozenset({G.Recipe}), 1338306, C.progression), + "Recipe: Alumina Solution": ItemData(frozenset({G.Recipe}), 1338307, C.progression), + "Recipe: Automated Miner": ItemData(frozenset({G.Recipe}), 1338308, C.progression), + "Recipe: Singularity Cell": ItemData(frozenset({G.Recipe}), 1338309, C.progression), # 1.0 + "Recipe: Aluminum Scrap": ItemData(frozenset({G.Recipe}), 1338310, C.progression), + "Recipe: Electrode Aluminum Scrap": ItemData(frozenset({G.Recipe}), 1338311, C.progression), + "Recipe: Instant Scrap": ItemData(frozenset({G.Recipe}), 1338312, C.progression), + "Recipe: Aluminum Ingot": ItemData(frozenset({G.Recipe}), 1338313, C.progression), + "Recipe: Pure Aluminum Ingot": ItemData(frozenset({G.Recipe}), 1338314, C.progression), + "Recipe: Alclad Aluminum Sheet": ItemData(frozenset({G.Recipe}), 1338315, C.progression), + "Recipe: Aluminum Casing": ItemData(frozenset({G.Recipe}), 1338316, C.progression), + "Recipe: Alclad Casing": ItemData(frozenset({G.Recipe}), 1338317, C.progression), + "Recipe: Heat Sink": ItemData(frozenset({G.Recipe}), 1338318, C.progression), + "Recipe: Heat Exchanger": ItemData(frozenset({G.Recipe}), 1338319, C.progression), + "Recipe: Synthetic Power Shard": ItemData(frozenset({G.Recipe}), 1338320, C.progression), + "Recipe: Nitric Acid": ItemData(frozenset({G.Recipe}), 1338321, C.progression), + "Recipe: Fused Modular Frame": ItemData(frozenset({G.Recipe}), 1338322, C.progression), + "Recipe: Heat-Fused Frame": ItemData(frozenset({G.Recipe}), 1338323, C.progression), + "Recipe: Radio Control Unit": ItemData(frozenset({G.Recipe}), 1338324, C.progression), + "Recipe: Radio Connection Unit": ItemData(frozenset({G.Recipe}), 1338325, C.progression), + "Recipe: Radio Control System": ItemData(frozenset({G.Recipe}), 1338326, C.progression), + "Recipe: Pressure Conversion Cube": ItemData(frozenset({G.Recipe}), 1338327, C.progression), + "Recipe: Cooling System": ItemData(frozenset({G.Recipe}), 1338328, C.progression), + "Recipe: Cooling Device": ItemData(frozenset({G.Recipe}), 1338329, C.progression), + "Recipe: Turbo Motor": ItemData(frozenset({G.Recipe}), 1338330, C.progression), + "Recipe: Turbo Electric Motor": ItemData(frozenset({G.Recipe}), 1338331, C.progression), + "Recipe: Turbo Pressure Motor": ItemData(frozenset({G.Recipe}), 1338332, C.progression), + "Recipe: Battery": ItemData(frozenset({G.Recipe}), 1338333, C.progression), + "Recipe: Classic Battery": ItemData(frozenset({G.Recipe}), 1338334, C.progression), + "Recipe: Supercomputer": ItemData(frozenset({G.Recipe}), 1338335, C.progression), + "Recipe: OC Supercomputer": ItemData(frozenset({G.Recipe}), 1338336, C.progression), + "Recipe: Super-State Computer": ItemData(frozenset({G.Recipe}), 1338337, C.progression), + "Recipe: Biochemical Sculptor": ItemData(frozenset({G.Recipe}), 1338338, C.progression), # 1.0 + "Recipe: Sulfuric Acid": ItemData(frozenset({G.Recipe}), 1338339, C.progression), + "Recipe: Ballistic Warp Drive": ItemData(frozenset({G.Recipe}), 1338340, C.progression), # 1.0 + "Recipe: Encased Uranium Cell": ItemData(frozenset({G.Recipe}), 1338341, C.progression), + "Recipe: Infused Uranium Cell": ItemData(frozenset({G.Recipe}), 1338342, C.progression), + "Recipe: Uranium Fuel Rod": ItemData(frozenset({G.Recipe}), 1338343, C.progression), + "Recipe: Uranium Fuel Unit": ItemData(frozenset({G.Recipe}), 1338344, C.progression), + "Recipe: Aluminum Beam": ItemData(frozenset({G.Recipe}), 1338345, C.progression), # 1.0 + "Recipe: Aluminum Rod": ItemData(frozenset({G.Recipe}), 1338346, C.progression), # 1.0 + "Recipe: Basic Iron Ingot": ItemData(frozenset({G.Recipe}), 1338347, C.progression), # 1.0 + "Recipe: Non-fissile Uranium": ItemData(frozenset({G.Recipe}), 1338348, C.progression), + "Recipe: Fertile Uranium": ItemData(frozenset({G.Recipe}), 1338349, C.progression), + "Recipe: Plutonium Pellet": ItemData(frozenset({G.Recipe}), 1338350), + "Recipe: Encased Plutonium Cell": ItemData(frozenset({G.Recipe}), 1338351), + "Recipe: Instant Plutonium Cell": ItemData(frozenset({G.Recipe}), 1338352), + "Recipe: Plutonium Fuel Rod": ItemData(frozenset({G.Recipe}), 1338353), + "Recipe: Plutonium Fuel Unit": ItemData(frozenset({G.Recipe}), 1338354), + "Recipe: Gas Filter": ItemData(frozenset({G.Recipe}), 1338355, C.progression), + "Recipe: Iodine Infused Filter": ItemData(frozenset({G.Recipe}), 1338356, C.progression), + "Recipe: Assembly Director System": ItemData(frozenset({G.Recipe}), 1338357, C.progression), + "Recipe: Magnetic Field Generator": ItemData(frozenset({G.Recipe}), 1338358, C.progression), + "Recipe: Copper Powder": ItemData(frozenset({G.Recipe}), 1338359, C.progression), + "Recipe: Nuclear Pasta": ItemData(frozenset({G.Recipe}), 1338360, C.progression), + "Recipe: Thermal Propulsion Rocket": ItemData(frozenset({G.Recipe}), 1338361, C.progression), + "Recipe: Ficsonium": ItemData(frozenset({G.Recipe}), 1338362), # 1.0 + "Recipe: Ficsonium Fuel Rod": ItemData(frozenset({G.Recipe}), 1338363), # 1.0 + "Recipe: Dark Matter Crystal": ItemData(frozenset({G.Recipe}), 1338364, C.progression), # 1.0 + "Recipe: Dark Matter Crystallization": ItemData(frozenset({G.Recipe}), 1338365, C.progression), # 1.0 + "Recipe: Dark Matter Trap": ItemData(frozenset({G.Recipe}), 1338366, C.progression), # 1.0 + "Recipe: Pulse Nobelisk": ItemData(frozenset({G.Recipe}), 1338367, C.useful), + "Recipe: Hatcher Protein": ItemData(frozenset({G.Recipe}), 1338368, C.progression), + "Recipe: Hog Protein": ItemData(frozenset({G.Recipe}), 1338369, C.progression), + "Recipe: Spitter Protein": ItemData(frozenset({G.Recipe}), 1338370, C.progression), + "Recipe: Stinger Protein": ItemData(frozenset({G.Recipe}), 1338371, C.progression), + "Recipe: Biomass (Leaves)": ItemData(frozenset({G.Recipe}), 1338372, C.progression), + "Recipe: Biomass (Wood)": ItemData(frozenset({G.Recipe}), 1338373, C.progression), + "Recipe: Biomass (Mycelia)": ItemData(frozenset({G.Recipe}), 1338374, C.progression), + "Recipe: Biomass (Alien Protein)": ItemData(frozenset({G.Recipe}), 1338375, C.progression), + "Recipe: Turbo Rifle Ammo (Packaged)": ItemData(frozenset({G.Recipe}), 1338376), + "Recipe: Fabric": ItemData(frozenset({G.Recipe}), 1338377, C.progression), + "Recipe: Polyester Fabric": ItemData(frozenset({G.Recipe}), 1338378, C.progression), + "Recipe: Solid Biofuel": ItemData(frozenset({G.Recipe}), 1338379, C.progression), + "Recipe: Liquid Biofuel": ItemData(frozenset({G.Recipe}), 1338380, C.progression), + "Recipe: Empty Canister": ItemData(frozenset({G.Recipe}), 1338381, C.progression), + "Recipe: Coated Iron Canister": ItemData(frozenset({G.Recipe}), 1338382, C.progression), + "Recipe: Steel Canister": ItemData(frozenset({G.Recipe}), 1338383, C.progression), + "Recipe: Empty Fluid Tank": ItemData(frozenset({G.Recipe}), 1338384, C.progression), + "Recipe: Packaged Alumina Solution": ItemData(frozenset({G.Recipe}), 1338385, C.progression), + "Recipe: Packaged Fuel": ItemData(frozenset({G.Recipe}), 1338386, C.progression), + "Recipe: Diluted Packaged Fuel": ItemData(frozenset({G.Recipe}), 1338387, C.progression), + "Recipe: Packaged Heavy Oil Residue": ItemData(frozenset({G.Recipe}), 1338388, C.progression), + "Recipe: Packaged Liquid Biofuel": ItemData(frozenset({G.Recipe}), 1338389, C.progression), + "Recipe: Packaged Nitric Acid": ItemData(frozenset({G.Recipe}), 1338390, C.progression), + "Recipe: Packaged Nitrogen Gas": ItemData(frozenset({G.Recipe}), 1338391, C.progression), + "Recipe: Packaged Oil": ItemData(frozenset({G.Recipe}), 1338392, C.progression), + "Recipe: Packaged Sulfuric Acid": ItemData(frozenset({G.Recipe}), 1338393, C.progression), + "Recipe: Packaged Turbofuel": ItemData(frozenset({G.Recipe}), 1338394, C.progression), + "Recipe: Packaged Water": ItemData(frozenset({G.Recipe}), 1338395, C.progression), + "Recipe: Turbofuel": ItemData(frozenset({G.Recipe}), 1338396, C.progression), + "Recipe: Turbo Heavy Fuel": ItemData(frozenset({G.Recipe}), 1338397, C.progression), + "Recipe: Turbo Blend Fuel": ItemData(frozenset({G.Recipe}), 1338398, C.progression), + "Recipe: Hazmat Suit": ItemData(frozenset({G.Recipe}), 1338399, C.progression), + "Recipe: Gas Mask": ItemData(frozenset({G.Recipe}), 1338400, C.progression), + "Recipe: Black Powder": ItemData(frozenset({G.Recipe}), 1338401, C.progression), + "Recipe: Blade Runners": ItemData(frozenset({G.Recipe}), 1338402, C.useful), + "Recipe: Chainsaw": ItemData(frozenset({G.Recipe}), 1338403, C.useful), + "Recipe: Cluster Nobelisk": ItemData(frozenset({G.Recipe}), 1338404), + "Recipe: Explosive Rebar": ItemData(frozenset({G.Recipe}), 1338405), + "Recipe: Factory Cart": ItemData(frozenset({G.Recipe}), 1338406, C.useful), + "Recipe: Gas Nobelisk": ItemData(frozenset({G.Recipe}), 1338407), + "Recipe: Golden Factory Cart": ItemData(frozenset({G.Recipe}), 1338408), + "Recipe: Homing Rifle Ammo": ItemData(frozenset({G.Recipe}), 1338409), + "Recipe: Iron Rebar": ItemData(frozenset({G.Recipe}), 1338410, C.progression), + "Recipe: Nobelisk": ItemData(frozenset({G.Recipe}), 1338411, C.progression), + "Recipe: Nuke Nobelisk": ItemData(frozenset({G.Recipe}), 1338412), + "Recipe: Nutritional Inhaler": ItemData(frozenset({G.Recipe}), 1338413, C.useful), + "Recipe: Object Scanner": ItemData(frozenset({G.Recipe}), 1338414, C.progression), + "Recipe: Parachute": ItemData(frozenset({G.Recipe}), 1338415, C.useful), + "Recipe: Protein Inhaler": ItemData(frozenset({G.Recipe}), 1338416, C.useful), + "Recipe: Rebar Gun": ItemData(frozenset({G.Recipe}), 1338417, C.useful), + "Recipe: Rifle": ItemData(frozenset({G.Recipe}), 1338418, C.useful), + "Recipe: Rifle Ammo": ItemData(frozenset({G.Recipe}), 1338419, C.progression), + "Recipe: Shatter Rebar": ItemData(frozenset({G.Recipe}), 1338420), + "Recipe: Stun Rebar": ItemData(frozenset({G.Recipe}), 1338421), + "Recipe: Therapeutic Inhaler": ItemData(frozenset({G.Recipe}), 1338422, C.useful), + "Recipe: Turbo Rifle Ammo": ItemData(frozenset({G.Recipe}), 1338423), + "Recipe: Vitamin Inhaler": ItemData(frozenset({G.Recipe}), 1338424, C.useful), + "Recipe: Xeno-Basher": ItemData(frozenset({G.Recipe}), 1338425, C.useful), + "Recipe: Xeno-Zapper": ItemData(frozenset({G.Recipe}), 1338426, C.useful), + "Recipe: Zipline": ItemData(frozenset({G.Recipe}), 1338427, C.useful), + "Recipe: Fine Black Powder": ItemData(frozenset({G.Recipe}), 1338428, C.progression), + "Recipe: Smokeless Powder": ItemData(frozenset({G.Recipe}), 1338429, C.progression), + "Recipe: Alien DNA Capsule": ItemData(frozenset({G.Recipe}), 1338430, C.progression), + "Recipe: Power Shard (1)": ItemData(frozenset({G.Recipe}), 1338431, C.progression), + "Recipe: Power Shard (2)": ItemData(frozenset({G.Recipe}), 1338432, C.useful), + "Recipe: Power Shard (5)": ItemData(frozenset({G.Recipe}), 1338433, C.useful), + +# 1.0 + "Recipe: Diamonds": ItemData(frozenset({G.Recipe}), 1338434, C.progression), + "Recipe: Cloudy Diamonds": ItemData(frozenset({G.Recipe}), 1338435, C.progression), + "Recipe: Oil-Based Diamonds": ItemData(frozenset({G.Recipe}), 1338436, C.progression), + "Recipe: Petroleum Diamonds": ItemData(frozenset({G.Recipe}), 1338437, C.progression), + "Recipe: Pink Diamonds": ItemData(frozenset({G.Recipe}), 1338438, C.progression), + "Recipe: Turbo Diamonds": ItemData(frozenset({G.Recipe}), 1338439, C.progression), + "Recipe: Time Crystal": ItemData(frozenset({G.Recipe}), 1338440, C.progression), + "Recipe: Superposition Oscillator": ItemData(frozenset({G.Recipe}), 1338441, C.progression), + #"Recipe: Excited Photonic Matter": ItemData(frozenset({G.Recipe}), 1338442, C.progression), should probably be unlocked with converter + "Recipe: Rocket Fuel": ItemData(frozenset({G.Recipe}), 1338443, C.progression), + "Recipe: Nitro Rocket Fuel": ItemData(frozenset({G.Recipe}), 1338444, C.progression), + "Recipe: Ionized Fuel": ItemData(frozenset({G.Recipe}), 1338445, C.useful), + "Recipe: Packaged Rocket Fuel": ItemData(frozenset({G.Recipe}), 1338446, C.progression), + "Recipe: Packaged Ionized Fuel": ItemData(frozenset({G.Recipe}), 1338447, C.useful), + "Recipe: Dark-Ion Fuel": ItemData(frozenset({G.Recipe}), 1338448, C.useful), + "Recipe: Quartz Purification": ItemData(frozenset({G.Recipe}), 1338449, C.progression), + "Recipe: Fused Quartz Crystal": ItemData(frozenset({G.Recipe}), 1338450, C.progression), + "Recipe: Leached Caterium Ingot": ItemData(frozenset({G.Recipe}), 1338451, C.progression), + "Recipe: Leached Copper Ingot": ItemData(frozenset({G.Recipe}), 1338452, C.progression), + "Recipe: Leached Iron ingot": ItemData(frozenset({G.Recipe}), 1338453, C.progression), + "Recipe: Molded Beam": ItemData(frozenset({G.Recipe}), 1338454, C.progression), + "Recipe: Molded Steel Pipe": ItemData(frozenset({G.Recipe}), 1338455, C.progression), + "Recipe: Plastic AI Limiter": ItemData(frozenset({G.Recipe}), 1338456, C.progression), + "Recipe: Tempered Caterium Ingot": ItemData(frozenset({G.Recipe}), 1338457, C.progression), + "Recipe: Tempered Copper Ingot": ItemData(frozenset({G.Recipe}), 1338458, C.progression), +# 1.0 + + #1338459 - 1338599 Reserved for future recipes + + #1338400 - 1338899 buildings / others + "Building: Constructor": ItemData(frozenset({G.Building}), 1338600, C.progression), + "Building: Assembler": ItemData(frozenset({G.Building}), 1338601, C.progression), + "Building: Manufacturer": ItemData(frozenset({G.Building}), 1338602, C.progression), + "Building: Packager": ItemData(frozenset({G.Building}), 1338603, C.progression), + "Building: Refinery": ItemData(frozenset({G.Building}), 1338604, C.progression), + "Building: Blender": ItemData(frozenset({G.Building}), 1338605, C.progression), + "Building: Particle Accelerator": ItemData(frozenset({G.Building}), 1338606, C.progression), + "Building: Biomass Burner": ItemData(frozenset({G.Building}), 1338607, C.progression), + "Building: Coal Generator": ItemData(frozenset({G.Building}), 1338608, C.progression), + "Building: Geothermal Generator": ItemData(frozenset({G.Building}), 1338609, C.progression), + "Building: Nuclear Power Plant": ItemData(frozenset({G.Building}), 1338610, C.progression), + "Building: Miner Mk.1": ItemData(frozenset({G.Building}), 1338611, C.progression), + "Building: Miner Mk.2": ItemData(frozenset({G.Building}), 1338612, C.progression), + "Building: Miner Mk.3": ItemData(frozenset({G.Building}), 1338613, C.progression), + "Building: Oil Extractor": ItemData(frozenset({G.Building}), 1338614, C.progression), + "Building: Water Extractor": ItemData(frozenset({G.Building}), 1338615, C.progression), + "Building: Smelter": ItemData(frozenset({G.Building}), 1338616, C.progression), + "Building: Foundry": ItemData(frozenset({G.Building}), 1338617, C.progression), + "Building: Fuel Generator": ItemData(frozenset({G.Building}), 1338618, C.progression), + "Building: Resource Well Pressurizer": ItemData(frozenset({G.Building}), 1338619, C.progression), + "Building: Equipment Workshop": ItemData(frozenset({G.Building}), 1338620, C.progression), + "Building: AWESOME Sink": ItemData(frozenset({G.Building}), 1338621, C.progression), + "Building: AWESOME Shop": ItemData(frozenset({G.Building}), 1338622, C.progression), + "Building: Painted Beams": ItemData(frozenset({G.Beams}), 1338623, C.filler), + "Building: Blueprint Designer": ItemData(frozenset({G.Building}), 1338624, C.filler), + "Building: Fluid Buffer": ItemData(frozenset({G.Building}), 1338625, C.filler), + "Building: Industrial Fluid Buffer": ItemData(frozenset({G.Building}), 1338626, C.filler), + "Building: Jump Pad": ItemData(frozenset({G.Building}), 1338627, C.filler), + "Building: Ladder": ItemData(frozenset({G.Building}), 1338628, C.filler), + "Building: MAM": ItemData(frozenset({G.Building}), 1338629, C.progression), + "Building: Personal Storage Box": ItemData(frozenset({G.Building}), 1338630, C.filler), + "Building: Power Storage": ItemData(frozenset({G.Building}), 1338631, C.progression), + "Building: U-Jelly Landing Pad": ItemData(frozenset({G.Building}), 1338632, C.useful), + "Building: Power Switch": ItemData(frozenset({G.Building}), 1338633, C.useful), + "Building: Priority Power Switch": ItemData(frozenset({G.Building}), 1338634, C.useful), + "Building: Storage Container": ItemData(frozenset({G.Building}), 1338635, C.useful, 0), + "Building: Lookout Tower": ItemData(frozenset({G.Building}), 1338636, C.filler), + #"Building: Power Pole Mk.1": ItemData(frozenset({G.Building}), 1338637, C.progression), # available from start + "Building: Power Pole Mk.2": ItemData(frozenset({G.Building}), 1338638, C.useful), + "Building: Power Pole Mk.3": ItemData(frozenset({G.Building}), 1338639, C.useful), + "Building: Industrial Storage Container": ItemData(frozenset({G.Building}), 1338640, C.filler), + "Building: Conveyor Merger": ItemData(frozenset({G.Building}), 1338641, C.progression), + "Building: Conveyor Splitter": ItemData(frozenset({G.Building}), 1338642, C.progression), + "Building: Conveyor Mk.1": ItemData(frozenset({G.Building, G.ConveyorMk1}), 1338643, C.progression), + "Building: Conveyor Mk.2": ItemData(frozenset({G.Building, G.ConveyorMk2}), 1338644, C.progression), + "Building: Conveyor Mk.3": ItemData(frozenset({G.Building, G.ConveyorMk3}), 1338645, C.progression), + "Building: Conveyor Mk.4": ItemData(frozenset({G.Building, G.ConveyorMk4}), 1338646, C.progression), + "Building: Conveyor Mk.5": ItemData(frozenset({G.Building, G.ConveyorMk5}), 1338647, C.progression), + "Building: Conveyor Lift Mk.1": ItemData(frozenset({G.Building, G.ConveyorMk1}), 1338648, C.useful), + "Building: Conveyor Lift Mk.2": ItemData(frozenset({G.Building, G.ConveyorMk2}), 1338649, C.useful), + "Building: Conveyor Lift Mk.3": ItemData(frozenset({G.Building, G.ConveyorMk3}), 1338650, C.useful), + "Building: Conveyor Lift Mk.4": ItemData(frozenset({G.Building, G.ConveyorMk4}), 1338651, C.useful), + "Building: Conveyor Lift Mk.5": ItemData(frozenset({G.Building, G.ConveyorMk5}), 1338652, C.useful), + "Building: Metal Beams": ItemData(frozenset({G.Beams}), 1338653, C.filler, 0), + "Building: Stackable Conveyor Pole": ItemData(frozenset({G.Building, G.ConveyorSupports}), 1338654, C.useful), + "Building: Conveyor Wall Mount": ItemData(frozenset({G.Building, G.ConveyorSupports}), 1338655, C.useful, 0), + "Building: Conveyor Lift Floor Hole": ItemData(frozenset({G.Building, G.ConveyorSupports}), 1338656, C.useful, 0), + "Building: Conveyor Ceiling Mount": ItemData(frozenset({G.Building, G.ConveyorSupports}), 1338657, C.useful, 0), + "Building: Pipes Mk.1": ItemData(frozenset({G.Building, G.PipesMk1}), 1338658, C.progression), + "Building: Pipes Mk.2": ItemData(frozenset({G.Building, G.PipesMk2}), 1338659, C.progression), + "Building: Pipeline Pump Mk.1": ItemData(frozenset({G.Building, G.PipesMk1}), 1338660, C.progression), + "Building: Pipeline Pump Mk.2": ItemData(frozenset({G.Building, G.PipesMk2}), 1338661, C.progression), + "Building: Pipeline Junction Cross": ItemData(frozenset({G.Building, G.PipesMk1, G.PipesMk2}), 1338662, C.progression), + "Building: Valve": ItemData(frozenset({G.Building, G.PipesMk1, G.PipesMk2}), 1338663, C.useful), + "Building: Stackable Pipeline Support": ItemData(frozenset({G.Building, G.PipelineSupports}), 1338664, C.useful, 0), + "Building: Wall Pipeline Support": ItemData(frozenset({G.Building, G.PipelineSupports}), 1338665, C.useful, 0), + "Building: Pipeline Wall Hole": ItemData(frozenset({G.Building, G.PipelineSupports}), 1338666, C.useful, 0), + "Building: Pipeline Floor Hole": ItemData(frozenset({G.Building, G.PipelineSupports}), 1338667, C.useful, 0), + "Building: Lights Control Panel": ItemData(frozenset({G.Building, G.Lights}), 1338668, C.filler, 0), + "Building: Wall Mounted Flood Light": ItemData(frozenset({G.Building, G.Lights}), 1338669, C.filler, 0), + "Building: Street Light": ItemData(frozenset({G.Building, G.Lights}), 1338670, C.filler, 0), + "Building: Flood Light Tower": ItemData(frozenset({G.Building, G.Lights}), 1338671, C.filler, 0), + "Building: Ceiling Light": ItemData(frozenset({G.Building, G.Lights}), 1338672, C.filler, 0), + "Building: Power Tower": ItemData(frozenset({G.Building}), 1338673, C.useful), + "Building: Walls Orange": ItemData(frozenset({G.Building, G.Walls}), 1338674, C.progression), + "Building: Radar Tower": ItemData(frozenset({G.Building}), 1338675, C.useful), + "Building: Smart Splitter": ItemData(frozenset({G.Building}), 1338676, C.useful), + "Building: Programmable Splitter": ItemData(frozenset({G.Building}), 1338677, C.useful), + "Building: Label Sign Bundle": ItemData(frozenset({G.Building, G.Signs}), 1338678, C.filler, 0), + "Building: Display Sign Bundle": ItemData(frozenset({G.Building, G.Signs}), 1338679, C.filler, 0), + "Building: Billboard Set": ItemData(frozenset({G.Building, G.Signs}), 1338680, C.filler, 0), + "Building: Walls Metal": ItemData(frozenset({G.Building, G.Walls}), 1338681, C.filler, 0), + "Building: Metal Pillar": ItemData(frozenset({G.Pilars}), 1338682, C.filler, 0), + "Building: Concrete Pillar": ItemData(frozenset({G.Pilars}), 1338683, C.filler, 0), + "Building: Frame Pillar": ItemData(frozenset({G.Pilars}), 1338684, C.filler, 0), + "Building: Walls Concrete": ItemData(frozenset({G.Building, G.Walls}), 1338685, C.filler, 0), + #"Building: Big Metal Pillar": ItemData(frozenset({G.Pilars}), 1338686, C.filler, 0), + #"Building: Big Concrete Pillar": ItemData(frozenset({G.Pilars}), 1338687, C.filler, 0), + #"Building: Big Frame Pillar": ItemData(frozenset({G.Pilars}), 1338688, C.filler, 0), + #"Building: Beam Support": ItemData(frozenset({G.Beams}), 1338689, C.filler, 0), + #"Building: Beam Connector": ItemData(frozenset({G.Beams}), 1338690, C.filler, 0), + #"Building: Beam Connector Double": ItemData(frozenset({G.Beams}), 1338691, C.filler, 0), + "Building: Foundation": ItemData(frozenset({G.Building, G.Foundations}), 1338692, C.progression), + "Building: Half Foundation": ItemData(frozenset({G.Foundations}), 1338693, C.filler, 0), + "Building: Corner Ramp Pack": ItemData(frozenset({G.Foundations}), 1338694, C.filler, 0), + "Building: Inverted Ramp Pack": ItemData(frozenset({G.Foundations}), 1338695, C.filler, 0), + "Building: Inverted Corner Ramp Pack": ItemData(frozenset({G.Foundations}), 1338696, C.filler, 0), + "Building: Quarter Pipes Pack": ItemData(frozenset({G.Foundations}), 1338697, C.filler, 0), + "Building: Quarter Pipe Extensions Pack": ItemData(frozenset({G.Foundations}), 1338698, C.filler, 0), + "Building: Frame foundation": ItemData(frozenset({G.Foundations}), 1338699, C.filler, 0), + "Building: Wall Outlet Mk.1": ItemData(frozenset({G.Building}), 1338700, C.useful), + "Building: Wall Outlet Mk.2": ItemData(frozenset({G.Building}), 1338701, C.useful), + "Building: Wall Outlet Mk.3": ItemData(frozenset({G.Building}), 1338702, C.useful), + "Building: Modern Catwalks": ItemData(frozenset({G.Building}), 1338703, C.filler, 0), + "Building: Industrial Walkways": ItemData(frozenset({G.Building}), 1338704, C.filler, 0), + "Building: Stairs": ItemData(frozenset({G.Building}), 1338705, C.filler, 0), + "Building: Clean Pipeline Mk.1": ItemData(frozenset({G.Building}), 1338706, C.filler, 0), + "Building: Clean Pipeline Mk.2": ItemData(frozenset({G.Building}), 1338707, C.filler, 0), + "Building: Road Barrier": ItemData(frozenset({G.Building}), 1338708, C.filler, 0), + "Building: Modern Railing": ItemData(frozenset({G.Building}), 1338709, C.filler, 0), + "Building: Industrial Railing": ItemData(frozenset({G.Building}), 1338710, C.filler, 0), + "Building: Double Ramp Pack": ItemData(frozenset({G.Foundations}), 1338711, C.filler, 0), + "Building: Conveyor Walls": ItemData(frozenset({G.Walls}), 1338712, C.filler, 0), + "Building: Inverted Ramp Wall Bundle": ItemData(frozenset({G.Walls}), 1338713, C.filler, 0), + "Building: Ramp Wall Bundle": ItemData(frozenset({G.Walls}), 1338714, C.filler, 0), + "Building: Door Walls": ItemData(frozenset({G.Walls}), 1338715, C.filler, 0), + "Building: Tilted Walls": ItemData(frozenset({G.Walls}), 1338716, C.filler, 0), + "Building: Windowed Walls": ItemData(frozenset({G.Walls}), 1338717, C.filler, 0), + "Building: Steel-framed Windows": ItemData(frozenset({G.Walls}), 1338718, C.filler, 0), + "Building: Gates": ItemData(frozenset({G.Walls}), 1338719, C.filler, 0), + "Building: Roofs": ItemData(frozenset({G.Walls}), 1338720, C.filler, 0), + "Building: Roof Corners": ItemData(frozenset({G.Walls}), 1338721, C.filler, 0), + +# 1.0 + "Building: Converter": ItemData(frozenset({G.Building}), 1338722, C.progression), + "Building: Quantum Encoder": ItemData(frozenset({G.Building}), 1338723, C.progression), + "Building: Portal": ItemData(frozenset({G.Building}), 1338724, C.filler), + "Building: Conveyor Mk.6": ItemData(frozenset({G.Building, G.ConveyorMk6}), 1338725, C.progression), + "Building: Conveyor Lift Mk.6": ItemData(frozenset({G.Building, G.ConveyorMk6}), 1338726, C.useful), + "Building: Alien Power Augmenter": ItemData(frozenset({G.Building}), 1338727, C.progression), + "Building: Dimensional Depot Uploader": ItemData(frozenset({G.Building}), 1338728, C.useful), +# 1.0 + + #1338729 - 1338749 Reserved for Cosmetics + + "Customizer: Asphalt Foundation Material": ItemData(frozenset({G.Customizer, G.Foundations}), 1338750, C.filler, 0), + "Customizer: Concrete Foundation Material": ItemData(frozenset({G.Customizer, G.Foundations}), 1338751, C.filler, 0), + "Customizer: Concrete Wall Material": ItemData(frozenset({G.Customizer, G.Walls}), 1338752, C.filler, 0), + "Customizer: Glass Roof Material": ItemData(frozenset({G.Customizer, G.Walls}), 1338753, C.filler, 0), + "Customizer: Grip Metal Foundation Material": ItemData(frozenset({G.Customizer, G.Foundations}), 1338754, C.filler, 0), + "Customizer: Coated Concrete Foundation Material": ItemData(frozenset({G.Customizer, G.Foundations}), 1338755, C.filler, 0), + "Customizer: Metal Roof Material": ItemData(frozenset({G.Customizer, G.Walls}), 1338756, C.filler, 0), + "Customizer: Steel Wall Material": ItemData(frozenset({G.Customizer, G.Walls}), 1338757, C.filler, 0), + "Customizer: Tar Roof Material": ItemData(frozenset({G.Customizer, G.Walls}), 1338758, C.filler, 0), + "Customizer: Arrow Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338759, C.filler, 0), + "Customizer: Dotted Line Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338760, C.filler, 0), + "Customizer: Solid Line Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338761, C.filler, 0), + "Customizer: Factory Icon Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338762, C.filler, 0), + "Customizer: Transportation Icon Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338763, C.filler, 0), + "Customizer: Number Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338764, C.filler, 0), + "Customizer: Pathway Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338765, C.filler, 0), + "Customizer: Factory Zone Patterns": ItemData(frozenset({G.Customizer, G.Foundations}), 1338766, C.filler, 0), + +# 1.0 + "Customizer: Steel-Framed Windows": ItemData(frozenset({G.Customizer, G.Walls}), 1338767, C.filler, 0), + "Customizer: Construction Fences": ItemData(frozenset({G.Customizer}), 1338768, C.filler, 0), + "Customizer: Unpainted Finish": ItemData(frozenset({G.Customizer}), 1338769, C.filler, 0), + "Customizer: Copper Paint Finish": ItemData(frozenset({G.Customizer}), 1338770, C.filler, 0), + "Customizer: Chrome Paint Finish": ItemData(frozenset({G.Customizer}), 1338771, C.filler, 0), + "Customizer: Carbon Steel Finish": ItemData(frozenset({G.Customizer}), 1338772, C.filler, 0), + "Customizer: Caterium Paint Finish": ItemData(frozenset({G.Customizer}), 1338773, C.filler, 0), +# 1.0 + + #1338773 - 1338799 Reserved for buildings + + # Transports 1338800 - 1338898 + # Drones (including Drone) + "Transport: Drones": ItemData(frozenset({G.Transport}), 1338800, C.useful), + + # Trains (including Empty Platform, rails, station, locomotive) + "Transport: Trains": ItemData(frozenset({G.Transport, G.Trains}), 1338801, C.useful), + "Transport: Fluid Trains": ItemData(frozenset({G.Transport, G.Trains}), 1338802, C.useful), + + # Tracker / Truck (including truck station) + "Transport: Tractor": ItemData(frozenset({G.Transport, G.Vehicles}), 1338803, C.useful), + "Transport: Truck": ItemData(frozenset({G.Transport, G.Vehicles}), 1338804, C.useful), + "Transport: Explorer": ItemData(frozenset({G.Transport, G.Vehicles}), 1338805, C.useful), + "Transport: Factory Cart": ItemData(frozenset({G.Transport, G.Vehicles}), 1338806, C.useful), + "Transport: Factory Cart (golden)": ItemData(frozenset({G.Transport, G.Vehicles}), 1338807, C.filler), + "Transport: Cyber Wagon": ItemData(frozenset({G.Transport, G.Vehicles}), 1338808, C.filler), + + # Hypertubes (including supports / pipes / entrance / holes) + "Transport: Hypertube": ItemData(frozenset({G.Transport, G.HyperTubes}), 1338809, C.useful), + "Transport: Hypertube Floor Hole": ItemData(frozenset({G.Transport, G.HyperTubes}), 1338810, C.filler), + "Transport: Hypertube Wall Support": ItemData(frozenset({G.Transport, G.HyperTubes}), 1338811, C.filler), + "Transport: Hypertube Wall Hole": ItemData(frozenset({G.Transport, G.HyperTubes}), 1338812, C.filler), + + #1338900 - 1338998 Handled by trap system (includes a few non-trap things) + # Regenerate via /Script/Blutility.EditorUtilityWidgetBlueprint'/Archipelago/Debug/EU_GenerateTrapIds.EU_GenerateTrapIds' + "Trap: Hog": ItemData(frozenset({G.Trap}), 1338900, C.trap), + "Trap: Alpha Hog": ItemData(frozenset({G.Trap}), 1338901, C.trap), + "Trap: Johnny": ItemData(frozenset({G.Trap}), 1338902, C.trap), + "Trap: Cliff Hog": ItemData(frozenset({G.Trap}), 1338903, C.trap), + "Trap: Nuclear Hog": ItemData(frozenset({G.Trap}), 1338904, C.trap), + "Trap: Not the Bees": ItemData(frozenset({G.Trap}), 1338905, C.trap), + "Trap: Hatcher": ItemData(frozenset({G.Trap}), 1338906, C.trap), + "Trap: Doggo with Pulse Nobelisk": ItemData(frozenset({G.Trap}), 1338907, C.trap), + "Trap: Doggo with Nuke Nobelisk": ItemData(frozenset({G.Trap}), 1338908, C.trap), + "Doggo with Power Slug": ItemData(frozenset({G.Parts}), 1338909, C.filler), + "Trap: Doggo with Gas Nobelisk": ItemData(frozenset({G.Trap}), 1338910, C.trap), + "Trap: Spore Flower": ItemData(frozenset({G.Trap}), 1338911, C.trap), + "Trap: Stinger": ItemData(frozenset({G.Trap}), 1338912, C.trap), + "Trap: Gas Stinger": ItemData(frozenset({G.Trap}), 1338913, C.trap), + "Trap: Small Stinger": ItemData(frozenset({G.Trap}), 1338914, C.trap), + "Trap: Spitter": ItemData(frozenset({G.Trap}), 1338915, C.trap), + "Trap: Alpha Spitter": ItemData(frozenset({G.Trap}), 1338916, C.trap), + "Trap: Nuclear Waste Drop": ItemData(frozenset({G.Trap}), 1338917, C.trap), + "Trap: Plutonium Waste Drop": ItemData(frozenset({G.Trap}), 1338918, C.trap), + "Trap: Elite Hatcher": ItemData(frozenset({G.Trap}), 1338919, C.trap), + "Trap: Can of Beans": ItemData(frozenset({G.Trap}), 1338920, C.trap), + "Trap: Fart Cloud": ItemData(frozenset({G.Trap}), 1338921, C.trap), + + #Item id range upper bound + "Building: Space Elevator": ItemData(frozenset({G.Building}), 1338999, C.progression) + } + + non_unique_item_categories: ClassVar[Set[G]] = frozenset({ G.Parts, G.Equipment, G.Ammo, G.Trap, G.Upgrades }) + pool_item_categories: ClassVar[Set[G]] = frozenset({G.Recipe, G.Building, G.Equipment, G.Transport, G.Upgrades}) + item_names_and_ids: ClassVar[Dict[str, int]] = {name: item_data.code for name, item_data in item_data.items()} + filler_items: ClassVar[Tuple[str, ...]] = tuple(item for item, details in item_data.items() + if not details.category.isdisjoint(frozenset({ G.Parts, G.Ammo }))) + + + @classmethod + def get_item_names_per_category(cls) -> Dict[str, Set[str]]: + categories: Dict[str, Set[str]] = {} + + for name, data in cls.item_data.items(): + for category in data.category: + categories.setdefault(category.name, set()).add(name) + + return categories + + + player: int + logic: GameLogic + random: Random + precalculated_progression_recipes: Optional[Dict[str, Recipe]] + precalculated_progression_recipes_names: Optional[Set[str]] + + + def __init__(self, player: Optional[int], logic: GameLogic, random: Random, options: SatisfactoryOptions): + self.player = player + self.logic = logic + self.random = random + + if options.experimental_generation: # TODO major performance boost if we can get it stable + self.precalculated_progression_recipes = self.select_progression_recipes() + self.precalculated_progression_recipes_names = set( + recipe.name for recipe in self.precalculated_progression_recipes.values() + ) + else: + self.precalculated_progression_recipes = None + self.precalculated_progression_recipes_names = None + + + def select_recipe_for_part_that_does_not_depend_on_parent_recipes(self, + part: str, parts_to_avoid: Dict[str, str]) -> Recipe: + + recipes: List[Recipe] = list(self.logic.recipes[part]) + + implicit_recipe = next(filter(lambda r: r.implicitly_unlocked, recipes), None) + if implicit_recipe: + return implicit_recipe + + while (len(recipes) > 0): + recipe: Recipe = recipes.pop(self.random.randrange(len(recipes))) + + if recipe.inputs and any(input in parts_to_avoid for input in recipe.inputs): + continue + + return recipe + + raise Exception(f"No recipe available for {part}") + + + def build_progression_recipe_tree(self, parts: tuple[str, ...], selected_recipes: Dict[str, str]): + for part in parts: + recipe: Recipe = \ + self.select_recipe_for_part_that_does_not_depend_on_parent_recipes(part, selected_recipes) + + selected_recipes[part] = recipe.name + + child_recipes: Dict[str, Recipe] = {} + if (recipe.inputs): + for input in recipe.inputs: + child_recipes[input] = \ + self.select_recipe_for_part_that_does_not_depend_on_parent_recipes(input, selected_recipes) + + for part, child_recipe in child_recipes.items(): + selected_recipes[part] = child_recipe.name + + for child_recipe in child_recipes.values(): + if child_recipe.inputs: + self.build_progression_recipe_tree(child_recipe.inputs, selected_recipes) + + + def select_progression_recipes(self) -> Dict[str, Recipe]: + selected_recipes: Dict[str, Recipe] = {} + + while not self.is_beatable(selected_recipes): + selected_recipes = self.select_random_progression_recipes() + + return selected_recipes + + + def is_beatable(self, recipes: Dict[str, Recipe]) -> bool: + if not recipes: + return False + + craftable_parts: Set[str] = set() + pending_recipes_by_part: Dict[str, Recipe] = copy.deepcopy(recipes) + + for part, recipe_tuples in self.logic.recipes.items(): + for recipe in recipe_tuples: + if recipe.implicitly_unlocked: + craftable_parts.add(part) + + while pending_recipes_by_part: + new_collected_parts: Set[str] = set() + + for part, recipe in pending_recipes_by_part.items(): + if all(input in craftable_parts for input in recipe.inputs): + new_collected_parts.add(part) + + if not new_collected_parts: + return False + + craftable_parts = craftable_parts.union(new_collected_parts) + + for part in new_collected_parts: + del pending_recipes_by_part[part] + + return True + + + def select_random_progression_recipes(self) -> Dict[str, Recipe]: + selected_recipes: Dict[str, str] = {} + + for part, recipes in self.logic.recipes.items(): + + implicit_recipe: Recipe = next(filter(lambda r: r.implicitly_unlocked, recipes), None) + if implicit_recipe: + continue + + selected_recipes[part] = self.random.choice(recipes) + + return selected_recipes + + + @classmethod + def create_item(cls, instance: Optional["Items"], name: str, player: int) -> Item: + data: ItemData = cls.item_data[name] + type = data.type + + if type == C.progression and not name.startswith("Building: ") and \ + instance and instance.precalculated_progression_recipes_names: + if name not in instance.precalculated_progression_recipes_names: + type = C.useful + logging.info(f"Downscaling .. {name}") + else: + logging.warn(f"Preserving .. {name}") + + return Item(name, type, data.code, player) + + + def get_filler_item_name(self, random: Random, options: SatisfactoryOptions) -> str: + trap_chance: int = options.trap_chance.value + enabled_traps: List[str] = options.trap_selection_override.value + + if enabled_traps and random.random() < (trap_chance / 100): + return random.choice(enabled_traps) + else: + return random.choice(self.filler_items) + + + def get_excluded_items(self, multiworld: MultiWorld, options: SatisfactoryOptions) -> Set[str]: + excluded_items: Set[str] = set() + + for item in multiworld.precollected_items[self.player]: + if item.name in self.item_data \ + and self.item_data[item.name].category.isdisjoint(self.non_unique_item_categories) \ + and item.name not in options.start_inventory_from_pool: + + excluded_items.add(item.name) + + return excluded_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()) + pool: List[Item] = [] + + for name, data in self.item_data.items(): + if name == "Building: Blueprint Designer": + halp = 10 + + if data.count > 0 \ + and not data.category.isdisjoint(self.pool_item_categories) \ + and name not in excluded_from_pool: + + for _ in range(data.count): + item = self.create_item(self, name, self.player) + pool.append(item) + + for _ in range(number_of_locations - len(pool)): + item = self.create_item(self, self.get_filler_item_name(random, options), self.player) + pool.append(item) + + return pool + + + def write_progression_chain(self, multiworld: MultiWorld, spoiler_handle: TextIO): + if self.precalculated_progression_recipes: + player_name = f'{multiworld.get_player_name(self.player)}: ' if multiworld.players > 1 else '' + spoiler_handle.write('\n\nSelected Satisfactory Recipes:\n\n') + spoiler_handle.write('\n'.join( + f"{player_name}{part} -> {recipe.name}" + for part, recipes_per_part in self.logic.recipes.items() + for recipe in recipes_per_part + if recipe.name in self.precalculated_progression_recipes_names + )) diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py new file mode 100644 index 0000000000..53ed83bba6 --- /dev/null +++ b/worlds/satisfactory/Locations.py @@ -0,0 +1,396 @@ +from typing import List, Optional, Callable, Tuple, Dict, Iterable, ClassVar +from BaseClasses import CollectionState +from .GameLogic import GameLogic, Recipe, Building, PowerInfrastructureLevel, DropPodData +from .StateLogic import StateLogic, EventId, part_event_prefix, building_event_prefix +from .Items import Items +from .Options import SatisfactoryOptions +from math import ceil, floor + + +class LocationData(): + region: str + name: str + event_name: str + code: Optional[int] + non_progression: Optional[bool] + rule: Optional[Callable[[CollectionState], bool]] + + def __init__(self, region: str, name: str, code: Optional[int], event_name: Optional[str] = None, + non_progression: Optional[bool] = False, rule: Optional[Callable[[CollectionState], bool]] = None): + self.region = region + self.name = name + self.code = code + self.rule = rule + self.non_progression = non_progression + self.event_name = event_name or name + + +class Part(LocationData): + @staticmethod + def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, items: Items) -> List[LocationData]: + recipes_per_region: Dict[str, List[Recipe]] = {} + + for recipe in recipes: + recipes_per_region.setdefault(recipe.building or "Overworld", []).append(recipe) + + return [Part(state_logic, region, recipes_for_region, name, items) + 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 can_produce_any_recipe_for_part(self, state_logic: StateLogic, recipes: Iterable[Recipe], + name: str, items: Items) -> Callable[[CollectionState], bool]: + def can_build_by_any_recipe(state: CollectionState) -> bool: + if name == "Steel Ingot": + debug = True + + if items.precalculated_progression_recipes and name in items.precalculated_progression_recipes: + can_produce: bool = state_logic.can_produce_specific_recipe_for_part( + state, items.precalculated_progression_recipes[name]) + + can_produce_anyway: bool + if can_produce: + return can_produce + else: + can_produce_anyway = \ + any(state_logic.can_produce_specific_recipe_for_part(state, recipe) for recipe in recipes) + + if can_produce_anyway: + debug = True + + return False # can_produce_anyway + else: + return any(state_logic.can_produce_specific_recipe_for_part(state, recipe) for recipe in recipes) + + return can_build_by_any_recipe + + +class EventBuilding(LocationData): + def __init__(self, game_logic: GameLogic, state_logic: StateLogic, building_name: str, building: Building): + super().__init__("Overworld", building_event_prefix + building_name, EventId, + rule = self.can_create_building(game_logic, state_logic, building)) + + def can_create_building(self, game_logic: GameLogic, state_logic: StateLogic, building: Building + ) -> Callable[[CollectionState], bool]: + + def can_build(state: CollectionState) -> bool: + if building.name == "Building: Foundry": + debug = True + + 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) + + return can_build + + +class PowerInfrastructure(LocationData): + def __init__(self, game_logic: GameLogic, state_logic: StateLogic, + powerLevel: PowerInfrastructureLevel, recipes: Iterable[Recipe]): + super().__init__("Overworld", building_event_prefix + powerLevel.to_name(), EventId, + rule = self.can_create_power_infrastructure(game_logic, state_logic, powerLevel, recipes)) + + def can_create_power_infrastructure(self, game_logic: GameLogic, state_logic: StateLogic, + powerLevel: PowerInfrastructureLevel, recipes: Iterable[Recipe] + ) -> Callable[[CollectionState], bool]: + + def can_power(state: CollectionState) -> bool: + return any(state_logic.can_power(state, level) for level in PowerInfrastructureLevel if level > powerLevel)\ + or any(state_logic.can_build(state, recipe.building) and + state_logic.can_produce_all_allowing_handcrafting(state, game_logic, recipe.inputs) + for recipe in recipes) + + return can_power + + +class ElevatorTier(LocationData): + def __init__(self, tier: int, state_logic: StateLogic, game_logic: GameLogic): + super().__init__("Overworld", f"Elevator Tier {tier + 1}", EventId, + rule = lambda state: state_logic.can_build(state, "Space Elevator") and \ + state_logic.can_produce_all(state, game_logic.space_elevator_tiers[tier].keys())) + + +class HubSlot(LocationData): + def __init__(self, tier: int, milestone: int, slot: int, locationId: int): + super().__init__(f"Hub {tier}-{milestone}", f"Hub {tier}-{milestone}, item {slot}", locationId) + + +class MamSlot(LocationData): + def __init__(self, tree: str, nodeName: str, locationId: int): + super().__init__(f"{tree}: {nodeName}", f"{tree}: {nodeName}", locationId) + + +class ShopSlot(LocationData): + def __init__(self, state_logic: Optional[StateLogic], slot: int, cost: int, locationId: int): + super().__init__("AWESOME Shop", f"AWESOME Shop purchase {slot}", locationId, + rule = self.can_purchase_from_shop(state_logic, cost)) + + def can_purchase_from_shop(self, state_logic: Optional[StateLogic], cost) -> Callable[[CollectionState], bool]: + def can_purchase(state: CollectionState) -> bool: + if not state_logic or cost < 20: + return True + elif (cost >= 20 and cost < 50): + return state_logic.is_game_phase(state, 1) + elif (cost >= 50 and cost < 100): + return state_logic.is_game_phase(state, 2) + else: + return state_logic.is_game_phase(state, 3) + + return can_purchase + + +class DropPod(LocationData): + def __init__(self, data: DropPodData, state_logic: Optional[StateLogic], + locationId: int, tier: int, can_hold_progression: bool): + + # drop pod locations are unlocked by hard drives, there is currently no direct mapping between location and hard drive + # we currently do not know how many hdd require gass or radioactive protection + # coordinates are for us to reference them, there is no real link between coordinate and check + def get_region(gassed: Optional[bool], radioactive: Optional[bool]) -> str: + #if radioactive: + # return "Radioactive Area" + #elif gassed: + # return "Gas Area" + #else: + # return "Overworld" + return f"Hub Tier {tier}" + + def get_rule(unlocked_by: Optional[str], power_needed: int) -> Callable[[CollectionState], bool]: + # Power is kept out of logic. with energy link its simple, + # without you just going to have to figure it your yourself + + def logic_rule(state: CollectionState): + return not unlocked_by or state_logic and state_logic.can_produce(state, unlocked_by) + + return logic_rule + + super().__init__(get_region(data.gassed, data.radioactive), f"Hard drive random check {locationId - 1338600}", locationId, + non_progression = not can_hold_progression, rule = get_rule(data.item, data.power)) + + +class Locations(): + game_logic: Optional[GameLogic] + options: Optional[SatisfactoryOptions] + state_logic: Optional[StateLogic] + items: Optional[Items] + + hub_location_start: ClassVar[int] = 1338000 + max_tiers: ClassVar[int] = 10 + max_milestones: ClassVar[int] = 5 + max_slots: ClassVar[int] = 10 + drop_pod_location_id_start: ClassVar[int] = 1338600 + drop_pod_location_id_end: ClassVar[int] = 1338699 + + def __init__(self, game_logic: Optional[GameLogic] = None, options: Optional[SatisfactoryOptions] = None, + state_logic: Optional[StateLogic] = None, items: Optional[Items] = None): + self.game_logic = game_logic + self.options = options + self.state_logic = state_logic + self.items = items + + def get_base_location_table(self) -> List[LocationData]: + return [ + MamSlot("Alien Organisms", "Inflated Pocket Dimension", 1338500), + MamSlot("Alien Organisms", "Hostile Organism Detection", 1338501), + MamSlot("Alien Organisms", "Expanded Toolbelt", 1338502), + MamSlot("Alien Organisms", "Bio-Organic Properties", 1338503), + MamSlot("Alien Organisms", "Stinger Research", 1338504), + MamSlot("Alien Organisms", "Hatcher Research", 1338505), + MamSlot("Alien Organisms", "Hog Research", 1338506), + MamSlot("Alien Organisms", "Spitter Research", 1338507), + MamSlot("Alien Organisms", "Structural Analysis", 1338508), + MamSlot("Alien Organisms", "Protein Inhaler", 1338509), + MamSlot("Alien Organisms", "The Rebar Gun", 1338510), + MamSlot("Caterium", "Caterium Electronics", 1338511), + MamSlot("Caterium", "Bullet Guidance System", 1338512), + MamSlot("Caterium", "High-Speed Connector", 1338513), + MamSlot("Caterium", "Caterium", 1338514), + MamSlot("Caterium", "Caterium Ingots", 1338515), + MamSlot("Caterium", "Quickwire", 1338516), + MamSlot("Caterium", "Power Switch", 1338517), + MamSlot("Caterium", "Power Poles Mk.2", 1338518), + MamSlot("Caterium", "AI Limiter", 1338519), + MamSlot("Caterium", "Smart Splitter", 1338520), + MamSlot("Caterium", "Programmable Splitter", 1338521), + MamSlot("Mycelia", "Gas Mask", 1338522), # 1.0 + MamSlot("Caterium", "Zipline", 1338523), + MamSlot("Caterium", "Geothermal Generator", 1338524), + MamSlot("Caterium", "Priority Power Switch", 1338525), + MamSlot("Caterium", "Stun Rebar", 1338526), + MamSlot("Caterium", "Power Poles Mk.3", 1338527), + MamSlot("Mycelia", "Therapeutic Inhaler", 1338528), + MamSlot("Mycelia", "Expanded Toolbelt", 1338529), + MamSlot("Mycelia", "Mycelia", 1338530), + MamSlot("Mycelia", "Fabric", 1338531), + MamSlot("Mycelia", "Medical Properties", 1338532), + MamSlot("Mycelia", "Toxic Cellular Modification", 1338533), + MamSlot("Mycelia", "Vitamin Inhaler", 1338534), + MamSlot("Mycelia", "Parachute", 1338535), + MamSlot("Mycelia", "Synthethic Polyester Fabric", 1338536), + MamSlot("Nutrients", "Bacon Agaric", 1338537), + MamSlot("Nutrients", "Beryl Nut", 1338538), + MamSlot("Nutrients", "Paleberry", 1338539), + MamSlot("Nutrients", "Nutritional Processor", 1338540), + MamSlot("Nutrients", "Nutritional Inhaler", 1338541), + MamSlot("Power Slugs", "Slug Scanning", 1338542), + MamSlot("Power Slugs", "Blue Power Slugs", 1338543), + MamSlot("Power Slugs", "Yellow Power Shards", 1338544), + MamSlot("Power Slugs", "Purple Power Shards", 1338545), + MamSlot("Power Slugs", "Overclock Production", 1338546), + MamSlot("Quartz", "Crystal Oscillator", 1338547), + MamSlot("Quartz", "Quartz Crystals", 1338548), + MamSlot("Quartz", "Quartz", 1338549), + MamSlot("Quartz", "Shatter Rebar", 1338550), + MamSlot("Quartz", "Silica", 1338551), + MamSlot("Quartz", "Explosive Resonance Application", 1338552), + MamSlot("Quartz", "Blade Runners", 1338553), + MamSlot("Quartz", "The Explorer", 1338554), + MamSlot("Quartz", "Radio Signal Scanning", 1338555), + MamSlot("Quartz", "Inflated Pocket Dimension", 1338556), + MamSlot("Quartz", "Radar Technology", 1338557), + MamSlot("Sulfur", "The Nobelisk Detonator", 1338558), + MamSlot("Sulfur", "Smokeless Powder", 1338559), + MamSlot("Sulfur", "Sulfur", 1338560), + MamSlot("Sulfur", "Inflated Pocket Dimension", 1338561), + MamSlot("Sulfur", "The Rifle", 1338562), + MamSlot("Sulfur", "Compacted Coal", 1338563), + MamSlot("Sulfur", "Black Powder", 1338564), + MamSlot("Sulfur", "Explosive Rebar", 1338565), + MamSlot("Sulfur", "Cluster Nobelisk", 1338566), + MamSlot("Sulfur", "Experimental Power Generation", 1338567), + MamSlot("Sulfur", "Turbo Rifle Ammo", 1338568), + MamSlot("Sulfur", "Turbo Fuel", 1338569), + MamSlot("Sulfur", "Expanded Toolbelt", 1338570), + MamSlot("Sulfur", "Nuclear Deterrent Development", 1338571), + + # 1.0 + MamSlot("Power Slugs", "Synthetic Power Shards", 1338572), + MamSlot("Sulfur", "Rocket Fuel", 1338573), + MamSlot("Sulfur", "Ionized Fuel", 1338574), + MamSlot("Alien Technology", "SAM Analysis", 1338575), + MamSlot("Alien Technology", "SAM Reanimation", 1338576), + MamSlot("Alien Technology", "SAM Fluctuator", 1338577), + MamSlot("Alien Technology", "Mercer Sphere Analysis", 1338578), + MamSlot("Alien Technology", "Dimensional Depot", 1338579), + MamSlot("Alien Technology", "Manual Depot Uploader", 1338580), + MamSlot("Alien Technology", "Depot Expansion (200%)", 1338581), + MamSlot("Alien Technology", "Depot Expansion (300%)", 1338582), + MamSlot("Alien Technology", "Depot Expansion (400%)", 1338583), + MamSlot("Alien Technology", "Depot Expansion (500%)", 1338584), + MamSlot("Alien Technology", "Upload Upgrade: 30/min", 1338585), + MamSlot("Alien Technology", "Upload Upgrade: 60/min", 1338586), + MamSlot("Alien Technology", "Upload Upgrade: 120/min", 1338587), + MamSlot("Alien Technology", "Upload Upgrade: 240/min", 1338588), + MamSlot("Alien Technology", "Somersloop Analysis", 1338589), + MamSlot("Alien Technology", "Alien Energy Harvesting", 1338590), + MamSlot("Alien Technology", "Production Amplifier", 1338591), + MamSlot("Alien Technology", "Power Augmenter", 1338592), + MamSlot("Alien Technology", "Alien Power Matrix", 1338593), + # 1.0 + + # 1338600 - 1338699 - Harddrives - Harddrives + + ShopSlot(self.state_logic, 1, 3, 1338700), + ShopSlot(self.state_logic, 2, 3, 1338701), + ShopSlot(self.state_logic, 3, 5, 1338702), + ShopSlot(self.state_logic, 4, 5, 1338703), + ShopSlot(self.state_logic, 5, 10, 1338704), + ShopSlot(self.state_logic, 6, 10, 1338705), + ShopSlot(self.state_logic, 7, 20, 1338706), + ShopSlot(self.state_logic, 8, 20, 1338707), + ShopSlot(self.state_logic, 9, 50, 1338708), + ShopSlot(self.state_logic, 10, 50, 1338709) + ] + + def get_locations_for_data_package(self) -> Dict[str, int]: + "Must include all posiable location names and thier id's" + + location_table = self.get_base_location_table() + location_table.extend(self.get_hub_locations()) + location_table.extend(self.get_drop_pod_locations()) + location_table.append(LocationData("Overworld", "UpperBound", 1338999)) + + return {location.name: location.code for location in location_table} + + def get_locations(self) -> List[LocationData]: + "Only return location used in this game based on settings" + + if not self.game_logic or not self.options or not self.state_logic or not self.items: + raise Exception("Locations need to be initialized with logic, options and items before using this method") + + location_table = self.get_base_location_table() + location_table.extend(self.get_hub_locations()) + location_table.extend(self.get_drop_pod_locations()) + location_table.extend(self.get_logical_event_locations()) + + return location_table + + def get_hub_locations(self) -> List[LocationData]: + location_table: List[LocationData] = [] + + hub_location_id = self.hub_location_start + for tier in range(1, self.max_tiers + 1): + for milestone in range(1, self.max_milestones + 1): + for slot in range(1, self.max_slots + 1): + if not self.game_logic: + location_table.append(HubSlot(tier, milestone, slot, hub_location_id)) + else: + if tier <= len(self.game_logic.hub_layout) \ + and milestone <= len(self.game_logic.hub_layout[tier - 1]) \ + and slot <= self.game_logic.slots_per_milestone: + location_table.append(HubSlot(tier, milestone, slot, hub_location_id)) + + hub_location_id += 1 + + return location_table + + def get_logical_event_locations(self) -> List[LocationData]: + location_table: List[LocationData] = [] + + # for performance plan is to upfront calculated everything we need + # and than create one massive state.has_all for each logical gate (hub tiers, elevator tiers) + + location_table.extend( + ElevatorTier(index, self.state_logic, self.game_logic) + for index, parts in enumerate(self.game_logic.space_elevator_tiers)) + location_table.extend( + part + for part_name, recipes in self.game_logic.recipes.items() + for part in Part.get_parts(self.state_logic, recipes, part_name, self.items)) + location_table.extend( + EventBuilding(self.game_logic, self.state_logic, name, building) + for name, building in self.game_logic.buildings.items()) + location_table.extend( + PowerInfrastructure(self.game_logic, self.state_logic, power_level, recipes) + for power_level, recipes in self.game_logic.requirement_per_powerlevel.items()) + + return location_table + + def get_drop_pod_locations(self) -> List[LocationData]: + drop_pod_locations: List[DropPod] = [] + + bucket_size: int = 0 + drop_pod_data: List[DropPodData] = [] + + if self.game_logic: + bucket_size = floor( + (self.drop_pod_location_id_end - self.drop_pod_location_id_start) / len(self.game_logic.hub_layout)) + + drop_pod_data = self.game_logic.drop_pods + # sort, easily obtainable first, should be deterministic + drop_pod_data.sort(key = lambda data: ("!" if data.item == None else data.item) + str(data.x - data.z)) + + for location_id in range(self.drop_pod_location_id_start, self.drop_pod_location_id_end + 1): + if not self.game_logic or not self.state_logic or not self.options: + drop_pod_locations.append(DropPod(DropPodData(0, 0, 0, None, 0), None, location_id, 1, False)) + else: + location_id_normalized: int = location_id - self.drop_pod_location_id_start + data: DropPodData = drop_pod_data[location_id_normalized] + can_hold_progression: bool = location_id_normalized < self.options.hard_drive_progression_limit.value + tier = min(ceil((location_id_normalized + 1) / bucket_size), len(self.game_logic.hub_layout)) + + drop_pod_locations.append(DropPod(data, self.state_logic, location_id, tier, can_hold_progression)) + + return drop_pod_locations \ No newline at end of file diff --git a/worlds/satisfactory/Options.py b/worlds/satisfactory/Options.py new file mode 100644 index 0000000000..0a57c59d33 --- /dev/null +++ b/worlds/satisfactory/Options.py @@ -0,0 +1,388 @@ +from dataclasses import dataclass +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 + +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): + """ + Ship these Space Elevator packages to finish. + Does nothing if *Space Elevator Tier* goal is not enabled. + """ + display_name = "Goal: Space Elevator shipment" + default = 2 + range_start = 1 + range_end = 4 + special_range_names = { + "one package (tiers 1-2)": 1, + "two packages (tiers 1-4)": 2, + "three packages (tiers 1-6)": 3, + "four packages (tiers 7-8)": 4, + "five packages (tier 9)": 5, + } + +class ResourceSinkPoints(NamedRange): + """ + Sink an amount of items totalling this amount of points to finish. + Does nothing if *AWESOME Sink Points* goal is not enabled. + + 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" + 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 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. + """ + display_name = "Hard Drive Progression Items" + default = 0 + 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" + +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(Toggle): + """ + Allow transferring energy to and from other worlds using the Power Storage building. + 0% of the energy is lost in the transfer. + """ + 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", + "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" + +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", + # "Exploration", + # "FICSMAS Tree", + } + default = {"Space Elevator Tier"} + +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): + goal_selection: GoalSelection + goal_requirement: GoalRequirement + final_elevator_package: ElevatorTier + final_awesome_sink_points: ResourceSinkPoints + 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 + trap_chance: TrapChance + trap_selection_preset: TrapSelectionPreset + trap_selection_override: TrapSelectionOverride + death_link: DeathLink + energy_link: EnergyLink + start_inventory_from_pool: StartInventoryPool + experimental_generation: ExperimentalGeneration diff --git a/worlds/satisfactory/Regions.py b/worlds/satisfactory/Regions.py new file mode 100644 index 0000000000..f39d951e47 --- /dev/null +++ b/worlds/satisfactory/Regions.py @@ -0,0 +1,186 @@ +from typing import List, Set, Dict, Tuple, Optional, Callable +from BaseClasses import MultiWorld, Region, Location, Item, CollectionState +from .Locations import LocationData +from .GameLogic import GameLogic, PowerInfrastructureLevel +from .StateLogic import StateLogic +from .Options import SatisfactoryOptions, Placement + +class SatisfactoryLocation(Location): + game: str = "Satisfactory" + event_name: Optional[str] + + def __init__(self, player: int, data: LocationData, region: Region): + super().__init__(player, data.name, data.code, region) + + self.event_name = data.event_name + + if data.code is None: + self.event = True + self.locked = True + + if (data.rule): + self.access_rule = data.rule + + if (data.non_progression): + self.item_rule = self.non_progression_only + + @staticmethod + def non_progression_only(item: Item) -> bool: + return not item.advancement + + +def create_regions_and_return_locations(world: MultiWorld, options: SatisfactoryOptions, player: int, + game_logic: GameLogic, state_logic: StateLogic, locations: List[LocationData]): + + region_names: List[str] = [ + "Menu", + "Overworld", + "Gas Area", + "Radioactive Area", + "Mam", + "AWESOME Shop" + ] + + for hub_tier, milestones_per_hub_tier in enumerate(game_logic.hub_layout, 1): + region_names.append(f"Hub Tier {hub_tier}") + + for minestone, _ in enumerate(milestones_per_hub_tier, 1): + region_names.append(f"Hub {hub_tier}-{minestone}") + + for building_name, building in game_logic.buildings.items(): + if building.can_produce: + region_names.append(building_name) + + for tree_name, tree in game_logic.man_trees.items(): + region_names.append(tree_name) + + for node in tree.nodes: + region_names.append(f"{tree_name}: {node.name}") + + locations_per_region: Dict[str, LocationData] = get_locations_per_region(locations) + regions: Dict[str, Region] = create_regions(world, player, locations_per_region, region_names) + + if __debug__: + throwIfAnyLocationIsNotAssignedToARegion(regions, locations_per_region.keys()) + + world.regions += regions.values() + + super_early_game_buildings: List[str] = [ + "Foundation", + "Walls Orange" + ] + + early_game_buildings: List[str] = [ + PowerInfrastructureLevel.Automated.to_name() + ] + + if options.mam_logic_placement.value == Placement.early: + early_game_buildings.append("MAM") + if options.awesome_logic_placement.value == Placement.early: + early_game_buildings.append("AWESOME Sink") + early_game_buildings.append("AWESOME Shop") + if options.energy_link_logic_placement.value == Placement.early: + early_game_buildings.append("Power Storage") + if options.splitter_placement == Placement.early: + super_early_game_buildings.append("Conveyor Splitter") + super_early_game_buildings.append("Conveyor Merger") + + connect(regions, "Menu", "Overworld") + connect(regions, "Overworld", "Hub Tier 1") + connect(regions, "Hub Tier 1", "Hub Tier 2", + lambda state: state_logic.can_build_all(state, super_early_game_buildings)) + connect(regions, "Hub Tier 2", "Hub Tier 3", lambda state: state.has("Elevator Tier 1", player) + and state_logic.can_build_all(state, early_game_buildings)) + connect(regions, "Hub Tier 3", "Hub Tier 4") + connect(regions, "Hub Tier 4", "Hub Tier 5", lambda state: state.has("Elevator Tier 2", player)) + connect(regions, "Hub Tier 5", "Hub Tier 6") + connect(regions, "Hub Tier 6", "Hub Tier 7", lambda state: state.has("Elevator Tier 3", player)) + connect(regions, "Hub Tier 7", "Hub Tier 8") + connect(regions, "Hub Tier 8", "Hub Tier 9", lambda state: state.has("Elevator Tier 4", player)) + connect(regions, "Overworld", "Gas Area", lambda state: + state_logic.can_produce_all(state, ("Gas Mask", "Gas Filter"))) + connect(regions, "Overworld", "Radioactive Area", lambda state: + state_logic.can_produce_all(state, ("Hazmat Suit", "Iodine Infused Filter"))) + connect(regions, "Overworld", "Mam", lambda state: state_logic.can_build(state, "MAM")) + connect(regions, "Overworld", "AWESOME Shop", lambda state: + state_logic.can_build_all(state, ("AWESOME Shop", "AWESOME Sink"))) + + def can_produce_all_allowing_handcrafting(parts: Tuple[str, ...]) -> Callable[[CollectionState], bool]: + def logic_rule(state: CollectionState): + return state_logic.can_produce_all_allowing_handcrafting(state, game_logic, parts) + + return logic_rule + + for hub_tier, milestones_per_hub_tier in enumerate(game_logic.hub_layout, 1): + for minestone, parts_per_milestone in enumerate(milestones_per_hub_tier, 1): + connect(regions, f"Hub Tier {hub_tier}", f"Hub {hub_tier}-{minestone}", + can_produce_all_allowing_handcrafting(parts_per_milestone.keys())) + + for building_name, building in game_logic.buildings.items(): + if building.can_produce: + connect(regions, "Overworld", building_name, + lambda state, building_name=building_name: state_logic.can_build(state, building_name)) + + for tree_name, tree in game_logic.man_trees.items(): + connect(regions, "Mam", tree_name) + + for node in tree.nodes: + if not node.depends_on: + connect(regions, tree_name, f"{tree_name}: {node.name}", + lambda state, parts=node.unlock_cost.keys(): state_logic.can_produce_all(state, parts)) + else: + for parent in node.depends_on: + connect(regions, f"{tree_name}: {parent}", f"{tree_name}: {node.name}", + lambda state, parts=node.unlock_cost.keys(): state_logic.can_produce_all(state, parts)) + + +def throwIfAnyLocationIsNotAssignedToARegion(regions: Dict[str, Region], regionNames: Set[str]): + existingRegions = set() + + for region in regions.keys(): + existingRegions.add(region) + + if (regionNames - existingRegions): + raise Exception(f"Satisfactory: the following regions are used in locations: {regionNames - existingRegions}, but no such region exists") + + +def create_region(world: MultiWorld, player: int, + locations_per_region: Dict[str, List[LocationData]], name: str) -> Region: + + region = Region(name, player, world) + + if name in locations_per_region: + for location_data in locations_per_region[name]: + location = SatisfactoryLocation(player, location_data, region) + region.locations.append(location) + + return region + + +def create_regions(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], + region_names: List[str]) -> Dict[str, Region]: + + regions: Dict[str, Region] = {} + + for name in region_names: + regions[name] = create_region(world, player, locations_per_region, name) + + return regions + + +def connect(regions: Dict[str, Region], source: str, target: str, + rule: Optional[Callable[[CollectionState], bool]] = None): + + sourceRegion = regions[source] + targetRegion = regions[target] + + sourceRegion.connect(targetRegion, rule=rule) + + +def get_locations_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]: + per_region: Dict[str, List[LocationData]] = {} + + for location in locations: + per_region.setdefault(location.region, []).append(location) + + return per_region diff --git a/worlds/satisfactory/StateLogic.py b/worlds/satisfactory/StateLogic.py new file mode 100644 index 0000000000..d0b1dc3aea --- /dev/null +++ b/worlds/satisfactory/StateLogic.py @@ -0,0 +1,95 @@ +from typing import Tuple, List, Optional, Set, Iterable +from BaseClasses import CollectionState +from .GameLogic import GameLogic, Recipe, PowerInfrastructureLevel +from .Options import SatisfactoryOptions + +EventId: Optional[int] = None + +part_event_prefix = "Can Produce: " +building_event_prefix = "Can Build: " + +class StateLogic: + player: int + options: SatisfactoryOptions + initial_unlocked_items: Set[str] + + def __init__(self, player: int, options: SatisfactoryOptions): + self.player = player + self.options = options + + def has_recipe(self, state: CollectionState, recipe: Recipe): + return recipe.implicitly_unlocked or state.has(recipe.name, self.player) + + 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) + + def can_build_any(self, state: CollectionState, building_names: Optional[Iterable[str]]) -> bool: + return building_names is None or \ + state.has_any(map(self.to_building_event, building_names), self.player) + + def can_build_all(self, state: CollectionState, building_names: Optional[Iterable[str]]) -> bool: + return building_names is None or \ + state.has_all(map(self.to_building_event, building_names), self.player) + + def can_produce(self, state: CollectionState, part_name: Optional[str]) -> bool: + return part_name is None or state.has(part_event_prefix + part_name, self.player) + + def can_power(self, state: CollectionState, power_level: Optional[PowerInfrastructureLevel]) -> bool: + return power_level is None or state.has(building_event_prefix + power_level.to_name(), self.player) + + def can_produce_all(self, state: CollectionState, parts: Optional[Iterable[str]]) -> bool: + if parts and "SAM" in parts: + debug = "Now" + + return parts is None or \ + state.has_all(map(self.to_part_event, parts), self.player) + + def can_produce_all_allowing_handcrafting(self, state: CollectionState, logic: GameLogic, + parts: Optional[Tuple[str, ...]]) -> bool: + + def can_handcraft_part(part: str) -> bool: + if self.can_produce(state, part): + return True + elif part not in logic.handcraftable_recipes: + return False + + recipes: List[Recipe] = logic.handcraftable_recipes[part] + + return any( + self.has_recipe(state, recipe) + and (not recipe.inputs or self.can_produce_all_allowing_handcrafting(state, logic, recipe.inputs)) + for recipe in recipes) + + return not parts or all(can_handcraft_part(part) for part in parts) + + def can_produce_specific_recipe_for_part(self, state: CollectionState, recipe: Recipe) -> bool: + if recipe.needs_pipes and ( + not self.can_build_any(state, ("Pipes Mk.1", "Pipes Mk.2")) or + not self.can_build_any(state, ("Pipeline Pump Mk.1", "Pipeline Pump Mk.2"))): + return False + + 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 \ + not self.can_build_any(state, map(self.to_belt_name, range(recipe.minimal_belt_speed, 6))): + return False + + return self.has_recipe(state, recipe) \ + and self.can_build(state, recipe.building) \ + and self.can_produce_all(state, recipe.inputs) + + def is_game_phase(self, state: CollectionState, phase: int) -> bool: + return state.has(f"Elevator Tier {phase}", self.player) + + @staticmethod + def to_part_event(part: str) -> str: + return part_event_prefix + part + + @staticmethod + def to_building_event(part: str) -> str: + return building_event_prefix + part + + @staticmethod + def to_belt_name(power_level: int) -> str: + return "Conveyor Mk." + str(power_level) diff --git a/worlds/satisfactory/Web.py b/worlds/satisfactory/Web.py new file mode 100644 index 0000000000..34701976f1 --- /dev/null +++ b/worlds/satisfactory/Web.py @@ -0,0 +1,16 @@ +from BaseClasses import Tutorial +from ..AutoWorld import WebWorld + + +class SatisfactoryWebWorld(WebWorld): + theme = "dirt" + setup = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Satisfactory Archipelago mod and connect it to an Archipelago Multiworld", + "English", + "setup_en.md", + "setup/en", + ["Robb", "Jarno"] + ) + tutorials = [setup] + rich_text_options_doc = True diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py new file mode 100644 index 0000000000..ef9e29b3fb --- /dev/null +++ b/worlds/satisfactory/__init__.py @@ -0,0 +1,172 @@ +from typing import Dict, List, Set, TextIO, ClassVar, Tuple +from BaseClasses import Item, MultiWorld, ItemClassification, CollectionState +from .GameLogic import GameLogic +from .Items import Items +from .Locations import Locations, LocationData +from .StateLogic import EventId, StateLogic +from .Options import SatisfactoryOptions, Placement +from .Regions import SatisfactoryLocation, create_regions_and_return_locations +from .Web import SatisfactoryWebWorld +from ..AutoWorld import World + + +class SatisfactoryWorld(World): + """ + Satisfactory is a first-person open-world factory building game with a dash of exploration and combat. + Explore an alien planet, create multi-story factories, and enter conveyor belt heaven! + """ + + game = "Satisfactory" + options_dataclass = SatisfactoryOptions + options: SatisfactoryOptions + topology_present = False + data_version = 0 + web = SatisfactoryWebWorld() + + item_name_to_id = Items.item_names_and_ids + location_name_to_id = Locations().get_locations_for_data_package() + item_name_groups = Items.get_item_names_per_category() + + game_logic: ClassVar[GameLogic] = GameLogic() + state_logic: StateLogic + items: Items + + 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.items = Items(self.player, self.game_logic, self.random, self.options) + + 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: + self.push_precollected("Building: AWESOME Sink") + self.push_precollected("Building: AWESOME Shop") + if self.options.energy_link_logic_placement.value == Placement.starting_inventory: + self.push_precollected("Building: Power Storage") + if self.options.splitter_placement == Placement.starting_inventory: + self.push_precollected("Building: Conveyor Splitter") + self.push_precollected("Building: Conveyor Merger") + + if not self.options.trap_selection_override.value: + self.options.trap_selection_override.value = self.options.trap_selection_preset.get_selected_list() + + starting_inventory: List[str] = self.options.starting_inventory_preset.get_selected_list() + for item_name in starting_inventory: + self.push_precollected(item_name) + + + def create_regions(self) -> None: + locations: List[LocationData] = \ + Locations(self.game_logic, self.options, self.state_logic, self.items).get_locations() + create_regions_and_return_locations( + self.multiworld, self.options, self.player, self.game_logic, self.state_logic, locations) + + + def create_items(self) -> None: + self.setup_events() + + number_of_locations: int = len(self.multiworld.get_unfilled_locations(self.player)) + self.multiworld.itempool += \ + self.items.build_item_pool(self.random, self.multiworld, self.options, number_of_locations) + + + def set_rules(self) -> None: + resource_sink_goal: bool = "AWESOME Sink Points" in self.options.goal_selection + + last_elevator_tier: int = \ + len(self.game_logic.space_elevator_tiers) if resource_sink_goal \ + else self.options.final_elevator_package.value + + required_parts: Set[str] = set(self.game_logic.space_elevator_tiers[last_elevator_tier - 1].keys()) + + if resource_sink_goal: + required_parts.union(self.game_logic.buildings["AWESOME Sink"].inputs) + + required_parts_tuple: Tuple[str, ...] = tuple(required_parts) + + self.multiworld.completion_condition[self.player] = \ + lambda state: self.state_logic.can_produce_all(state, required_parts_tuple) + + + def collect(self, state: CollectionState, item: Item) -> bool: + change = super().collect(state, item) + if change and item.name == "Recipe: Quartz Purification": + state.prog_items[self.player]["Recipe: Distilled Silica"] = 1 + return change + + + def remove(self, state: CollectionState, item: Item) -> bool: + change = super().remove(state, item) + if change and item.name == "Recipe: Quartz Purification": + del state.prog_items[self.player]["Recipe: Distilled Silica"] + return change + + + def fill_slot_data(self) -> Dict[str, object]: + slot_hub_layout: List[List[Dict[str, int]]] = [] + + for tier, milestones in enumerate(self.game_logic.hub_layout, 1): + slot_hub_layout.append([]) + for milestone, parts in enumerate(milestones, 1): + slot_hub_layout[tier - 1].append({}) + for part, amount in parts.items(): + # ItemIDs of bundles are shared with their component item + bundled_name = f"Bundle: {part}" + slot_hub_layout[tier - 1][milestone - 1][self.item_name_to_id[bundled_name]] = amount + + return { + "Data": { + "HubLayout": slot_hub_layout, + "SlotsPerMilestone": self.game_logic.slots_per_milestone, + "Options": { + "GoalSelection": self.options.goal_selection.value, + "GoalRequirement": self.options.goal_requirement.value, + "FinalElevatorTier": self.options.final_elevator_package.value, + "FinalResourceSinkPoints": self.options.final_awesome_sink_points.value, + "EnableHardDriveGacha": True if self.options.hard_drive_progression_limit else False, + "FreeSampleEquipment": self.options.free_sample_equipment.value, + "FreeSampleBuildings": self.options.free_sample_buildings.value, + "FreeSampleParts": self.options.free_sample_parts.value, + "FreeSampleRadioactive": bool(self.options.free_sample_radioactive), + "EnergyLink": bool(self.options.energy_link) + } + }, + "DeathLink": bool(self.options.death_link) + } + + + def write_spoiler(self, spoiler_handle: TextIO): + self.items.write_progression_chain(self.multiworld, spoiler_handle) + + + def get_filler_item_name(self) -> str: + return self.items.get_filler_item_name(self.random, self.options) + + + def setup_events(self): + location: SatisfactoryLocation + for location in self.multiworld.get_locations(self.player): + if location.address == EventId: + item_name = location.event_name + + item = Item(item_name, ItemClassification.progression, EventId, self.player) + + location.place_locked_item(item) + location.show_in_spoiler = False + + + def create_item(self, name: str) -> Item: + return Items.create_item(self.items, name, self.player) + + + def push_precollected(self, item_name: str) -> None: + item = self.create_item(item_name) + self.multiworld.push_precollected(item) diff --git a/worlds/satisfactory/docs/en_Satisfactory.md b/worlds/satisfactory/docs/en_Satisfactory.md new file mode 100644 index 0000000000..b2b96f1654 --- /dev/null +++ b/worlds/satisfactory/docs/en_Satisfactory.md @@ -0,0 +1,174 @@ +# Satisfactory + + + +## Where is the settings page? + +The [player settings page for this game](../player-settings) +contains all the options you need to configure and export a config file. + +> ⚠ Pre-Release Note: The above link does not work because it would go to the live Archipelago site. +> Manually construct a yaml yourself from the one pinned in the Discord: +> + +## What does randomization do to this game? + +In Satisfactory, the HUB Milestones and MAM Research Nodes are shuffled, +causing technologies to be obtained in a non-standard order. +The costs of unlocking these technologies are also shuffled. +There are also a few new purchases in the AWESOME Shop. +An alternate recipe you've never used before may end up required to progress, for example. + + + +## What is the goal of Satisfactory? + +The player can choose from a number of goals using their YAML settings: + +- Complete a certain [Space Elevator](https://satisfactory.wiki.gg/wiki/Space_Elevator) tier + +- Supply items to the [AWESOME Sink](https://satisfactory.wiki.gg/wiki/AWESOME_Sink) totalling a configurable amount of points to finish. + +In the current implementation, selecting multiple goals +requires completion of any one goal to complete the slot. + +## What Satisfactory items can appear in other players' worlds? + +Satisfactory's technologies are removed from the HUB and MAM and placed into other players' worlds. +When those technologies are found, they are sent back to Satisfactory +along with, optionally, free samples of those technologies. + +Other players' worlds may have Resource Bundles of building materials, equipment, ammunition, or FICSIT Coupons. +They may also contain Traps. + +## What is a Free Sample? + +A free sample is a package of items in Satisfactory granted in addition to a technology received from another world. +For equipment and component crafting recipes, this is the output product. +For buildings, this is the ingredients for the building. +For example, receiving the [Nobelisk Detonator MAM Node](https://satisfactory.wiki.gg/wiki/Nobelisk_Detonator#Unlocking) +would give you one Nobelisk Detonator and 50 Nobelisk, +receiving the [Jump Pads Milestone](https://satisfactory.wiki.gg/wiki/Milestones#Tier_2) +would give you the ingredients to construct 5 Jump Pads and 5 U-Jelly Landing Pads, etc. +In Satisfactory multiplayer, each Satisfactory player gets a copy of the sample. + +You can separately configure how many samples to receive for buildings, equipment, and crafting components +in your player settings. + +## What is a Resource Bundle? + +A resource bundle is a package of items received as a check from another world. +They must be collected by constructing an Archipelago Portal. +For example, `Bundle: Jetpack` would contain a single jetpack. + +## What does another world's item look like in Satisfactory? + +In Satisfactory, items which need to be sent to other worlds appear in the HUB and MAM as info cards +in a similar manner to the base game's building and recipe unlocks. +Info cards have the Archipelago icon +and are color coded to indicate what Archipelago progression type they are. + +Hover over them to read a description, since many Satisfactory UIs (such as the MAM) cut this information off. + +![screenshot of HUB with some remote and some local items](https://raw.githubusercontent.com/Jarno458/SatisfactoryArchipelagoMod/main/Docs/localAndRemoteItems.JPG) + +Upon successful unlock of the technology, the item will be sent to its home world. + +## When the pioneer receives an item, what happens? + +When the player receives a technology, it is instantly unlocked and able to be crafted or constructed. +A message will appear in the chat to notify the player, +and if free samples are enabled the player may also receive some items delivered directly to their inventory. +Bundles will instantly be added to the Archipelago Portal network and can be collected at any Archipelago Portal. + +## What is EnergyLink? + +EnergyLink is an energy storage supported by certain games that is shared across all worlds in a multiworld. +In Satisfactory, if enabled in the player settings, all base-game Power Storage buildings will act as Energy Link interfaces. +They will deposit surplus produced energy and draw energy from the shared storage when needed. + +Just like the base game, there is no limit to the discharge/draw rate of one building, +and each Power Storage provides TODO MW of charging throughput. +The shared storage has unlimited capacity, but TODO% of energy is lost during depositing. +The amount of energy currently in the shared storage is displayed in the Archipelago client +and appears in the Power Storage building UI. + +## What is the Archipelago Portal? + +The Archipelago Portal is a building that serves multiple purposes: + +- Collecting received "Resource Bundle"-type items. +- Transfering items within your Satisfactory world +- Transfering items between multiple Satisfactory worlds +- Gifting items to other games that support the Archipelago Gifting system. + +The building requires power to operate. +You can build multiple portals or use faster belts to increase their bandwith. +However, they currently have no filtering capabilities, +so you must deal with this problem when handling their output items. + +## What is a Trap? + +You can optionally enable that some Traps be mixed into the item pool. +Traps are items that will instantly trigger some sort of surprise on the player when received. +Their severity varies from annoyance to killing the player. +A few traps are included in the default options. + +## Where do I run Archipelago commands? + +You can use the game's build-in chat menu. +Check the game's keybinding options to see how to open it. +Run the `/help` command to list all available commands. +Note that Archipelago commands are _not_ prefixed with `!` inside of Satisfactory. + +Note that multiple base-game bugs affect the chat menu's functionality +and Archipelago can put a lot of info into the chat. +You may wish to launch the Archipelago Text Client and use it to run commands instead of the game's chat. + +## Multiplayer and Dedicated Servers + +It is possible to host a Satisfactory Archipelago Slot using the game's built in host-and-play multiplayer, allowing other Satisfactory players to join in constructing your factory. +This experience is wonderfull - but there are few things not yet properly working for multiplayer +* Death-links do not kill clients +* Starting inventory for clients is missing +Remember that client players must have the same mods installed as the host player to join, +however, they do not need to configure Archipelago connection settings. + +Dedicated server support is only working for windows at the moment. + +## Additional Mods + +It is possible to load other Satisfactory mods in tandem with the Archipelago Satisfactory mod. +However, no guarantee is made that any mods except the "Certified Compatible Mods" listed below will work correctly, +especially if they affect game progression, recipes, or add unlocks to base-game technologies. + +Content added by unaffiliated mods may end up inaccessible based on your chosen slot settings, +for example, its milestones could be in a tier that is after your goal. +You may be able to write patches using [ContentLib](https://ficsit.app/mod/ContentLib) +to adjust other mods to work with your slot settings, +but doing so is out of the scope of this guide. + +Use unaffiliated mods at your own risk, support will not be offered. + +The following mods are **required dependencies** of the Archipelago mod and **will automatically be installed for you** +when you install it using the Satisfactory Mod Manager: + +- [ContentLib](https://ficsit.app/mod/ContentLib) - Runtime content generation. +- [Free Samples](https://ficsit.app/mod/FreeSamples) - Used to implement the Free Samples options. Even if you don't have this game option enabled, the mod will still be present, but its functionality will be disabled. +- [MAM Enhancer](https://ficsit.app/mod/MAMTips) - Allows viewing MAM research nodes in detail. Enables you to hover over the items/unlocks of a node to see more info, especially important when their names get long. +- [FixClientResourceSinkPoints](https://ficsit.app/mod/FixClientResourceSinkPoints) - Fixes a bug where AWESOME Sink points values aren't loaded properly on multiplayer clients. + +### Certified Compatible Mods + +The following mods are known to work with Archipelago: + + + +- [The FICSIT Information Tool](https://ficsit.app/mod/TFIT) - View how many Sink Points items are worth and how points-profitable recipes are. Helpful for the AWESOME Points goal. +- [Faster Manual Crafting Redux](https://ficsit.app/mod/FasterManualCraftingRedux) - Reduce the early game manual crafting grind with a manual crafting speed that ramps up as you craft larger batches at once. + + + diff --git a/worlds/satisfactory/docs/setup_en.md b/worlds/satisfactory/docs/setup_en.md new file mode 100644 index 0000000000..0883e14260 --- /dev/null +++ b/worlds/satisfactory/docs/setup_en.md @@ -0,0 +1,196 @@ +# Satisfactory Setup Guide + + + +## Required Software + +- Satisfactory, either + - Steam [Satisfactory (Steam)](https://store.steampowered.com/app/526870/Satisfactory/) + - Epic [Satisfactory (Epic)](https://www.epicgames.com/store/en-US/product/satisfactory/home) +- Satisfactory Mod Manager, either + - Automatically via [smm.ficsit.app](https://smm.ficsit.app/) or + - Manually via [latest stable release on GitHub](https://github.com/satisfactorymodding/SatisfactoryModManager/releases/latest/) + +## Overview + +This guide will walk you through installing the Satisfactory Archipelago mod via the Mod Manager +and entering Archipelago server connection details in the mod configuration options. +The server will send the required data to the game client and create the content required by the seed at runtime. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +Your config file contains a set of configuration options +which provide the generator with information about how it should generate your game. +Each player of a multiworld will provide their own config file. +This setup allows each player to enjoy an experience customized for their taste, +and different players in the same multiworld can all have different options. + +### Where do I get a config file? + +The Player Settings page on the website +allows you to configure your personal settings and export a config file from them. +Satisfactory player settings page: [Satisfactory Settings Page](/games/Satisfactory/player-settings) + +> ⚠ Pre-Release Note: The above link does not work because it would go to the live Archipelago site. +> Manually construct a yaml yourself from the one pinned in the Discord: +> + +### Verifying Your Config File + +If you would like to validate your config file to make sure it works, +you may do so on the YAML Validator page. +YAML Validator page: [Yaml Validation Page](/mysterycheck) + +> ⚠ Pre-Release Note: The above link does not work because it would go to the live Archipelago site. +> Manually construct a yaml yourself from the one pinned in the Discord: +> + +### Starting Inventory + +TODO talk about how you can use the Plando? Weighted Options? Manual yaml editing? page to edit your starting inventory, +giving yourself specific technologies (ex. splitters/mergers) and item bundles (ex. start with extra ) out of the gates. + +### Advanced Configuration + +Advanced users can utilize the +[Weighted Options Page](/weighted-options) +and [Plando](/tutorial/Archipelago/plando) +to futher customize their experience. + +> ⚠ Pre-Release Note: The above links do not work because it would go to the live Archipelago site. +> See these links instead: +> +> - +> - + +## Prepare to Host Your Own Satisfactory Game + +### Defining Some Terms + +In Archipelago, multiple Satisfactory worlds may be played simultaneously. +Each of these worlds must be hosted by a Satisfactory game client, each of which is connected to the Archipelago Server. + + + +This guide uses the following terms to refer to the software: + +- **Archipelago Server** - The central Archipelago server, which connects all games to each other. + + +- **Satisfactory Client** - The Satisfactory instance which will be used to host, and play, the game. + +It is important to note that the Satisfactory Archipelago mod +is not yet compatible with dedicated servers or in-game multiplayer. +Each Satisfactory world must be hosted and played by an individual player. + +### Installing Satisfactory + +Purchase and install Satisfactory via one the sources linked [above](#required-software). +Launch the game at least once to ensure that the Mod Manager can detect the game's install location. + +Make sure that you are running the correct branch of the game (Early Access or Experimental) that Archipelago supports. +Learn how to switch branches here: +[Satisfactory Modding Documentation FAQ: Switching Branches](https://docs.ficsit.app/satisfactory-modding/latest/faq.html#_how_do_i_get_the_experimental_or_early_access_branch_of_the_game) + +### Installing Satisfactory Mod Manager + +The Mod Manager is used to install and manage mods for Satisfactory. +It automatically detects your game install location and automatically handles mod dependencies for you. + +Download the Mod Manager here: +[Satisfactory Mod Manager automatic download via ficsit.app](https://smm.ficsit.app/) + +Directions for setting and using up the Mod Manager can be found here: +[Satisfactory Modding Documentation FAQ: Installing the Mod Manager](https://docs.ficsit.app/satisfactory-modding/latest/ForUsers/SatisfactoryModManager.html) + +### Installing the Archipelago Mod + +Once the Mod Manager is installed you can install mods directly in the manager or via the Satisfactory Mod Repository website. + +Inside the Mod Manager, search for and install the "Archipelago Randomizer". +Alternatively, visit the mod page: [Archipelago Randomizer mod on ficsit.app](https://ficsit.app/mod/Archipelago). +Once on the mod page, click the "Install" link in the Latest Versions card. + +The Mod Manager will install all required dependency mods for you with no additional action required. + +As soon as you have the relevant mods installed, you do not need to launch the game through the Mod Manager - desktop shortcuts, Steam, Epic. etc. will all launch the game with mods still loaded. + +### Installing Additional Mods + +You may also wish to install some of the suggested mods mentioned on the +[Archipelago Info page for Satisfactory](/games/Satisfactory/info/en#additional-mods). + +> ⚠ Pre-Release Note: The above link does not work because it would go to the live Archipelago site. +> Use this link instead: +> + +### Entering Connection Details + +After you have installed the mods, launch the game via the Mod Manager or via your preferred method. +Once the game has launched, click on the 'Mods' button on the main menu and open the Archipelago entry. + +Next, enter the connection details in the relevant fields. +You can hover over the fields in the menu for more information and example values. + +- **URI**: Archipelago Server URI and port, for example, `archipelago.gg:49236` +- **Username**: The name you entered as your Player Name when you created your config file. It's also listed in the Name column of your room page. +- **Password**: The password for your slot, blank if you did not assign one. +- **Archipelago Enabled**: Make sure this is checked, otherwise no server connection will be attempted. +- **Debug Mode**: Don't enable it unless the developers ask you to when reporting problems. +- **Force override settings in save**: Leave false for now. It is useful when the server changed ports. Read its tooltip for more info. + +Note that the Satisfactory Client does _not_ need a copy of your Archipelago config file. +The mod communicates with the Archipelago Server, which already has your config file, +to generate the required content at runtime. + +### Creating a New World + +Once you have entered connection details, create a new world using the game's New Game menu. +Make sure to check 'Skip Intro' if you don't want to deal with the game's tutorial sequence. +Consider enabling Advanced Game Settings to allow dealing with bugs that may arise. +Within the Advanced Game Settings menus, +you may wish to switch the "Keep Inventory" setting to "Keep Everything" to avoid dropping items on death, +although this will never lock you out of progression. + +### Verifying Connection Success + +Once connected to the AP server, +you can issue the `/help` command in the game's chat to list available commands, such as `/hint`. +For more information about the commands you can use, see the [Commands Guide](/tutorial/Archipelago/commands/en). +Note that Archipelago commands are not prefixed with `!` inside of Satisfactory. +You may wish to use the Text Client to run commands since Satisfactory's in game chat is not very user friendly. + +> ⚠ Pre-Release Note: The above link does not work because it would go to the live Archipelago site. +> Use this link instead: +> + + + +## Troubleshooting + +TODO what is the scope of this section? How much do we help with vs. sending people somewhere else + +- If you are having trouble connecting to the Archipelago server, + make sure you have entered the correct server address and port. + The server port may have changed if the room went to sleep. + If you need to enter a new port, + use the "Force override settings in save" option on the mod options menu before loading into a save. +- If you are having trouble using the Satisfactory Mod Manager, join the [Satisfactory Modding Discord](https://discord.ficsit.app) for support. +- If you encounter a game crash, please report it to us via the [Satisfactory Modding Discord](https://discord.ficsit.app). + Please include the following information: + - What you were doing when the crash occurred. + + - Use the Mod Manager to generate a debug zip and attach that file. + [Satisfactory Modding Documentation FAQ: Generating a debug zip](https://docs.ficsit.app/satisfactory-modding/latest/faq.html#_where_can_i_find_the_games_log_files) + - Attach your Archipelago config file to your report. + +## Additional Resources + +- Satisfactory Wiki: [Satisfactory Official Wiki](https://satisfactory.wiki.gg/wiki/) +- Satisfactory Modding FAQ page: [Satisfactory Modding Documentation FAQ](https://docs.ficsit.app/satisfactory-modding/latest/faq.html) +- Satisfactory Archipelago Item names (for hints/starting inventory/etc.) can be found [on the mod's github](https://github.com/Jarno458/Archipelago/blob/Satisfactory/worlds/satisfactory/Items.py)