From 8c68f23dc7d5da0dd9395f4036f71fd4bf535a93 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sat, 22 Feb 2025 15:48:38 +0100 Subject: [PATCH 01/12] progrees i think --- test/general/test_reachability.py | 3 +- worlds/satisfactory/CriticalPathCalculator.py | 79 + worlds/satisfactory/GameLogic.py | 2 - worlds/satisfactory/ItemData.py | 64 +- worlds/satisfactory/Items.py | 1297 +++++++++-------- worlds/satisfactory/Locations.py | 70 +- worlds/satisfactory/Options.py | 6 +- worlds/satisfactory/Regions.py | 29 +- worlds/satisfactory/StateLogic.py | 9 +- worlds/satisfactory/__init__.py | 9 +- 10 files changed, 845 insertions(+), 723 deletions(-) create mode 100644 worlds/satisfactory/CriticalPathCalculator.py diff --git a/test/general/test_reachability.py b/test/general/test_reachability.py index b45a2bdfc0..0837c8f444 100644 --- a/test/general/test_reachability.py +++ b/test/general/test_reachability.py @@ -52,7 +52,8 @@ class TestBase(unittest.TestCase): state = multiworld.get_all_state(False) for location in multiworld.get_locations(): with self.subTest("Location should be reached", location=location.name): - self.assertTrue(location.can_reach(state), f"{location.name} unreachable") + if not location.can_reach(state): + self.assertTrue(location.can_reach(state), f"{location.name} unreachable") for region in multiworld.get_regions(): if region.name in unreachable_regions: diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py new file mode 100644 index 0000000000..75a16ec484 --- /dev/null +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -0,0 +1,79 @@ +from random import Random +from typing import Set, Tuple +from .GameLogic import GameLogic, Recipe +from .Options import SatisfactoryOptions +from .Options import SatisfactoryOptions + +class CriticalPathCalculator: + logic: GameLogic + random: Random + + potential_required_parts: Set[str] + potential_required_buildings: Set[str] + potential_required_belt_speed: int + potential_required_pipes: bool + potential_required_radioactive: bool + potential_required_power: int + potential_required_recipes_names: Set[str] + + def __init__(self, logic: GameLogic, random: Random, options: SatisfactoryOptions): + self.logic = logic + self.random = random + + self.potential_required_parts = set() + self.potential_required_buildings = set() + self.potential_required_belt_speed = 1 + self.potential_required_pipes = False + self.potential_required_radioactive = False + self.potential_required_power: int = 1 + + self.select_minimal_required_parts_for( + tuple(self.logic.space_elevator_tiers[options.final_elevator_package - 1].keys()) + ) + for i in range(self.potential_required_belt_speed, 1): + self.select_minimal_required_parts_for(self.logic.buildings[f"Conveyor Mk.{i}"].inputs) + if self.potential_required_pipes: + self.select_minimal_required_parts_for(self.logic.buildings["Pipeline Pump Mk.1"].inputs) + self.select_minimal_required_parts_for(self.logic.buildings["Pipeline Pump Mk.2"].inputs) + if self.potential_required_radioactive: + self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) + self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) + for i in range(self.potential_required_belt_speed, 1): + power_recipe = random.choice(self.logic.requirement_per_powerlevel[i]) + self.select_minimal_required_parts_for(power_recipe.inputs) + self.potential_required_buildings.add(power_recipe.building) + + + self.potential_required_recipes_names = set( + recipe.name + for part in self.potential_required_parts + for recipe in self.logic.recipes[part] + ) + self.potential_required_recipes_names.update( + "Building: "+ building + for building in self.potential_required_buildings + ) + + debug = True + + + def select_minimal_required_parts_for(self, parts: Tuple[str]) -> None: + if parts: + for part in parts: + if part in self.potential_required_parts: + continue + + self.potential_required_parts.add(part) + + for recipe in self.logic.recipes[part]: + self.potential_required_belt_speed = \ + max(self.potential_required_belt_speed, recipe.minimal_belt_speed) + + self.select_minimal_required_parts_for(recipe.inputs) + self.select_minimal_required_parts_for(self.logic.buildings[recipe.building].inputs) + + self.potential_required_buildings.add(recipe.building) + + if self.logic.buildings[recipe.building].power_requirement: + self.potential_required_power = \ + max(self.potential_required_power, self.logic.buildings[recipe.building].power_requirement) \ No newline at end of file diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index b7b71392d4..aae7f67ee8 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -572,8 +572,6 @@ class GameLogic: 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] = { diff --git a/worlds/satisfactory/ItemData.py b/worlds/satisfactory/ItemData.py index 2f8dd991e2..7fc3105cf4 100644 --- a/worlds/satisfactory/ItemData.py +++ b/worlds/satisfactory/ItemData.py @@ -1,40 +1,42 @@ -from enum import Enum +from enum import IntFlag 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 ItemGroups(IntFlag): + Parts = 1 << 1 + Equipment = 1 << 2 + Ammo = 1 << 3 + Recipe = 1 << 4 + Building = 1 << 5 + Trap = 1 << 6 + Lights = 1 << 7 + Foundations = 1 << 8 + Transport = 1 << 9 + Trains = 1 << 10 + ConveyorMk1 = 1 << 11 + ConveyorMk2 = 1 << 12 + ConveyorMk3 = 1 << 13 + ConveyorMk4 = 1 << 14 + ConveyorMk5 = 1 << 15 + ConveyorSupports = 1 << 16 + PipesMk1 = 1 << 17 + PipesMk2 = 1 << 18 + PipelineSupports = 1 << 19 + HyperTubes = 1 << 20 + Signs = 1 << 21 + Pilars = 1 << 22 + Beams = 1 << 23 + Walls = 1 << 24 + Upgrades = 1 << 25 + Vehicles = 1 << 26 + Customizer = 1 << 27 + ConveyorMk6 = 1 << 28 + BasicNeeds = 1 << 29 + class ItemData(NamedTuple): """Represents an item in the pool, it could be a resource bundle, production recipe, trap, etc.""" - category: Set[ItemGroups] + category: ItemGroups code: int type: ItemClassification = ItemClassification.filler count: int = 1 diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index 735b06e0a1..f45732cba3 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -6,685 +6,686 @@ from .GameLogic import GameLogic, Recipe from .Options import SatisfactoryOptions from .ItemData import ItemData, ItemGroups as G from .Options import SatisfactoryOptions +from .CriticalPathCalculator import CriticalPathCalculator 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), + "Bundle: Adaptive Control Unit": ItemData(G.Parts, 1338000), + "Bundle: AI Limiter": ItemData(G.Parts, 1338001), + "Bundle: Alclad Aluminum Sheet": ItemData(G.Parts, 1338002), + "Bundle: Blue Power Slug": ItemData(G.Parts, 1338003), + "Bundle: Yellow Power Slug": ItemData(G.Parts, 1338004), + "Bundle: Alien Protein": ItemData(G.Parts, 1338005), + "Bundle: Purple Power Slug": ItemData(G.Parts, 1338006), + "Bundle: Aluminum Casing": ItemData(G.Parts, 1338007), + "Bundle: Aluminum Ingot": ItemData(G.Parts, 1338008), + "Bundle: Aluminum Scrap": ItemData(G.Parts, 1338009), + "Bundle: Assembly Director System": ItemData(G.Parts, 1338010), + "Bundle: Automated Wiring": ItemData(G.Parts, 1338011), + "Bundle: Battery": ItemData(G.Parts, 1338012), + "Bundle: Bauxite": ItemData(G.Parts, 1338013), + "Bundle: Neural-Quantum Processor": ItemData(G.Parts, 1338014), #1.0 + "Bundle: Biomass": ItemData(G.Parts, 1338015), + "Bundle: Black Powder": ItemData(G.Parts, 1338016), + "Bundle: Cable": ItemData(G.Parts, 1338017), + "Bundle: Caterium Ingot": ItemData(G.Parts, 1338018), + "Bundle: Caterium Ore": ItemData(G.Parts, 1338019), + "Bundle: Circuit Board": ItemData(G.Parts, 1338020), + "Bundle: Coal": ItemData(G.Parts, 1338021), + "Bundle: Singularity Cell": ItemData(G.Parts, 1338022), #1.0 + "Bundle: Compacted Coal": ItemData(G.Parts, 1338023), + "Bundle: Computer": ItemData(G.Parts, 1338024), + "Bundle: Concrete": ItemData(G.Parts, 1338025), + "Bundle: Cooling System": ItemData(G.Parts, 1338026), + "Bundle: Copper Ingot": ItemData(G.Parts, 1338027), + "Bundle: Copper Ore": ItemData(G.Parts, 1338028), + "Bundle: Copper Powder": ItemData(G.Parts, 1338029), + "Bundle: Copper Sheet": ItemData(G.Parts, 1338030), + "Bundle: Adequate Pioneering Statue": ItemData(G.Parts, 1338031), + "Bundle: Crystal Oscillator": ItemData(G.Parts, 1338032), + "Bundle: Electromagnetic Control Rod": ItemData(G.Parts, 1338033), + "Bundle: Empty Canister": ItemData(G.Parts, 1338034), + "Bundle: Empty Fluid Tank": ItemData(G.Parts, 1338035), + "Bundle: Encased Industrial Beam": ItemData(G.Parts, 1338036), + "Bundle: Encased Plutonium Cell": ItemData(G.Trap, 1338037, C.trap), + "Bundle: Encased Uranium Cell": ItemData(G.Trap, 1338038, C.trap), + "Bundle: Fabric": ItemData(G.Parts, 1338039), + "Bundle: FICSIT Coupon": ItemData(G.Parts, 1338040), + "Bundle: AI Expansion Server": ItemData(G.Parts, 1338041), #1.0 + "Bundle: Fused Modular Frame": ItemData(G.Parts, 1338042), + "Bundle: Hard Drive": ItemData(G.Parts, 1338043), + "Bundle: Heat Sink": ItemData(G.Parts, 1338044), + "Bundle: Heavy Modular Frame": ItemData(G.Parts, 1338045), + "Bundle: High-Speed Connector": ItemData(G.Parts, 1338046), + "Bundle: Satisfactory Pioneering Statue": ItemData(G.Parts, 1338047), + "Bundle: Pretty Good Pioneering Statue": ItemData(G.Parts, 1338048), + "Bundle: Iron Ingot": ItemData(G.Parts, 1338049), + "Bundle: Iron Ore": ItemData(G.Parts, 1338050), + "Bundle: Iron Plate": ItemData(G.Parts, 1338051), + "Bundle: Iron Rod": ItemData(G.Parts, 1338052), + "Bundle: Golden Nut Statue": ItemData(G.Parts, 1338053), + "Bundle: Leaves": ItemData(G.Parts, 1338054), + "Bundle: Limestone": ItemData(G.Parts, 1338055), + "Bundle: Magnetic Field Generator": ItemData(G.Parts, 1338056), + "Bundle: Mercer Sphere": ItemData(G.Parts, 1338057), + "Bundle: Modular Engine": ItemData(G.Parts, 1338058), + "Bundle: Modular Frame": ItemData(G.Parts, 1338059), + "Bundle: Motor": ItemData(G.Parts, 1338060), + "Bundle: Mycelia": ItemData(G.Parts, 1338061), + "Bundle: Non-fissile Uranium": ItemData(G.Trap, 1338062, C.trap), + "Bundle: Nuclear Pasta": ItemData(G.Parts, 1338063), + "Bundle: Lizard Doggo Statue": ItemData(G.Parts, 1338064), + "Bundle: Organic Data Capsule": ItemData(G.Parts, 1338065), + "Bundle: Packaged Alumina Solution": ItemData(G.Parts, 1338066), + "Bundle: Packaged Fuel": ItemData(G.Parts, 1338067), + "Bundle: Packaged Heavy Oil Residue": ItemData(G.Parts, 1338068), + "Bundle: Packaged Liquid Biofuel": ItemData(G.Parts, 1338069), + "Bundle: Packaged Nitric Acid": ItemData(G.Parts, 1338070), + "Bundle: Packaged Nitrogen Gas": ItemData(G.Parts, 1338071), + "Bundle: Packaged Oil": ItemData(G.Parts, 1338072), + "Bundle: Packaged Sulfuric Acid": ItemData(G.Parts, 1338073), + "Bundle: Packaged Turbofuel": ItemData(G.Parts, 1338074), + "Bundle: Packaged Water": ItemData(G.Parts, 1338075), + "Bundle: Petroleum Coke": ItemData(G.Parts, 1338076), + "Bundle: Plastic": ItemData(G.Parts, 1338077), + "Bundle: Plutonium Fuel Rod": ItemData(G.Trap, 1338078, C.trap), + "Bundle: Plutonium Pellet": ItemData(G.Trap, 1338079, C.trap), + "Bundle: Plutonium Waste": ItemData(G.Trap, 1338080, C.trap), + "Bundle: Polymer Resin": ItemData(G.Parts, 1338081), + "Bundle: Power Shard": ItemData(G.Parts, 1338082), + "Bundle: Confusing Creature Statue": ItemData(G.Parts, 1338083), + "Bundle: Pressure Conversion Cube": ItemData(G.Parts, 1338084), + "Bundle: Alien Power Matrix": ItemData(G.Parts, 1338085), #1.0 + "Bundle: Quartz Crystal": ItemData(G.Parts, 1338086), + "Bundle: Quickwire": ItemData(G.Parts, 1338087), + "Bundle: Radio Control Unit": ItemData(G.Parts, 1338088), + "Bundle: Raw Quartz": ItemData(G.Parts, 1338089), + "Bundle: Reinforced Iron Plate": ItemData(G.Parts, 1338090), + "Bundle: Rotor": ItemData(G.Parts, 1338091), + "Bundle: Rubber": ItemData(G.Parts, 1338092), + "Bundle: SAM": ItemData(G.Parts, 1338093), # 1.0 + "Bundle: Screw": ItemData(G.Parts, 1338094), + "Bundle: Silica": ItemData(G.Parts, 1338095), + "Bundle: Smart Plating": ItemData(G.Parts, 1338096), + "Bundle: Smokeless Powder": ItemData(G.Parts, 1338097), + "Bundle: Solid Biofuel": ItemData(G.Parts, 1338098), + "Bundle: Somersloop": ItemData(G.Parts, 1338099), + "Bundle: Stator": ItemData(G.Parts, 1338100), + "Bundle: Silver Hog Statue": ItemData(G.Parts, 1338101), + "Bundle: Steel Beam": ItemData(G.Parts, 1338102), + "Bundle: Steel Ingot": ItemData(G.Parts, 1338103), + "Bundle: Steel Pipe": ItemData(G.Parts, 1338104), + "Bundle: Sulfur": ItemData(G.Parts, 1338105), + "Bundle: Supercomputer": ItemData(G.Parts, 1338106), + "Bundle: Superposition Oscillator": ItemData(G.Parts, 1338107), + "Bundle: Thermal Propulsion Rocket": ItemData(G.Parts, 1338108), + "Bundle: Turbo Motor": ItemData(G.Parts, 1338109), + "Bundle: Hog Remains": ItemData(G.Parts, 1338110), + "Bundle: Uranium": ItemData(G.Trap, 1338111, C.trap), + "Bundle: Uranium Fuel Rod": ItemData(G.Trap, 1338112, C.trap), + "Bundle: Uranium Waste": ItemData(G.Trap, 1338113, C.trap), + "Bundle: Versatile Framework": ItemData(G.Parts, 1338114), + "Bundle: Wire": ItemData(G.Parts, 1338115), + "Bundle: Wood": ItemData(G.Parts, 1338116), + "Bundle: Plasma Spitter Remains": ItemData(G.Parts, 1338117), + "Bundle: Stinger Remains": ItemData(G.Parts, 1338118), + "Bundle: Hatcher Remains": ItemData(G.Parts, 1338119), + "Bundle: Alien DNA Capsule": ItemData(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), + "Bundle: Diamonds": ItemData(G.Parts, 1338121), + "Bundle: Time Crystal": ItemData(G.Parts, 1338122), + "Bundle: Ficsite Ingot": ItemData(G.Parts, 1338123), + "Bundle: Ficsite Trigon": ItemData(G.Parts, 1338124), + "Bundle: Reanimated SAM": ItemData(G.Parts, 1338125), + "Bundle: SAM Fluctuator": ItemData(G.Parts, 1338126), + "Bundle: Biochemical Sculptor": ItemData(G.Parts, 1338127), + "Bundle: Ballistic Warp Drive": ItemData(G.Parts, 1338128), + "Bundle: Ficsonium": ItemData(G.Trap, 1338129, C.trap), + "Bundle: Ficsonium Fuel Rod": ItemData(G.Trap, 1338130, C.trap), + "Bundle: Packaged Rocket Fuel": ItemData(G.Parts, 1338131), + "Bundle: Packaged Ionized Fuel": ItemData(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), + "Bundle: Bacon Agaric": ItemData(G.Ammo, 1338150), + "Bundle: Beryl Nut": ItemData(G.Ammo, 1338151), + "Bundle: Blade Runners": ItemData(G.Equipment, 1338152), + "Bundle: Boom Box": ItemData(G.Equipment, 1338153), + "Bundle: Chainsaw": ItemData(G.Equipment, 1338154), + "Bundle: Cluster Nobelisk": ItemData(G.Ammo, 1338155), + #"Bundle: Color Gun": ItemData(G.Equipment, 1338156), Removed in U8 + "Bundle: Cup": ItemData(G.Equipment, 1338157), + "Bundle: Cup (gold)": ItemData(G.Equipment, 1338158, count=0), + "Bundle: Explosive Rebar": ItemData(G.Ammo, 1338159), + "Bundle: Factory Cart": ItemData(G.Equipment, 1338160), + "Bundle: Factory Cart (golden)": ItemData(G.Equipment, 1338161, count=0), + "Bundle: Gas Mask": ItemData(G.Equipment, 1338162), + "Bundle: Gas Nobelisk": ItemData(G.Ammo, 1338163), + "Bundle: Hazmat Suit": ItemData(G.Equipment, 1338164), + "Bundle: Homing Rifle Ammo": ItemData(G.Ammo, 1338165), + "Bundle: Hover Pack": ItemData(G.Equipment, 1338166), + "Bundle: Iron Rebar": ItemData(G.Ammo, 1338167), + "Bundle: Jetpack": ItemData(G.Equipment, 1338168), + "Bundle: Medicinal Inhaler": ItemData(G.Ammo, 1338169), + "Bundle: Nobelisk": ItemData(G.Ammo, 1338170), + "Bundle: Nobelisk Detonator": ItemData(G.Equipment, 1338171), + "Bundle: Nuke Nobelisk": ItemData(G.Ammo, 1338172), + "Bundle: Object Scanner": ItemData(G.Equipment, 1338173), + "Bundle: Paleberry": ItemData(G.Ammo, 1338174), + "Bundle: Parachute": ItemData(G.Equipment, 1338175), + "Bundle: Pulse Nobelisk": ItemData(G.Ammo, 1338176), + "Bundle: Rebar Gun": ItemData(G.Equipment, 1338177), + "Bundle: Rifle": ItemData(G.Equipment, 1338178), + "Bundle: Rifle Ammo": ItemData(G.Ammo, 1338179), + "Bundle: Shatter Rebar": ItemData(G.Ammo, 1338180), + "Bundle: Stun Rebar": ItemData(G.Ammo, 1338181), + "Bundle: Turbo Rifle Ammo": ItemData(G.Ammo, 1338182), + "Bundle: Xeno-Basher": ItemData(G.Equipment, 1338183), + "Bundle: Xeno-Zapper": ItemData(G.Equipment, 1338184), + "Bundle: Zipline": ItemData(G.Equipment, 1338185), + "Bundle: Portable Miner": ItemData(G.Equipment, 1338186), + "Bundle: Gas Filter": ItemData(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), + "Small Inflated Pocket Dimension": ItemData(G.Upgrades, 1338188, C.useful, 11), + "Inflated Pocket Dimension": ItemData(G.Upgrades, 1338189, C.useful, 5), + "Expanded Toolbelt": ItemData(G.Upgrades, 1338190, C.useful, 5), + "Dimensional Depot upload from inventory": ItemData(G.Upgrades | G.BasicNeeds, 1338191, C.useful), #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), + "Recipe: Reinforced Iron Plate": ItemData(G.Recipe, 1338200, C.progression), + "Recipe: Adhered Iron Plate": ItemData(G.Recipe, 1338201, C.progression), + "Recipe: Bolted Iron Plate": ItemData(G.Recipe, 1338202, C.progression), + "Recipe: Stitched Iron Plate": ItemData(G.Recipe, 1338203, C.progression), + "Recipe: Rotor": ItemData(G.Recipe, 1338204, C.progression), + "Recipe: Copper Rotor": ItemData(G.Recipe, 1338205, C.progression), + "Recipe: Steel Rotor": ItemData(G.Recipe, 1338206, C.progression), + "Recipe: Stator": ItemData(G.Recipe, 1338207, C.progression), + "Recipe: Quickwire Stator": ItemData(G.Recipe, 1338208, C.progression), + "Recipe: Plastic": ItemData(G.Recipe, 1338209, C.progression), + "Recipe: Residual Plastic": ItemData(G.Recipe, 1338210, C.progression), + "Recipe: Recycled Plastic": ItemData(G.Recipe, 1338211, C.progression), + "Recipe: Rubber": ItemData(G.Recipe, 1338212, C.progression), + "Recipe: Residual Rubber": ItemData(G.Recipe, 1338213, C.progression), + "Recipe: Recycled Rubber": ItemData(G.Recipe, 1338214, C.progression), + "Recipe: Iron Plate": ItemData(G.Recipe, 1338215, C.progression), + "Recipe: Coated Iron Plate": ItemData(G.Recipe, 1338216, C.progression), + "Recipe: Steel Cast Plate": ItemData(G.Recipe, 1338217, C.progression), # 1.0 + "Recipe: Iron Rod": ItemData(G.Recipe, 1338218, C.progression), + "Recipe: Steel Rod": ItemData(G.Recipe, 1338219, C.progression), + "Recipe: Screw": ItemData(G.Recipe, 1338220, C.progression), + "Recipe: Cast Screw": ItemData(G.Recipe, 1338221, C.progression), + "Recipe: Steel Screw": ItemData(G.Recipe, 1338222, C.progression), + "Recipe: Wire": ItemData(G.Recipe, 1338223, C.progression), + "Recipe: Fused Wire": ItemData(G.Recipe, 1338224, C.progression), + "Recipe: Iron Wire": ItemData(G.Recipe, 1338225, C.progression), + "Recipe: Caterium Wire": ItemData(G.Recipe, 1338226, C.progression), + "Recipe: Cable": ItemData(G.Recipe, 1338227, C.progression), + "Recipe: Coated Cable": ItemData(G.Recipe, 1338228, C.progression), + "Recipe: Insulated Cable": ItemData(G.Recipe, 1338229, C.progression), + "Recipe: Quickwire Cable": ItemData(G.Recipe, 1338230, C.progression), + "Recipe: Quickwire": ItemData(G.Recipe, 1338231, C.progression), + "Recipe: Fused Quickwire": ItemData(G.Recipe, 1338232, C.progression), + "Recipe: Copper Sheet": ItemData(G.Recipe, 1338233, C.progression), + "Recipe: Steamed Copper Sheet": ItemData(G.Recipe, 1338234, C.progression), + "Recipe: Steel Pipe": ItemData(G.Recipe, 1338235, C.progression), + "Recipe: Steel Beam": ItemData(G.Recipe, 1338236, C.progression), + "Recipe: Neural-Quantum Processor": ItemData(G.Recipe, 1338237, C.progression), # 1.0 + "Recipe: Heavy Oil Residue": ItemData(G.Recipe, 1338238, C.progression), + "Recipe: Polymer Resin": ItemData(G.Recipe, 1338239, C.progression), + "Recipe: Fuel": ItemData(G.Recipe, 1338240, C.progression), + "Recipe: Residual Fuel": ItemData(G.Recipe, 1338241, C.progression), + "Recipe: Diluted Fuel (refinery)": ItemData(G.Recipe, 1338242, C.progression), + "Recipe: AI Expansion Server": ItemData(G.Recipe, 1338243, C.progression), # 1.0 + "Recipe: Concrete": ItemData(G.Recipe, 1338244, C.progression), + "Recipe: Rubber Concrete": ItemData(G.Recipe, 1338245, C.progression), + "Recipe: Wet Concrete": ItemData(G.Recipe, 1338246, C.progression), + "Recipe: Fine Concrete": ItemData(G.Recipe, 1338247, C.progression), + "Recipe: Silica": ItemData(G.Recipe, 1338248, C.progression), + "Recipe: Cheap Silica": ItemData(G.Recipe, 1338249, C.progression), + "Recipe: Quartz Crystal": ItemData(G.Recipe, 1338250, C.progression), + "Recipe: Pure Quartz Crystal": ItemData(G.Recipe, 1338251, C.progression), + "Recipe: Iron Ingot": ItemData(G.Recipe, 1338252, C.progression), + "Recipe: Pure Iron Ingot": ItemData(G.Recipe, 1338253, C.progression), + "Recipe: Iron Alloy Ingot": ItemData(G.Recipe, 1338254, C.progression), + "Recipe: Steel Ingot": ItemData(G.Recipe, 1338255, C.progression), + "Recipe: Coke Steel Ingot": ItemData(G.Recipe, 1338256, C.progression), + "Recipe: Compacted Steel Ingot": ItemData(G.Recipe, 1338257, C.progression), + "Recipe: Solid Steel Ingot": ItemData(G.Recipe, 1338258, C.progression), + "Recipe: Copper Ingot": ItemData(G.Recipe, 1338259, C.progression), + "Recipe: Copper Alloy Ingot": ItemData(G.Recipe, 1338260, C.progression), + "Recipe: Pure Copper Ingot": ItemData(G.Recipe, 1338261, C.progression), + "Recipe: Caterium Ingot": ItemData(G.Recipe, 1338262, C.progression), + "Recipe: Pure Caterium Ingot": ItemData(G.Recipe, 1338263, C.progression), + "Recipe: Alien Power Matrix": ItemData(G.Recipe, 1338264), # 1.0 + "Recipe: Ficsite Ingot (Aluminum)": ItemData(G.Recipe, 1338265, C.progression), # 1.0 + "Recipe: Ficsite Ingot (Caterium)": ItemData(G.Recipe, 1338266, C.progression), # 1.0 + "Recipe: Ficsite Ingot (Iron)": ItemData(G.Recipe, 1338267, C.progression), # 1.0 + "Recipe: Ficsite Trigon": ItemData(G.Recipe, 1338268, C.progression), # 1.0 + "Recipe: Reanimated SAM": ItemData(G.Recipe, 1338269, C.progression), # 1.0 + "Recipe: SAM Fluctuator": ItemData(G.Recipe, 1338270, C.progression), # 1.0 + "Recipe: Petroleum Coke": ItemData(G.Recipe, 1338271, C.progression), + "Recipe: Compacted Coal": ItemData(G.Recipe, 1338272, C.progression), + "Recipe: Motor": ItemData(G.Recipe, 1338273, C.progression), + "Recipe: Rigor Motor": ItemData(G.Recipe, 1338274, C.progression), + "Recipe: Electric Motor": ItemData(G.Recipe, 1338275, C.progression), + "Recipe: Modular Frame": ItemData(G.Recipe, 1338276, C.progression), + "Recipe: Bolted Frame": ItemData(G.Recipe, 1338277, C.progression), + "Recipe: Steeled Frame": ItemData(G.Recipe, 1338278, C.progression), + "Recipe: Heavy Modular Frame": ItemData(G.Recipe, 1338279, C.progression), + "Recipe: Heavy Flexible Frame": ItemData(G.Recipe, 1338280, C.progression), + "Recipe: Heavy Encased Frame": ItemData(G.Recipe, 1338281, C.progression), + "Recipe: Encased Industrial Beam": ItemData(G.Recipe, 1338282, C.progression), + "Recipe: Encased Industrial Pipe": ItemData(G.Recipe, 1338283, C.progression), + "Recipe: Computer": ItemData(G.Recipe, 1338284, C.progression), + "Recipe: Crystal Computer": ItemData(G.Recipe, 1338285, C.progression), + "Recipe: Caterium Computer": ItemData(G.Recipe, 1338286, C.progression), + "Recipe: Circuit Board": ItemData(G.Recipe, 1338287, C.progression), + "Recipe: Electrode Circuit Board": ItemData(G.Recipe, 1338288, C.progression), + "Recipe: Silicon Circuit Board": ItemData(G.Recipe, 1338289, C.progression), + "Recipe: Caterium Circuit Board": ItemData(G.Recipe, 1338290, C.progression), + "Recipe: Crystal Oscillator": ItemData(G.Recipe, 1338291, C.progression), + "Recipe: Insulated Crystal Oscillator": ItemData(G.Recipe, 1338292, C.progression), + "Recipe: AI Limiter": ItemData(G.Recipe, 1338293, C.progression), + "Recipe: Electromagnetic Control Rod": ItemData(G.Recipe, 1338294, C.progression), + "Recipe: Electromagnetic Connection Rod": ItemData(G.Recipe, 1338295, C.progression), + "Recipe: High-Speed Connector": ItemData(G.Recipe, 1338296, C.progression), + "Recipe: Silicon High-Speed Connector": ItemData(G.Recipe, 1338297, C.progression), + "Recipe: Smart Plating": ItemData(G.Recipe, 1338298, C.progression), + "Recipe: Plastic Smart Plating": ItemData(G.Recipe, 1338299, C.progression), + "Recipe: Versatile Framework": ItemData(G.Recipe, 1338300, C.progression), + "Recipe: Flexible Framework": ItemData(G.Recipe, 1338301, C.progression), + "Recipe: Automated Wiring": ItemData(G.Recipe, 1338302, C.progression), + "Recipe: Automated Speed Wiring": ItemData(G.Recipe, 1338303, C.progression), + "Recipe: Modular Engine": ItemData(G.Recipe, 1338304, C.progression), + "Recipe: Adaptive Control Unit": ItemData(G.Recipe, 1338305, C.progression), + "Recipe: Diluted Fuel": ItemData(G.Recipe, 1338306, C.progression), + "Recipe: Alumina Solution": ItemData(G.Recipe, 1338307, C.progression), + "Recipe: Automated Miner": ItemData(G.Recipe, 1338308, C.progression), + "Recipe: Singularity Cell": ItemData(G.Recipe, 1338309, C.progression), # 1.0 + "Recipe: Aluminum Scrap": ItemData(G.Recipe, 1338310, C.progression), + "Recipe: Electrode Aluminum Scrap": ItemData(G.Recipe, 1338311, C.progression), + "Recipe: Instant Scrap": ItemData(G.Recipe, 1338312, C.progression), + "Recipe: Aluminum Ingot": ItemData(G.Recipe, 1338313, C.progression), + "Recipe: Pure Aluminum Ingot": ItemData(G.Recipe, 1338314, C.progression), + "Recipe: Alclad Aluminum Sheet": ItemData(G.Recipe, 1338315, C.progression), + "Recipe: Aluminum Casing": ItemData(G.Recipe, 1338316, C.progression), + "Recipe: Alclad Casing": ItemData(G.Recipe, 1338317, C.progression), + "Recipe: Heat Sink": ItemData(G.Recipe, 1338318, C.progression), + "Recipe: Heat Exchanger": ItemData(G.Recipe, 1338319, C.progression), + "Recipe: Synthetic Power Shard": ItemData(G.Recipe, 1338320, C.progression), + "Recipe: Nitric Acid": ItemData(G.Recipe, 1338321, C.progression), + "Recipe: Fused Modular Frame": ItemData(G.Recipe, 1338322, C.progression), + "Recipe: Heat-Fused Frame": ItemData(G.Recipe, 1338323, C.progression), + "Recipe: Radio Control Unit": ItemData(G.Recipe, 1338324, C.progression), + "Recipe: Radio Connection Unit": ItemData(G.Recipe, 1338325, C.progression), + "Recipe: Radio Control System": ItemData(G.Recipe, 1338326, C.progression), + "Recipe: Pressure Conversion Cube": ItemData(G.Recipe, 1338327, C.progression), + "Recipe: Cooling System": ItemData(G.Recipe, 1338328, C.progression), + "Recipe: Cooling Device": ItemData(G.Recipe, 1338329, C.progression), + "Recipe: Turbo Motor": ItemData(G.Recipe, 1338330, C.progression), + "Recipe: Turbo Electric Motor": ItemData(G.Recipe, 1338331, C.progression), + "Recipe: Turbo Pressure Motor": ItemData(G.Recipe, 1338332, C.progression), + "Recipe: Battery": ItemData(G.Recipe, 1338333, C.progression), + "Recipe: Classic Battery": ItemData(G.Recipe, 1338334, C.progression), + "Recipe: Supercomputer": ItemData(G.Recipe, 1338335, C.progression), + "Recipe: OC Supercomputer": ItemData(G.Recipe, 1338336, C.progression), + "Recipe: Super-State Computer": ItemData(G.Recipe, 1338337, C.progression), + "Recipe: Biochemical Sculptor": ItemData(G.Recipe, 1338338, C.progression), # 1.0 + "Recipe: Sulfuric Acid": ItemData(G.Recipe, 1338339, C.progression), + "Recipe: Ballistic Warp Drive": ItemData(G.Recipe, 1338340, C.progression), # 1.0 + "Recipe: Encased Uranium Cell": ItemData(G.Recipe, 1338341, C.progression), + "Recipe: Infused Uranium Cell": ItemData(G.Recipe, 1338342, C.progression), + "Recipe: Uranium Fuel Rod": ItemData(G.Recipe, 1338343, C.progression), + "Recipe: Uranium Fuel Unit": ItemData(G.Recipe, 1338344, C.progression), + "Recipe: Aluminum Beam": ItemData(G.Recipe, 1338345, C.progression), # 1.0 + "Recipe: Aluminum Rod": ItemData(G.Recipe, 1338346, C.progression), # 1.0 + "Recipe: Basic Iron Ingot": ItemData(G.Recipe, 1338347, C.progression), # 1.0 + "Recipe: Non-fissile Uranium": ItemData(G.Recipe, 1338348, C.progression), + "Recipe: Fertile Uranium": ItemData(G.Recipe, 1338349, C.progression), + "Recipe: Plutonium Pellet": ItemData(G.Recipe, 1338350), + "Recipe: Encased Plutonium Cell": ItemData(G.Recipe, 1338351), + "Recipe: Instant Plutonium Cell": ItemData(G.Recipe, 1338352), + "Recipe: Plutonium Fuel Rod": ItemData(G.Recipe, 1338353), + "Recipe: Plutonium Fuel Unit": ItemData(G.Recipe, 1338354), + "Recipe: Gas Filter": ItemData(G.Recipe, 1338355, C.progression), + "Recipe: Iodine Infused Filter": ItemData(G.Recipe, 1338356, C.progression), + "Recipe: Assembly Director System": ItemData(G.Recipe, 1338357, C.progression), + "Recipe: Magnetic Field Generator": ItemData(G.Recipe, 1338358, C.progression), + "Recipe: Copper Powder": ItemData(G.Recipe, 1338359, C.progression), + "Recipe: Nuclear Pasta": ItemData(G.Recipe, 1338360, C.progression), + "Recipe: Thermal Propulsion Rocket": ItemData(G.Recipe, 1338361, C.progression), + "Recipe: Ficsonium": ItemData(G.Recipe, 1338362), # 1.0 + "Recipe: Ficsonium Fuel Rod": ItemData(G.Recipe, 1338363), # 1.0 + "Recipe: Dark Matter Crystal": ItemData(G.Recipe, 1338364, C.progression), # 1.0 + "Recipe: Dark Matter Crystallization": ItemData(G.Recipe, 1338365, C.progression), # 1.0 + "Recipe: Dark Matter Trap": ItemData(G.Recipe, 1338366, C.progression), # 1.0 + "Recipe: Pulse Nobelisk": ItemData(G.Recipe, 1338367, C.useful), + "Recipe: Hatcher Protein": ItemData(G.Recipe, 1338368, C.progression), + "Recipe: Hog Protein": ItemData(G.Recipe, 1338369, C.progression), + "Recipe: Spitter Protein": ItemData(G.Recipe, 1338370, C.progression), + "Recipe: Stinger Protein": ItemData(G.Recipe, 1338371, C.progression), + "Recipe: Biomass (Leaves)": ItemData(G.Recipe, 1338372, C.progression), + "Recipe: Biomass (Wood)": ItemData(G.Recipe, 1338373, C.progression), + "Recipe: Biomass (Mycelia)": ItemData(G.Recipe, 1338374, C.progression), + "Recipe: Biomass (Alien Protein)": ItemData(G.Recipe, 1338375, C.progression), + "Recipe: Turbo Rifle Ammo (Packaged)": ItemData(G.Recipe, 1338376), + "Recipe: Fabric": ItemData(G.Recipe, 1338377, C.progression), + "Recipe: Polyester Fabric": ItemData(G.Recipe, 1338378, C.progression), + "Recipe: Solid Biofuel": ItemData(G.Recipe, 1338379, C.progression), + "Recipe: Liquid Biofuel": ItemData(G.Recipe, 1338380, C.progression), + "Recipe: Empty Canister": ItemData(G.Recipe, 1338381, C.progression), + "Recipe: Coated Iron Canister": ItemData(G.Recipe, 1338382, C.progression), + "Recipe: Steel Canister": ItemData(G.Recipe, 1338383, C.progression), + "Recipe: Empty Fluid Tank": ItemData(G.Recipe, 1338384, C.progression), + "Recipe: Packaged Alumina Solution": ItemData(G.Recipe, 1338385, C.progression), + "Recipe: Packaged Fuel": ItemData(G.Recipe, 1338386, C.progression), + "Recipe: Diluted Packaged Fuel": ItemData(G.Recipe, 1338387, C.progression), + "Recipe: Packaged Heavy Oil Residue": ItemData(G.Recipe, 1338388, C.progression), + "Recipe: Packaged Liquid Biofuel": ItemData(G.Recipe, 1338389, C.progression), + "Recipe: Packaged Nitric Acid": ItemData(G.Recipe, 1338390, C.progression), + "Recipe: Packaged Nitrogen Gas": ItemData(G.Recipe, 1338391, C.progression), + "Recipe: Packaged Oil": ItemData(G.Recipe, 1338392, C.progression), + "Recipe: Packaged Sulfuric Acid": ItemData(G.Recipe, 1338393, C.progression), + "Recipe: Packaged Turbofuel": ItemData(G.Recipe, 1338394, C.progression), + "Recipe: Packaged Water": ItemData(G.Recipe, 1338395, C.progression), + "Recipe: Turbofuel": ItemData(G.Recipe, 1338396, C.progression), + "Recipe: Turbo Heavy Fuel": ItemData(G.Recipe, 1338397, C.progression), + "Recipe: Turbo Blend Fuel": ItemData(G.Recipe, 1338398, C.progression), + "Recipe: Hazmat Suit": ItemData(G.Recipe, 1338399, C.progression), + "Recipe: Gas Mask": ItemData(G.Recipe, 1338400, C.progression), + "Recipe: Black Powder": ItemData(G.Recipe, 1338401, C.progression), + "Recipe: Blade Runners": ItemData(G.Recipe, 1338402, C.useful), + "Recipe: Chainsaw": ItemData(G.Recipe, 1338403, C.useful), + "Recipe: Cluster Nobelisk": ItemData(G.Recipe, 1338404), + "Recipe: Explosive Rebar": ItemData(G.Recipe, 1338405), + "Recipe: Factory Cart": ItemData(G.Recipe, 1338406, C.useful), + "Recipe: Gas Nobelisk": ItemData(G.Recipe, 1338407), + "Recipe: Golden Factory Cart": ItemData(G.Recipe, 1338408), + "Recipe: Homing Rifle Ammo": ItemData(G.Recipe, 1338409), + "Recipe: Iron Rebar": ItemData(G.Recipe, 1338410, C.progression), + "Recipe: Nobelisk": ItemData(G.Recipe, 1338411, C.progression), + "Recipe: Nuke Nobelisk": ItemData(G.Recipe, 1338412), + "Recipe: Nutritional Inhaler": ItemData(G.Recipe, 1338413, C.useful), + "Recipe: Object Scanner": ItemData(G.Recipe, 1338414, C.progression), + "Recipe: Parachute": ItemData(G.Recipe, 1338415, C.useful), + "Recipe: Protein Inhaler": ItemData(G.Recipe, 1338416, C.useful), + "Recipe: Rebar Gun": ItemData(G.Recipe, 1338417, C.useful), + "Recipe: Rifle": ItemData(G.Recipe, 1338418, C.useful), + "Recipe: Rifle Ammo": ItemData(G.Recipe, 1338419, C.progression), + "Recipe: Shatter Rebar": ItemData(G.Recipe, 1338420), + "Recipe: Stun Rebar": ItemData(G.Recipe, 1338421), + "Recipe: Therapeutic Inhaler": ItemData(G.Recipe, 1338422, C.useful), + "Recipe: Turbo Rifle Ammo": ItemData(G.Recipe, 1338423), + "Recipe: Vitamin Inhaler": ItemData(G.Recipe, 1338424, C.useful), + "Recipe: Xeno-Basher": ItemData(G.Recipe, 1338425, C.useful), + "Recipe: Xeno-Zapper": ItemData(G.Recipe, 1338426, C.useful), + "Recipe: Zipline": ItemData(G.Recipe, 1338427, C.useful), + "Recipe: Fine Black Powder": ItemData(G.Recipe, 1338428, C.progression), + "Recipe: Smokeless Powder": ItemData(G.Recipe, 1338429, C.progression), + "Recipe: Alien DNA Capsule": ItemData(G.Recipe, 1338430, C.progression), + "Recipe: Power Shard (1)": ItemData(G.Recipe, 1338431, C.progression), + "Recipe: Power Shard (2)": ItemData(G.Recipe, 1338432, C.useful), + "Recipe: Power Shard (5)": ItemData(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), + "Recipe: Diamonds": ItemData(G.Recipe, 1338434, C.progression), + "Recipe: Cloudy Diamonds": ItemData(G.Recipe, 1338435, C.progression), + "Recipe: Oil-Based Diamonds": ItemData(G.Recipe, 1338436, C.progression), + "Recipe: Petroleum Diamonds": ItemData(G.Recipe, 1338437, C.progression), + "Recipe: Pink Diamonds": ItemData(G.Recipe, 1338438, C.progression), + "Recipe: Turbo Diamonds": ItemData(G.Recipe, 1338439, C.progression), + "Recipe: Time Crystal": ItemData(G.Recipe, 1338440, C.progression), + "Recipe: Superposition Oscillator": ItemData(G.Recipe, 1338441, C.progression), + #"Recipe: Excited Photonic Matter": ItemData(G.Recipe, 1338442, C.progression), should probably be unlocked with converter + "Recipe: Rocket Fuel": ItemData(G.Recipe, 1338443, C.progression), + "Recipe: Nitro Rocket Fuel": ItemData(G.Recipe, 1338444, C.progression), + "Recipe: Ionized Fuel": ItemData(G.Recipe, 1338445, C.useful), + "Recipe: Packaged Rocket Fuel": ItemData(G.Recipe, 1338446, C.progression), + "Recipe: Packaged Ionized Fuel": ItemData(G.Recipe, 1338447, C.useful), + "Recipe: Dark-Ion Fuel": ItemData(G.Recipe, 1338448, C.useful), + "Recipe: Quartz Purification": ItemData(G.Recipe, 1338449, C.progression), + "Recipe: Fused Quartz Crystal": ItemData(G.Recipe, 1338450, C.progression), + "Recipe: Leached Caterium Ingot": ItemData(G.Recipe, 1338451, C.progression), + "Recipe: Leached Copper Ingot": ItemData(G.Recipe, 1338452, C.progression), + "Recipe: Leached Iron ingot": ItemData(G.Recipe, 1338453, C.progression), + "Recipe: Molded Beam": ItemData(G.Recipe, 1338454, C.progression), + "Recipe: Molded Steel Pipe": ItemData(G.Recipe, 1338455, C.progression), + "Recipe: Plastic AI Limiter": ItemData(G.Recipe, 1338456, C.progression), + "Recipe: Tempered Caterium Ingot": ItemData(G.Recipe, 1338457, C.progression), + "Recipe: Tempered Copper Ingot": ItemData(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), + "Building: Constructor": ItemData(G.Building, 1338600, C.progression), # unlocked by default + "Building: Assembler": ItemData(G.Building, 1338601, C.progression), + "Building: Manufacturer": ItemData(G.Building, 1338602, C.progression), + "Building: Packager": ItemData(G.Building, 1338603, C.progression), + "Building: Refinery": ItemData(G.Building, 1338604, C.progression), + "Building: Blender": ItemData(G.Building, 1338605, C.progression), + "Building: Particle Accelerator": ItemData(G.Building, 1338606, C.progression), + "Building: Biomass Burner": ItemData(G.Building, 1338607, C.progression), # unlocked by default + "Building: Coal Generator": ItemData(G.Building, 1338608, C.progression), + "Building: Geothermal Generator": ItemData(G.Building, 1338609, C.progression), + "Building: Nuclear Power Plant": ItemData(G.Building, 1338610, C.progression), + "Building: Miner Mk.1": ItemData(G.Building, 1338611, C.progression), # unlocked by default + "Building: Miner Mk.2": ItemData(G.Building, 1338612, C.progression), + "Building: Miner Mk.3": ItemData(G.Building, 1338613, C.progression), + "Building: Oil Extractor": ItemData(G.Building, 1338614, C.progression), + "Building: Water Extractor": ItemData(G.Building, 1338615, C.progression), + "Building: Smelter": ItemData(G.Building, 1338616, C.progression), # unlocked by default + "Building: Foundry": ItemData(G.Building, 1338617, C.progression), + "Building: Fuel Generator": ItemData(G.Building, 1338618, C.progression), + "Building: Resource Well Pressurizer": ItemData(G.Building, 1338619, C.progression), + "Building: Equipment Workshop": ItemData(G.Building, 1338620, C.progression), + "Building: AWESOME Sink": ItemData(G.Building | G.BasicNeeds, 1338621, C.progression), + "Building: AWESOME Shop": ItemData(G.Building | G.BasicNeeds, 1338622, C.progression), + "Building: Painted Beams": ItemData(G.Beams, 1338623, C.filler), + "Building: Blueprint Designer": ItemData(G.Building, 1338624, C.filler, 0), # unlocked by default + "Building: Fluid Buffer": ItemData(G.Building, 1338625, C.filler), + "Building: Industrial Fluid Buffer": ItemData(G.Building, 1338626, C.filler), + "Building: Jump Pad": ItemData(G.Building, 1338627, C.filler), + "Building: Ladder": ItemData(G.Building, 1338628, C.filler), + "Building: MAM": ItemData(G.Building | G.BasicNeeds, 1338629, C.progression), + "Building: Personal Storage Box": ItemData(G.Building, 1338630, C.filler), + "Building: Power Storage": ItemData(G.Building | G.BasicNeeds, 1338631, C.progression), + "Building: U-Jelly Landing Pad": ItemData(G.Building, 1338632, C.useful), + "Building: Power Switch": ItemData(G.Building, 1338633, C.useful), + "Building: Priority Power Switch": ItemData(G.Building, 1338634, C.useful), + "Building: Storage Container": ItemData(G.Building, 1338635, C.useful, 0), + "Building: Lookout Tower": ItemData(G.Building, 1338636, C.filler), + #"Building: Power Pole Mk.1": ItemData(G.Building, 1338637, C.progression), # unlocked by default + "Building: Power Pole Mk.2": ItemData(G.Building, 1338638, C.useful), + "Building: Power Pole Mk.3": ItemData(G.Building, 1338639, C.useful), + "Building: Industrial Storage Container": ItemData(G.Building, 1338640, C.filler), + "Building: Conveyor Merger": ItemData(G.Building | G.BasicNeeds, 1338641, C.progression), + "Building: Conveyor Splitter": ItemData(G.Building | G.BasicNeeds, 1338642, C.progression), + "Building: Conveyor Mk.1": ItemData(G.Building | G.ConveyorMk1, 1338643, C.progression), # unlocked by default + "Building: Conveyor Mk.2": ItemData(G.Building | G.ConveyorMk2, 1338644, C.progression), + "Building: Conveyor Mk.3": ItemData(G.Building | G.ConveyorMk3, 1338645, C.progression), + "Building: Conveyor Mk.4": ItemData(G.Building | G.ConveyorMk4, 1338646, C.progression), + "Building: Conveyor Mk.5": ItemData(G.Building | G.ConveyorMk5, 1338647, C.progression), + "Building: Conveyor Lift Mk.1": ItemData(G.Building | G.ConveyorMk1, 1338648, C.useful), + "Building: Conveyor Lift Mk.2": ItemData(G.Building | G.ConveyorMk2, 1338649, C.useful), + "Building: Conveyor Lift Mk.3": ItemData(G.Building | G.ConveyorMk3, 1338650, C.useful), + "Building: Conveyor Lift Mk.4": ItemData(G.Building | G.ConveyorMk4, 1338651, C.useful), + "Building: Conveyor Lift Mk.5": ItemData(G.Building | G.ConveyorMk5, 1338652, C.useful), + "Building: Metal Beams": ItemData(G.Beams, 1338653, C.filler, 0), + "Building: Stackable Conveyor Pole": ItemData(G.Building | G.ConveyorSupports, 1338654, C.useful), + "Building: Conveyor Wall Mount": ItemData(G.Building | G.ConveyorSupports, 1338655, C.useful, 0), + "Building: Conveyor Lift Floor Hole": ItemData(G.Building | G.ConveyorSupports, 1338656, C.useful, 0), + "Building: Conveyor Ceiling Mount": ItemData(G.Building | G.ConveyorSupports, 1338657, C.useful, 0), + "Building: Pipes Mk.1": ItemData(G.Building | G.PipesMk1, 1338658, C.progression), + "Building: Pipes Mk.2": ItemData(G.Building | G.PipesMk2, 1338659, C.progression), + "Building: Pipeline Pump Mk.1": ItemData(G.Building | G.PipesMk1, 1338660, C.progression), + "Building: Pipeline Pump Mk.2": ItemData(G.Building | G.PipesMk2, 1338661, C.progression), + "Building: Pipeline Junction Cross": ItemData(G.Building | G.PipesMk1 | G.PipesMk2, 1338662, C.progression), + "Building: Valve": ItemData(G.Building | G.PipesMk1 | G.PipesMk2, 1338663, C.useful), + "Building: Stackable Pipeline Support": ItemData(G.Building | G.PipelineSupports, 1338664, C.useful, 0), + "Building: Wall Pipeline Support": ItemData(G.Building | G.PipelineSupports, 1338665, C.useful, 0), + "Building: Pipeline Wall Hole": ItemData(G.Building | G.PipelineSupports, 1338666, C.useful, 0), + "Building: Pipeline Floor Hole": ItemData(G.Building | G.PipelineSupports, 1338667, C.useful, 0), + "Building: Lights Control Panel": ItemData(G.Building | G.Lights, 1338668, C.filler, 0), + "Building: Wall Mounted Flood Light": ItemData(G.Building | G.Lights, 1338669, C.filler, 0), + "Building: Street Light": ItemData(G.Building | G.Lights, 1338670, C.filler, 0), + "Building: Flood Light Tower": ItemData(G.Building | G.Lights, 1338671, C.filler, 0), + "Building: Ceiling Light": ItemData(G.Building | G.Lights, 1338672, C.filler, 0), + "Building: Power Tower": ItemData(G.Building, 1338673, C.useful), + "Building: Walls Orange": ItemData(G.Building | G.Walls, 1338674, C.progression), + "Building: Radar Tower": ItemData(G.Building, 1338675, C.useful), + "Building: Smart Splitter": ItemData(G.Building, 1338676, C.useful), + "Building: Programmable Splitter": ItemData(G.Building, 1338677, C.useful), + "Building: Label Sign Bundle": ItemData(G.Building | G.Signs, 1338678, C.filler, 0), + "Building: Display Sign Bundle": ItemData(G.Building | G.Signs, 1338679, C.filler, 0), + "Building: Billboard Set": ItemData(G.Building | G.Signs, 1338680, C.filler, 0), + "Building: Walls Metal": ItemData(G.Building | G.Walls, 1338681, C.filler, 0), + "Building: Metal Pillar": ItemData(G.Pilars, 1338682, C.filler, 0), + "Building: Concrete Pillar": ItemData(G.Pilars, 1338683, C.filler, 0), + "Building: Frame Pillar": ItemData(G.Pilars, 1338684, C.filler, 0), + "Building: Walls Concrete": ItemData(G.Building | G.Walls, 1338685, C.filler, 0), + #"Building: Big Metal Pillar": ItemData(G.Pilars, 1338686, C.filler, 0), + #"Building: Big Concrete Pillar": ItemData(G.Pilars, 1338687, C.filler, 0), + #"Building: Big Frame Pillar": ItemData(G.Pilars, 1338688, C.filler, 0), + #"Building: Beam Support": ItemData(G.Beams, 1338689, C.filler, 0), + #"Building: Beam Connector": ItemData(G.Beams, 1338690, C.filler, 0), + #"Building: Beam Connector Double": ItemData(G.Beams, 1338691, C.filler, 0), + "Building: Foundation": ItemData(G.Building | G.Foundations | G.BasicNeeds, 1338692, C.progression), + "Building: Half Foundation": ItemData(G.Foundations, 1338693, C.filler, 0), + "Building: Corner Ramp Pack": ItemData(G.Foundations, 1338694, C.filler, 0), + "Building: Inverted Ramp Pack": ItemData(G.Foundations, 1338695, C.filler, 0), + "Building: Inverted Corner Ramp Pack": ItemData(G.Foundations, 1338696, C.filler, 0), + "Building: Quarter Pipes Pack": ItemData(G.Foundations, 1338697, C.filler, 0), + "Building: Quarter Pipe Extensions Pack": ItemData(G.Foundations, 1338698, C.filler, 0), + "Building: Frame foundation": ItemData(G.Foundations, 1338699, C.filler, 0), + "Building: Wall Outlet Mk.1": ItemData(G.Building, 1338700, C.useful), + "Building: Wall Outlet Mk.2": ItemData(G.Building, 1338701, C.useful), + "Building: Wall Outlet Mk.3": ItemData(G.Building, 1338702, C.useful), + "Building: Modern Catwalks": ItemData(G.Building, 1338703, C.filler, 0), + "Building: Industrial Walkways": ItemData(G.Building, 1338704, C.filler, 0), + "Building: Stairs": ItemData(G.Building, 1338705, C.filler, 0), + "Building: Clean Pipeline Mk.1": ItemData(G.Building, 1338706, C.filler, 0), + "Building: Clean Pipeline Mk.2": ItemData(G.Building, 1338707, C.filler, 0), + "Building: Road Barrier": ItemData(G.Building, 1338708, C.filler, 0), + "Building: Modern Railing": ItemData(G.Building, 1338709, C.filler, 0), + "Building: Industrial Railing": ItemData(G.Building, 1338710, C.filler, 0), + "Building: Double Ramp Pack": ItemData(G.Foundations, 1338711, C.filler, 0), + "Building: Conveyor Walls": ItemData(G.Walls, 1338712, C.filler, 0), + "Building: Inverted Ramp Wall Bundle": ItemData(G.Walls, 1338713, C.filler, 0), + "Building: Ramp Wall Bundle": ItemData(G.Walls, 1338714, C.filler, 0), + "Building: Door Walls": ItemData(G.Walls, 1338715, C.filler, 0), + "Building: Tilted Walls": ItemData(G.Walls, 1338716, C.filler, 0), + "Building: Windowed Walls": ItemData(G.Walls, 1338717, C.filler, 0), + "Building: Steel-framed Windows": ItemData(G.Walls, 1338718, C.filler, 0), + "Building: Gates": ItemData(G.Walls, 1338719, C.filler, 0), + "Building: Roofs": ItemData(G.Walls, 1338720, C.filler, 0), + "Building: Roof Corners": ItemData(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), + "Building: Converter": ItemData(G.Building, 1338722, C.progression), + "Building: Quantum Encoder": ItemData(G.Building, 1338723, C.progression), + "Building: Portal": ItemData(G.Building, 1338724, C.filler), + "Building: Conveyor Mk.6": ItemData(G.Building | G.ConveyorMk6, 1338725, C.progression), + "Building: Conveyor Lift Mk.6": ItemData(G.Building | G.ConveyorMk6, 1338726, C.useful), + "Building: Alien Power Augmenter": ItemData(G.Building, 1338727, C.progression), + "Building: Dimensional Depot Uploader": ItemData(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), + "Customizer: Asphalt Foundation Material": ItemData(G.Customizer | G.Foundations, 1338750, C.filler, 0), + "Customizer: Concrete Foundation Material": ItemData(G.Customizer | G.Foundations, 1338751, C.filler, 0), + "Customizer: Concrete Wall Material": ItemData(G.Customizer | G.Walls, 1338752, C.filler, 0), + "Customizer: Glass Roof Material": ItemData(G.Customizer | G.Walls, 1338753, C.filler, 0), + "Customizer: Grip Metal Foundation Material": ItemData(G.Customizer | G.Foundations, 1338754, C.filler, 0), + "Customizer: Coated Concrete Foundation Material": ItemData(G.Customizer | G.Foundations, 1338755, C.filler, 0), + "Customizer: Metal Roof Material": ItemData(G.Customizer | G.Walls, 1338756, C.filler, 0), + "Customizer: Steel Wall Material": ItemData(G.Customizer | G.Walls, 1338757, C.filler, 0), + "Customizer: Tar Roof Material": ItemData(G.Customizer | G.Walls, 1338758, C.filler, 0), + "Customizer: Arrow Patterns": ItemData(G.Customizer | G.Foundations, 1338759, C.filler, 0), + "Customizer: Dotted Line Patterns": ItemData(G.Customizer | G.Foundations, 1338760, C.filler, 0), + "Customizer: Solid Line Patterns": ItemData(G.Customizer | G.Foundations, 1338761, C.filler, 0), + "Customizer: Factory Icon Patterns": ItemData(G.Customizer | G.Foundations, 1338762, C.filler, 0), + "Customizer: Transportation Icon Patterns": ItemData(G.Customizer | G.Foundations, 1338763, C.filler, 0), + "Customizer: Number Patterns": ItemData(G.Customizer | G.Foundations, 1338764, C.filler, 0), + "Customizer: Pathway Patterns": ItemData(G.Customizer | G.Foundations, 1338765, C.filler, 0), + "Customizer: Factory Zone Patterns": ItemData(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), + "Customizer: Steel-Framed Windows": ItemData(G.Customizer | G.Walls, 1338767, C.filler, 0), + "Customizer: Construction Fences": ItemData(G.Customizer, 1338768, C.filler, 0), + "Customizer: Unpainted Finish": ItemData(G.Customizer, 1338769, C.filler, 0), + "Customizer: Copper Paint Finish": ItemData(G.Customizer, 1338770, C.filler, 0), + "Customizer: Chrome Paint Finish": ItemData(G.Customizer, 1338771, C.filler, 0), + "Customizer: Carbon Steel Finish": ItemData(G.Customizer, 1338772, C.filler, 0), + "Customizer: Caterium Paint Finish": ItemData(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), + "Transport: Drones": ItemData(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), + "Transport: Trains": ItemData(G.Transport | G.Trains, 1338801, C.useful), + "Transport: Fluid Trains": ItemData(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), + "Transport: Tractor": ItemData(G.Transport | G.Vehicles, 1338803, C.useful), + "Transport: Truck": ItemData(G.Transport | G.Vehicles, 1338804, C.useful), + "Transport: Explorer": ItemData(G.Transport | G.Vehicles, 1338805, C.useful), + "Transport: Factory Cart": ItemData(G.Transport | G.Vehicles, 1338806, C.useful), + "Transport: Factory Cart (golden)": ItemData(G.Transport | G.Vehicles, 1338807, C.filler), + "Transport: Cyber Wagon": ItemData(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), + "Transport: Hypertube": ItemData(G.Transport | G.HyperTubes, 1338809, C.useful), + "Transport: Hypertube Floor Hole": ItemData(G.Transport | G.HyperTubes, 1338810, C.filler), + "Transport: Hypertube Wall Support": ItemData(G.Transport | G.HyperTubes, 1338811, C.filler), + "Transport: Hypertube Wall Hole": ItemData(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), + "Trap: Hog": ItemData(G.Trap, 1338900, C.trap), + "Trap: Alpha Hog": ItemData(G.Trap, 1338901, C.trap), + "Trap: Johnny": ItemData(G.Trap, 1338902, C.trap), + "Trap: Cliff Hog": ItemData(G.Trap, 1338903, C.trap), + "Trap: Nuclear Hog": ItemData(G.Trap, 1338904, C.trap), + "Trap: Not the Bees": ItemData(G.Trap, 1338905, C.trap), + "Trap: Hatcher": ItemData(G.Trap, 1338906, C.trap), + "Trap: Doggo with Pulse Nobelisk": ItemData(G.Trap, 1338907, C.trap), + "Trap: Doggo with Nuke Nobelisk": ItemData(G.Trap, 1338908, C.trap), + "Doggo with Power Slug": ItemData(G.Parts, 1338909, C.filler), + "Trap: Doggo with Gas Nobelisk": ItemData(G.Trap, 1338910, C.trap), + "Trap: Spore Flower": ItemData(G.Trap, 1338911, C.trap), + "Trap: Stinger": ItemData(G.Trap, 1338912, C.trap), + "Trap: Gas Stinger": ItemData(G.Trap, 1338913, C.trap), + "Trap: Small Stinger": ItemData(G.Trap, 1338914, C.trap), + "Trap: Spitter": ItemData(G.Trap, 1338915, C.trap), + "Trap: Alpha Spitter": ItemData(G.Trap, 1338916, C.trap), + "Trap: Nuclear Waste Drop": ItemData(G.Trap, 1338917, C.trap), + "Trap: Plutonium Waste Drop": ItemData(G.Trap, 1338918, C.trap), + "Trap: Elite Hatcher": ItemData(G.Trap, 1338919, C.trap), + "Trap: Can of Beans": ItemData(G.Trap, 1338920, C.trap), + "Trap: Fart Cloud": ItemData(G.Trap, 1338921, C.trap), #Item id range upper bound - "Building: Space Elevator": ItemData(frozenset({G.Building}), 1338999, C.progression) + "Building: Space Elevator": ItemData(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}) + non_unique_item_categories: ClassVar[G] = G.Parts | G.Equipment | G.Ammo | G.Trap | G.Upgrades + pool_item_categories: ClassVar[G] = 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 }))) + if details.category & (G.Parts | G.Ammo)) @classmethod @@ -697,18 +698,19 @@ class Items: return categories - player: int logic: GameLogic random: Random + critical_path: CriticalPathCalculator 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): + def __init__(self, player: Optional[int], logic: GameLogic, random: Random, + options: SatisfactoryOptions, critical_path: CriticalPathCalculator): self.player = player self.logic = logic self.random = random + self.critical_path = critical_path if options.experimental_generation: # TODO major performance boost if we can get it stable self.precalculated_progression_recipes = self.select_progression_recipes() @@ -819,13 +821,20 @@ class Items: 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}") + if instance and type == C.progression: + if instance.precalculated_progression_recipes_names: + if not name.startswith("Building: "): + if name not in instance.precalculated_progression_recipes_names: + type = C.useful + logging.info(f"Downscaling .. {name}") + else: + logging.warning(f"Preserving .. {name}") + if instance.critical_path.potential_required_recipes_names: + if not (data.category & G.BasicNeeds) and name not in instance.critical_path.potential_required_recipes_names: + type = C.filler + logging.info(f"Dropping... {name}") + else: + logging.warning(f"Required .. {name}") return Item(name, type, data.code, player) @@ -845,7 +854,7 @@ class Items: 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 not (self.item_data[item.name].category & self.non_unique_item_categories) \ and item.name not in options.start_inventory_from_pool: excluded_items.add(item.name) @@ -860,18 +869,20 @@ class Items: 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 data.category & 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) + if item.classification != C.filler: + pool.append(item) - for _ in range(number_of_locations - len(pool)): + filler_pool_size: int = number_of_locations - len(pool) + if (filler_pool_size < 0): + raise Exception(f"Location pool starved, trying to add {len(pool)} items to {number_of_locations} locations") + + for _ in range(filler_pool_size): item = self.create_item(self, self.get_filler_item_name(random, options), self.player) pool.append(item) diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index f0776968e1..55ddbe7879 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -4,6 +4,7 @@ from .GameLogic import GameLogic, Recipe, Building, PowerInfrastructureLevel, Dr from .StateLogic import StateLogic, EventId, part_event_prefix, building_event_prefix from .Items import Items from .Options import SatisfactoryOptions +from .CriticalPathCalculator import CriticalPathCalculator from math import ceil, floor @@ -43,9 +44,6 @@ class Part(LocationData): 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]) @@ -76,9 +74,6 @@ class EventBuilding(LocationData): ) -> 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) @@ -177,6 +172,7 @@ class Locations(): options: Optional[SatisfactoryOptions] state_logic: Optional[StateLogic] items: Optional[Items] + critical_path: Optional[CriticalPathCalculator] hub_location_start: ClassVar[int] = 1338000 max_tiers: ClassVar[int] = 10 @@ -186,11 +182,13 @@ class Locations(): 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): + state_logic: Optional[StateLogic] = None, items: Optional[Items] = None, + critical_path: Optional[CriticalPathCalculator] = None): self.game_logic = game_logic self.options = options self.state_logic = state_logic self.items = items + self.critical_path = critical_path def get_base_location_table(self) -> List[LocationData]: return [ @@ -310,8 +308,8 @@ class Locations(): "Must include all possible location names and their 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.extend(self.get_hub_locations(True, self.max_tiers)) + location_table.extend(self.get_drop_pod_locations(True, self.max_tiers)) location_table.append(LocationData("Overworld", "UpperBound", 1338999)) return {location.name: location.code for location in location_table} @@ -322,26 +320,38 @@ class Locations(): 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") + max_tier_for_game = min(self.options.final_elevator_package * 2, len(self.game_logic.hub_layout)) + 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_hub_locations(False, max_tier_for_game)) + location_table.extend(self.get_drop_pod_locations(False, max_tier_for_game)) location_table.extend(self.get_logical_event_locations()) return location_table - def get_hub_locations(self) -> List[LocationData]: + def get_hub_locations(self, for_data_package: bool, max_tier: int) -> List[LocationData]: location_table: List[LocationData] = [] + number_of_slots_per_milestone_for_game: int + if (for_data_package): + number_of_slots_per_milestone_for_game = self.max_slots + else: + if self.options.final_elevator_package <= 2: + number_of_slots_per_milestone_for_game = 10 + else: + number_of_slots_per_milestone_for_game = self.game_logic.slots_per_milestone + 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: + for milestone in range(1, max_tier + 1): + for slot in range(1, number_of_slots_per_milestone_for_game + 1): + if for_data_package: location_table.append(HubSlot(tier, milestone, slot, hub_location_id)) else: - if tier <= len(self.game_logic.hub_layout) \ + if tier <= max_tier \ and milestone <= len(self.game_logic.hub_layout[tier - 1]) \ - and slot <= self.game_logic.slots_per_milestone: + and slot <= number_of_slots_per_milestone_for_game: + location_table.append(HubSlot(tier, milestone, slot, hub_location_id)) hub_location_id += 1 @@ -356,11 +366,13 @@ class Locations(): location_table.extend( ElevatorTier(index, self.state_logic, self.game_logic) - for index, parts in enumerate(self.game_logic.space_elevator_tiers)) + for index, parts in enumerate(self.game_logic.space_elevator_tiers) + if index < self.options.final_elevator_package) 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)) + for part in Part.get_parts(self.state_logic, recipes, part_name, self.items) + if part in self.critical_path.potential_required_parts) location_table.extend( EventBuilding(self.game_logic, self.state_logic, name, building) for name, building in self.game_logic.buildings.items()) @@ -370,28 +382,28 @@ class Locations(): return location_table - def get_drop_pod_locations(self) -> List[LocationData]: + def get_drop_pod_locations(self, for_data_package: bool, max_tier: int) -> 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)) - + bucket_size: int + drop_pod_data: List[DropPodData] + if for_data_package: + bucket_size = 0 + drop_pod_data = [] + else: + bucket_size = floor((self.drop_pod_location_id_end - self.drop_pod_location_id_start) / max_tier) 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: + if for_data_package: 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)) + tier = min(ceil((location_id_normalized + 1) / bucket_size), max_tier) drop_pod_locations.append(DropPod(data, self.state_logic, location_id, tier, can_hold_progression)) diff --git a/worlds/satisfactory/Options.py b/worlds/satisfactory/Options.py index f9ca8bd72a..c11db3e52e 100644 --- a/worlds/satisfactory/Options.py +++ b/worlds/satisfactory/Options.py @@ -44,13 +44,13 @@ class ChoiceMap(Choice, metaclass=ChoiceMapMeta): class ElevatorTier(NamedRange): """ - Ship these Space Elevator packages to finish. - Does nothing if *Space Elevator Tier* goal is not enabled. + Put these Shipments to Space Elevator packages in logic. + if your goal selection contains *Space Elevator Tier* then the goal will be to complete these shipments. """ display_name = "Goal: Space Elevator shipment" default = 2 range_start = 1 - range_end = 4 + range_end = 5 special_range_names = { "one package (tiers 1-2)": 1, "two packages (tiers 1-4)": 2, diff --git a/worlds/satisfactory/Regions.py b/worlds/satisfactory/Regions.py index f39d951e47..8cacb10537 100644 --- a/worlds/satisfactory/Regions.py +++ b/worlds/satisfactory/Regions.py @@ -42,6 +42,9 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory ] for hub_tier, milestones_per_hub_tier in enumerate(game_logic.hub_layout, 1): + if (hub_tier > (options.final_elevator_package * 2)): + break + region_names.append(f"Hub Tier {hub_tier}") for minestone, _ in enumerate(milestones_per_hub_tier, 1): @@ -85,18 +88,27 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory super_early_game_buildings.append("Conveyor Splitter") super_early_game_buildings.append("Conveyor Merger") + if options.final_elevator_package == 1: + super_early_game_buildings.append(early_game_buildings) + 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) + + if options.final_elevator_package >= 2: + 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, "Hub Tier 3", "Hub Tier 4") + if options.final_elevator_package >= 3: + connect(regions, "Hub Tier 4", "Hub Tier 5", lambda state: state.has("Elevator Tier 2", player)) + connect(regions, "Hub Tier 5", "Hub Tier 6") + if options.final_elevator_package >= 4: + connect(regions, "Hub Tier 6", "Hub Tier 7", lambda state: state.has("Elevator Tier 3", player)) + connect(regions, "Hub Tier 7", "Hub Tier 8") + if options.final_elevator_package >= 5: + 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: @@ -112,6 +124,9 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory return logic_rule for hub_tier, milestones_per_hub_tier in enumerate(game_logic.hub_layout, 1): + if (hub_tier > (options.final_elevator_package * 2)): + break + 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())) diff --git a/worlds/satisfactory/StateLogic.py b/worlds/satisfactory/StateLogic.py index d0b1dc3aea..6890752da3 100644 --- a/worlds/satisfactory/StateLogic.py +++ b/worlds/satisfactory/StateLogic.py @@ -38,10 +38,7 @@ class StateLogic: 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 \ + 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, @@ -80,7 +77,9 @@ class StateLogic: 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) + limited_phase = min(self.options.final_elevator_package * 2, phase) + + return state.has(f"Elevator Tier {limited_phase}", self.player) @staticmethod def to_part_event(part: str) -> str: diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index ef9e29b3fb..2707dcba68 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -6,6 +6,7 @@ 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 .CriticalPathCalculator import CriticalPathCalculator from .Web import SatisfactoryWebWorld from ..AutoWorld import World @@ -30,6 +31,7 @@ class SatisfactoryWorld(World): game_logic: ClassVar[GameLogic] = GameLogic() state_logic: StateLogic items: Items + critical_path: CriticalPathCalculator def __init__(self, multiworld: "MultiWorld", player: int): super().__init__(multiworld, player) @@ -37,8 +39,11 @@ class SatisfactoryWorld(World): def generate_early(self) -> None: + self.options.final_elevator_package.value = 1 + self.state_logic = StateLogic(self.player, self.options) - self.items = Items(self.player, self.game_logic, self.random, self.options) + self.critical_path = CriticalPathCalculator(self.game_logic, self.random, self.options) + self.items = Items(self.player, self.game_logic, self.random, self.options, self.critical_path) if not self.options.goal_selection.value: raise Exception("""Satisfactory: player {} needs to choose a goal, the option goal_selection is empty""" @@ -65,7 +70,7 @@ class SatisfactoryWorld(World): def create_regions(self) -> None: locations: List[LocationData] = \ - Locations(self.game_logic, self.options, self.state_logic, self.items).get_locations() + Locations(self.game_logic, self.options, self.state_logic, self.items, self.critical_path).get_locations() create_regions_and_return_locations( self.multiworld, self.options, self.player, self.game_logic, self.state_logic, locations) From 4b65e45ecc21b154860f3db3b5cb938469a379a1 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Mon, 24 Feb 2025 22:59:40 +0100 Subject: [PATCH 02/12] Fixed some bugs --- test/general/test_ids.py | 17 ++++++++++++++++- worlds/satisfactory/Locations.py | 14 ++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/test/general/test_ids.py b/test/general/test_ids.py index e51a070c1f..b09170d1a9 100644 --- a/test/general/test_ids.py +++ b/test/general/test_ids.py @@ -52,8 +52,23 @@ class TestIDs(unittest.TestCase): def test_duplicate_location_ids(self): """Test that a game doesn't have location id overlap within its own datapackage""" for gamename, world_type in AutoWorldRegister.world_types.items(): + self.maxDiff = None + with self.subTest(game=gamename): - self.assertEqual(len(world_type.location_id_to_name), len(world_type.location_name_to_id)) + len_location_id_to_name = len(world_type.location_id_to_name) + len_location_name_to_id = len(world_type.location_name_to_id) + + if len_location_id_to_name != len_location_name_to_id: + self.assertCountEqual( + world_type.location_id_to_name.values(), + world_type.location_name_to_id.keys(), + "\nThese locations have overlapping ids with other locations in its own world") + self.assertCountEqual( + world_type.location_id_to_name.keys(), + world_type.location_name_to_id.values(), + "\nThese locations have overlapping names with other locations in its own world") + + self.assertEqual(len_location_id_to_name, len_location_name_to_id) def test_postgen_datapackage(self): """Generates a solo multiworld and checks that the datapackage is still valid""" diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index 55ddbe7879..9b5f75e532 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -342,8 +342,8 @@ class Locations(): number_of_slots_per_milestone_for_game = self.game_logic.slots_per_milestone hub_location_id = self.hub_location_start - for tier in range(1, self.max_tiers + 1): - for milestone in range(1, max_tier + 1): + for tier in range(1, max_tier + 1): + for milestone in range(1, self.max_milestones + 1): for slot in range(1, number_of_slots_per_milestone_for_game + 1): if for_data_package: location_table.append(HubSlot(tier, milestone, slot, hub_location_id)) @@ -371,14 +371,16 @@ class Locations(): 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) - if part in self.critical_path.potential_required_parts) + if part_name in self.critical_path.potential_required_parts + 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()) + for name, building in self.game_logic.buildings.items() + if name in self.critical_path.potential_required_buildings) 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()) + for power_level, recipes in self.game_logic.requirement_per_powerlevel.items() + if power_level <= self.critical_path.potential_required_power) return location_table From 26f95748946b5de1945a07133309fa720ef1aaac Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Tue, 11 Mar 2025 23:14:40 +0100 Subject: [PATCH 03/12] Progress commmit incase my pc crashes --- worlds/satisfactory/CriticalPathCalculator.py | 113 ++++++++++++++---- worlds/satisfactory/GameLogic.py | 103 ++++++++-------- worlds/satisfactory/Locations.py | 43 +++++-- worlds/satisfactory/Regions.py | 4 +- worlds/satisfactory/StateLogic.py | 7 +- worlds/satisfactory/__init__.py | 1 + 6 files changed, 180 insertions(+), 91 deletions(-) diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py index 75a16ec484..7664a50998 100644 --- a/worlds/satisfactory/CriticalPathCalculator.py +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -1,5 +1,6 @@ from random import Random -from typing import Set, Tuple +from typing import Optional +from collections.abc import Iterable from .GameLogic import GameLogic, Recipe from .Options import SatisfactoryOptions from .Options import SatisfactoryOptions @@ -8,17 +9,18 @@ class CriticalPathCalculator: logic: GameLogic random: Random - potential_required_parts: Set[str] - potential_required_buildings: Set[str] + potential_required_parts: set[str] + potential_required_buildings: set[str] potential_required_belt_speed: int potential_required_pipes: bool potential_required_radioactive: bool potential_required_power: int - potential_required_recipes_names: Set[str] + potential_required_recipes_names: set[str] def __init__(self, logic: GameLogic, random: Random, options: SatisfactoryOptions): self.logic = logic self.random = random + self.options = options self.potential_required_parts = set() self.potential_required_buildings = set() @@ -27,27 +29,59 @@ class CriticalPathCalculator: self.potential_required_radioactive = False self.potential_required_power: int = 1 - self.select_minimal_required_parts_for( - tuple(self.logic.space_elevator_tiers[options.final_elevator_package - 1].keys()) - ) - for i in range(self.potential_required_belt_speed, 1): - self.select_minimal_required_parts_for(self.logic.buildings[f"Conveyor Mk.{i}"].inputs) + selected_power_infrastructure: dict[int, Recipe] = {} + + self.select_minimal_required_parts_for(self.logic.space_elevator_tiers[options.final_elevator_package-1].keys()) + + for tree in self.logic.man_trees.values(): + self.select_minimal_required_parts_for(tree.access_items) + + for node in tree.nodes: + # Bullet Guidance System - Rifle Ammo + # Stun Rebar - Iron Rebar + # Radar Technology - Heavy Modular Frame + # Turbo Rifle Ammo - Packaged Turbofuel, Rifle Ammo + # Nuclear Deterrent Development - Encased Uranium Cell + # Rocket Fuel - Packaged Turbofuel + # Ionized Fuel - Ionized Fuel + if node.name == "Hostile Organism Detection": + Debug = True + + if node.minimal_tier > options.final_elevator_package: + continue + + self.select_minimal_required_parts_for(node.unlock_cost.keys()) + + self.select_minimal_required_parts_for_building("MAM") + self.select_minimal_required_parts_for_building("AWESOME Sink") + self.select_minimal_required_parts_for_building("AWESOME Shop") + self.select_minimal_required_parts_for_building("Space Elevator") + self.select_minimal_required_parts_for_building("Conveyor Splitter") + self.select_minimal_required_parts_for_building("Conveyor Merger") + self.select_minimal_required_parts_for_building("Equipment Workshop") + self.select_minimal_required_parts_for_building("Foundation") + self.select_minimal_required_parts_for_building("Walls Orange") + self.select_minimal_required_parts_for_building("Power Storage") + + for i in range(1, self.potential_required_belt_speed + 1): + self.select_minimal_required_parts_for_building(f"Conveyor Mk.{i}") if self.potential_required_pipes: - self.select_minimal_required_parts_for(self.logic.buildings["Pipeline Pump Mk.1"].inputs) - self.select_minimal_required_parts_for(self.logic.buildings["Pipeline Pump Mk.2"].inputs) + self.select_minimal_required_parts_for_building("Pipes Mk.1") + self.select_minimal_required_parts_for_building("Pipeline Pump Mk.1") if self.potential_required_radioactive: self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) - for i in range(self.potential_required_belt_speed, 1): + for i in range(1, self.potential_required_power + 1): power_recipe = random.choice(self.logic.requirement_per_powerlevel[i]) + selected_power_infrastructure[i] = power_recipe self.select_minimal_required_parts_for(power_recipe.inputs) - self.potential_required_buildings.add(power_recipe.building) - + self.select_minimal_required_parts_for_building(power_recipe.building) self.potential_required_recipes_names = set( recipe.name for part in self.potential_required_parts for recipe in self.logic.recipes[part] + if recipe.minimal_tier <= self.options.final_elevator_package ) self.potential_required_recipes_names.update( "Building: "+ building @@ -56,24 +90,53 @@ class CriticalPathCalculator: debug = True + def select_minimal_required_parts_for_building(self, building: str) -> None: + self.select_minimal_required_parts_for(self.logic.buildings[building].inputs) + self.potential_required_buildings.add(building) - def select_minimal_required_parts_for(self, parts: Tuple[str]) -> None: - if parts: - for part in parts: - if part in self.potential_required_parts: + def select_minimal_required_parts_for(self, parts: Optional[Iterable[str]]) -> None: + if parts is None: + return + + for part in parts: + if part in self.potential_required_parts: + continue + + if part == "Radio Control Unit": + Debug = True + + self.potential_required_parts.add(part) + + for recipe in self.logic.recipes[part]: + if part == "Fuel": + Debug = True + + if recipe.minimal_tier > self.options.final_elevator_package: continue - self.potential_required_parts.add(part) + if recipe.minimal_belt_speed == 5: + Debug = True - for recipe in self.logic.recipes[part]: - self.potential_required_belt_speed = \ - max(self.potential_required_belt_speed, recipe.minimal_belt_speed) + self.potential_required_belt_speed = \ + max(self.potential_required_belt_speed, recipe.minimal_belt_speed) + + self.select_minimal_required_parts_for(recipe.inputs) + + if recipe.needs_pipes: + self.potential_required_pipes = True + if recipe.is_radio_active: + self.potential_required_radioactive = True + + if recipe.building: + if recipe.building == "Blender": + debug = True - self.select_minimal_required_parts_for(recipe.inputs) self.select_minimal_required_parts_for(self.logic.buildings[recipe.building].inputs) - self.potential_required_buildings.add(recipe.building) if self.logic.buildings[recipe.building].power_requirement: self.potential_required_power = \ - max(self.potential_required_power, self.logic.buildings[recipe.building].power_requirement) \ No newline at end of file + max(self.potential_required_power, + self.logic.buildings[recipe.building].power_requirement) + + debug = True \ No newline at end of file diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index aae7f67ee8..63c15c3d94 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -56,13 +56,14 @@ class Recipe(): 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, ...] + minimal_tier: int 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): + additional_outputs: Optional[Tuple[str, ...]] = None, minimal_tier: Optional[int] = 1): self.name = "Recipe: " + name self.building = building self.inputs = inputs @@ -70,6 +71,7 @@ class Recipe(): self.handcraftable = handcraftable self.implicitly_unlocked = implicitly_unlocked self.additional_outputs = additional_outputs + self.minimal_tier = minimal_tier all_parts: List[str] = [name] if inputs: @@ -100,11 +102,14 @@ class MamNode(): """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""" + minimal_tier: Optional[int] - def __init__(self, name: str, unlock_cost: Dict[str, int], depends_on: Tuple[str, ...]): + def __init__(self, name: str, unlock_cost: Dict[str, int], depends_on: Tuple[str, ...], + minimal_tier: Optional[int] = 1): self.name = name self.unlock_cost = unlock_cost self.depends_on = depends_on + self.minimal_tier = minimal_tier class MamTree(): @@ -253,7 +258,7 @@ class GameLogic: 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("Aluminum Beam", "Constructor", ("Aluminum Ingot", ), minimal_tier=2), 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", )), @@ -266,7 +271,7 @@ class GameLogic: 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("Diluted Fuel", "Blender", ("Heavy Oil Residue", "Water"), minimal_tier=2), Recipe("Residual Fuel", "Refinery", ("Heavy Oil Residue", ))), "Concrete": ( Recipe("Concrete", "Constructor", ("Limestone", ), handcraftable=True, implicitly_unlocked=True), @@ -277,14 +282,14 @@ class GameLogic: 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", ))), + Recipe("Distilled Silica", "Blender", ("Dissolved Silica", "Limestone", "Water"), additional_outputs=("Water", ), minimal_tier=2)), "Dissolved Silica": ( - Recipe("Quartz Purification", "Refinery", ("Raw Quartz", "Nitric Acid"), additional_outputs=("Quartz Crystal", ), minimal_belt_speed=2), ), + Recipe("Quartz Purification", "Refinery", ("Raw Quartz", "Nitric Acid"), additional_outputs=("Quartz Crystal", ), minimal_belt_speed=2, minimal_tier=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)), + Recipe("Quartz Purification", "Refinery", ("Raw Quartz", "Nitric Acid"), additional_outputs=("Dissolved Silica", ), minimal_belt_speed=2, minimal_tier=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), @@ -364,48 +369,48 @@ class GameLogic: 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)), + Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Silica", ), minimal_belt_speed=2, minimal_tier=2), + Recipe("Sloppy Alumina", "Refinery", ("Bauxite", "Water"), minimal_belt_speed=3, minimal_tier=2)), "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)), + Recipe("Aluminum Scrap", "Refinery", ("Alumina Solution", "Coal"), additional_outputs=("Water", ), minimal_belt_speed=4, minimal_tier=2), + Recipe("Electrode Aluminum Scrap", "Refinery", ("Alumina Solution", "Petroleum Coke"), additional_outputs=("Water", ), minimal_belt_speed=4, minimal_tier=2), + Recipe("Instant Scrap", "Blender", ("Bauxite", "Coal", "Sulfuric Acid", "Water"), additional_outputs=("Water", ), minimal_belt_speed=3, minimal_tier=2)), "Aluminum Ingot": ( - Recipe("Aluminum Ingot", "Foundry", ("Aluminum Scrap", "Silica"), minimal_belt_speed=2, handcraftable=True), - Recipe("Pure Aluminum Ingot", "Smelter", ("Aluminum Scrap", ))), + Recipe("Aluminum Ingot", "Foundry", ("Aluminum Scrap", "Silica"), minimal_belt_speed=2, handcraftable=True, minimal_tier=2), + Recipe("Pure Aluminum Ingot", "Smelter", ("Aluminum Scrap", ), minimal_tier=2)), "Alclad Aluminum Sheet": ( - Recipe("Alclad Aluminum Sheet", "Assembler", ("Aluminum Ingot", "Copper Ingot"), handcraftable=True), ), + Recipe("Alclad Aluminum Sheet", "Assembler", ("Aluminum Ingot", "Copper Ingot"), handcraftable=True, minimal_tier=2), ), "Aluminum Casing": ( - Recipe("Aluminum Casing", "Constructor", ("Alclad Aluminum Sheet", ), handcraftable=True), - Recipe("Alclad Casing", "Assembler", ("Aluminum Ingot", "Copper Ingot"))), + Recipe("Aluminum Casing", "Constructor", ("Alclad Aluminum Sheet", ), handcraftable=True, minimal_tier=2), + Recipe("Alclad Casing", "Assembler", ("Aluminum Ingot", "Copper Ingot"), minimal_tier=2)), "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)), + Recipe("Heat Sink", "Assembler", ("Alclad Aluminum Sheet", "Silica"), minimal_belt_speed=2, handcraftable=True, minimal_tier=2), + Recipe("Heat Exchanger", "Assembler", ("Aluminum Casing", "Rubber"), minimal_belt_speed=3, minimal_tier=2)), "Nitric Acid": ( - Recipe("Nitric Acid", "Blender", ("Nitrogen Gas", "Water", "Iron Plate")), ), + Recipe("Nitric Acid", "Blender", ("Nitrogen Gas", "Water", "Iron Plate"), minimal_tier=2), ), "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)), + Recipe("Fused Modular Frame", "Blender", ("Heavy Modular Frame", "Aluminum Casing", "Nitrogen Gas"), minimal_belt_speed=2, minimal_tier=2), + Recipe("Heat-Fused Frame", "Blender", ("Heavy Modular Frame", "Aluminum Ingot", "Nitric Acid", "Fuel"), minimal_belt_speed=3, minimal_tier=2)), "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)), + Recipe("Radio Control Unit", "Manufacturer", ("Aluminum Casing", "Crystal Oscillator", "Computer"), handcraftable=True, minimal_tier=2), + Recipe("Radio Connection Unit", "Manufacturer", ("Heat Sink", "High-Speed Connector", "Quartz Crystal"), minimal_tier=2), + Recipe("Radio Control System", "Manufacturer", ("Crystal Oscillator", "Circuit Board", "Aluminum Casing", "Rubber"), minimal_belt_speed=2, minimal_tier=2)), "Pressure Conversion Cube": ( - Recipe("Pressure Conversion Cube", "Assembler", ("Fused Modular Frame", "Radio Control Unit"), handcraftable=True), ), + Recipe("Pressure Conversion Cube", "Assembler", ("Fused Modular Frame", "Radio Control Unit"), handcraftable=True, minimal_tier=2), ), "Cooling System": ( - Recipe("Cooling System", "Blender", ("Heat Sink", "Rubber", "Water", "Nitrogen Gas")), - Recipe("Cooling Device", "Blender", ("Heat Sink", "Motor", "Nitrogen Gas"))), + Recipe("Cooling System", "Blender", ("Heat Sink", "Rubber", "Water", "Nitrogen Gas"), minimal_tier=2), + Recipe("Cooling Device", "Blender", ("Heat Sink", "Motor", "Nitrogen Gas"), minimal_tier=2)), "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"))), + Recipe("Turbo Motor", "Manufacturer", ("Cooling System", "Radio Control Unit", "Motor", "Rubber"), handcraftable=True, minimal_tier=2), + Recipe("Turbo Electric Motor", "Manufacturer", ("Motor", "Radio Control Unit", "Electromagnetic Control Rod", "Rotor"), minimal_tier=2), + Recipe("Turbo Pressure Motor", "Manufacturer", ("Motor", "Pressure Conversion Cube", "Packaged Nitrogen Gas", "Stator"), minimal_tier=2)), "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)), + Recipe("Battery", "Blender", ("Sulfuric Acid", "Alumina Solution", "Aluminum Casing"), additional_outputs=("Water", ), minimal_tier=2), + Recipe("Classic Battery", "Manufacturer", ("Sulfur", "Alclad Aluminum Sheet", "Plastic", "Wire"), minimal_belt_speed=2, minimal_tier=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"))), + Recipe("OC Supercomputer", "Assembler", ("Radio Control Unit", "Cooling System"), minimal_tier=2), + Recipe("Super-State Computer", "Manufacturer", ("Computer", "Electromagnetic Control Rod", "Battery", "Wire"), minimal_tier=2)), "Sulfuric Acid": ( Recipe("Sulfuric Acid", "Refinery", ("Sulfur", "Water")), ), "Encased Uranium Cell": ( @@ -438,7 +443,7 @@ class GameLogic: "Copper Powder": ( Recipe("Copper Powder", "Constructor", ("Copper Ingot", ), handcraftable=True), ), "Nuclear Pasta": ( - Recipe("Nuclear Pasta", "Particle Accelerator", ("Copper Powder", "Pressure Conversion Cube")), ), + Recipe("Nuclear Pasta", "Particle Accelerator", ("Copper Powder", "Pressure Conversion Cube"), minimal_tier=2), ), "Thermal Propulsion Rocket": ( Recipe("Thermal Propulsion Rocket", "Manufacturer", ("Modular Engine", "Turbo Motor", "Cooling System", "Fused Modular Frame")), ), "Alien Protein": ( @@ -450,7 +455,7 @@ class GameLogic: 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)), + Recipe("Biomass (Alien Protein)", "Constructor", ("Alien Protein", ), minimal_belt_speed=4, handcraftable=True)), "Fabric": ( Recipe("Fabric", "Assembler", ("Biomass", "Mycelia"), handcraftable=True, minimal_belt_speed=2), Recipe("Polyester Fabric", "Refinery", ("Polymer Resin", "Water"))), @@ -463,7 +468,7 @@ class GameLogic: 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), ), + Recipe("Empty Fluid Tank", "Constructor", ("Aluminum Ingot", ), handcraftable=True, minimal_tier=2), ), "Packaged Alumina Solution": ( Recipe("Packaged Alumina Solution", "Packager", ("Alumina Solution", "Empty Canister"), minimal_belt_speed=2), ), "Packaged Fuel": ( @@ -488,7 +493,7 @@ class GameLogic: "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"))), + Recipe("Turbo Blend Fuel", "Blender", ("Fuel", "Heavy Oil Residue", "Sulfur", "Petroleum Coke"), minimal_tier=2)), "Gas Mask": ( Recipe("Gas Mask", "Equipment Workshop", ("Rubber", "Plastic", "Fabric"), handcraftable=True, minimal_belt_speed=0), ), "Alien DNA Capsule": ( @@ -508,7 +513,7 @@ class GameLogic: Recipe("Power Shard (1)", "Constructor", ("Blue Power Slug", ), handcraftable=True), Recipe("Power Shard (2)", "Constructor", ("Yellow Power Slug", ), handcraftable=True), Recipe("Power Shard (5)", "Constructor", ("Purple Power Slug", ), handcraftable=True), - Recipe("Synthetic Power Shard", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Dark Matter Crystal", "Quartz Crystal"))), # 1.0 + Recipe("Synthetic Power Shard", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Dark Matter Crystal", "Quartz Crystal"), minimal_tier=2)), # 1.0 "Object Scanner": ( Recipe("Object Scanner", "Equipment Workshop", ("Reinforced Iron Plate", "Wire", "Screw"), handcraftable=True), ), "Xeno-Zapper": ( @@ -516,8 +521,8 @@ class GameLogic: #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", ))), + Recipe("Rocket Fuel", "Blender", ("Turbofuel", "Nitric Acid"), additional_outputs=("Compacted Coal", ), minimal_tier=2), + Recipe("Nitro Rocket Fuel", "Blender", ("Fuel", "Nitrogen Gas", "Sulfur", "Coal"), minimal_belt_speed=2, additional_outputs=("Compacted Coal", ), minimal_tier=2)), #"Ionized Fuel": ( # Recipe("Ionized Fuel", "Refinery", ("Rocket Fuel", "Power Shard"), additional_outputs=("Compacted Coal", )), ), "Packaged Rocket Fuel": ( @@ -580,7 +585,7 @@ class GameLogic: "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), + "Blender": Building("Blender", ("Motor", "Heavy Modular Frame", "Aluminum Casing", "Radio Control Unit"), PowerInfrastructureLevel.Advanced), "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")), @@ -783,7 +788,7 @@ class GameLogic: 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",)), + MamNode("Alien Power Matrix", {"Singularity Cell":50,"Power Shard":100,"SAM Fluctuator":500}, depends_on=("Power Augmenter",), minimal_tier=4), )), # 1.0 "Caterium": MamTree(("Caterium Ore", ), ( # Caterium (BPD_ResearchTree_Caterium_C) @@ -829,7 +834,7 @@ class GameLogic: 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 + MamNode("Synthetic Power Shards", {"Power Shard":10,"Time Crystal":100,"Quartz Crystal":200,}, depends_on=("Purple Power Shards", ), minimal_tier=4), # 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) @@ -855,12 +860,12 @@ class GameLogic: 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 Rifle Ammo", {"Rifle Ammo":1000,"Packaged Turbofuel":50,"Aluminum Casing":100,}, depends_on=("The Rifle", ), minimal_tier=2), #(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 + MamNode("Nuclear Deterrent Development", {"Nobelisk":500,"Encased Uranium Cell":10,"AI Limiter":100,}, depends_on=("Cluster Nobelisk", ), minimal_tier=2), #(Research_Sulfur_5_1_C) # 1.0 + MamNode("Rocket Fuel", {"Hard Drive":1,"Empty Fluid Tank":10,"Packaged Turbofuel":100,}, depends_on=("Turbo Fuel", ), minimal_tier=3), # 1.0 + MamNode("Ionized Fuel", {"Hard Drive":1,"Power Shard":100,"Packaged Rocket Fuel":200,}, depends_on=("Turbo Fuel", ), minimal_tier=4), # 1.0 )) } diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index 9b5f75e532..dc7756488d 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -28,10 +28,14 @@ class LocationData(): class Part(LocationData): @staticmethod - def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, items: Items) -> List[LocationData]: + def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, items: Items, + final_elevator_tier: int) -> List[LocationData]: recipes_per_region: Dict[str, List[Recipe]] = {} for recipe in recipes: + if recipe.minimal_tier > final_elevator_tier: + continue + recipes_per_region.setdefault(recipe.building or "Overworld", []).append(recipe) return [Part(state_logic, region, recipes_for_region, name, items) @@ -44,6 +48,9 @@ class Part(LocationData): 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 == "Iron 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]) @@ -74,7 +81,10 @@ class EventBuilding(LocationData): ) -> Callable[[CollectionState], bool]: def can_build(state: CollectionState) -> bool: - return state_logic.has_recipe(state, building) \ + if building.name == "Building: Manufacturer": + debug = True + + return (building.implicitly_unlocked or 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) @@ -92,6 +102,9 @@ class PowerInfrastructure(LocationData): ) -> Callable[[CollectionState], bool]: def can_power(state: CollectionState) -> bool: + if powerLevel == PowerInfrastructureLevel.Automated: + debug = True + 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) @@ -127,11 +140,11 @@ class ShopSlot(LocationData): if not state_logic or cost < 20: return True elif (cost >= 20 and cost < 50): - return state_logic.is_game_phase(state, 1) + return state_logic.is_elevator_tier(state, 1) elif (cost >= 50 and cost < 100): - return state_logic.is_game_phase(state, 2) + return state_logic.is_elevator_tier(state, 2) else: - return state_logic.is_game_phase(state, 3) + return state_logic.is_elevator_tier(state, 3) return can_purchase @@ -309,7 +322,7 @@ class Locations(): location_table = self.get_base_location_table() location_table.extend(self.get_hub_locations(True, self.max_tiers)) - location_table.extend(self.get_drop_pod_locations(True, self.max_tiers)) + location_table.extend(self.get_drop_pod_locations(True, self.max_tiers, set())) location_table.append(LocationData("Overworld", "UpperBound", 1338999)) return {location.name: location.code for location in location_table} @@ -324,8 +337,8 @@ class Locations(): location_table = self.get_base_location_table() location_table.extend(self.get_hub_locations(False, max_tier_for_game)) - location_table.extend(self.get_drop_pod_locations(False, max_tier_for_game)) - location_table.extend(self.get_logical_event_locations()) + location_table.extend(self.get_drop_pod_locations(False, max_tier_for_game, self.critical_path.potential_required_parts)) + location_table.extend(self.get_logical_event_locations(self.options.final_elevator_package)) return location_table @@ -358,7 +371,7 @@ class Locations(): return location_table - def get_logical_event_locations(self) -> List[LocationData]: + def get_logical_event_locations(self, final_elevator_tier: int) -> List[LocationData]: location_table: List[LocationData] = [] # for performance plan is to upfront calculated everything we need @@ -372,7 +385,7 @@ class Locations(): part for part_name, recipes in self.game_logic.recipes.items() if part_name in self.critical_path.potential_required_parts - for part in Part.get_parts(self.state_logic, recipes, part_name, self.items)) + for part in Part.get_parts(self.state_logic, recipes, part_name, self.items, final_elevator_tier)) location_table.extend( EventBuilding(self.game_logic, self.state_logic, name, building) for name, building in self.game_logic.buildings.items() @@ -384,7 +397,8 @@ class Locations(): return location_table - def get_drop_pod_locations(self, for_data_package: bool, max_tier: int) -> List[LocationData]: + def get_drop_pod_locations(self, for_data_package: bool, max_tier: int, available_parts: set[str]) \ + -> List[LocationData]: drop_pod_locations: List[DropPod] = [] bucket_size: int @@ -403,10 +417,15 @@ class Locations(): 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 + + if location_id_normalized == 81: + debug = True + 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), max_tier) - drop_pod_locations.append(DropPod(data, self.state_logic, location_id, tier, can_hold_progression)) + if not data.item or data.item in available_parts: + 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/Regions.py b/worlds/satisfactory/Regions.py index 8cacb10537..aab87bf344 100644 --- a/worlds/satisfactory/Regions.py +++ b/worlds/satisfactory/Regions.py @@ -33,7 +33,6 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory game_logic: GameLogic, state_logic: StateLogic, locations: List[LocationData]): region_names: List[str] = [ - "Menu", "Overworld", "Gas Area", "Radioactive Area", @@ -89,9 +88,8 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory super_early_game_buildings.append("Conveyor Merger") if options.final_elevator_package == 1: - super_early_game_buildings.append(early_game_buildings) + super_early_game_buildings.extend(early_game_buildings) - 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)) diff --git a/worlds/satisfactory/StateLogic.py b/worlds/satisfactory/StateLogic.py index 6890752da3..a4bf40af6c 100644 --- a/worlds/satisfactory/StateLogic.py +++ b/worlds/satisfactory/StateLogic.py @@ -60,6 +60,9 @@ class StateLogic: 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.name == "Recipe: Iron Ingot": + debug = True + 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"))): @@ -76,8 +79,8 @@ class StateLogic: 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: - limited_phase = min(self.options.final_elevator_package * 2, phase) + def is_elevator_tier(self, state: CollectionState, phase: int) -> bool: + limited_phase = min(self.options.final_elevator_package, phase) return state.has(f"Elevator Tier {limited_phase}", self.player) diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index 2707dcba68..ebc521dad3 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -23,6 +23,7 @@ class SatisfactoryWorld(World): topology_present = False data_version = 0 web = SatisfactoryWebWorld() + origin_region_name = "Overworld" item_name_to_id = Items.item_names_and_ids location_name_to_id = Locations().get_locations_for_data_package() From f6a151970426ab09caaecdff24539bfb9b3f7b2c Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sun, 23 Mar 2025 12:56:10 +0100 Subject: [PATCH 04/12] progress i think as test passed --- worlds/satisfactory/GameLogic.py | 8 +++--- worlds/satisfactory/Items.py | 7 ++++++ worlds/satisfactory/Locations.py | 43 ++++++++++++++++++++++++-------- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index 63c15c3d94..aa14a4b345 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -483,13 +483,13 @@ class GameLogic: "Packaged Nitrogen Gas": ( Recipe("Packaged Nitrogen Gas", "Packager", ("Nitrogen Gas", "Empty Fluid Tank")), ), "Packaged Oil": ( - Recipe("Packaged Oil", "Packager", ("Crude Oil", "Empty Fluid Tank")), ), + Recipe("Packaged Oil", "Packager", ("Crude Oil", "Empty Canister")), ), "Packaged Sulfuric Acid": ( - Recipe("Packaged Sulfuric Acid", "Packager", ("Sulfuric Acid", "Empty Fluid Tank")), ), + Recipe("Packaged Sulfuric Acid", "Packager", ("Sulfuric Acid", "Empty Canister")), ), "Packaged Turbofuel": ( - Recipe("Packaged Turbofuel", "Packager", ("Turbofuel", "Empty Fluid Tank")), ), + Recipe("Packaged Turbofuel", "Packager", ("Turbofuel", "Empty Canister")), ), "Packaged Water": ( - Recipe("Packaged Water", "Packager", ("Water", "Empty Fluid Tank")), ), + Recipe("Packaged Water", "Packager", ("Water", "Empty Canister")), ), "Turbofuel": ( Recipe("Turbofuel", "Refinery", ("Fuel", "Compacted Coal")), Recipe("Turbo Heavy Fuel", "Refinery", ("Heavy Oil Residue", "Compacted Coal")), diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index f45732cba3..01f3e18f83 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -462,6 +462,13 @@ class Items: "Recipe: Tempered Copper Ingot": ItemData(G.Recipe, 1338458, C.progression), # 1.0 +# Missed + "Recipe: Iron Pipe": ItemData(G.Recipe, 1338459, C.progression), + "Recipe: Biocoal": ItemData(G.Recipe, 1338460, C.useful), + "Recipe: Charcoal": ItemData(G.Recipe, 1338461, C.useful), + "Recipe: Sloppy Alumina": ItemData(G.Recipe, 1338462, C.progression), +# Missed + #1338459 - 1338599 Reserved for future recipes #1338400 - 1338899 buildings / others diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index dc7756488d..04c860c30d 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -203,8 +203,9 @@ class Locations(): self.items = items self.critical_path = critical_path - def get_base_location_table(self) -> List[LocationData]: - return [ + + def get_base_location_table(self, max_tier: int) -> List[LocationData]: + all_locations = [ MamSlot("Alien Organisms", "Inflated Pocket Dimension", 1338500), MamSlot("Alien Organisms", "Hostile Organism Detection", 1338501), MamSlot("Alien Organisms", "Expanded Toolbelt", 1338502), @@ -273,15 +274,15 @@ class Locations(): MamSlot("Sulfur", "Explosive Rebar", 1338565), MamSlot("Sulfur", "Cluster Nobelisk", 1338566), MamSlot("Sulfur", "Experimental Power Generation", 1338567), - MamSlot("Sulfur", "Turbo Rifle Ammo", 1338568), + # 1338568 Turbo Rifle Ammo MamSlot("Sulfur", "Turbo Fuel", 1338569), MamSlot("Sulfur", "Expanded Toolbelt", 1338570), - MamSlot("Sulfur", "Nuclear Deterrent Development", 1338571), + # 1338571 Nuclear Deterrent Development # 1.0 - MamSlot("Power Slugs", "Synthetic Power Shards", 1338572), - MamSlot("Sulfur", "Rocket Fuel", 1338573), - MamSlot("Sulfur", "Ionized Fuel", 1338574), + # 1338572 Synthetic Power Shards + # 1338573 Rocket Fuel + # 1338574 Ionized Fuel MamSlot("Alien Technology", "SAM Analysis", 1338575), MamSlot("Alien Technology", "SAM Reanimation", 1338576), MamSlot("Alien Technology", "SAM Fluctuator", 1338577), @@ -300,7 +301,7 @@ class Locations(): 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), + # 1338593 Alien Power Matrix # 1.0 # 1338600 - 1338699 - Harddrives - Harddrives @@ -317,10 +318,32 @@ class Locations(): ShopSlot(self.state_logic, 10, 50, 1338709) ] + #TODO: should be based on self.game_logic + if 4 <= max_tier: + all_locations.append(MamSlot("Power Slugs", "Synthetic Power Shards", 1338572)) + if 4 <= max_tier: + all_locations.append(MamSlot("Alien Technology", "Alien Power Matrix", 1338593)) + if 2 <= max_tier: + all_locations.append(MamSlot("Sulfur", "Turbo Rifle Ammo", 1338568)) + if 2 <= max_tier: + all_locations.append(MamSlot("Sulfur", "Nuclear Deterrent Development", 1338571)) + if 3 <= max_tier: + all_locations.append(MamSlot("Sulfur", "Rocket Fuel", 1338573)) + if 4 <= max_tier: + all_locations.append(MamSlot("Sulfur", "Ionized Fuel", 1338574)) + + return all_locations + def get_locations_for_data_package(self) -> Dict[str, int]: "Must include all possible location names and their id's" - location_table = self.get_base_location_table() + # 1338000 - 1338499 - Milestones + # 1338500 - 1338599 - Mam + # 1338600 - 1338699 - Harddrives + # 1338700 - 1338709 - Shop + # 1338999 - Upper bound + + location_table = self.get_base_location_table(self.max_tiers) location_table.extend(self.get_hub_locations(True, self.max_tiers)) location_table.extend(self.get_drop_pod_locations(True, self.max_tiers, set())) location_table.append(LocationData("Overworld", "UpperBound", 1338999)) @@ -335,7 +358,7 @@ class Locations(): max_tier_for_game = min(self.options.final_elevator_package * 2, len(self.game_logic.hub_layout)) - location_table = self.get_base_location_table() + location_table = self.get_base_location_table(max_tier_for_game) location_table.extend(self.get_hub_locations(False, max_tier_for_game)) location_table.extend(self.get_drop_pod_locations(False, max_tier_for_game, self.critical_path.potential_required_parts)) location_table.extend(self.get_logical_event_locations(self.options.final_elevator_package)) From 6b57ec381b666f28ccf792579dbc1f4a2789f118 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sun, 23 Mar 2025 21:23:57 +0100 Subject: [PATCH 05/12] I guess test pass, game still unbeatable tho --- worlds/satisfactory/CriticalPathCalculator.py | 26 +++--------------- worlds/satisfactory/GameLogic.py | 4 +-- worlds/satisfactory/Items.py | 6 ++--- worlds/satisfactory/Locations.py | 27 +++++-------------- worlds/satisfactory/Regions.py | 27 ++++++++++--------- worlds/satisfactory/__init__.py | 3 ++- 6 files changed, 31 insertions(+), 62 deletions(-) diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py index 7664a50998..74f9e47404 100644 --- a/worlds/satisfactory/CriticalPathCalculator.py +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -37,16 +37,6 @@ class CriticalPathCalculator: self.select_minimal_required_parts_for(tree.access_items) for node in tree.nodes: - # Bullet Guidance System - Rifle Ammo - # Stun Rebar - Iron Rebar - # Radar Technology - Heavy Modular Frame - # Turbo Rifle Ammo - Packaged Turbofuel, Rifle Ammo - # Nuclear Deterrent Development - Encased Uranium Cell - # Rocket Fuel - Packaged Turbofuel - # Ionized Fuel - Ionized Fuel - if node.name == "Hostile Organism Detection": - Debug = True - if node.minimal_tier > options.final_elevator_package: continue @@ -63,6 +53,10 @@ class CriticalPathCalculator: self.select_minimal_required_parts_for_building("Walls Orange") self.select_minimal_required_parts_for_building("Power Storage") + #equipment + self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) + self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) + for i in range(1, self.potential_required_belt_speed + 1): self.select_minimal_required_parts_for_building(f"Conveyor Mk.{i}") if self.potential_required_pipes: @@ -102,21 +96,12 @@ class CriticalPathCalculator: if part in self.potential_required_parts: continue - if part == "Radio Control Unit": - Debug = True - self.potential_required_parts.add(part) for recipe in self.logic.recipes[part]: - if part == "Fuel": - Debug = True - if recipe.minimal_tier > self.options.final_elevator_package: continue - if recipe.minimal_belt_speed == 5: - Debug = True - self.potential_required_belt_speed = \ max(self.potential_required_belt_speed, recipe.minimal_belt_speed) @@ -128,9 +113,6 @@ class CriticalPathCalculator: self.potential_required_radioactive = True if recipe.building: - if recipe.building == "Blender": - debug = True - self.select_minimal_required_parts_for(self.logic.buildings[recipe.building].inputs) self.potential_required_buildings.add(recipe.building) diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index aa14a4b345..f9cc8b447f 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -593,8 +593,8 @@ class GameLogic: "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), + "Miner Mk.2": Building("Miner Mk.2", ("Encased Industrial Beam", "Steel Pipe", "Modular Frame"), PowerInfrastructureLevel.Automated, can_produce=False), + "Miner Mk.3": Building("Miner Mk.3", ("Steel Pipe", "Supercomputer", "Fused Modular Frame", "Turbo Motor"), PowerInfrastructureLevel.Advanced, can_produce=False), "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), diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index 01f3e18f83..6ac4f0f857 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -840,8 +840,8 @@ class Items: if not (data.category & G.BasicNeeds) and name not in instance.critical_path.potential_required_recipes_names: type = C.filler logging.info(f"Dropping... {name}") - else: - logging.warning(f"Required .. {name}") + #else: + # logging.warning(f"Required .. {name}") return Item(name, type, data.code, player) @@ -888,7 +888,7 @@ class Items: filler_pool_size: int = number_of_locations - len(pool) if (filler_pool_size < 0): raise Exception(f"Location pool starved, trying to add {len(pool)} items to {number_of_locations} locations") - + logging.warning(f"Itempool size: {len(pool)}, number of locations: {number_of_locations}, spare: {filler_pool_size}") for _ in range(filler_pool_size): item = self.create_item(self, self.get_filler_item_name(random, options), self.player) pool.append(item) diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index 04c860c30d..53059a9118 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -48,9 +48,6 @@ class Part(LocationData): 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 == "Iron 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]) @@ -81,9 +78,6 @@ class EventBuilding(LocationData): ) -> Callable[[CollectionState], bool]: def can_build(state: CollectionState) -> bool: - if building.name == "Building: Manufacturer": - debug = True - return (building.implicitly_unlocked or 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) @@ -102,9 +96,6 @@ class PowerInfrastructure(LocationData): ) -> Callable[[CollectionState], bool]: def can_power(state: CollectionState) -> bool: - if powerLevel == PowerInfrastructureLevel.Automated: - debug = True - 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) @@ -157,12 +148,6 @@ class DropPod(LocationData): # we currently do not know how many hdd require gas 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]: @@ -319,17 +304,17 @@ class Locations(): ] #TODO: should be based on self.game_logic - if 4 <= max_tier: + if max_tier > 8: all_locations.append(MamSlot("Power Slugs", "Synthetic Power Shards", 1338572)) - if 4 <= max_tier: + if max_tier > 8: all_locations.append(MamSlot("Alien Technology", "Alien Power Matrix", 1338593)) - if 2 <= max_tier: + if max_tier > 2: all_locations.append(MamSlot("Sulfur", "Turbo Rifle Ammo", 1338568)) - if 2 <= max_tier: + if max_tier > 2: all_locations.append(MamSlot("Sulfur", "Nuclear Deterrent Development", 1338571)) - if 3 <= max_tier: + if max_tier > 4: all_locations.append(MamSlot("Sulfur", "Rocket Fuel", 1338573)) - if 4 <= max_tier: + if max_tier > 6: all_locations.append(MamSlot("Sulfur", "Ionized Fuel", 1338574)) return all_locations diff --git a/worlds/satisfactory/Regions.py b/worlds/satisfactory/Regions.py index aab87bf344..b0ba5b0023 100644 --- a/worlds/satisfactory/Regions.py +++ b/worlds/satisfactory/Regions.py @@ -4,6 +4,7 @@ from .Locations import LocationData from .GameLogic import GameLogic, PowerInfrastructureLevel from .StateLogic import StateLogic from .Options import SatisfactoryOptions, Placement +from .CriticalPathCalculator import CriticalPathCalculator class SatisfactoryLocation(Location): game: str = "Satisfactory" @@ -29,13 +30,12 @@ class SatisfactoryLocation(Location): 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]): +def create_regions_and_return_locations(world: MultiWorld, options: SatisfactoryOptions, player: int, + game_logic: GameLogic, state_logic: StateLogic, critical_path: CriticalPathCalculator, + locations: List[LocationData]): region_names: List[str] = [ "Overworld", - "Gas Area", - "Radioactive Area", "Mam", "AWESOME Shop" ] @@ -50,14 +50,15 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory region_names.append(f"Hub {hub_tier}-{minestone}") for building_name, building in game_logic.buildings.items(): - if building.can_produce: + if building.can_produce and building_name in critical_path.potential_required_buildings: 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}") + if node.minimal_tier <= options.final_elevator_package: + 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) @@ -107,10 +108,6 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory if options.final_elevator_package >= 5: 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"))) @@ -130,7 +127,7 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory can_produce_all_allowing_handcrafting(parts_per_milestone.keys())) for building_name, building in game_logic.buildings.items(): - if building.can_produce: + if building.can_produce and building_name in critical_path.potential_required_buildings: connect(regions, "Overworld", building_name, lambda state, building_name=building_name: state_logic.can_build(state, building_name)) @@ -138,13 +135,17 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory connect(regions, "Mam", tree_name) for node in tree.nodes: + if node.minimal_tier > options.final_elevator_package: + continue + 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)) + if f"{tree_name}: {parent}" in region_names: + 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]): diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index ebc521dad3..e25d4752db 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -73,7 +73,8 @@ class SatisfactoryWorld(World): locations: List[LocationData] = \ Locations(self.game_logic, self.options, self.state_logic, self.items, self.critical_path).get_locations() create_regions_and_return_locations( - self.multiworld, self.options, self.player, self.game_logic, self.state_logic, locations) + self.multiworld, self.options, self.player, self.game_logic, self.state_logic, self.critical_path, + locations) def create_items(self) -> None: From 5fdb33460de186623077b9f427d0df3cde583608 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sun, 23 Mar 2025 22:08:12 +0100 Subject: [PATCH 06/12] its generating --- worlds/satisfactory/__init__.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index e25d4752db..b12093d412 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -40,8 +40,6 @@ class SatisfactoryWorld(World): def generate_early(self) -> None: - self.options.final_elevator_package.value = 1 - self.state_logic = StateLogic(self.player, self.options) self.critical_path = CriticalPathCalculator(self.game_logic, self.random, self.options) self.items = Items(self.player, self.game_logic, self.random, self.options, self.critical_path) @@ -88,19 +86,14 @@ class SatisfactoryWorld(World): 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()) + required_parts: Set[str] = \ + set(self.game_logic.space_elevator_tiers[self.options.final_elevator_package.value - 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) + lambda state: self.state_logic.can_produce_all(state, required_parts) def collect(self, state: CollectionState, item: Item) -> bool: @@ -146,6 +139,7 @@ class SatisfactoryWorld(World): "EnergyLink": bool(self.options.energy_link) } }, + "SlotDataVersion": 1, "DeathLink": bool(self.options.death_link) } From dcb820b483b1b6c8605e9eb90b0ba1fc942d2148 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Mon, 24 Mar 2025 20:38:58 +0100 Subject: [PATCH 07/12] Some refactorings --- worlds/satisfactory/CriticalPathCalculator.py | 75 +++++----- worlds/satisfactory/Items.py | 141 +----------------- worlds/satisfactory/Locations.py | 44 ++---- worlds/satisfactory/Regions.py | 4 +- worlds/satisfactory/StateLogic.py | 3 - worlds/satisfactory/__init__.py | 2 +- 6 files changed, 61 insertions(+), 208 deletions(-) diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py index 74f9e47404..14b4695a25 100644 --- a/worlds/satisfactory/CriticalPathCalculator.py +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -8,26 +8,29 @@ from .Options import SatisfactoryOptions class CriticalPathCalculator: logic: GameLogic random: Random + options: SatisfactoryOptions - potential_required_parts: set[str] - potential_required_buildings: set[str] - potential_required_belt_speed: int - potential_required_pipes: bool - potential_required_radioactive: bool - potential_required_power: int - potential_required_recipes_names: set[str] + required_parts: set[str] + required_buildings: set[str] + required_item_names: set[str] + required_power_level: int + + __potential_required_belt_speed: int + __potential_required_pipes: bool + __potential_required_radioactive: bool def __init__(self, logic: GameLogic, random: Random, options: SatisfactoryOptions): self.logic = logic self.random = random self.options = options - self.potential_required_parts = set() - self.potential_required_buildings = set() - self.potential_required_belt_speed = 1 - self.potential_required_pipes = False - self.potential_required_radioactive = False - self.potential_required_power: int = 1 + self.required_parts = set() + self.required_buildings = set() + self.required_power_level: int = 1 + + self.__potential_required_belt_speed = 1 + self.__potential_required_pipes = False + self.__potential_required_radioactive = False selected_power_infrastructure: dict[int, Recipe] = {} @@ -52,73 +55,69 @@ class CriticalPathCalculator: self.select_minimal_required_parts_for_building("Foundation") self.select_minimal_required_parts_for_building("Walls Orange") self.select_minimal_required_parts_for_building("Power Storage") + self.select_minimal_required_parts_for_building("Miner Mk.2") #equipment self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) - for i in range(1, self.potential_required_belt_speed + 1): + for i in range(1, self.__potential_required_belt_speed + 1): self.select_minimal_required_parts_for_building(f"Conveyor Mk.{i}") - if self.potential_required_pipes: + if self.__potential_required_pipes: self.select_minimal_required_parts_for_building("Pipes Mk.1") + self.select_minimal_required_parts_for_building("Pipes Mk.2") self.select_minimal_required_parts_for_building("Pipeline Pump Mk.1") - if self.potential_required_radioactive: + self.select_minimal_required_parts_for_building("Pipeline Pump Mk.2") + if self.__potential_required_radioactive: self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) - for i in range(1, self.potential_required_power + 1): + for i in range(1, self.required_power_level + 1): power_recipe = random.choice(self.logic.requirement_per_powerlevel[i]) selected_power_infrastructure[i] = power_recipe self.select_minimal_required_parts_for(power_recipe.inputs) self.select_minimal_required_parts_for_building(power_recipe.building) - self.potential_required_recipes_names = set( + self.required_item_names = set( recipe.name - for part in self.potential_required_parts + for part in self.required_parts for recipe in self.logic.recipes[part] if recipe.minimal_tier <= self.options.final_elevator_package ) - self.potential_required_recipes_names.update( - "Building: "+ building - for building in self.potential_required_buildings - ) - - debug = True + self.required_item_names.update("Building: "+ building for building in self.required_buildings) def select_minimal_required_parts_for_building(self, building: str) -> None: self.select_minimal_required_parts_for(self.logic.buildings[building].inputs) - self.potential_required_buildings.add(building) + self.required_buildings.add(building) def select_minimal_required_parts_for(self, parts: Optional[Iterable[str]]) -> None: if parts is None: return for part in parts: - if part in self.potential_required_parts: + if part in self.required_parts: continue - self.potential_required_parts.add(part) + self.required_parts.add(part) for recipe in self.logic.recipes[part]: if recipe.minimal_tier > self.options.final_elevator_package: continue - self.potential_required_belt_speed = \ - max(self.potential_required_belt_speed, recipe.minimal_belt_speed) + self.__potential_required_belt_speed = \ + max(self.__potential_required_belt_speed, recipe.minimal_belt_speed) self.select_minimal_required_parts_for(recipe.inputs) if recipe.needs_pipes: - self.potential_required_pipes = True + self.__potential_required_pipes = True if recipe.is_radio_active: - self.potential_required_radioactive = True + self.__potential_required_radioactive = True if recipe.building: self.select_minimal_required_parts_for(self.logic.buildings[recipe.building].inputs) - self.potential_required_buildings.add(recipe.building) + self.required_buildings.add(recipe.building) if self.logic.buildings[recipe.building].power_requirement: - self.potential_required_power = \ - max(self.potential_required_power, - self.logic.buildings[recipe.building].power_requirement) - - debug = True \ No newline at end of file + self.required_power_level = \ + max(self.required_power_level, + self.logic.buildings[recipe.building].power_requirement) \ No newline at end of file diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index 6ac4f0f857..49f113ff48 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -1,8 +1,8 @@ import copy from random import Random -from typing import ClassVar, Dict, Set, List, TextIO, Tuple, Optional +from typing import ClassVar, Dict, Set, List, Tuple, Optional from BaseClasses import Item, ItemClassification as C, MultiWorld -from .GameLogic import GameLogic, Recipe +from .GameLogic import GameLogic from .Options import SatisfactoryOptions from .ItemData import ItemData, ItemGroups as G from .Options import SatisfactoryOptions @@ -709,8 +709,6 @@ class Items: logic: GameLogic random: Random critical_path: CriticalPathCalculator - 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, critical_path: CriticalPathCalculator): @@ -719,129 +717,18 @@ class Items: self.random = random self.critical_path = critical_path - 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 instance and type == C.progression: - if instance.precalculated_progression_recipes_names: - if not name.startswith("Building: "): - if name not in instance.precalculated_progression_recipes_names: - type = C.useful - logging.info(f"Downscaling .. {name}") - else: - logging.warning(f"Preserving .. {name}") - if instance.critical_path.potential_required_recipes_names: - if not (data.category & G.BasicNeeds) and name not in instance.critical_path.potential_required_recipes_names: - type = C.filler - logging.info(f"Dropping... {name}") - #else: - # logging.warning(f"Required .. {name}") + if type == C.progression \ + and instance and instance.critical_path.required_item_names \ + and (data.category & (G.Recipe | G.Building)) and not (data.category & G.BasicNeeds) \ + and name not in instance.critical_path.required_item_names: + type = C.filler + logging.info(f"Dropping... {name}") return Item(name, type, data.code, player) @@ -894,15 +781,3 @@ class Items: 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 index 53059a9118..a239b6bd7c 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -48,23 +48,7 @@ class Part(LocationData): 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 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 any(state_logic.can_produce_specific_recipe_for_part(state, recipe) for recipe in recipes) return can_build_by_any_recipe @@ -140,7 +124,7 @@ class ShopSlot(LocationData): return can_purchase -class DropPod(LocationData): +class HardDrive(LocationData): def __init__(self, data: DropPodData, state_logic: Optional[StateLogic], locationId: int, tier: int, can_hold_progression: bool): @@ -330,7 +314,7 @@ class Locations(): location_table = self.get_base_location_table(self.max_tiers) location_table.extend(self.get_hub_locations(True, self.max_tiers)) - location_table.extend(self.get_drop_pod_locations(True, self.max_tiers, set())) + location_table.extend(self.get_hard_drive_locations(True, self.max_tiers, set())) location_table.append(LocationData("Overworld", "UpperBound", 1338999)) return {location.name: location.code for location in location_table} @@ -345,7 +329,7 @@ class Locations(): location_table = self.get_base_location_table(max_tier_for_game) location_table.extend(self.get_hub_locations(False, max_tier_for_game)) - location_table.extend(self.get_drop_pod_locations(False, max_tier_for_game, self.critical_path.potential_required_parts)) + location_table.extend(self.get_hard_drive_locations(False, max_tier_for_game,self.critical_path.required_parts)) location_table.extend(self.get_logical_event_locations(self.options.final_elevator_package)) return location_table @@ -392,22 +376,22 @@ class Locations(): location_table.extend( part for part_name, recipes in self.game_logic.recipes.items() - if part_name in self.critical_path.potential_required_parts + if part_name in self.critical_path.required_parts for part in Part.get_parts(self.state_logic, recipes, part_name, self.items, final_elevator_tier)) location_table.extend( EventBuilding(self.game_logic, self.state_logic, name, building) for name, building in self.game_logic.buildings.items() - if name in self.critical_path.potential_required_buildings) + if name in self.critical_path.required_buildings) 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() - if power_level <= self.critical_path.potential_required_power) + if power_level <= self.critical_path.required_power_level) return location_table - def get_drop_pod_locations(self, for_data_package: bool, max_tier: int, available_parts: set[str]) \ + def get_hard_drive_locations(self, for_data_package: bool, max_tier: int, available_parts: set[str]) \ -> List[LocationData]: - drop_pod_locations: List[DropPod] = [] + hard_drive_locations: List[HardDrive] = [] bucket_size: int drop_pod_data: List[DropPodData] @@ -422,18 +406,16 @@ class Locations(): for location_id in range(self.drop_pod_location_id_start, self.drop_pod_location_id_end + 1): if for_data_package: - drop_pod_locations.append(DropPod(DropPodData(0, 0, 0, None, 0), None, location_id, 1, False)) + hard_drive_locations.append(HardDrive(DropPodData(0, 0, 0, None, 0), None, location_id, 1, False)) else: location_id_normalized: int = location_id - self.drop_pod_location_id_start - if location_id_normalized == 81: - debug = True - 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), max_tier) if not data.item or data.item in available_parts: - drop_pod_locations.append(DropPod(data, self.state_logic, location_id, tier, can_hold_progression)) + hard_drive_locations.append( + HardDrive(data, self.state_logic, location_id, tier, can_hold_progression)) - return drop_pod_locations \ No newline at end of file + return hard_drive_locations \ No newline at end of file diff --git a/worlds/satisfactory/Regions.py b/worlds/satisfactory/Regions.py index b0ba5b0023..852ad11eb5 100644 --- a/worlds/satisfactory/Regions.py +++ b/worlds/satisfactory/Regions.py @@ -50,7 +50,7 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory region_names.append(f"Hub {hub_tier}-{minestone}") for building_name, building in game_logic.buildings.items(): - if building.can_produce and building_name in critical_path.potential_required_buildings: + if building.can_produce and building_name in critical_path.required_buildings: region_names.append(building_name) for tree_name, tree in game_logic.man_trees.items(): @@ -127,7 +127,7 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory can_produce_all_allowing_handcrafting(parts_per_milestone.keys())) for building_name, building in game_logic.buildings.items(): - if building.can_produce and building_name in critical_path.potential_required_buildings: + if building.can_produce and building_name in critical_path.required_buildings: connect(regions, "Overworld", building_name, lambda state, building_name=building_name: state_logic.can_build(state, building_name)) diff --git a/worlds/satisfactory/StateLogic.py b/worlds/satisfactory/StateLogic.py index a4bf40af6c..8659707ad6 100644 --- a/worlds/satisfactory/StateLogic.py +++ b/worlds/satisfactory/StateLogic.py @@ -60,9 +60,6 @@ class StateLogic: 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.name == "Recipe: Iron Ingot": - debug = True - 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"))): diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index b12093d412..9cf049ae08 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -145,7 +145,7 @@ class SatisfactoryWorld(World): def write_spoiler(self, spoiler_handle: TextIO): - self.items.write_progression_chain(self.multiworld, spoiler_handle) + pass def get_filler_item_name(self) -> str: From 3b49aae19e0069b6c77248cc52af048f6e2efd40 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Thu, 3 Apr 2025 22:50:06 +0200 Subject: [PATCH 08/12] Fixed generation with different elevator tiers --- test/general/test_reachability.py | 3 +- worlds/satisfactory/CriticalPathCalculator.py | 62 ++++++++++++++++--- worlds/satisfactory/GameLogic.py | 42 ++++++------- worlds/satisfactory/ItemData.py | 2 +- worlds/satisfactory/Items.py | 34 +++++----- worlds/satisfactory/Locations.py | 19 +++--- worlds/satisfactory/Options.py | 4 +- worlds/satisfactory/__init__.py | 20 ++---- 8 files changed, 116 insertions(+), 70 deletions(-) diff --git a/test/general/test_reachability.py b/test/general/test_reachability.py index 0837c8f444..3210daeabe 100644 --- a/test/general/test_reachability.py +++ b/test/general/test_reachability.py @@ -61,7 +61,8 @@ class TestBase(unittest.TestCase): self.assertFalse(region.can_reach(state)) else: with self.subTest("Region should be reached", region=region.name): - self.assertTrue(region.can_reach(state)) + if not region.can_reach(state): + self.assertTrue(region.can_reach(state)) with self.subTest("Completion Condition"): self.assertTrue(multiworld.can_beat_game(state)) diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py index 14b4695a25..ec3b655e7e 100644 --- a/worlds/satisfactory/CriticalPathCalculator.py +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -19,6 +19,10 @@ class CriticalPathCalculator: __potential_required_pipes: bool __potential_required_radioactive: bool + parts_to_exclude: set[str] + recipes_to_exclude: set[str] + buildings_to_exclude: set[str] + def __init__(self, logic: GameLogic, random: Random, options: SatisfactoryOptions): self.logic = logic self.random = random @@ -30,7 +34,6 @@ class CriticalPathCalculator: self.__potential_required_belt_speed = 1 self.__potential_required_pipes = False - self.__potential_required_radioactive = False selected_power_infrastructure: dict[int, Recipe] = {} @@ -57,9 +60,8 @@ class CriticalPathCalculator: self.select_minimal_required_parts_for_building("Power Storage") self.select_minimal_required_parts_for_building("Miner Mk.2") - #equipment - self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) - self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) + if self.logic.recipes["Uranium"][0].minimal_tier <= options.final_elevator_package: + self.select_minimal_required_parts_for(("Hazmat Suit", "Iodine Infused Filter")) for i in range(1, self.__potential_required_belt_speed + 1): self.select_minimal_required_parts_for_building(f"Conveyor Mk.{i}") @@ -68,9 +70,6 @@ class CriticalPathCalculator: self.select_minimal_required_parts_for_building("Pipes Mk.2") self.select_minimal_required_parts_for_building("Pipeline Pump Mk.1") self.select_minimal_required_parts_for_building("Pipeline Pump Mk.2") - if self.__potential_required_radioactive: - self.select_minimal_required_parts_for(self.logic.recipes["Hazmat Suit"][0].inputs) - self.select_minimal_required_parts_for(self.logic.recipes["Iodine Infused Filter"][0].inputs) for i in range(1, self.required_power_level + 1): power_recipe = random.choice(self.logic.requirement_per_powerlevel[i]) selected_power_infrastructure[i] = power_recipe @@ -85,6 +84,55 @@ class CriticalPathCalculator: ) self.required_item_names.update("Building: "+ building for building in self.required_buildings) + self.parts_to_exclude = set() + self.buildings_to_exclude = set() + self.recipes_to_exclude = set( + recipe.name + for part in self.logic.recipes + for recipe in self.logic.recipes[part] + if recipe.minimal_tier > self.options.final_elevator_package + ) + + excluded_count = len(self.recipes_to_exclude) + while True: + for part in self.logic.recipes: + if part in self.parts_to_exclude: + continue + + for recipe in self.logic.recipes[part]: + if recipe.name in self.recipes_to_exclude: + continue + + if recipe.inputs and any(input in self.parts_to_exclude for input in recipe.inputs): + self.recipes_to_exclude.add(recipe.name) + + if all(r.name in self.recipes_to_exclude for r in self.logic.recipes[part]): + self.parts_to_exclude.add(part) + + new_buildings_to_exclude = set( + building_name + for building_name, building in self.logic.buildings.items() + if building_name not in self.buildings_to_exclude + and building.inputs and any(input in self.parts_to_exclude for input in building.inputs) + ) + + self.recipes_to_exclude.update( + recipe_per_part.name + for building_to_exclude in new_buildings_to_exclude + for recipes_per_part in self.logic.recipes.values() + for recipe_per_part in recipes_per_part + if recipe_per_part.building == building_to_exclude + ) + + self.buildings_to_exclude.update(new_buildings_to_exclude) + + new_length = len(self.recipes_to_exclude) + if new_length == excluded_count: + break + excluded_count = new_length + + Debug = True + def select_minimal_required_parts_for_building(self, building: str) -> None: self.select_minimal_required_parts_for(self.logic.buildings[building].inputs) self.required_buildings.add(building) diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index f9cc8b447f..50c0b49a16 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -191,15 +191,15 @@ class GameLogic: "Crude Oil": ( Recipe("Crude Oil", "Oil Extractor", implicitly_unlocked=True), ), "Bauxite": ( - Recipe("Bauxite", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + Recipe("Bauxite", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True, minimal_tier=2), ), "Nitrogen Gas": ( - Recipe("Nitrogen Gas", "Resource Well Pressurizer", implicitly_unlocked=True), ), + Recipe("Nitrogen Gas", "Resource Well Pressurizer", implicitly_unlocked=True, minimal_tier=2), ), "Uranium": ( - Recipe("Uranium", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), + Recipe("Uranium", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True, minimal_tier=2), ), # Special Items "Uranium Waste": ( - Recipe("Uranium Waste", "Nuclear Power Plant", ("Uranium Fuel Rod", "Water"), implicitly_unlocked=True), ), + Recipe("Uranium Waste", "Nuclear Power Plant", ("Uranium Fuel Rod", "Water"), implicitly_unlocked=True, minimal_tier=2), ), #"Plutonium Waste": ( # Recipe("Plutonium Waste", "Nuclear Power Plant", ("Plutonium Fuel Rod", "Water"), implicitly_unlocked=True), ), @@ -280,7 +280,7 @@ class GameLogic: Recipe("Wet Concrete", "Refinery", ("Limestone", "Water"), minimal_belt_speed=2)), "Silica": ( Recipe("Silica", "Constructor", ("Raw Quartz", ), handcraftable=True), - Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Alumina Solution", ), minimal_belt_speed=2), + Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Alumina Solution", ), minimal_belt_speed=2, minimal_tier=2), Recipe("Cheap Silica", "Assembler", ("Raw Quartz", "Limestone")), Recipe("Distilled Silica", "Blender", ("Dissolved Silica", "Limestone", "Water"), additional_outputs=("Water", ), minimal_tier=2)), "Dissolved Silica": ( @@ -356,18 +356,18 @@ class GameLogic: Recipe("Smart Plating", "Assembler", ("Reinforced Iron Plate", "Rotor")), Recipe("Plastic Smart Plating", "Manufacturer", ("Reinforced Iron Plate", "Rotor", "Plastic"))), "Versatile Framework": ( - Recipe("Versatile Framework", "Assembler", ("Modular Frame", "Steel Beam")), - Recipe("Flexible Framework", "Manufacturer", ("Modular Frame", "Steel Beam", "Rubber"))), + Recipe("Versatile Framework", "Assembler", ("Modular Frame", "Steel Beam"), minimal_tier=2), + Recipe("Flexible Framework", "Manufacturer", ("Modular Frame", "Steel Beam", "Rubber"), minimal_tier=2)), "Automated Wiring": ( - Recipe("Automated Wiring", "Assembler", ("Stator", "Cable")), - Recipe("Automated Speed Wiring", "Manufacturer", ("Stator", "Wire", "High-Speed Connector"), minimal_belt_speed=2)), + Recipe("Automated Wiring", "Assembler", ("Stator", "Cable"), minimal_tier=2), + Recipe("Automated Speed Wiring", "Manufacturer", ("Stator", "Wire", "High-Speed Connector"), minimal_belt_speed=2, minimal_tier=2)), "Modular Engine": ( - Recipe("Modular Engine", "Manufacturer", ("Motor", "Rubber", "Smart Plating")), ), + Recipe("Modular Engine", "Manufacturer", ("Motor", "Rubber", "Smart Plating"), minimal_tier=3), ), "Adaptive Control Unit": ( - Recipe("Adaptive Control Unit", "Manufacturer", ("Automated Wiring", "Circuit Board", "Heavy Modular Frame", "Computer")), ), + Recipe("Adaptive Control Unit", "Manufacturer", ("Automated Wiring", "Circuit Board", "Heavy Modular Frame", "Computer"), minimal_tier=3), ), "Portable Miner": ( Recipe("Portable Miner", "Equipment Workshop", ("Iron Rod", "Iron Plate"), handcraftable=True, minimal_belt_speed=0, implicitly_unlocked=True), - Recipe("Automated Miner", "Manufacturer", ("Steel Pipe", "Iron Plate")), ), + Recipe("Automated Miner", "Assembler", ("Steel Pipe", "Iron Plate")), ), "Alumina Solution": ( Recipe("Alumina Solution", "Refinery", ("Bauxite", "Water"), additional_outputs=("Silica", ), minimal_belt_speed=2, minimal_tier=2), Recipe("Sloppy Alumina", "Refinery", ("Bauxite", "Water"), minimal_belt_speed=3, minimal_tier=2)), @@ -433,19 +433,19 @@ class GameLogic: "Gas Filter": ( Recipe("Gas Filter", "Manufacturer", ("Coal", "Rubber", "Fabric"), handcraftable=True), ), "Iodine Infused Filter": ( - Recipe("Iodine Infused Filter", "Manufacturer", ("Gas Filter", "Quickwire", "Aluminum Casing"), handcraftable=True), ), + Recipe("Iodine Infused Filter", "Manufacturer", ("Gas Filter", "Quickwire", "Aluminum Casing"), handcraftable=True, minimal_tier=2), ), "Hazmat Suit": ( - Recipe("Hazmat Suit", "Equipment Workshop", ("Rubber", "Plastic", "Fabric", "Alclad Aluminum Sheet"), handcraftable=True, minimal_belt_speed=0), ), + Recipe("Hazmat Suit", "Equipment Workshop", ("Rubber", "Plastic", "Fabric", "Alclad Aluminum Sheet"), handcraftable=True, minimal_tier=2), ), "Assembly Director System": ( - Recipe("Assembly Director System", "Assembler", ("Adaptive Control Unit", "Supercomputer")), ), + Recipe("Assembly Director System", "Assembler", ("Adaptive Control Unit", "Supercomputer"), minimal_tier=4), ), "Magnetic Field Generator": ( - Recipe("Magnetic Field Generator", "Assembler", ("Versatile Framework", "Electromagnetic Control Rod")), ), + Recipe("Magnetic Field Generator", "Assembler", ("Versatile Framework", "Electromagnetic Control Rod"), minimal_tier=4), ), "Copper Powder": ( Recipe("Copper Powder", "Constructor", ("Copper Ingot", ), handcraftable=True), ), "Nuclear Pasta": ( Recipe("Nuclear Pasta", "Particle Accelerator", ("Copper Powder", "Pressure Conversion Cube"), minimal_tier=2), ), "Thermal Propulsion Rocket": ( - Recipe("Thermal Propulsion Rocket", "Manufacturer", ("Modular Engine", "Turbo Motor", "Cooling System", "Fused Modular Frame")), ), + Recipe("Thermal Propulsion Rocket", "Manufacturer", ("Modular Engine", "Turbo Motor", "Cooling System", "Fused Modular Frame"), minimal_tier=4), ), "Alien Protein": ( Recipe("Hatcher Protein", "Constructor", ("Hatcher Remains", ), handcraftable=True), Recipe("Hog Protein", "Constructor", ("Hog Remains", ), handcraftable=True), @@ -513,7 +513,7 @@ class GameLogic: Recipe("Power Shard (1)", "Constructor", ("Blue Power Slug", ), handcraftable=True), Recipe("Power Shard (2)", "Constructor", ("Yellow Power Slug", ), handcraftable=True), Recipe("Power Shard (5)", "Constructor", ("Purple Power Slug", ), handcraftable=True), - Recipe("Synthetic Power Shard", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Dark Matter Crystal", "Quartz Crystal"), minimal_tier=2)), # 1.0 + Recipe("Synthetic Power Shard", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Dark Matter Crystal", "Quartz Crystal"), minimal_tier=4)), # 1.0 "Object Scanner": ( Recipe("Object Scanner", "Equipment Workshop", ("Reinforced Iron Plate", "Wire", "Screw"), handcraftable=True), ), "Xeno-Zapper": ( @@ -559,9 +559,9 @@ class GameLogic: "Singularity Cell": ( Recipe("Singularity Cell", "Manufacturer", ("Nuclear Pasta", "Dark Matter Crystal", "Iron Plate", "Concrete"), minimal_belt_speed=3), ), "Biochemical Sculptor": ( - Recipe("Biochemical Sculptor", "Blender", ("Assembly Director System", "Ficsite Trigon", "Water")), ), + Recipe("Biochemical Sculptor", "Blender", ("Assembly Director System", "Ficsite Trigon", "Water"), minimal_tier=5), ), "Ballistic Warp Drive": ( - Recipe("Ballistic Warp Drive", "Manufacturer", ("Thermal Propulsion Rocket", "Singularity Cell", "Superposition Oscillator", "Dark Matter Crystal")), ), + Recipe("Ballistic Warp Drive", "Manufacturer", ("Thermal Propulsion Rocket", "Singularity Cell", "Superposition Oscillator", "Dark Matter Crystal"), minimal_tier=5), ), # All Quantum Encoder recipes have `Dark Matter Residue` set as an input, this hack makes the logic make sure you can get rid of it "Dark Matter Residue": ( @@ -574,7 +574,7 @@ class GameLogic: "Neural-Quantum Processor": ( Recipe("Neural-Quantum Processor", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Time Crystal", "Supercomputer", "Ficsite Trigon")), ), "AI Expansion Server": ( - Recipe("AI Expansion Server", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Magnetic Field Generator", "Neural-Quantum Processor", "Superposition Oscillator")), ), + Recipe("AI Expansion Server", "Quantum Encoder", ("Dark Matter Residue", "Excited Photonic Matter", "Magnetic Field Generator", "Neural-Quantum Processor", "Superposition Oscillator"), minimal_tier=5), ), ### #1.0 } diff --git a/worlds/satisfactory/ItemData.py b/worlds/satisfactory/ItemData.py index 7fc3105cf4..a437c3916e 100644 --- a/worlds/satisfactory/ItemData.py +++ b/worlds/satisfactory/ItemData.py @@ -31,7 +31,7 @@ class ItemGroups(IntFlag): Vehicles = 1 << 26 Customizer = 1 << 27 ConveyorMk6 = 1 << 28 - BasicNeeds = 1 << 29 + AlwaysUseful = 1 << 29 class ItemData(NamedTuple): diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index 49f113ff48..15aa483e93 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -1,4 +1,3 @@ -import copy from random import Random from typing import ClassVar, Dict, Set, List, Tuple, Optional from BaseClasses import Item, ItemClassification as C, MultiWorld @@ -194,7 +193,7 @@ class Items: "Small Inflated Pocket Dimension": ItemData(G.Upgrades, 1338188, C.useful, 11), "Inflated Pocket Dimension": ItemData(G.Upgrades, 1338189, C.useful, 5), "Expanded Toolbelt": ItemData(G.Upgrades, 1338190, C.useful, 5), - "Dimensional Depot upload from inventory": ItemData(G.Upgrades | G.BasicNeeds, 1338191, C.useful), + "Dimensional Depot upload from inventory": ItemData(G.Upgrades, 1338191, C.useful), #1338191 - 1338199 Reserved for future equipment/ammo @@ -493,17 +492,17 @@ class Items: "Building: Fuel Generator": ItemData(G.Building, 1338618, C.progression), "Building: Resource Well Pressurizer": ItemData(G.Building, 1338619, C.progression), "Building: Equipment Workshop": ItemData(G.Building, 1338620, C.progression), - "Building: AWESOME Sink": ItemData(G.Building | G.BasicNeeds, 1338621, C.progression), - "Building: AWESOME Shop": ItemData(G.Building | G.BasicNeeds, 1338622, C.progression), + "Building: AWESOME Sink": ItemData(G.Building | G.AlwaysUseful, 1338621, C.progression), + "Building: AWESOME Shop": ItemData(G.Building | G.AlwaysUseful, 1338622, C.progression), "Building: Painted Beams": ItemData(G.Beams, 1338623, C.filler), "Building: Blueprint Designer": ItemData(G.Building, 1338624, C.filler, 0), # unlocked by default "Building: Fluid Buffer": ItemData(G.Building, 1338625, C.filler), "Building: Industrial Fluid Buffer": ItemData(G.Building, 1338626, C.filler), "Building: Jump Pad": ItemData(G.Building, 1338627, C.filler), "Building: Ladder": ItemData(G.Building, 1338628, C.filler), - "Building: MAM": ItemData(G.Building | G.BasicNeeds, 1338629, C.progression), + "Building: MAM": ItemData(G.Building | G.AlwaysUseful, 1338629, C.progression), "Building: Personal Storage Box": ItemData(G.Building, 1338630, C.filler), - "Building: Power Storage": ItemData(G.Building | G.BasicNeeds, 1338631, C.progression), + "Building: Power Storage": ItemData(G.Building | G.AlwaysUseful, 1338631, C.progression), "Building: U-Jelly Landing Pad": ItemData(G.Building, 1338632, C.useful), "Building: Power Switch": ItemData(G.Building, 1338633, C.useful), "Building: Priority Power Switch": ItemData(G.Building, 1338634, C.useful), @@ -513,8 +512,8 @@ class Items: "Building: Power Pole Mk.2": ItemData(G.Building, 1338638, C.useful), "Building: Power Pole Mk.3": ItemData(G.Building, 1338639, C.useful), "Building: Industrial Storage Container": ItemData(G.Building, 1338640, C.filler), - "Building: Conveyor Merger": ItemData(G.Building | G.BasicNeeds, 1338641, C.progression), - "Building: Conveyor Splitter": ItemData(G.Building | G.BasicNeeds, 1338642, C.progression), + "Building: Conveyor Merger": ItemData(G.Building | G.AlwaysUseful, 1338641, C.progression), + "Building: Conveyor Splitter": ItemData(G.Building | G.AlwaysUseful, 1338642, C.progression), "Building: Conveyor Mk.1": ItemData(G.Building | G.ConveyorMk1, 1338643, C.progression), # unlocked by default "Building: Conveyor Mk.2": ItemData(G.Building | G.ConveyorMk2, 1338644, C.progression), "Building: Conveyor Mk.3": ItemData(G.Building | G.ConveyorMk3, 1338645, C.progression), @@ -564,7 +563,7 @@ class Items: #"Building: Beam Support": ItemData(G.Beams, 1338689, C.filler, 0), #"Building: Beam Connector": ItemData(G.Beams, 1338690, C.filler, 0), #"Building: Beam Connector Double": ItemData(G.Beams, 1338691, C.filler, 0), - "Building: Foundation": ItemData(G.Building | G.Foundations | G.BasicNeeds, 1338692, C.progression), + "Building: Foundation": ItemData(G.Building | G.Foundations | G.AlwaysUseful, 1338692, C.progression), "Building: Half Foundation": ItemData(G.Foundations, 1338693, C.filler, 0), "Building: Corner Ramp Pack": ItemData(G.Foundations, 1338694, C.filler, 0), "Building: Inverted Ramp Pack": ItemData(G.Foundations, 1338695, C.filler, 0), @@ -725,15 +724,14 @@ class Items: if type == C.progression \ and instance and instance.critical_path.required_item_names \ - and (data.category & (G.Recipe | G.Building)) and not (data.category & G.BasicNeeds) \ + and (data.category & (G.Recipe | G.Building)) and not (data.category & G.AlwaysUseful) \ and name not in instance.critical_path.required_item_names: - type = C.filler - logging.info(f"Dropping... {name}") + type = C.useful return Item(name, type, data.code, player) - def get_filler_item_name(self, random: Random, options: SatisfactoryOptions) -> str: + def get_filler_item_name(self, filler_items: Tuple[str, ...], random: Random, options: SatisfactoryOptions) -> str: trap_chance: int = options.trap_chance.value enabled_traps: List[str] = options.trap_selection_override.value @@ -745,6 +743,9 @@ class Items: def get_excluded_items(self, multiworld: MultiWorld, options: SatisfactoryOptions) -> Set[str]: excluded_items: Set[str] = set() + excluded_items.update("Bundle: "+ part for part in self.critical_path.parts_to_exclude) + excluded_items.update(recipe for recipe in self.critical_path.recipes_to_exclude) + excluded_items.update("Building: "+ building for building in self.critical_path.buildings_to_exclude) for item in multiworld.precollected_items[self.player]: if item.name in self.item_data \ @@ -775,9 +776,12 @@ class Items: filler_pool_size: int = number_of_locations - len(pool) if (filler_pool_size < 0): raise Exception(f"Location pool starved, trying to add {len(pool)} items to {number_of_locations} locations") - logging.warning(f"Itempool size: {len(pool)}, number of locations: {number_of_locations}, spare: {filler_pool_size}") + + filtered_filler_items = tuple(item for item in self.filler_items if item not in excluded_from_pool) + for _ in range(filler_pool_size): - item = self.create_item(self, self.get_filler_item_name(random, options), self.player) + filler_item_name = self.get_filler_item_name(filtered_filler_items, random, options) + item = self.create_item(self, filler_item_name, self.player) pool.append(item) return pool diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index a239b6bd7c..0089612c18 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -9,6 +9,7 @@ from math import ceil, floor class LocationData(): + __slots__ = ("region", "name", "event_name", "code", "non_progression", "rule") region: str name: str event_name: str @@ -28,7 +29,7 @@ class LocationData(): class Part(LocationData): @staticmethod - def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, items: Items, + def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, final_elevator_tier: int) -> List[LocationData]: recipes_per_region: Dict[str, List[Recipe]] = {} @@ -38,15 +39,15 @@ class Part(LocationData): recipes_per_region.setdefault(recipe.building or "Overworld", []).append(recipe) - return [Part(state_logic, region, recipes_for_region, name, items) + return [Part(state_logic, region, recipes_for_region, name) for region, recipes_for_region in recipes_per_region.items()] - def __init__(self, state_logic: StateLogic, region: str, recipes: Iterable[Recipe], name: str, items: Items): - super().__init__(region, part_event_prefix + name + region, EventId, part_event_prefix + name, - rule = self.can_produce_any_recipe_for_part(state_logic, recipes, name, items)) + def __init__(self, state_logic: StateLogic, region: str, recipes: Iterable[Recipe], name: str): + super().__init__(region, part_event_prefix + name + " in " + region, EventId, part_event_prefix + name, + rule = self.can_produce_any_recipe_for_part(state_logic, recipes)) - def can_produce_any_recipe_for_part(self, state_logic: StateLogic, recipes: Iterable[Recipe], - name: str, items: Items) -> Callable[[CollectionState], bool]: + def can_produce_any_recipe_for_part(self, state_logic: StateLogic, recipes: Iterable[Recipe]) \ + -> Callable[[CollectionState], bool]: def can_build_by_any_recipe(state: CollectionState) -> bool: return any(state_logic.can_produce_specific_recipe_for_part(state, recipe) for recipe in recipes) @@ -374,10 +375,10 @@ class Locations(): for index, parts in enumerate(self.game_logic.space_elevator_tiers) if index < self.options.final_elevator_package) location_table.extend( - part + part for part_name, recipes in self.game_logic.recipes.items() if part_name in self.critical_path.required_parts - for part in Part.get_parts(self.state_logic, recipes, part_name, self.items, final_elevator_tier)) + for part in Part.get_parts(self.state_logic, recipes, part_name, final_elevator_tier)) location_table.extend( EventBuilding(self.game_logic, self.state_logic, name, building) for name, building in self.game_logic.buildings.items() diff --git a/worlds/satisfactory/Options.py b/worlds/satisfactory/Options.py index c11db3e52e..4c417b5dce 100644 --- a/worlds/satisfactory/Options.py +++ b/worlds/satisfactory/Options.py @@ -3,6 +3,7 @@ from typing import Dict, List, Any, Tuple, ClassVar, cast from enum import IntEnum from Options import PerGameCommonOptions, DeathLink, AssembleOptions, Visibility from Options import Range, Toggle, OptionSet, StartInventoryPool, NamedRange, Choice +from schema import Schema, And, Use class Placement(IntEnum): starting_inventory = 0 @@ -41,7 +42,6 @@ class ChoiceMap(Choice, metaclass=ChoiceMapMeta): if index == self.value: return self.choices[choice] - class ElevatorTier(NamedRange): """ Put these Shipments to Space Elevator packages in logic. @@ -347,6 +347,8 @@ class GoalSelection(OptionSet): # "FICSMAS Tree", } default = {"Space Elevator Tier"} + schema = Schema(And(set, len), + error = "yaml does not specify a goal, the Satisfactory option `goal_selection` is empty") class GoalRequirement(Choice): """ diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index 9cf049ae08..d14e725b9d 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -1,5 +1,5 @@ -from typing import Dict, List, Set, TextIO, ClassVar, Tuple -from BaseClasses import Item, MultiWorld, ItemClassification, CollectionState +from typing import Dict, List, Set, TextIO, ClassVar +from BaseClasses import Item, ItemClassification, CollectionState from .GameLogic import GameLogic from .Items import Items from .Locations import Locations, LocationData @@ -21,7 +21,6 @@ class SatisfactoryWorld(World): options_dataclass = SatisfactoryOptions options: SatisfactoryOptions topology_present = False - data_version = 0 web = SatisfactoryWebWorld() origin_region_name = "Overworld" @@ -34,20 +33,11 @@ class SatisfactoryWorld(World): items: Items critical_path: CriticalPathCalculator - def __init__(self, multiworld: "MultiWorld", player: int): - super().__init__(multiworld, player) - self.items = None - - def generate_early(self) -> None: self.state_logic = StateLogic(self.player, self.options) self.critical_path = CriticalPathCalculator(self.game_logic, self.random, self.options) self.items = Items(self.player, self.game_logic, self.random, self.options, self.critical_path) - if not self.options.goal_selection.value: - raise Exception("""Satisfactory: player {} needs to choose a goal, the option goal_selection is empty""" - .format(self.multiworld.player_name[self.player])) - if self.options.mam_logic_placement.value == Placement.starting_inventory: self.push_precollected("Building: MAM") if self.options.awesome_logic_placement.value == Placement.starting_inventory: @@ -144,15 +134,15 @@ class SatisfactoryWorld(World): } - def write_spoiler(self, spoiler_handle: TextIO): + def write_spoiler(self, spoiler_handle: TextIO) -> None: pass def get_filler_item_name(self) -> str: - return self.items.get_filler_item_name(self.random, self.options) + return self.items.get_filler_item_name(self.items.filler_items, self.random, self.options) - def setup_events(self): + def setup_events(self) -> None: location: SatisfactoryLocation for location in self.multiworld.get_locations(self.player): if location.address == EventId: From 132a3957ed23a07b482542128550b60f0af91759 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Thu, 3 Apr 2025 22:51:08 +0200 Subject: [PATCH 09/12] Remove debug statement --- worlds/satisfactory/CriticalPathCalculator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py index ec3b655e7e..8cc40bbc58 100644 --- a/worlds/satisfactory/CriticalPathCalculator.py +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -131,8 +131,6 @@ class CriticalPathCalculator: break excluded_count = new_length - Debug = True - def select_minimal_required_parts_for_building(self, building: str) -> None: self.select_minimal_required_parts_for(self.logic.buildings[building].inputs) self.required_buildings.add(building) From 5cf65edc11d3df46629a682799e8bfd987a88b9a Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sat, 5 Apr 2025 19:13:52 +0200 Subject: [PATCH 10/12] Implemented abstract base classes + some fixes --- worlds/satisfactory/GameLogic.py | 50 ++++++++++---------- worlds/satisfactory/ItemData.py | 2 +- worlds/satisfactory/Items.py | 77 +++++++++++++++---------------- worlds/satisfactory/Locations.py | 34 +++++++------- worlds/satisfactory/Options.py | 16 +++---- worlds/satisfactory/Regions.py | 33 ++++++------- worlds/satisfactory/StateLogic.py | 9 ++-- worlds/satisfactory/Web.py | 1 - worlds/satisfactory/__init__.py | 13 +++--- 9 files changed, 117 insertions(+), 118 deletions(-) diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index 50c0b49a16..4e0537e527 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -1,4 +1,4 @@ -from typing import Tuple, Optional, Dict, Set, List +from typing import Optional from dataclasses import dataclass from enum import IntEnum @@ -11,7 +11,7 @@ class PowerInfrastructureLevel(IntEnum): def to_name(self): return "Power level: " + self.name -liquids: Set[str] = { +liquids: set[str] = { "Water", "Liquid Biofuel", "Crude Oil", @@ -29,7 +29,7 @@ liquids: Set[str] = { "Dark Matter Residue" } -radio_actives: Set[str] = { +radio_actives: set[str] = { "Uranium", "Encased Uranium Cell", "Uranium Fuel Rod" @@ -50,20 +50,20 @@ class Recipe(): """ name: str building: str - inputs: Tuple[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, ...] + additional_outputs: tuple[str, ...] minimal_tier: int needs_pipes: bool is_radio_active: bool - def __init__(self, name: str, building: Optional[str] = None, inputs: Optional[Tuple[str, ...]] = None, + 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, minimal_tier: Optional[int] = 1): + additional_outputs: Optional[tuple[str, ...]] = None, minimal_tier: Optional[int] = 1): self.name = "Recipe: " + name self.building = building self.inputs = inputs @@ -73,7 +73,7 @@ class Recipe(): self.additional_outputs = additional_outputs self.minimal_tier = minimal_tier - all_parts: List[str] = [name] + all_parts: list[str] = [name] if inputs: all_parts += inputs if additional_outputs: @@ -86,7 +86,7 @@ class Building(Recipe): power_requirement: Optional[PowerInfrastructureLevel] can_produce: bool - def __init__(self, name: str, inputs: Optional[Tuple[str, ...]] = None, + 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) @@ -98,13 +98,13 @@ class Building(Recipe): class MamNode(): name: str - unlock_cost: Dict[str, int] + unlock_cost: dict[str, int] """All game items must be submitted to purchase this MamNode""" - depends_on: Tuple[str, ...] + depends_on: tuple[str, ...] """At least one of these prerequisite MamNodes must be unlocked to purchase this MamNode""" minimal_tier: Optional[int] - def __init__(self, name: str, unlock_cost: Dict[str, int], depends_on: Tuple[str, ...], + def __init__(self, name: str, unlock_cost: dict[str, int], depends_on: tuple[str, ...], minimal_tier: Optional[int] = 1): self.name = name self.unlock_cost = unlock_cost @@ -113,11 +113,11 @@ class MamNode(): class MamTree(): - access_items: Tuple[str, ...] + 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, ...] + nodes: tuple[MamNode, ...] - def __init__(self, access_items: Tuple[str, ...], nodes: Tuple[MamNode, ...]): + def __init__(self, access_items: tuple[str, ...], nodes: tuple[MamNode, ...]): self.access_items = access_items self.nodes = nodes @@ -134,7 +134,7 @@ class DropPodData: class GameLogic: - recipes: Dict[str, Tuple[Recipe, ...]] = { + recipes: dict[str, tuple[Recipe, ...]] = { # This Dict should only contain items that are used somewhere in a logic chain # Exploration Items @@ -499,7 +499,7 @@ class GameLogic: "Alien DNA Capsule": ( Recipe("Alien DNA Capsule", "Constructor", ("Alien Protein", ), handcraftable=True), ), "Black Powder": ( - Recipe("Black Powder", "Assembler", ("Coal", "Sulfur"), handcraftable=True), + Recipe("Black Powder", "Equipment Workshop", ("Coal", "Sulfur"), handcraftable=True), Recipe("Fine Black Powder", "Assembler", ("Sulfur", "Compacted Coal"))), "Smokeless Powder": ( Recipe("Smokeless Powder", "Refinery", ("Black Powder", "Heavy Oil Residue")), ), @@ -579,7 +579,7 @@ class GameLogic: #1.0 } - buildings: Dict[str, Building] = { + 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), @@ -632,13 +632,13 @@ class GameLogic: #1.0 } - handcraftable_recipes: Dict[str, List[Recipe]] = {} + 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] = { + 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 @@ -648,7 +648,7 @@ class GameLogic: for building in buildings.values() if building.implicitly_unlocked }) - requirement_per_powerlevel: Dict[PowerInfrastructureLevel, Tuple[Recipe, ...]] = { + 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), @@ -678,7 +678,7 @@ class GameLogic: slots_per_milestone: int = 8 - hub_layout: Tuple[Tuple[Dict[str, int], ...], ...] = ( + 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) @@ -744,7 +744,7 @@ class GameLogic: ) # Values from /Game/FactoryGame/Schematics/Progression/BP_GamePhaseManager.BP_GamePhaseManager - space_elevator_tiers: Tuple[Dict[str, int], ...] = ( + 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 }, @@ -754,7 +754,7 @@ class GameLogic: # Do not regenerate as format got changed # Regenerate via /Script/Engine.Blueprint'/Archipelago/Debug/CC_BuildMamData.CC_BuildMamData' - man_trees: Dict[str, MamTree] = { + 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) @@ -869,7 +869,7 @@ class GameLogic: )) } - drop_pods: List[DropPodData] = [ + 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 diff --git a/worlds/satisfactory/ItemData.py b/worlds/satisfactory/ItemData.py index a437c3916e..b62dd1acd4 100644 --- a/worlds/satisfactory/ItemData.py +++ b/worlds/satisfactory/ItemData.py @@ -1,5 +1,5 @@ from enum import IntFlag -from typing import NamedTuple, Set +from typing import NamedTuple from BaseClasses import ItemClassification class ItemGroups(IntFlag): diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index 15aa483e93..cdf009843c 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -1,16 +1,14 @@ from random import Random -from typing import ClassVar, Dict, Set, List, Tuple, Optional +from typing import ClassVar, Optional from BaseClasses import Item, ItemClassification as C, MultiWorld from .GameLogic import GameLogic from .Options import SatisfactoryOptions from .ItemData import ItemData, ItemGroups as G from .Options import SatisfactoryOptions from .CriticalPathCalculator import CriticalPathCalculator -import logging - class Items: - item_data: ClassVar[Dict[str, ItemData]] = { + item_data: ClassVar[dict[str, ItemData]] = { # Resource Bundles "Bundle: Adaptive Control Unit": ItemData(G.Parts, 1338000), "Bundle: AI Limiter": ItemData(G.Parts, 1338001), @@ -167,7 +165,7 @@ class Items: "Bundle: Gas Nobelisk": ItemData(G.Ammo, 1338163), "Bundle: Hazmat Suit": ItemData(G.Equipment, 1338164), "Bundle: Homing Rifle Ammo": ItemData(G.Ammo, 1338165), - "Bundle: Hover Pack": ItemData(G.Equipment, 1338166), + "Bundle: Hoverpack": ItemData(G.Equipment, 1338166), "Bundle: Iron Rebar": ItemData(G.Ammo, 1338167), "Bundle: Jetpack": ItemData(G.Equipment, 1338168), "Bundle: Medicinal Inhaler": ItemData(G.Ammo, 1338169), @@ -195,7 +193,14 @@ class Items: "Expanded Toolbelt": ItemData(G.Upgrades, 1338190, C.useful, 5), "Dimensional Depot upload from inventory": ItemData(G.Upgrades, 1338191, C.useful), - #1338191 - 1338199 Reserved for future equipment/ammo + # added in 1.1 + "Recipe: Hoverpack": ItemData(G.Recipe, 1338192, C.useful), + "Bundle: Iodine-Infused Filter": ItemData(G.Ammo, 1338193), + "Recipe: Jetpack": ItemData(G.Recipe, 1338194, C.useful), + "Recipe: Nobelisk Detonator": ItemData(G.Recipe, 1338195, C.progression), + "Recipe: Portable Miner": ItemData(G.Equipment, 1338196, C.progression), + + #1338197 - 1338199 Reserved for future equipment/ammo #1338200+ Recipes / buildings / schematics "Recipe: Reinforced Iron Plate": ItemData(G.Recipe, 1338200, C.progression), @@ -354,7 +359,7 @@ class Items: "Recipe: Plutonium Fuel Rod": ItemData(G.Recipe, 1338353), "Recipe: Plutonium Fuel Unit": ItemData(G.Recipe, 1338354), "Recipe: Gas Filter": ItemData(G.Recipe, 1338355, C.progression), - "Recipe: Iodine Infused Filter": ItemData(G.Recipe, 1338356, C.progression), + "Recipe: Iodine-Infused Filter": ItemData(G.Recipe, 1338356, C.progression), "Recipe: Assembly Director System": ItemData(G.Recipe, 1338357, C.progression), "Recipe: Magnetic Field Generator": ItemData(G.Recipe, 1338358, C.progression), "Recipe: Copper Powder": ItemData(G.Recipe, 1338359, C.progression), @@ -374,7 +379,7 @@ class Items: "Recipe: Biomass (Wood)": ItemData(G.Recipe, 1338373, C.progression), "Recipe: Biomass (Mycelia)": ItemData(G.Recipe, 1338374, C.progression), "Recipe: Biomass (Alien Protein)": ItemData(G.Recipe, 1338375, C.progression), - "Recipe: Turbo Rifle Ammo (Packaged)": ItemData(G.Recipe, 1338376), + "Recipe: Turbo Rifle Ammo (Packaged)": ItemData(G.Recipe, 1338376, C.useful), "Recipe: Fabric": ItemData(G.Recipe, 1338377, C.progression), "Recipe: Polyester Fabric": ItemData(G.Recipe, 1338378, C.progression), "Recipe: Solid Biofuel": ItemData(G.Recipe, 1338379, C.progression), @@ -402,15 +407,15 @@ class Items: "Recipe: Black Powder": ItemData(G.Recipe, 1338401, C.progression), "Recipe: Blade Runners": ItemData(G.Recipe, 1338402, C.useful), "Recipe: Chainsaw": ItemData(G.Recipe, 1338403, C.useful), - "Recipe: Cluster Nobelisk": ItemData(G.Recipe, 1338404), - "Recipe: Explosive Rebar": ItemData(G.Recipe, 1338405), + "Recipe: Cluster Nobelisk": ItemData(G.Recipe, 1338404, C.useful), + "Recipe: Explosive Rebar": ItemData(G.Recipe, 1338405, C.useful), "Recipe: Factory Cart": ItemData(G.Recipe, 1338406, C.useful), - "Recipe: Gas Nobelisk": ItemData(G.Recipe, 1338407), + "Recipe: Gas Nobelisk": ItemData(G.Recipe, 1338407, C.useful), "Recipe: Golden Factory Cart": ItemData(G.Recipe, 1338408), - "Recipe: Homing Rifle Ammo": ItemData(G.Recipe, 1338409), + "Recipe: Homing Rifle Ammo": ItemData(G.Recipe, 1338409, C.useful), "Recipe: Iron Rebar": ItemData(G.Recipe, 1338410, C.progression), "Recipe: Nobelisk": ItemData(G.Recipe, 1338411, C.progression), - "Recipe: Nuke Nobelisk": ItemData(G.Recipe, 1338412), + "Recipe: Nuke Nobelisk": ItemData(G.Recipe, 1338412, C.useful), "Recipe: Nutritional Inhaler": ItemData(G.Recipe, 1338413, C.useful), "Recipe: Object Scanner": ItemData(G.Recipe, 1338414, C.progression), "Recipe: Parachute": ItemData(G.Recipe, 1338415, C.useful), @@ -418,10 +423,10 @@ class Items: "Recipe: Rebar Gun": ItemData(G.Recipe, 1338417, C.useful), "Recipe: Rifle": ItemData(G.Recipe, 1338418, C.useful), "Recipe: Rifle Ammo": ItemData(G.Recipe, 1338419, C.progression), - "Recipe: Shatter Rebar": ItemData(G.Recipe, 1338420), - "Recipe: Stun Rebar": ItemData(G.Recipe, 1338421), + "Recipe: Shatter Rebar": ItemData(G.Recipe, 1338420, C.useful), + "Recipe: Stun Rebar": ItemData(G.Recipe, 1338421, C.useful), "Recipe: Therapeutic Inhaler": ItemData(G.Recipe, 1338422, C.useful), - "Recipe: Turbo Rifle Ammo": ItemData(G.Recipe, 1338423), + "Recipe: Turbo Rifle Ammo": ItemData(G.Recipe, 1338423, C.useful), "Recipe: Vitamin Inhaler": ItemData(G.Recipe, 1338424, C.useful), "Recipe: Xeno-Basher": ItemData(G.Recipe, 1338425, C.useful), "Recipe: Xeno-Zapper": ItemData(G.Recipe, 1338426, C.useful), @@ -442,7 +447,7 @@ class Items: "Recipe: Turbo Diamonds": ItemData(G.Recipe, 1338439, C.progression), "Recipe: Time Crystal": ItemData(G.Recipe, 1338440, C.progression), "Recipe: Superposition Oscillator": ItemData(G.Recipe, 1338441, C.progression), - #"Recipe: Excited Photonic Matter": ItemData(G.Recipe, 1338442, C.progression), should probably be unlocked with converter + #"Recipe: Excited Photonic Matter": ItemData(G.Recipe, 1338442, C.progression), unlocked with converter "Recipe: Rocket Fuel": ItemData(G.Recipe, 1338443, C.progression), "Recipe: Nitro Rocket Fuel": ItemData(G.Recipe, 1338444, C.progression), "Recipe: Ionized Fuel": ItemData(G.Recipe, 1338445, C.useful), @@ -552,17 +557,11 @@ class Items: "Building: Label Sign Bundle": ItemData(G.Building | G.Signs, 1338678, C.filler, 0), "Building: Display Sign Bundle": ItemData(G.Building | G.Signs, 1338679, C.filler, 0), "Building: Billboard Set": ItemData(G.Building | G.Signs, 1338680, C.filler, 0), - "Building: Walls Metal": ItemData(G.Building | G.Walls, 1338681, C.filler, 0), + #1338681 Moved to cosmetics "Building: Metal Pillar": ItemData(G.Pilars, 1338682, C.filler, 0), "Building: Concrete Pillar": ItemData(G.Pilars, 1338683, C.filler, 0), "Building: Frame Pillar": ItemData(G.Pilars, 1338684, C.filler, 0), - "Building: Walls Concrete": ItemData(G.Building | G.Walls, 1338685, C.filler, 0), - #"Building: Big Metal Pillar": ItemData(G.Pilars, 1338686, C.filler, 0), - #"Building: Big Concrete Pillar": ItemData(G.Pilars, 1338687, C.filler, 0), - #"Building: Big Frame Pillar": ItemData(G.Pilars, 1338688, C.filler, 0), - #"Building: Beam Support": ItemData(G.Beams, 1338689, C.filler, 0), - #"Building: Beam Connector": ItemData(G.Beams, 1338690, C.filler, 0), - #"Building: Beam Connector Double": ItemData(G.Beams, 1338691, C.filler, 0), + #1338685 - 1338691 Moved to cosmetics "Building: Foundation": ItemData(G.Building | G.Foundations | G.AlwaysUseful, 1338692, C.progression), "Building: Half Foundation": ItemData(G.Foundations, 1338693, C.filler, 0), "Building: Corner Ramp Pack": ItemData(G.Foundations, 1338694, C.filler, 0), @@ -570,7 +569,7 @@ class Items: "Building: Inverted Corner Ramp Pack": ItemData(G.Foundations, 1338696, C.filler, 0), "Building: Quarter Pipes Pack": ItemData(G.Foundations, 1338697, C.filler, 0), "Building: Quarter Pipe Extensions Pack": ItemData(G.Foundations, 1338698, C.filler, 0), - "Building: Frame foundation": ItemData(G.Foundations, 1338699, C.filler, 0), + "Building: Frame Foundation": ItemData(G.Foundations, 1338699, C.filler, 0), "Building: Wall Outlet Mk.1": ItemData(G.Building, 1338700, C.useful), "Building: Wall Outlet Mk.2": ItemData(G.Building, 1338701, C.useful), "Building: Wall Outlet Mk.3": ItemData(G.Building, 1338702, C.useful), @@ -634,7 +633,7 @@ class Items: "Customizer: Caterium Paint Finish": ItemData(G.Customizer, 1338773, C.filler, 0), # 1.0 - #1338773 - 1338799 Reserved for buildings + #1338774 - 1338799 Reserved for buildings # Transports 1338800 - 1338898 # Drones (including Drone) @@ -689,14 +688,14 @@ class Items: non_unique_item_categories: ClassVar[G] = G.Parts | G.Equipment | G.Ammo | G.Trap | G.Upgrades pool_item_categories: ClassVar[G] = 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() + 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 details.category & (G.Parts | G.Ammo)) @classmethod - def get_item_names_per_category(cls) -> Dict[str, Set[str]]: - categories: Dict[str, Set[str]] = {} + 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: @@ -731,18 +730,18 @@ class Items: return Item(name, type, data.code, player) - def get_filler_item_name(self, filler_items: Tuple[str, ...], random: Random, options: SatisfactoryOptions) -> str: + def get_filler_item_name(self, filler_items: tuple[str, ...], random: Random, options: SatisfactoryOptions) -> str: trap_chance: int = options.trap_chance.value - enabled_traps: List[str] = options.trap_selection_override.value + 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) + return random.choice(filler_items) - def get_excluded_items(self, multiworld: MultiWorld, options: SatisfactoryOptions) -> Set[str]: - excluded_items: Set[str] = set() + def get_excluded_items(self, multiworld: MultiWorld, options: SatisfactoryOptions) -> set[str]: + excluded_items: set[str] = set() excluded_items.update("Bundle: "+ part for part in self.critical_path.parts_to_exclude) excluded_items.update(recipe for recipe in self.critical_path.recipes_to_exclude) excluded_items.update("Building: "+ building for building in self.critical_path.buildings_to_exclude) @@ -758,10 +757,10 @@ class Items: def build_item_pool(self, random: Random, multiworld: MultiWorld, - options: SatisfactoryOptions, number_of_locations: int) -> List[Item]: - excluded_from_pool: Set[str] = self.get_excluded_items(multiworld, options) \ + 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] = [] + pool: list[Item] = [] for name, data in self.item_data.items(): if data.count > 0 \ diff --git a/worlds/satisfactory/Locations.py b/worlds/satisfactory/Locations.py index 0089612c18..68543b054f 100644 --- a/worlds/satisfactory/Locations.py +++ b/worlds/satisfactory/Locations.py @@ -1,12 +1,12 @@ -from typing import List, Optional, Callable, Tuple, Dict, Iterable, ClassVar +from typing import ClassVar, Optional +from collections.abc import Iterable, Callable +from math import ceil, floor 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 .CriticalPathCalculator import CriticalPathCalculator -from math import ceil, floor - class LocationData(): __slots__ = ("region", "name", "event_name", "code", "non_progression", "rule") @@ -29,9 +29,9 @@ class LocationData(): class Part(LocationData): @staticmethod - def get_parts(state_logic: StateLogic, recipes: Tuple[Recipe, ...], name: str, - final_elevator_tier: int) -> List[LocationData]: - recipes_per_region: Dict[str, List[Recipe]] = {} + def get_parts(state_logic: StateLogic, recipes: tuple[Recipe, ...], name: str, + final_elevator_tier: int) -> list[LocationData]: + recipes_per_region: dict[str, list[Recipe]] = {} for recipe in recipes: if recipe.minimal_tier > final_elevator_tier: @@ -174,7 +174,7 @@ class Locations(): self.critical_path = critical_path - def get_base_location_table(self, max_tier: int) -> List[LocationData]: + def get_base_location_table(self, max_tier: int) -> list[LocationData]: all_locations = [ MamSlot("Alien Organisms", "Inflated Pocket Dimension", 1338500), MamSlot("Alien Organisms", "Hostile Organism Detection", 1338501), @@ -304,7 +304,7 @@ class Locations(): return all_locations - def get_locations_for_data_package(self) -> Dict[str, int]: + def get_locations_for_data_package(self) -> dict[str, int]: "Must include all possible location names and their id's" # 1338000 - 1338499 - Milestones @@ -320,7 +320,7 @@ class Locations(): return {location.name: location.code for location in location_table} - def get_locations(self) -> List[LocationData]: + 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: @@ -335,8 +335,8 @@ class Locations(): return location_table - def get_hub_locations(self, for_data_package: bool, max_tier: int) -> List[LocationData]: - location_table: List[LocationData] = [] + def get_hub_locations(self, for_data_package: bool, max_tier: int) -> list[LocationData]: + location_table: list[LocationData] = [] number_of_slots_per_milestone_for_game: int if (for_data_package): @@ -364,8 +364,8 @@ class Locations(): return location_table - def get_logical_event_locations(self, final_elevator_tier: int) -> List[LocationData]: - location_table: List[LocationData] = [] + def get_logical_event_locations(self, final_elevator_tier: int) -> 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) @@ -391,17 +391,17 @@ class Locations(): return location_table def get_hard_drive_locations(self, for_data_package: bool, max_tier: int, available_parts: set[str]) \ - -> List[LocationData]: - hard_drive_locations: List[HardDrive] = [] + -> list[LocationData]: + hard_drive_locations: list[HardDrive] = [] bucket_size: int - drop_pod_data: List[DropPodData] + drop_pod_data: list[DropPodData] if for_data_package: bucket_size = 0 drop_pod_data = [] else: bucket_size = floor((self.drop_pod_location_id_end - self.drop_pod_location_id_start) / max_tier) - drop_pod_data = self.game_logic.drop_pods + drop_pod_data: list[DropPodData] = 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)) diff --git a/worlds/satisfactory/Options.py b/worlds/satisfactory/Options.py index 4c417b5dce..5b5b2a98e9 100644 --- a/worlds/satisfactory/Options.py +++ b/worlds/satisfactory/Options.py @@ -1,9 +1,9 @@ from dataclasses import dataclass -from typing import Dict, List, Any, Tuple, ClassVar, cast +from typing import ClassVar, Any, cast from enum import IntEnum from Options import PerGameCommonOptions, DeathLink, AssembleOptions, Visibility from Options import Range, Toggle, OptionSet, StartInventoryPool, NamedRange, Choice -from schema import Schema, And, Use +from schema import Schema, And class Placement(IntEnum): starting_inventory = 0 @@ -11,7 +11,7 @@ class Placement(IntEnum): somewhere = 2 class PlacementLogicMeta(AssembleOptions): - def __new__(mcs, name: str, bases: Tuple[type], attrs: Dict[Any, Any]) -> "PlacementLogicMeta": + 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"]) @@ -24,7 +24,7 @@ class PlacementLogic(Choice, metaclass=PlacementLogicMeta): option_somewhere = Placement.somewhere class ChoiceMapMeta(AssembleOptions): - def __new__(mcs, name: str, bases: Tuple[type], attrs: Dict[Any, Any]) -> "ChoiceMapMeta": + 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(' ', '_') @@ -35,9 +35,9 @@ class ChoiceMapMeta(AssembleOptions): 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]]] + choices: ClassVar[dict[str, list[str]]] - def get_selected_list(self) -> 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] @@ -55,8 +55,8 @@ class ElevatorTier(NamedRange): "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, + "four packages (tiers 1-8)": 4, + "five packages (tiers 1-9)": 5, } class ResourceSinkPoints(NamedRange): diff --git a/worlds/satisfactory/Regions.py b/worlds/satisfactory/Regions.py index 852ad11eb5..9909942013 100644 --- a/worlds/satisfactory/Regions.py +++ b/worlds/satisfactory/Regions.py @@ -1,4 +1,5 @@ -from typing import List, Set, Dict, Tuple, Optional, Callable +from typing import Optional +from collections.abc import Callable from BaseClasses import MultiWorld, Region, Location, Item, CollectionState from .Locations import LocationData from .GameLogic import GameLogic, PowerInfrastructureLevel @@ -32,9 +33,9 @@ class SatisfactoryLocation(Location): def create_regions_and_return_locations(world: MultiWorld, options: SatisfactoryOptions, player: int, game_logic: GameLogic, state_logic: StateLogic, critical_path: CriticalPathCalculator, - locations: List[LocationData]): + locations: list[LocationData]): - region_names: List[str] = [ + region_names: list[str] = [ "Overworld", "Mam", "AWESOME Shop" @@ -60,20 +61,20 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory if node.minimal_tier <= options.final_elevator_package: 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) + 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] = [ + super_early_game_buildings: list[str] = [ "Foundation", "Walls Orange" ] - early_game_buildings: List[str] = [ + early_game_buildings: list[str] = [ PowerInfrastructureLevel.Automated.to_name() ] @@ -112,7 +113,7 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory 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 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) @@ -148,7 +149,7 @@ def create_regions_and_return_locations(world: MultiWorld, options: Satisfactory lambda state, parts=node.unlock_cost.keys(): state_logic.can_produce_all(state, parts)) -def throwIfAnyLocationIsNotAssignedToARegion(regions: Dict[str, Region], regionNames: Set[str]): +def throwIfAnyLocationIsNotAssignedToARegion(regions: dict[str, Region], regionNames: set[str]): existingRegions = set() for region in regions.keys(): @@ -159,7 +160,7 @@ def throwIfAnyLocationIsNotAssignedToARegion(regions: Dict[str, Region], regionN def create_region(world: MultiWorld, player: int, - locations_per_region: Dict[str, List[LocationData]], name: str) -> Region: + locations_per_region: dict[str, list[LocationData]], name: str) -> Region: region = Region(name, player, world) @@ -171,10 +172,10 @@ def create_region(world: MultiWorld, player: int, return region -def create_regions(world: MultiWorld, player: int, locations_per_region: Dict[str, List[LocationData]], - region_names: List[str]) -> Dict[str, 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] = {} + regions: dict[str, Region] = {} for name in region_names: regions[name] = create_region(world, player, locations_per_region, name) @@ -182,7 +183,7 @@ def create_regions(world: MultiWorld, player: int, locations_per_region: Dict[st return regions -def connect(regions: Dict[str, Region], source: str, target: str, +def connect(regions: dict[str, Region], source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None): sourceRegion = regions[source] @@ -191,8 +192,8 @@ def connect(regions: Dict[str, Region], source: str, target: str, sourceRegion.connect(targetRegion, rule=rule) -def get_locations_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]: - per_region: Dict[str, List[LocationData]] = {} +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) diff --git a/worlds/satisfactory/StateLogic.py b/worlds/satisfactory/StateLogic.py index 8659707ad6..ffe7bf5135 100644 --- a/worlds/satisfactory/StateLogic.py +++ b/worlds/satisfactory/StateLogic.py @@ -1,4 +1,5 @@ -from typing import Tuple, List, Optional, Set, Iterable +from typing import Optional +from collections.abc import Iterable from BaseClasses import CollectionState from .GameLogic import GameLogic, Recipe, PowerInfrastructureLevel from .Options import SatisfactoryOptions @@ -11,7 +12,7 @@ building_event_prefix = "Can Build: " class StateLogic: player: int options: SatisfactoryOptions - initial_unlocked_items: Set[str] + initial_unlocked_items: set[str] def __init__(self, player: int, options: SatisfactoryOptions): self.player = player @@ -42,7 +43,7 @@ class StateLogic: 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: + parts: Optional[tuple[str, ...]]) -> bool: def can_handcraft_part(part: str) -> bool: if self.can_produce(state, part): @@ -50,7 +51,7 @@ class StateLogic: elif part not in logic.handcraftable_recipes: return False - recipes: List[Recipe] = logic.handcraftable_recipes[part] + recipes: list[Recipe] = logic.handcraftable_recipes[part] return any( self.has_recipe(state, recipe) diff --git a/worlds/satisfactory/Web.py b/worlds/satisfactory/Web.py index 34701976f1..1be0e10f57 100644 --- a/worlds/satisfactory/Web.py +++ b/worlds/satisfactory/Web.py @@ -1,7 +1,6 @@ from BaseClasses import Tutorial from ..AutoWorld import WebWorld - class SatisfactoryWebWorld(WebWorld): theme = "dirt" setup = Tutorial( diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index d14e725b9d..5877101b62 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Set, TextIO, ClassVar +from typing import TextIO, ClassVar from BaseClasses import Item, ItemClassification, CollectionState from .GameLogic import GameLogic from .Items import Items @@ -52,13 +52,13 @@ class SatisfactoryWorld(World): 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() + 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: list[LocationData] = \ Locations(self.game_logic, self.options, self.state_logic, self.items, self.critical_path).get_locations() create_regions_and_return_locations( self.multiworld, self.options, self.player, self.game_logic, self.state_logic, self.critical_path, @@ -76,8 +76,7 @@ class SatisfactoryWorld(World): def set_rules(self) -> None: resource_sink_goal: bool = "AWESOME Sink Points" in self.options.goal_selection - required_parts: Set[str] = \ - set(self.game_logic.space_elevator_tiers[self.options.final_elevator_package.value - 1].keys()) + required_parts = set(self.game_logic.space_elevator_tiers[self.options.final_elevator_package.value - 1].keys()) if resource_sink_goal: required_parts.union(self.game_logic.buildings["AWESOME Sink"].inputs) @@ -100,8 +99,8 @@ class SatisfactoryWorld(World): return change - def fill_slot_data(self) -> Dict[str, object]: - slot_hub_layout: List[List[Dict[str, int]]] = [] + 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([]) From 1b28294a4f7bee01354c8287f6d6f5cbcc3ced1f Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sun, 6 Apr 2025 00:56:27 +0200 Subject: [PATCH 11/12] Implemented many many new options --- worlds/satisfactory/Options.py | 84 ++++++++++++++++++++++++++++----- worlds/satisfactory/__init__.py | 10 ++-- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/worlds/satisfactory/Options.py b/worlds/satisfactory/Options.py index 5b5b2a98e9..ee95e8708a 100644 --- a/worlds/satisfactory/Options.py +++ b/worlds/satisfactory/Options.py @@ -1,8 +1,8 @@ from dataclasses import dataclass from typing import ClassVar, Any, cast from enum import IntEnum -from Options import PerGameCommonOptions, DeathLink, AssembleOptions, Visibility -from Options import Range, Toggle, OptionSet, StartInventoryPool, NamedRange, Choice +from Options import PerGameCommonOptions, DeathLinkMixin, AssembleOptions, Visibility +from Options import Range, NamedRange, Toggle, DefaultOnToggle, OptionSet, StartInventoryPool, Choice from schema import Schema, And class Placement(IntEnum): @@ -59,11 +59,12 @@ class ElevatorTier(NamedRange): "five packages (tiers 1-9)": 5, } -class ResourceSinkPoints(NamedRange): +class ResourceSinkPointsTotal(NamedRange): """ + Does nothing if *AWESOME Sink Points (total)* goal is not enabled. + Sink an amount of items totalling this amount of points to finish. This setting is a *point count*, not a *coupon* count! - 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. @@ -72,7 +73,7 @@ class ResourceSinkPoints(NamedRange): If you have *Free Samples* enabled, consider setting this higher so that you can't reach the goal just by sinking your Free Samples. """ # Coupon data for above comment from https://satisfactory.wiki.gg/wiki/AWESOME_Shop - display_name = "Goal: AWESOME Sink points" + display_name = "Goal: AWESOME Sink points total" default = 2166000 range_start = 2166000 range_end = 18436379500 @@ -99,6 +100,37 @@ class ResourceSinkPoints(NamedRange): "1000 coupons (~18b points)": 18436379500 } +class ResourceSinkPointsPerMinute(NamedRange): + """ + Does nothing if *AWESOME Sink Points (per minute)* goal is not enabled. + + Continuously Sink an amount of items to maintain a sink points per minute of this amount of points for 10 minutes to finish. + This setting is in *points per minute* on the orange track so no DNA Capsules! + + Use the **TFIT - Ficsit Information Tool** mod or the Satisfactory wiki to find out how many points items are worth. + """ + # Coupon data for above comment from https://satisfactory.wiki.gg/wiki/AWESOME_Shop + display_name = "Goal: AWESOME Sink points per minute" + default = 50000 + range_start = 1000 + range_end = 10000000 + special_range_names = { + "~500 screw/min": 1000, + "~100 reinforced iron plate/min": 12000, + "~100 stator/min": 24000, + "~100 modular frame/min": 40000, + "~100 smart plating/min": 50000, + "~20 crystal oscillator/min": 60000, + "~50 motor/min": 76000, + "~10 heavy modular frame/min": 100000, + "~10 radio control unit": 300000, + "~10 heavy modular frame/min": 625000, + "~10 supercomputer/min": 1000000, + "~10 pressure conversion cube/min": 2500000, + "~10 nuclear pasta/min": 5000000, + "~4 ballistic warp drive/min": 10000000, + } + class HardDriveProgressionLimit(Range): """ How many Hard Drives can contain progression items. @@ -242,7 +274,7 @@ class TrapSelectionOverride(OptionSet): valid_keys = _trap_types default = {} -class EnergyLink(Toggle): +class EnergyLink(DefaultOnToggle): """ Allow transferring energy to and from other worlds using the Power Storage building. No energy is lost in the transfer on Satisfactory's side, but other worlds may have other settings. @@ -334,6 +366,31 @@ class StartingInventoryPreset(ChoiceMap): } # default = "Archipelago" # TODO `default` doesn't do anything, default is always the first `choices` value. if uncommented it messes up the template file generation (caps mismatch) +class ExplorationCollectableCount(Range): + """ + Does nothing if *Exploration Collectables* goal is not enabled. + + Collect this amount of Mercer Spheres and Summer Sloops each to finish. + """ + display_name = "Goal: Exploration Collectables" + default = 20 + range_start = 20 + range_end = 100 + +class MilestoneCostMultiplier(Range): + """ + Multiplies the amount of resources needed to unlock a milestone by this factor + + The value is in percentage: + 50 = half cost + 100 = normal milestone cost + 200 = double the cost + """ + display_name = "Milestone cost multiplier %" + default = 100 + range_start = 1 + range_end = 500 + class GoalSelection(OptionSet): """ What will be your goal(s)? @@ -342,9 +399,10 @@ class GoalSelection(OptionSet): display_name = "Select your Goals" valid_keys = { "Space Elevator Tier", - "AWESOME Sink Points", - # "Exploration", - # "FICSMAS Tree", + "AWESOME Sink Points (total)", + "AWESOME Sink Points (per minute)", + "Exploration Collectables", + # "Erect a FICSMAS Tree", } default = {"Space Elevator Tier"} schema = Schema(And(set, len), @@ -368,11 +426,13 @@ class ExperimentalGeneration(Toggle): visibility = Visibility.none @dataclass -class SatisfactoryOptions(PerGameCommonOptions): +class SatisfactoryOptions(PerGameCommonOptions, DeathLinkMixin): goal_selection: GoalSelection goal_requirement: GoalRequirement final_elevator_package: ElevatorTier - final_awesome_sink_points: ResourceSinkPoints + final_awesome_sink_points_total: ResourceSinkPointsTotal + final_awesome_sink_points_per_minute: ResourceSinkPointsPerMinute + final_exploration_collectables_amount: ExplorationCollectableCount hard_drive_progression_limit: HardDriveProgressionLimit free_sample_equipment: FreeSampleEquipment free_sample_buildings: FreeSampleBuildings @@ -383,10 +443,10 @@ class SatisfactoryOptions(PerGameCommonOptions): awesome_logic_placement: AwesomeLogic energy_link_logic_placement: EnergyLinkLogic splitter_placement: SplitterLogic + milestone_cost_multiplier: MilestoneCostMultiplier trap_chance: TrapChance trap_selection_preset: TrapSelectionPreset trap_selection_override: TrapSelectionOverride - death_link: DeathLink energy_link: EnergyLink start_inventory_from_pool: StartInventoryPool experimental_generation: ExperimentalGeneration diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index 5877101b62..6effd58f93 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -74,7 +74,8 @@ class SatisfactoryWorld(World): def set_rules(self) -> None: - resource_sink_goal: bool = "AWESOME Sink Points" in self.options.goal_selection + resource_sink_goal: bool = "AWESOME Sink Points (total)" in self.options.goal_selection \ + or "AWESOME Sink Points (per minute)" in self.options.goal_selection required_parts = set(self.game_logic.space_elevator_tiers[self.options.final_elevator_package.value - 1].keys()) @@ -109,7 +110,8 @@ class SatisfactoryWorld(World): 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 + multiplied_amount = max(amount * (self.options.milestone_cost_multiplier / 100), 1) + slot_hub_layout[tier - 1][milestone - 1][self.item_name_to_id[bundled_name]] = multiplied_amount return { "Data": { @@ -119,8 +121,8 @@ class SatisfactoryWorld(World): "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, + "FinalResourceSinkPointsTotal": self.options.final_awesome_sink_points_total.value, + "FinalResourceSinkPointsPerMinute": self.options.final_awesome_sink_points_per_minute.value, "FreeSampleEquipment": self.options.free_sample_equipment.value, "FreeSampleBuildings": self.options.free_sample_buildings.value, "FreeSampleParts": self.options.free_sample_parts.value, From d223ede9c39c172220ee3855f7bb5b008f9801b5 Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sun, 6 Apr 2025 13:27:11 +0200 Subject: [PATCH 12/12] Yay more stuff --- worlds/satisfactory/Items.py | 276 +++++++++++++++++++++++++++-------- 1 file changed, 218 insertions(+), 58 deletions(-) diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index cdf009843c..d00e583007 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -53,7 +53,7 @@ class Items: "Bundle: FICSIT Coupon": ItemData(G.Parts, 1338040), "Bundle: AI Expansion Server": ItemData(G.Parts, 1338041), #1.0 "Bundle: Fused Modular Frame": ItemData(G.Parts, 1338042), - "Bundle: Hard Drive": ItemData(G.Parts, 1338043), + "Bundle: Hard Drive": ItemData(G.Parts, 1338043, count=0), "Bundle: Heat Sink": ItemData(G.Parts, 1338044), "Bundle: Heavy Modular Frame": ItemData(G.Parts, 1338045), "Bundle: High-Speed Connector": ItemData(G.Parts, 1338046), @@ -67,7 +67,7 @@ class Items: "Bundle: Leaves": ItemData(G.Parts, 1338054), "Bundle: Limestone": ItemData(G.Parts, 1338055), "Bundle: Magnetic Field Generator": ItemData(G.Parts, 1338056), - "Bundle: Mercer Sphere": ItemData(G.Parts, 1338057), + "Bundle: Mercer Sphere": ItemData(G.Parts, 1338057, count=0), "Bundle: Modular Engine": ItemData(G.Parts, 1338058), "Bundle: Modular Frame": ItemData(G.Parts, 1338059), "Bundle: Motor": ItemData(G.Parts, 1338060), @@ -92,7 +92,7 @@ class Items: "Bundle: Plutonium Pellet": ItemData(G.Trap, 1338079, C.trap), "Bundle: Plutonium Waste": ItemData(G.Trap, 1338080, C.trap), "Bundle: Polymer Resin": ItemData(G.Parts, 1338081), - "Bundle: Power Shard": ItemData(G.Parts, 1338082), + "Bundle: Power Shard": ItemData(G.Parts, 1338082, count=0), "Bundle: Confusing Creature Statue": ItemData(G.Parts, 1338083), "Bundle: Pressure Conversion Cube": ItemData(G.Parts, 1338084), "Bundle: Alien Power Matrix": ItemData(G.Parts, 1338085), #1.0 @@ -109,7 +109,7 @@ class Items: "Bundle: Smart Plating": ItemData(G.Parts, 1338096), "Bundle: Smokeless Powder": ItemData(G.Parts, 1338097), "Bundle: Solid Biofuel": ItemData(G.Parts, 1338098), - "Bundle: Somersloop": ItemData(G.Parts, 1338099), + "Bundle: Somersloop": ItemData(G.Parts, 1338099, count=0), "Bundle: Stator": ItemData(G.Parts, 1338100), "Bundle: Silver Hog Statue": ItemData(G.Parts, 1338101), "Bundle: Steel Beam": ItemData(G.Parts, 1338102), @@ -131,7 +131,6 @@ class Items: "Bundle: Stinger Remains": ItemData(G.Parts, 1338118), "Bundle: Hatcher Remains": ItemData(G.Parts, 1338119), "Bundle: Alien DNA Capsule": ItemData(G.Parts, 1338120), -# 1.0 "Bundle: Diamonds": ItemData(G.Parts, 1338121), "Bundle: Time Crystal": ItemData(G.Parts, 1338122), "Bundle: Ficsite Ingot": ItemData(G.Parts, 1338123), @@ -144,63 +143,58 @@ class Items: "Bundle: Ficsonium Fuel Rod": ItemData(G.Trap, 1338130, C.trap), "Bundle: Packaged Rocket Fuel": ItemData(G.Parts, 1338131), "Bundle: Packaged Ionized Fuel": ItemData(G.Parts, 1338132), -# 1.0 - #1338131 - 1338149 Reserved for future parts - - # Equipment / Ammo + #1338150 - 1338199 Equipment / Ammo "Bundle: Bacon Agaric": ItemData(G.Ammo, 1338150), "Bundle: Beryl Nut": ItemData(G.Ammo, 1338151), - "Bundle: Blade Runners": ItemData(G.Equipment, 1338152), - "Bundle: Boom Box": ItemData(G.Equipment, 1338153), - "Bundle: Chainsaw": ItemData(G.Equipment, 1338154), + "Bundle: Blade Runners": ItemData(G.Equipment, 1338152, count=0), + "Bundle: Boom Box": ItemData(G.Equipment, 1338153, count=0), + "Bundle: Chainsaw": ItemData(G.Equipment, 1338154, count=0), "Bundle: Cluster Nobelisk": ItemData(G.Ammo, 1338155), - #"Bundle: Color Gun": ItemData(G.Equipment, 1338156), Removed in U8 - "Bundle: Cup": ItemData(G.Equipment, 1338157), + "Bundle: Iodine-Infused Filter": ItemData(G.Ammo, 1338156), #1.1 + "Bundle: Cup": ItemData(G.Equipment, 1338157, count=0), "Bundle: Cup (gold)": ItemData(G.Equipment, 1338158, count=0), "Bundle: Explosive Rebar": ItemData(G.Ammo, 1338159), - "Bundle: Factory Cart": ItemData(G.Equipment, 1338160), + "Bundle: Factory Cart": ItemData(G.Equipment, 1338160, count=0), "Bundle: Factory Cart (golden)": ItemData(G.Equipment, 1338161, count=0), - "Bundle: Gas Mask": ItemData(G.Equipment, 1338162), + "Bundle: Gas Mask": ItemData(G.Equipment, 1338162, count=0), "Bundle: Gas Nobelisk": ItemData(G.Ammo, 1338163), - "Bundle: Hazmat Suit": ItemData(G.Equipment, 1338164), + "Bundle: Hazmat Suit": ItemData(G.Equipment, 1338164, count=0), "Bundle: Homing Rifle Ammo": ItemData(G.Ammo, 1338165), - "Bundle: Hoverpack": ItemData(G.Equipment, 1338166), + "Bundle: Hoverpack": ItemData(G.Equipment, 1338166, count=0), "Bundle: Iron Rebar": ItemData(G.Ammo, 1338167), - "Bundle: Jetpack": ItemData(G.Equipment, 1338168), + "Bundle: Jetpack": ItemData(G.Equipment, 1338168, count=0), "Bundle: Medicinal Inhaler": ItemData(G.Ammo, 1338169), "Bundle: Nobelisk": ItemData(G.Ammo, 1338170), - "Bundle: Nobelisk Detonator": ItemData(G.Equipment, 1338171), + "Bundle: Nobelisk Detonator": ItemData(G.Equipment, 1338171, count=0), "Bundle: Nuke Nobelisk": ItemData(G.Ammo, 1338172), - "Bundle: Object Scanner": ItemData(G.Equipment, 1338173), + "Bundle: Object Scanner": ItemData(G.Equipment, 1338173, count=0), "Bundle: Paleberry": ItemData(G.Ammo, 1338174), - "Bundle: Parachute": ItemData(G.Equipment, 1338175), + "Bundle: Parachute": ItemData(G.Equipment, 1338175, count=0), "Bundle: Pulse Nobelisk": ItemData(G.Ammo, 1338176), - "Bundle: Rebar Gun": ItemData(G.Equipment, 1338177), - "Bundle: Rifle": ItemData(G.Equipment, 1338178), + "Bundle: Rebar Gun": ItemData(G.Equipment, 1338177, count=0), + "Bundle: Rifle": ItemData(G.Equipment, 1338178, count=0), "Bundle: Rifle Ammo": ItemData(G.Ammo, 1338179), "Bundle: Shatter Rebar": ItemData(G.Ammo, 1338180), "Bundle: Stun Rebar": ItemData(G.Ammo, 1338181), "Bundle: Turbo Rifle Ammo": ItemData(G.Ammo, 1338182), - "Bundle: Xeno-Basher": ItemData(G.Equipment, 1338183), - "Bundle: Xeno-Zapper": ItemData(G.Equipment, 1338184), - "Bundle: Zipline": ItemData(G.Equipment, 1338185), - "Bundle: Portable Miner": ItemData(G.Equipment, 1338186), + "Bundle: Xeno-Basher": ItemData(G.Equipment, 1338183, count=0), + "Bundle: Xeno-Zapper": ItemData(G.Equipment, 1338184, count=0), + "Bundle: Zipline": ItemData(G.Equipment, 1338185, count=0), + "Bundle: Portable Miner": ItemData(G.Equipment, 1338186, count=0), "Bundle: Gas Filter": ItemData(G.Ammo, 1338187), - # Other + # Special cases "Small Inflated Pocket Dimension": ItemData(G.Upgrades, 1338188, C.useful, 11), "Inflated Pocket Dimension": ItemData(G.Upgrades, 1338189, C.useful, 5), "Expanded Toolbelt": ItemData(G.Upgrades, 1338190, C.useful, 5), "Dimensional Depot upload from inventory": ItemData(G.Upgrades, 1338191, C.useful), - - # added in 1.1 - "Recipe: Hoverpack": ItemData(G.Recipe, 1338192, C.useful), - "Bundle: Iodine-Infused Filter": ItemData(G.Ammo, 1338193), - "Recipe: Jetpack": ItemData(G.Recipe, 1338194, C.useful), - "Recipe: Nobelisk Detonator": ItemData(G.Recipe, 1338195, C.progression), - "Recipe: Portable Miner": ItemData(G.Equipment, 1338196, C.progression), - - #1338197 - 1338199 Reserved for future equipment/ammo +# added in 1.1 + "Bundle of Three: Power Shards": ItemData(G.Parts, 1338192), + "Bundle of Three: Mercer Spheres": ItemData(G.Parts, 1338193), + "Bundle of Four: Somersloops": ItemData(G.Parts, 1338194), + "Bundle of Three: Hard Drives": ItemData(G.Parts, 1338195), +# + #1338196 - 1338199 Reserved for future equipment/ammo #1338200+ Recipes / buildings / schematics "Recipe: Reinforced Iron Plate": ItemData(G.Recipe, 1338200, C.progression), @@ -466,15 +460,18 @@ class Items: "Recipe: Tempered Copper Ingot": ItemData(G.Recipe, 1338458, C.progression), # 1.0 -# Missed +# added in 1.1 or missed "Recipe: Iron Pipe": ItemData(G.Recipe, 1338459, C.progression), "Recipe: Biocoal": ItemData(G.Recipe, 1338460, C.useful), "Recipe: Charcoal": ItemData(G.Recipe, 1338461, C.useful), "Recipe: Sloppy Alumina": ItemData(G.Recipe, 1338462, C.progression), -# Missed - - #1338459 - 1338599 Reserved for future recipes + "Recipe: Hoverpack": ItemData(G.Recipe, 1338463, C.useful), + "Recipe: Jetpack": ItemData(G.Recipe, 1338464, C.useful), + "Recipe: Nobelisk Detonator": ItemData(G.Recipe, 1338465, C.progression), + "Recipe: Portable Miner": ItemData(G.Recipe, 1338466, C.progression), +# + #1338467 - 1338599 Reserved for future recipes #1338400 - 1338899 buildings / others "Building: Constructor": ItemData(G.Building, 1338600, C.progression), # unlocked by default "Building: Assembler": ItemData(G.Building, 1338601, C.progression), @@ -557,11 +554,11 @@ class Items: "Building: Label Sign Bundle": ItemData(G.Building | G.Signs, 1338678, C.filler, 0), "Building: Display Sign Bundle": ItemData(G.Building | G.Signs, 1338679, C.filler, 0), "Building: Billboard Set": ItemData(G.Building | G.Signs, 1338680, C.filler, 0), - #1338681 Moved to cosmetics + #1338681 Moved to cosmetics - 1.1 "Building: Metal Pillar": ItemData(G.Pilars, 1338682, C.filler, 0), "Building: Concrete Pillar": ItemData(G.Pilars, 1338683, C.filler, 0), "Building: Frame Pillar": ItemData(G.Pilars, 1338684, C.filler, 0), - #1338685 - 1338691 Moved to cosmetics + #1338685 - 1338691 Moved to cosmetics - 1.1 "Building: Foundation": ItemData(G.Building | G.Foundations | G.AlwaysUseful, 1338692, C.progression), "Building: Half Foundation": ItemData(G.Foundations, 1338693, C.filler, 0), "Building: Corner Ramp Pack": ItemData(G.Foundations, 1338694, C.filler, 0), @@ -592,8 +589,6 @@ class Items: "Building: Gates": ItemData(G.Walls, 1338719, C.filler, 0), "Building: Roofs": ItemData(G.Walls, 1338720, C.filler, 0), "Building: Roof Corners": ItemData(G.Walls, 1338721, C.filler, 0), - -# 1.0 "Building: Converter": ItemData(G.Building, 1338722, C.progression), "Building: Quantum Encoder": ItemData(G.Building, 1338723, C.progression), "Building: Portal": ItemData(G.Building, 1338724, C.filler), @@ -601,10 +596,7 @@ class Items: "Building: Conveyor Lift Mk.6": ItemData(G.Building | G.ConveyorMk6, 1338726, C.useful), "Building: Alien Power Augmenter": ItemData(G.Building, 1338727, C.progression), "Building: Dimensional Depot Uploader": ItemData(G.Building, 1338728, C.useful), -# 1.0 - #1338729 - 1338749 Reserved for Cosmetics - "Customizer: Asphalt Foundation Material": ItemData(G.Customizer | G.Foundations, 1338750, C.filler, 0), "Customizer: Concrete Foundation Material": ItemData(G.Customizer | G.Foundations, 1338751, C.filler, 0), "Customizer: Concrete Wall Material": ItemData(G.Customizer | G.Walls, 1338752, C.filler, 0), @@ -622,8 +614,6 @@ class Items: "Customizer: Number Patterns": ItemData(G.Customizer | G.Foundations, 1338764, C.filler, 0), "Customizer: Pathway Patterns": ItemData(G.Customizer | G.Foundations, 1338765, C.filler, 0), "Customizer: Factory Zone Patterns": ItemData(G.Customizer | G.Foundations, 1338766, C.filler, 0), - -# 1.0 "Customizer: Steel-Framed Windows": ItemData(G.Customizer | G.Walls, 1338767, C.filler, 0), "Customizer: Construction Fences": ItemData(G.Customizer, 1338768, C.filler, 0), "Customizer: Unpainted Finish": ItemData(G.Customizer, 1338769, C.filler, 0), @@ -631,26 +621,22 @@ class Items: "Customizer: Chrome Paint Finish": ItemData(G.Customizer, 1338771, C.filler, 0), "Customizer: Carbon Steel Finish": ItemData(G.Customizer, 1338772, C.filler, 0), "Customizer: Caterium Paint Finish": ItemData(G.Customizer, 1338773, C.filler, 0), -# 1.0 #1338774 - 1338799 Reserved for buildings # Transports 1338800 - 1338898 # Drones (including Drone) "Transport: Drones": ItemData(G.Transport, 1338800, C.useful), - # Trains (including Empty Platform, rails, station, locomotive) "Transport: Trains": ItemData(G.Transport | G.Trains, 1338801, C.useful), "Transport: Fluid Trains": ItemData(G.Transport | G.Trains, 1338802, C.useful), - # Tracker / Truck (including truck station) "Transport: Tractor": ItemData(G.Transport | G.Vehicles, 1338803, C.useful), "Transport: Truck": ItemData(G.Transport | G.Vehicles, 1338804, C.useful), "Transport: Explorer": ItemData(G.Transport | G.Vehicles, 1338805, C.useful), "Transport: Factory Cart": ItemData(G.Transport | G.Vehicles, 1338806, C.useful), "Transport: Factory Cart (golden)": ItemData(G.Transport | G.Vehicles, 1338807, C.filler), - "Transport: Cyber Wagon": ItemData(G.Transport | G.Vehicles, 1338808, C.filler), - + "Transport: Cyber Wagon": ItemData(G.Transport | G.Vehicles | G.Trap, 1338808, C.filler), # Hypertubes (including supports / pipes / entrance / holes) "Transport: Hypertube": ItemData(G.Transport | G.HyperTubes, 1338809, C.useful), "Transport: Hypertube Floor Hole": ItemData(G.Transport | G.HyperTubes, 1338810, C.filler), @@ -682,8 +668,182 @@ class Items: "Trap: Can of Beans": ItemData(G.Trap, 1338920, C.trap), "Trap: Fart Cloud": ItemData(G.Trap, 1338921, C.trap), - #Item id range upper bound - "Building: Space Elevator": ItemData(G.Building, 1338999, C.progression) + "Building: Space Elevator": ItemData(G.Building, 1338999, C.progression), + + # Resource singles + "Single: Adaptive Control Unit": ItemData(G.Parts, 1339000, count=0), + "Single: AI Limiter": ItemData(G.Parts, 1339001, count=0), + "Single: Alclad Aluminum Sheet": ItemData(G.Parts, 1339002, count=0), + "Single: Blue Power Slug": ItemData(G.Parts, 1339003, count=0), + "Single: Yellow Power Slug": ItemData(G.Parts, 1339004, count=0), + "Single: Alien Protein": ItemData(G.Parts, 1339005, count=0), + "Single: Purple Power Slug": ItemData(G.Parts, 1339006, count=0), + "Single: Aluminum Casing": ItemData(G.Parts, 1339007, count=0), + "Single: Aluminum Ingot": ItemData(G.Parts, 1339008, count=0), + "Single: Aluminum Scrap": ItemData(G.Parts, 1339009, count=0), + "Single: Assembly Director System": ItemData(G.Parts, 1339010, count=0), + "Single: Automated Wiring": ItemData(G.Parts, 1339011, count=0), + "Single: Battery": ItemData(G.Parts, 1339012, count=0), + "Single: Bauxite": ItemData(G.Parts, 1339013, count=0), + "Single: Neural-Quantum Processor": ItemData(G.Parts, 1339014, count=0), + "Single: Biomass": ItemData(G.Parts, 1339015, count=0), + "Single: Black Powder": ItemData(G.Parts, 1339016, count=0), + "Single: Cable": ItemData(G.Parts, 1339017, count=0), + "Single: Caterium Ingot": ItemData(G.Parts, 1339018, count=0), + "Single: Caterium Ore": ItemData(G.Parts, 1339019, count=0), + "Single: Circuit Board": ItemData(G.Parts, 1339020, count=0), + "Single: Coal": ItemData(G.Parts, 1339021, count=0), + "Single: Singularity Cell": ItemData(G.Parts, 1339022, count=0), + "Single: Compacted Coal": ItemData(G.Parts, 1339023, count=0), + "Single: Computer": ItemData(G.Parts, 1339024, count=0), + "Single: Concrete": ItemData(G.Parts, 1339025, count=0), + "Single: Cooling System": ItemData(G.Parts, 1339026, count=0), + "Single: Copper Ingot": ItemData(G.Parts, 1339027, count=0), + "Single: Copper Ore": ItemData(G.Parts, 1339028, count=0), + "Single: Copper Powder": ItemData(G.Parts, 1339029, count=0), + "Single: Copper Sheet": ItemData(G.Parts, 1339030, count=0), + "Single: Adequate Pioneering Statue": ItemData(G.Parts, 1339031, count=0), + "Single: Crystal Oscillator": ItemData(G.Parts, 1339032, count=0), + "Single: Electromagnetic Control Rod": ItemData(G.Parts, 1339033, count=0), + "Single: Empty Canister": ItemData(G.Parts, 1339034, count=0), + "Single: Empty Fluid Tank": ItemData(G.Parts, 1339035, count=0), + "Single: Encased Industrial Beam": ItemData(G.Parts, 1339036, count=0), + "Single: Encased Plutonium Cell": ItemData(G.Trap, 1339037, C.trap, count=0), + "Single: Encased Uranium Cell": ItemData(G.Trap, 1339038, C.trap, count=0), + "Single: Fabric": ItemData(G.Parts, 1339039, count=0), + "Single: FICSIT Coupon": ItemData(G.Parts, 1339040, count=0), + "Single: AI Expansion Server": ItemData(G.Parts, 1339041, count=0), + "Single: Fused Modular Frame": ItemData(G.Parts, 1339042, count=0), + "Single: Hard Drive": ItemData(G.Parts, 1339043, count=0), + "Single: Heat Sink": ItemData(G.Parts, 1339044, count=0), + "Single: Heavy Modular Frame": ItemData(G.Parts, 1339045, count=0), + "Single: High-Speed Connector": ItemData(G.Parts, 1339046, count=0), + "Single: Satisfactory Pioneering Statue": ItemData(G.Parts, 1339047, count=0), + "Single: Pretty Good Pioneering Statue": ItemData(G.Parts, 1339048, count=0), + "Single: Iron Ingot": ItemData(G.Parts, 1339049, count=0), + "Single: Iron Ore": ItemData(G.Parts, 1339050, count=0), + "Single: Iron Plate": ItemData(G.Parts, 1339051, count=0), + "Single: Iron Rod": ItemData(G.Parts, 1339052, count=0), + "Single: Golden Nut Statue": ItemData(G.Parts, 1339053, count=0), + "Single: Leaves": ItemData(G.Parts, 1339054, count=0), + "Single: Limestone": ItemData(G.Parts, 1339055, count=0), + "Single: Magnetic Field Generator": ItemData(G.Parts, 1339056, count=0), + "Single: Mercer Sphere": ItemData(G.Parts, 1339057, count=0), + "Single: Modular Engine": ItemData(G.Parts, 1339058, count=0), + "Single: Modular Frame": ItemData(G.Parts, 1339059, count=0), + "Single: Motor": ItemData(G.Parts, 1339060, count=0), + "Single: Mycelia": ItemData(G.Parts, 1339061, count=0), + "Single: Non-fissile Uranium": ItemData(G.Trap, 1339062, C.trap, count=0), + "Single: Nuclear Pasta": ItemData(G.Parts, 1339063, count=0), + "Single: Lizard Doggo Statue": ItemData(G.Parts, 1339064, count=0), + "Single: Organic Data Capsule": ItemData(G.Parts, 1339065, count=0), + "Single: Packaged Alumina Solution": ItemData(G.Parts, 1339066, count=0), + "Single: Packaged Fuel": ItemData(G.Parts, 1339067, count=0), + "Single: Packaged Heavy Oil Residue": ItemData(G.Parts, 1339068, count=0), + "Single: Packaged Liquid Biofuel": ItemData(G.Parts, 1339069, count=0), + "Single: Packaged Nitric Acid": ItemData(G.Parts, 1339070, count=0), + "Single: Packaged Nitrogen Gas": ItemData(G.Parts, 1339071, count=0), + "Single: Packaged Oil": ItemData(G.Parts, 1339072, count=0), + "Single: Packaged Sulfuric Acid": ItemData(G.Parts, 1339073, count=0), + "Single: Packaged Turbofuel": ItemData(G.Parts, 1339074, count=0), + "Single: Packaged Water": ItemData(G.Parts, 1339075, count=0), + "Single: Petroleum Coke": ItemData(G.Parts, 1339076, count=0), + "Single: Plastic": ItemData(G.Parts, 1339077, count=0), + "Single: Plutonium Fuel Rod": ItemData(G.Trap, 1339078, C.trap, count=0), + "Single: Plutonium Pellet": ItemData(G.Trap, 1339079, C.trap, count=0), + "Single: Plutonium Waste": ItemData(G.Trap, 1339080, C.trap, count=0), + "Single: Polymer Resin": ItemData(G.Parts, 1339081, count=0), + "Single: Power Shard": ItemData(G.Parts, 1339082, count=0), + "Single: Confusing Creature Statue": ItemData(G.Parts, 1339083, count=0), + "Single: Pressure Conversion Cube": ItemData(G.Parts, 1339084, count=0), + "Single: Alien Power Matrix": ItemData(G.Parts, 1339085, count=0), + "Single: Quartz Crystal": ItemData(G.Parts, 1339086, count=0), + "Single: Quickwire": ItemData(G.Parts, 1339087, count=0), + "Single: Radio Control Unit": ItemData(G.Parts, 1339088, count=0), + "Single: Raw Quartz": ItemData(G.Parts, 1339089, count=0), + "Single: Reinforced Iron Plate": ItemData(G.Parts, 1339090, count=0), + "Single: Rotor": ItemData(G.Parts, 1339091, count=0), + "Single: Rubber": ItemData(G.Parts, 1339092, count=0), + "Single: SAM": ItemData(G.Parts, 1339093, count=0), + "Single: Screw": ItemData(G.Parts, 1339094, count=0), + "Single: Silica": ItemData(G.Parts, 1339095, count=0), + "Single: Smart Plating": ItemData(G.Parts, 1339096, count=0), + "Single: Smokeless Powder": ItemData(G.Parts, 1339097, count=0), + "Single: Solid Biofuel": ItemData(G.Parts, 1339098, count=0), + "Single: Somersloop": ItemData(G.Parts, 1339099, count=0), + "Single: Stator": ItemData(G.Parts, 1339100, count=0), + "Single: Silver Hog Statue": ItemData(G.Parts, 1339101, count=0), + "Single: Steel Beam": ItemData(G.Parts, 1339102, count=0), + "Single: Steel Ingot": ItemData(G.Parts, 1339103, count=0), + "Single: Steel Pipe": ItemData(G.Parts, 1339104, count=0), + "Single: Sulfur": ItemData(G.Parts, 1339105, count=0), + "Single: Supercomputer": ItemData(G.Parts, 1339106, count=0), + "Single: Superposition Oscillator": ItemData(G.Parts, 1339107, count=0), + "Single: Thermal Propulsion Rocket": ItemData(G.Parts, 1339108, count=0), + "Single: Turbo Motor": ItemData(G.Parts, 1339109, count=0), + "Single: Hog Remains": ItemData(G.Parts, 1339110, count=0), + "Single: Uranium": ItemData(G.Trap, 1339111, C.trap, count=0), + "Single: Uranium Fuel Rod": ItemData(G.Trap, 1339112, C.trap, count=0), + "Single: Uranium Waste": ItemData(G.Trap, 1339113, C.trap, count=0), + "Single: Versatile Framework": ItemData(G.Parts, 1339114, count=0), + "Single: Wire": ItemData(G.Parts, 1339115, count=0), + "Single: Wood": ItemData(G.Parts, 1339116, count=0), + "Single: Plasma Spitter Remains": ItemData(G.Parts, 1339117, count=0), + "Single: Stinger Remains": ItemData(G.Parts, 1339118, count=0), + "Single: Hatcher Remains": ItemData(G.Parts, 1339119, count=0), + "Single: Alien DNA Capsule": ItemData(G.Parts, 1339120, count=0), + "Single: Diamonds": ItemData(G.Parts, 1339121, count=0), + "Single: Time Crystal": ItemData(G.Parts, 1339122, count=0), + "Single: Ficsite Ingot": ItemData(G.Parts, 1339123, count=0), + "Single: Ficsite Trigon": ItemData(G.Parts, 1339124, count=0), + "Single: Reanimated SAM": ItemData(G.Parts, 1339125, count=0), + "Single: SAM Fluctuator": ItemData(G.Parts, 1339126, count=0), + "Single: Biochemical Sculptor": ItemData(G.Parts, 1339127, count=0), + "Single: Ballistic Warp Drive": ItemData(G.Parts, 1339128, count=0), + "Single: Ficsonium": ItemData(G.Trap, 1339129, C.trap, count=0), + "Single: Ficsonium Fuel Rod": ItemData(G.Trap, 1339130, C.trap, count=0), + "Single: Packaged Rocket Fuel": ItemData(G.Parts, 1339131, count=0), + "Single: Packaged Ionized Fuel": ItemData(G.Parts, 1339132, count=0), + #1339133 - 1339149 Reserved for future parts + #1339150 - 1339199 Equipment / Ammo + "Single: Bacon Agaric": ItemData(G.Ammo, 1339150, count=0), + "Single: Beryl Nut": ItemData(G.Ammo, 1339151, count=0), + "Single: Blade Runners": ItemData(G.Equipment, 1339152), + "Single: Boom Box": ItemData(G.Equipment, 1339153), + "Single: Chainsaw": ItemData(G.Equipment, 1339154), + "Single: Cluster Nobelisk": ItemData(G.Ammo, 1339155, count=0), + "Single: Iodine-Infused Filter": ItemData(G.Ammo, 1339156, count=0), #1.1 + "Single: Cup": ItemData(G.Equipment, 1339157), + "Single: Cup (gold)": ItemData(G.Equipment, 1339158, count=0), + "Single: Explosive Rebar": ItemData(G.Ammo, 1339159, count=0), + "Single: Factory Cart": ItemData(G.Equipment, 1339160), + "Single: Factory Cart (golden)": ItemData(G.Equipment, 1339161, count=0), + "Single: Gas Mask": ItemData(G.Equipment, 1339162), + "Single: Gas Nobelisk": ItemData(G.Ammo, 1339163, count=0), + "Single: Hazmat Suit": ItemData(G.Equipment, 1339164), + "Single: Homing Rifle Ammo": ItemData(G.Ammo, 1339165, count=0), + "Single: Hoverpack": ItemData(G.Equipment, 1339166), + "Single: Iron Rebar": ItemData(G.Ammo, 1339167, count=0), + "Single: Jetpack": ItemData(G.Equipment, 1339168), + "Single: Medicinal Inhaler": ItemData(G.Ammo, 1339169, count=0), + "Single: Nobelisk": ItemData(G.Ammo, 1339170, count=0), + "Single: Nobelisk Detonator": ItemData(G.Equipment, 1339171), + "Single: Nuke Nobelisk": ItemData(G.Ammo, 1339172, count=0), + "Single: Object Scanner": ItemData(G.Equipment, 1339173), + "Single: Paleberry": ItemData(G.Ammo, 1339174, count=0), + "Single: Parachute": ItemData(G.Equipment, 1339175), + "Single: Pulse Nobelisk": ItemData(G.Ammo, 1339176, count=0), + "Single: Rebar Gun": ItemData(G.Equipment, 1339177), + "Single: Rifle": ItemData(G.Equipment, 1339178), + "Single: Rifle Ammo": ItemData(G.Ammo, 1339179, count=0), + "Single: Shatter Rebar": ItemData(G.Ammo, 1339180, count=0), + "Single: Stun Rebar": ItemData(G.Ammo, 1339181, count=0), + "Single: Turbo Rifle Ammo": ItemData(G.Ammo, 1339182, count=0), + "Single: Xeno-Basher": ItemData(G.Equipment, 1339183), + "Single: Xeno-Zapper": ItemData(G.Equipment, 1339184), + "Single: Zipline": ItemData(G.Equipment, 1339185), + "Single: Portable Miner": ItemData(G.Equipment, 1339186), + "Single: Gas Filter": ItemData(G.Ammo, 1339187, count=0) } non_unique_item_categories: ClassVar[G] = G.Parts | G.Equipment | G.Ammo | G.Trap | G.Upgrades