Implemented abstract base classes + some fixes

This commit is contained in:
Jarno Westhof
2025-04-05 19:13:52 +02:00
parent 132a3957ed
commit 5cf65edc11
9 changed files with 117 additions and 118 deletions

View File

@@ -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

View File

@@ -1,5 +1,5 @@
from enum import IntFlag
from typing import NamedTuple, Set
from typing import NamedTuple
from BaseClasses import ItemClassification
class ItemGroups(IntFlag):

View File

@@ -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 \

View File

@@ -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))

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -1,7 +1,6 @@
from BaseClasses import Tutorial
from ..AutoWorld import WebWorld
class SatisfactoryWebWorld(WebWorld):
theme = "dirt"
setup = Tutorial(

View File

@@ -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([])