From 5cf65edc11d3df46629a682799e8bfd987a88b9a Mon Sep 17 00:00:00 2001 From: Jarno Westhof Date: Sat, 5 Apr 2025 19:13:52 +0200 Subject: [PATCH] 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([])