From 7e40cba0dabde240731239eb365bc795f157409f Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sun, 15 Jun 2025 23:40:56 +0200 Subject: [PATCH] Fixed Itemlinks Removed space elevator parts from fillers Removed more AWESOME shop purchaseables from minimal item pool Added all equipment to minimal item pool Removed non fissile and fertile uranium from minimal item pool Removed portal from minimal item pool Removed Ionized fuel from minimal item pool Removed recipes for Hoverpack and Turbo Rifle Ammo from minimal item pool Lowered the chance for rolling steel on randomized starter recipes --- worlds/satisfactory/CriticalPathCalculator.py | 11 +- worlds/satisfactory/GameLogic.py | 62 ++++++--- worlds/satisfactory/Items.py | 131 +++++++++++------- worlds/satisfactory/__init__.py | 39 ++++-- 4 files changed, 156 insertions(+), 87 deletions(-) diff --git a/worlds/satisfactory/CriticalPathCalculator.py b/worlds/satisfactory/CriticalPathCalculator.py index d730d4a9f2..39c09a1122 100644 --- a/worlds/satisfactory/CriticalPathCalculator.py +++ b/worlds/satisfactory/CriticalPathCalculator.py @@ -67,7 +67,7 @@ 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.logic.recipes["Uranium"][0].minimal_tier <= self.final_elevator_package: self.select_minimal_required_parts_for(("Hazmat Suit", "Iodine-Infused Filter")) @@ -214,12 +214,14 @@ class CriticalPathCalculator: self.tier_0_recipes.add(self.random.choice( ("Recipe: Iron Ingot", "Recipe: Basic Iron Ingot", "Recipe: Iron Alloy Ingot"))) - selected_recipe = self.random.choice(("Recipe: Iron Plate", "Recipe: Steel Cast Plate")) + selected_recipe = self.random.choice( + ("Recipe: Iron Plate", "Recipe: Iron Plate", "Recipe: Iron Plate", "Recipe: Steel Cast Plate")) self.tier_0_recipes.add(selected_recipe) if selected_recipe == "Recipe: Steel Cast Plate": self.add_steel_ingot_to_starter_recipes() - selected_recipe = self.random.choice(("Recipe: Iron Rod", "Recipe: Steel Rod")) + selected_recipe = self.random.choice( + ("Recipe: Iron Rod", "Recipe: Iron Rod", "Recipe: Iron Rod", "Recipe: Steel Rod")) self.tier_0_recipes.add(selected_recipe) if selected_recipe == "Recipe: Steel Rod": self.add_steel_ingot_to_starter_recipes() @@ -239,7 +241,8 @@ class CriticalPathCalculator: # add Silica self.tier_0_recipes.add(self.random.choice(("Recipe: Silica", "Recipe: Cheap Silica"))) - selected_recipe = self.random.choice(("Recipe: Screw", "Recipe: Cast Screw", "Recipe: Steel Screw")) + selected_recipe = self.random.choice( + ("Recipe: Screw", "Recipe: Screw","Recipe: Cast Screw", "Recipe: Cast Screw", "Recipe: Steel Screw")) self.tier_0_recipes.add(selected_recipe) if selected_recipe == "Recipe: Steel Screw": # add Steel Beam and steel Ingot diff --git a/worlds/satisfactory/GameLogic.py b/worlds/satisfactory/GameLogic.py index be112ae195..49b4590f3f 100644 --- a/worlds/satisfactory/GameLogic.py +++ b/worlds/satisfactory/GameLogic.py @@ -134,6 +134,10 @@ class DropPodData: class GameLogic: + indirect_recipes: dict[str, str] = { + "Recipe: Quartz Purification": "Recipe: Distilled Silica" + } + recipes: dict[str, tuple[Recipe, ...]] = { # This Dict should only contain items that are used somewhere in a logic chain @@ -173,7 +177,8 @@ class GameLogic: # Raw Resources "Water": ( - Recipe("Water", "Water Extractor", implicitly_unlocked=True), ), + Recipe("Water", "Water Extractor", implicitly_unlocked=True), + Recipe("Water (Resource Well)", "Resource Well Pressurizer", implicitly_unlocked=True, minimal_tier=2)), "Limestone": ( Recipe("Limestone", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), "Raw Quartz": ( @@ -189,7 +194,8 @@ class GameLogic: "Caterium Ore": ( Recipe("Caterium Ore", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True), ), "Crude Oil": ( - Recipe("Crude Oil", "Oil Extractor", implicitly_unlocked=True), ), + Recipe("Crude Oil", "Oil Extractor", implicitly_unlocked=True), + Recipe("Crude Oil (Resource Well)", "Resource Well Pressurizer", implicitly_unlocked=True, minimal_tier=2)), "Bauxite": ( Recipe("Bauxite", "Miner Mk.1", handcraftable=True, implicitly_unlocked=True, minimal_tier=2), ), "Nitrogen Gas": ( @@ -408,7 +414,7 @@ class GameLogic: 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("Supercomputer", "Manufacturer", ("Computer", "AI Limiter", "High-Speed Connector", "Plastic"), handcraftable=True, minimal_tier=2), 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": ( @@ -419,17 +425,17 @@ class GameLogic: "Uranium Fuel Rod": ( Recipe("Uranium Fuel Rod", "Manufacturer", ("Encased Uranium Cell", "Encased Industrial Beam", "Electromagnetic Control Rod")), Recipe("Uranium Fuel Unit", "Manufacturer", ("Encased Uranium Cell", "Electromagnetic Control Rod", "Crystal Oscillator", "Rotor"))), - #"Non-fissile Uranium": ( - # Recipe("Non-fissile Uranium", "Blender", ("Uranium Waste", "Silica", "Nitric Acid", "Sulfuric Acid"), additional_outputs=("Water", )), - # Recipe("Fertile Uranium", "Blender", ("Uranium", "Uranium Waste", "Nitric Acid", "Sulfuric Acid"), additional_outputs=("Water", ), minimal_belt_speed=2)), - #"Plutonium Pellet": ( - # Recipe("Plutonium Pellet", "Particle Accelerator", ("Non-fissile Uranium", "Uranium Waste"), minimal_belt_speed=2), ), - #"Encased Plutonium Cell": ( - # Recipe("Encased Plutonium Cell", "Assembler", ("Plutonium Pellet", "Concrete")), - # Recipe("Instant Plutonium Cell", "Particle Accelerator", ("Non-fissile Uranium", "Aluminum Casing"), minimal_belt_speed=2)), - #"Plutonium Fuel Rod": ( - # Recipe("Plutonium Fuel Rod", "Manufacturer", ("Encased Plutonium Cell", "Steel Beam", "Electromagnetic Control Rod", "Heat Sink")), - # Recipe("Plutonium Fuel Unit", "Assembler", ("Encased Plutonium Cell", "Pressure Conversion Cube"))), + "Non-fissile Uranium": ( + Recipe("Non-fissile Uranium", "Blender", ("Uranium Waste", "Silica", "Nitric Acid", "Sulfuric Acid"), additional_outputs=("Water", )), + Recipe("Fertile Uranium", "Blender", ("Uranium", "Uranium Waste", "Nitric Acid", "Sulfuric Acid"), additional_outputs=("Water", ), minimal_belt_speed=2)), + "Plutonium Pellet": ( + Recipe("Plutonium Pellet", "Particle Accelerator", ("Non-fissile Uranium", "Uranium Waste"), minimal_belt_speed=2), ), + "Encased Plutonium Cell": ( + Recipe("Encased Plutonium Cell", "Assembler", ("Plutonium Pellet", "Concrete")), + Recipe("Instant Plutonium Cell", "Particle Accelerator", ("Non-fissile Uranium", "Aluminum Casing"), minimal_belt_speed=2)), + "Plutonium Fuel Rod": ( + Recipe("Plutonium Fuel Rod", "Manufacturer", ("Encased Plutonium Cell", "Steel Beam", "Electromagnetic Control Rod", "Heat Sink")), + Recipe("Plutonium Fuel Unit", "Assembler", ("Encased Plutonium Cell", "Pressure Conversion Cube"))), "Gas Filter": ( Recipe("Gas Filter", "Manufacturer", ("Coal", "Rubber", "Fabric"), handcraftable=True), ), "Iodine-Infused Filter": ( @@ -523,12 +529,13 @@ class GameLogic: "Rocket Fuel": ( 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", )), ), + "Ionized Fuel": ( + Recipe("Ionized Fuel", "Refinery", ("Rocket Fuel", "Power Shard"), additional_outputs=("Compacted Coal", )), + Recipe("Dark-Ion Fuel", "Blender", ("Packaged Rocket Fuel", "Dark Matter Crystal"), minimal_belt_speed=3, additional_outputs=("Compacted Coal", ), minimal_tier=4)), "Packaged Rocket Fuel": ( Recipe("Packaged Rocket Fuel", "Packager", ("Rocket Fuel", "Empty Fluid Tank")), ), - #"Packaged Ionized Fuel": ( - # Recipe("Packaged Ionized Fuel", "Packager", ("Ionized Fuel", "Empty Fluid Tank")), ), + "Packaged Ionized Fuel": ( + Recipe("Packaged Ionized Fuel", "Packager", ("Ionized Fuel", "Empty Fluid Tank")), ), "Diamonds": ( Recipe("Diamonds", "Particle Accelerator", ("Coal", ), minimal_belt_speed=5), Recipe("Cloudy Diamonds", "Particle Accelerator", ("Coal", "Limestone"), minimal_belt_speed=4), @@ -577,6 +584,17 @@ class GameLogic: 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 + #For exclusion logic + "Hoverpack": ( + Recipe("Hoverpack", "Equipment Workshop", ("Motor", "Heavy Modular Frame", "Computer", "Alclad Aluminum Sheet")), ), + "Rifle Ammo": ( + Recipe("Rifle Ammo", "Assembler", ("Copper Sheet", "Smokeless Powder"), minimal_belt_speed=2), ), + "Turbo Rifle Ammo": ( + Recipe("Turbo Rifle Ammo", "Blender", ("Rifle Ammo", "Aluminum Casing", "Turbofuel"), minimal_belt_speed=3), + Recipe("Turbo Rifle Ammo (Packaged)", "Manufacturer", ("Rifle Ammo", "Aluminum Casing", "Packaged Turbofuel"), minimal_belt_speed=2, minimal_tier=2)), + "Homing Rifle Ammo": ( + Recipe("Homing Rifle Ammo", "Assembler", ("Rifle Ammo", "High-Speed Connector")), ), + ### } buildings: dict[str, Building] = { @@ -598,8 +616,8 @@ class GameLogic: "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), - "Foundry": Building("Foundry", ("Reinforced Iron Plate", "Iron Rod", "Concrete"), PowerInfrastructureLevel.Basic), # Simplified , used ("Modular Frame", "Rotor", "Concrete") - "Resource Well Pressurizer": Building("Resource Well Pressurizer", ("Wire", "Rubber", "Encased Industrial Beam", "Motor", "Steel Beam", "Plastic"), PowerInfrastructureLevel.Advanced), + "Foundry": Building("Foundry", ("Reinforced Iron Plate", "Iron Rod", "Concrete"), PowerInfrastructureLevel.Basic), # Simplified, used ("Modular Frame", "Rotor", "Concrete") + "Resource Well Pressurizer": Building("Resource Well Pressurizer", ("Steel Pipe", "Heavy Modular Frame", "Motor", "Reinforced Iron Plate", "Copper Sheet", "Steel Beam"), PowerInfrastructureLevel.Advanced), # Simplified, used ("Radio Control Unit", "Heavy Modular Frame", "Motor", "Alclad Aluminum Sheet", "Rubber", "Steel Beam", "Aluminum Casing") "Equipment Workshop": Building("Equipment Workshop", ("Iron Plate", "Iron Rod"), implicitly_unlocked=True), "AWESOME Sink": Building("AWESOME Sink", ("Reinforced Iron Plate", "Cable", "Concrete"), can_produce=False), "AWESOME Shop": Building("AWESOME Shop", ("Screw", "Iron Plate", "Cable"), can_produce=False), @@ -630,6 +648,10 @@ class GameLogic: "Quantum Encoder": Building("Quantum Encoder", ("Turbo Motor", "Supercomputer", "Cooling System", "Time Crystal", "Ficsite Trigon"), PowerInfrastructureLevel.Complex), "Alien Power Augmenter": Building("Alien Power Augmenter", ("SAM Fluctuator", "Cable", "Encased Industrial Beam", "Motor", "Computer")), #1.0 + + #For exclusion logic + "Portal": Building("Portal", ("Turbo Motor", "Radio Control Unit", "Superposition Oscillator", "SAM Fluctuator", "Ficsite Trigon", "Singularity Cell"), PowerInfrastructureLevel.Advanced), + ### } requirement_per_powerlevel: dict[PowerInfrastructureLevel, tuple[Recipe, ...]] = { diff --git a/worlds/satisfactory/Items.py b/worlds/satisfactory/Items.py index 0cf1b41177..ea48e1b8ac 100644 --- a/worlds/satisfactory/Items.py +++ b/worlds/satisfactory/Items.py @@ -1,5 +1,6 @@ from random import Random from typing import ClassVar, Optional +from collections.abc import Collection from BaseClasses import Item, ItemClassification as C, MultiWorld from .GameLogic import GameLogic from .Options import SatisfactoryOptions @@ -10,7 +11,7 @@ from .CriticalPathCalculator import CriticalPathCalculator class Items: item_data: ClassVar[dict[str, ItemData]] = { # Resource Bundles - "Bundle: Adaptive Control Unit": ItemData(G.Parts, 1338000), + "Bundle: Adaptive Control Unit": ItemData(G.Parts, 1338000, count=0), "Bundle: AI Limiter": ItemData(G.Parts, 1338001), "Bundle: Alclad Aluminum Sheet": ItemData(G.Parts, 1338002), "Bundle: Blue Power Slug": ItemData(G.Parts, 1338003), @@ -20,8 +21,8 @@ class Items: "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: Assembly Director System": ItemData(G.Parts, 1338010, count=0), + "Bundle: Automated Wiring": ItemData(G.Parts, 1338011, count=0), "Bundle: Battery": ItemData(G.Parts, 1338012), "Bundle: Bauxite": ItemData(G.Parts, 1338013), "Bundle: Neural-Quantum Processor": ItemData(G.Parts, 1338014), #1.0 @@ -51,7 +52,7 @@ class Items: "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: AI Expansion Server": ItemData(G.Parts, 1338041, count=0), #1.0 "Bundle: Fused Modular Frame": ItemData(G.Parts, 1338042), "Bundle: Hard Drive": ItemData(G.Parts, 1338043, count=0), "Bundle: Heat Sink": ItemData(G.Parts, 1338044), @@ -66,9 +67,9 @@ class Items: "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: Magnetic Field Generator": ItemData(G.Parts, 1338056, count=0), "Bundle: Mercer Sphere": ItemData(G.Parts, 1338057, count=0), - "Bundle: Modular Engine": ItemData(G.Parts, 1338058), + "Bundle: Modular Engine": ItemData(G.Parts, 1338058, count=0), "Bundle: Modular Frame": ItemData(G.Parts, 1338059), "Bundle: Motor": ItemData(G.Parts, 1338060), "Bundle: Mycelia": ItemData(G.Parts, 1338061), @@ -106,9 +107,9 @@ class Items: "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: Smart Plating": ItemData(G.Parts, 1338096, count=0), "Bundle: Smokeless Powder": ItemData(G.Parts, 1338097), - "Bundle: Solid Biofuel": ItemData(G.Parts, 1338098), + "Bundle: Solid Biofuel": ItemData(G.Parts, 1338098, C.useful), "Bundle: Somersloop": ItemData(G.Parts, 1338099, count=0), "Bundle: Stator": ItemData(G.Parts, 1338100), "Bundle: Silver Hog Statue": ItemData(G.Parts, 1338101), @@ -118,13 +119,13 @@ class Items: "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: Thermal Propulsion Rocket": ItemData(G.Parts, 1338108, count=0), "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: Versatile Framework": ItemData(G.Parts, 1338114, count=0), "Bundle: Wire": ItemData(G.Parts, 1338115), "Bundle: Wood": ItemData(G.Parts, 1338116), "Bundle: Plasma Spitter Remains": ItemData(G.Parts, 1338117), @@ -137,8 +138,8 @@ class Items: "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: Biochemical Sculptor": ItemData(G.Parts, 1338127, count=0), + "Bundle: Ballistic Warp Drive": ItemData(G.Parts, 1338128, count=0), "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), @@ -146,13 +147,13 @@ class Items: "Bundle: Dark Matter Crystal": ItemData(G.Parts, 1338133), #1338134 - 1338149 Reserved for future parts #1338150 - 1338199 Equipment / Ammo - "Bundle: Bacon Agaric": ItemData(G.Ammo, 1338150), - "Bundle: Beryl Nut": ItemData(G.Ammo, 1338151), + "Bundle: Bacon Agaric": ItemData(G.Ammo, 1338150, count=0), + "Bundle: Beryl Nut": ItemData(G.Ammo, 1338151, count=0), "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: Iodine-Infused Filter": ItemData(G.Equipment, 1338156, C.useful, count=3), #1.1 + "Bundle: Iodine-Infused Filter": ItemData(G.Equipment, 1338156, count=3), #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), @@ -170,7 +171,7 @@ class Items: "Bundle: Nobelisk Detonator": ItemData(G.Equipment, 1338171, count=0), "Bundle: Nuke Nobelisk": ItemData(G.Ammo, 1338172), "Bundle: Object Scanner": ItemData(G.Equipment, 1338173, count=0), - "Bundle: Paleberry": ItemData(G.Ammo, 1338174), + "Bundle: Paleberry": ItemData(G.Ammo, 1338174, count=0), "Bundle: Parachute": ItemData(G.Equipment, 1338175, count=0), "Bundle: Pulse Nobelisk": ItemData(G.Ammo, 1338176), "Bundle: Rebar Gun": ItemData(G.Equipment, 1338177, count=0), @@ -183,7 +184,7 @@ class Items: "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.Equipment, 1338187, C.useful, count=3), + "Bundle: Gas Filter": ItemData(G.Equipment, 1338187, count=3), # Special cases "Small Inflated Pocket Dimension": ItemData(G.Upgrades, 1338188, C.useful, 11), "Inflated Pocket Dimension": ItemData(G.Upgrades, 1338189, C.useful, 5), @@ -240,7 +241,7 @@ class Items: "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: Diluted Packaged Fuel": 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), @@ -358,7 +359,7 @@ class Items: "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: Nuclear Pasta": ItemData(G.Recipe, 1338360, C.progression, count=0), "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 @@ -385,7 +386,7 @@ class Items: "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: Diluted Packaged Fuel": ItemData(G.Recipe, 1338387, C.progression), # Duplicated "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), @@ -599,9 +600,9 @@ class Items: "Building: Dimensional Depot Uploader": ItemData(G.Building, 1338728, C.useful), # Added in 1.1 "Building: Priority Merger": ItemData(G.Building | G.NeverExclude, 1338729, C.useful), - "Building: Conveyor Wall Hole": ItemData(G.Building, 1338730, C.useful), - "Building: Conveyor Throughput Monitor": ItemData(G.Building, 1338731, C.useful), - "Building: Basic Shelf Unit": ItemData(G.Building, 1338732, C.useful), + "Building: Conveyor Wall Hole": ItemData(G.Building, 1338730, C.useful, 0), + "Building: Conveyor Throughput Monitor": ItemData(G.Building, 1338731, C.useful, 0), + "Building: Basic Shelf Unit": ItemData(G.Building, 1338732, C.useful, 0), "Building: Beam Expansion Pack": ItemData(G.Beams, 1338733, C.filler, 0), "Building: Ventilation Bundle": ItemData(G.Building, 1338734, C.filler, 0), ### @@ -820,7 +821,7 @@ class Items: #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, C.useful), + "Single: Blade Runners": ItemData(G.Equipment, 1339152), "Single: Boom Box": ItemData(G.Equipment, 1339153), "Single: Chainsaw": ItemData(G.Equipment, 1339154, C.useful), "Single: Cluster Nobelisk": ItemData(G.Ammo, 1339155, count=0), @@ -858,8 +859,6 @@ class Items: "Single: Gas Filter": ItemData(G.Equipment, 1339187, count=0) } - 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 details.count > 0 and details.category & (G.Parts | G.Ammo)) @@ -871,7 +870,15 @@ class Items: # To allow hinting for first part recipe in logic for part, recipes in game_logic.recipes.items(): - groups[part] = {recipe.name for recipe in recipes} + recipes_for_part: set[str] = {recipe.name for recipe in recipes if not recipe.implicitly_unlocked} + if recipes_for_part: + + for original, indirect in game_logic.indirect_recipes.items(): + if indirect in recipes_for_part: + recipes_for_part.remove(indirect) + recipes_for_part.add(original) + + groups[part] = recipes_for_part for name, data in cls.item_data.items(): for category in data.category: @@ -901,65 +908,89 @@ class Items: @classmethod - def create_item(cls, instance: Optional["Items"], name: str, player: int) -> Item: + def create_item_uninitialized(cls, name: str, player: int) -> Item: data: ItemData = cls.item_data[name] + return Item(name, data.type, data.code, player) + + def create_item(self, name: str, player: int) -> Item: + data: ItemData = self.item_data[name] type = data.type if type == C.progression \ and (data.category & (G.Recipe | G.Building)) and not (data.category & G.NeverExclude) \ - and instance and instance.critical_path.required_item_names \ - and name not in instance.critical_path.required_item_names: + and self.critical_path.required_item_names and name not in self.critical_path.required_item_names: type = C.useful return Item(name, type, data.code, player) - def get_filler_item_name(self, filler_items: tuple[str, ...], random: Random) -> str: + @classmethod + def get_filler_item_name_uninitialized(cls, random: Random) -> str: + return random.choice(cls.filler_items) + + def get_filler_item_name(self, random: Random, filler_items: Collection[str] | None) -> str: if self.enabled_traps and random.random() < (self.trap_chance / 100): return random.choice(self.enabled_traps) else: - return random.choice(filler_items) + if filler_items: + return random.choice(filler_items) + else: + return Items.get_filler_item_name_uninitialized(random) - def get_excluded_items(self, multiworld: MultiWorld) -> set[str]: + def get_excluded_items(self, precollected_items: list[Item]) -> set[str]: excluded_items: set[str] = { item.name - for item in multiworld.precollected_items[self.player] + for item in precollected_items if item.name in self.item_data - and not (self.item_data[item.name].category & self.non_unique_item_categories) and item.name not in self.options.start_inventory_from_pool.value } + excluded_items.update({"Building: "+ building for building in self.critical_path.buildings_to_exclude}) excluded_items.update({"Bundle: "+ part for part in self.critical_path.parts_to_exclude}) excluded_items.update({"Single: "+ part for part in self.critical_path.parts_to_exclude}) - excluded_items.update({"Building: "+ building for building in self.critical_path.buildings_to_exclude}) + excluded_items.update({recipe for recipe in self.critical_path.recipes_to_exclude}) + excluded_items.update(self.critical_path.implicitly_unlocked) + + # since we dont have part logic setup for Transports + if (self.options.final_elevator_package == 1): + excluded_items.add("Transport: Drones") + + # Remove excluded items that arent unique + excluded_items = excluded_items - { + item_name + for item_name, data in self.item_data.items() + if data.category & (G.Parts | G.Equipment | G.Ammo | G.Trap | G.Upgrades) + } return excluded_items - def build_item_pool(self, random: Random, multiworld: MultiWorld, number_of_locations: int) -> list[Item]: - excluded_from_pool: set[str] = self.get_excluded_items(multiworld) \ - .union(self.critical_path.implicitly_unlocked) + def build_item_pool(self, random: Random, precollected_items: list[Item], number_of_locations: int) -> list[Item]: + excluded_from_pool: set[str] = self.get_excluded_items(precollected_items) pool_items: list[Item] = [ - self.create_item(self, name, self.player) + self.create_item(name, self.player) for name, data in self.item_data.items() for _ in range(data.count) - if data.category & self.pool_item_categories - and (data.category & G.Equipment or name not in excluded_from_pool) + if data.category & (G.Recipe | G.Building | G.Equipment | G.Ammo | G.Transport | G.Upgrades) + and name not in excluded_from_pool ] - pool: list[Item] = [item for item in pool_items if item.classification != C.filler] - - filler_pool_size: int = number_of_locations - len(pool) - - if (filler_pool_size < 0): + pool: list[Item] = [ + item + for item in pool_items + if item.classification != C.filler or self.item_data[item.name].category & (G.Equipment | G.Ammo) + ] + + free_space: int = number_of_locations - len(pool) + if (free_space < 0): raise Exception(f"Location pool starved, trying to add {len(pool)} items to {number_of_locations} locations") - filtered_filler_items = tuple(item for item in self.filler_items if item not in excluded_from_pool) + non_excluded_filler_items: list[str] = [item for item in self.filler_items if item not in excluded_from_pool] pool += [ - self.create_item(self, self.get_filler_item_name(filtered_filler_items, random), self.player) - for _ in range(filler_pool_size) + self.create_item(self.get_filler_item_name(random, non_excluded_filler_items), self.player) + for _ in range(free_space) ] return pool diff --git a/worlds/satisfactory/__init__.py b/worlds/satisfactory/__init__.py index d1f01322aa..a46d83d1ab 100644 --- a/worlds/satisfactory/__init__.py +++ b/worlds/satisfactory/__init__.py @@ -27,10 +27,13 @@ class SatisfactoryWorld(World): ut_can_gen_without_yaml = True game_logic: ClassVar[GameLogic] = GameLogic() - state_logic: StateLogic - items: Items - critical_path: CriticalPathCalculator + + # These are set in generate_early and thus aren't always available + state_logic: StateLogic | None = None + items: Items | None = None + critical_path: CriticalPathCalculator | None = None critical_path_seed: float | None = None + # item_name_to_id = Items.item_names_and_ids location_name_to_id = Locations().get_locations_for_data_package() @@ -79,8 +82,10 @@ class SatisfactoryWorld(World): self.setup_events() number_of_locations: int = len(self.multiworld.get_unfilled_locations(self.player)) + precollected_items: list[Item] = self.multiworld.precollected_items[self.player] + self.multiworld.itempool += \ - self.items.build_item_pool(self.random, self.multiworld, number_of_locations) + self.items.build_item_pool(self.random, precollected_items, number_of_locations) def set_rules(self) -> None: @@ -98,15 +103,15 @@ class SatisfactoryWorld(World): def collect(self, state: CollectionState, item: Item) -> bool: change = super().collect(state, item) - if change and item.name == "Recipe: Quartz Purification": - state.prog_items[self.player]["Recipe: Distilled Silica"] = 1 + if change and item.name in self.game_logic.indirect_recipes: + state.prog_items[self.player][self.game_logic.indirect_recipes[item.name]] = 1 return change def remove(self, state: CollectionState, item: Item) -> bool: change = super().remove(state, item) - if change and item.name == "Recipe: Quartz Purification": - del state.prog_items[self.player]["Recipe: Distilled Silica"] + if change and item.name in self.game_logic.indirect_recipes: + del state.prog_items[self.player][self.game_logic.indirect_recipes[item.name]] return change @@ -159,6 +164,7 @@ class SatisfactoryWorld(World): "DeathLink": bool(self.options.death_link) } + def interpret_slot_data(self, slot_data: dict[str, Any] | None) -> dict[str, Any] | None: """Used by Universal Tracker to correctly rebuild state""" @@ -197,15 +203,12 @@ class SatisfactoryWorld(World): return slot_data + def write_spoiler_header(self, spoiler_handle: TextIO) -> None: if self.options.randomize_starter_recipes: spoiler_handle.write(f'Starter Recipes: {sorted(self.critical_path.tier_0_recipes)}\n') - def get_filler_item_name(self) -> str: - return self.items.get_filler_item_name(self.items.filler_items, self.random) - - def setup_events(self) -> None: location: SatisfactoryLocation for location in self.multiworld.get_locations(self.player): @@ -218,8 +221,18 @@ class SatisfactoryWorld(World): location.show_in_spoiler = False + def get_filler_item_name(self) -> str: + if self.items: + return self.items.get_filler_item_name(self.random) + else: + return Items.get_filler_item_name_uninitialized(self.random) + + def create_item(self, name: str) -> Item: - return Items.create_item(self.items, name, self.player) + if self.items: + return self.items.create_item(name, self.player) + else: + return Items.create_item_uninitialized(name, self.player) def push_precollected(self, item_name: str) -> None: