From 26f95748946b5de1945a07133309fa720ef1aaac Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Tue, 11 Mar 2025 23:14:40 +0100 Subject: [PATCH] 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()