diff --git a/BaseClasses.py b/BaseClasses.py index 206e804e87..2629fc6963 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -49,7 +49,7 @@ class ThreadBarrierProxy: return getattr(self.obj, name) else: raise RuntimeError("You are in a threaded context and global random state was removed for your safety. " - "Please use multiworld.per_slot_randoms[player] or randomize ahead of output.") + "Please use world.random or randomize ahead of output.") class HasNameAndPlayer(Protocol): @@ -100,8 +100,6 @@ class MultiWorld(): game: Dict[int, str] random: random.Random - per_slot_randoms: Utils.DeprecateDict[int, random.Random] - """Deprecated. Please use `self.random` instead.""" class AttributeProxy(): def __init__(self, rule): @@ -177,8 +175,6 @@ class MultiWorld(): set_player_attr('game', "Archipelago") set_player_attr('completion_condition', lambda state: True) self.worlds = {} - self.per_slot_randoms = Utils.DeprecateDict("Using per_slot_randoms is now deprecated. Please use the " - "world's random object instead (usually self.random)", True) self.plando_options = PlandoOptions.none def get_all_ids(self) -> Tuple[int, ...]: diff --git a/Utils.py b/Utils.py index 36bff1b32e..2e5b1658ba 100644 --- a/Utils.py +++ b/Utils.py @@ -1012,23 +1012,6 @@ def deprecate(message: str, add_stacklevels: int = 0): warnings.warn(message, stacklevel=2 + add_stacklevels) -class DeprecateDict(dict): - log_message: str - should_error: bool - - def __init__(self, message: str, error: bool = False) -> None: - self.log_message = message - self.should_error = error - super().__init__() - - def __getitem__(self, item: Any) -> Any: - if self.should_error: - deprecate(self.log_message, add_stacklevels=1) - elif __debug__: - warnings.warn(self.log_message, stacklevel=2) - return super().__getitem__(item) - - def _extend_freeze_support() -> None: """Extend multiprocessing.freeze_support() to also work on Non-Windows and without setting spawn method first.""" # original upstream issue: https://github.com/python/cpython/issues/76327 diff --git a/requirements.txt b/requirements.txt index 3a91b16680..f75b29a03d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ jinja2==3.1.6 schema==0.7.8 kivy==2.3.1 bsdiff4==1.2.6 -platformdirs==4.9.4 +platformdirs==4.10.1 certifi==2026.2.25 cython==3.2.4 cymem==2.0.13 diff --git a/test/programs/test_generate.py b/test/programs/test_generate.py index 51800a0ec5..66f1a1b016 100644 --- a/test/programs/test_generate.py +++ b/test/programs/test_generate.py @@ -115,6 +115,7 @@ class TestGenerateWeights(TestGenerateMain): settings = get_settings() settings.generator.player_files_path = settings.generator.PlayerFilesPath(self.yaml_input_dir) settings.generator.players = 5 # arbitrary number, should be enough + settings.generator.race = 0 # make sure race mode is disabled so the below seed is actually respected settings._filename = None user_path_backup = user_path.cached_path user_path.cached_path = local_path() diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py index 6f0d211c1a..99b75f7c7a 100644 --- a/worlds/AutoWorld.py +++ b/worlds/AutoWorld.py @@ -363,7 +363,6 @@ class World(metaclass=AutoWorldRegister): self.multiworld = multiworld self.player = player self.random = Random(multiworld.random.getrandbits(64)) - multiworld.per_slot_randoms[player] = self.random def __getattr__(self, item: str) -> Any: if item == "settings": diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index 24910f0e24..51b129a70b 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -405,7 +405,6 @@ if not is_frozen(): from worlds import AutoWorldRegister from worlds.Files import APWorldContainer - from Launcher import open_folder import argparse parser = argparse.ArgumentParser(prog="Build APWorlds", description="Build script for APWorlds") diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py index d1b672db7d..a05ff0aa88 100644 --- a/worlds/messenger/__init__.py +++ b/worlds/messenger/__init__.py @@ -19,7 +19,7 @@ from .rules import MessengerHardRules, MessengerOOBRules, MessengerRules from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS, shuffle_shop_prices from .subclasses import MessengerItem, MessengerRegion, MessengerShopLocation from .transitions import disconnect_entrances, shuffle_transitions -from .universal_tracker import reverse_portal_exits_into_portal_plando, reverse_transitions_into_plando_connections +from .universal_tracker import reverse_portal_exits_into_portal_plando, reverse_shop_prices, reverse_transitions_into_plando_connections components.append( Component( @@ -169,7 +169,11 @@ class MessengerWorld(World): if self.options.early_meditation: self.multiworld.early_items[self.player]["Meditation"] = 1 - self.shop_prices, self.figurine_prices = shuffle_shop_prices(self) + if not hasattr(self.multiworld, "re_gen_passthrough"): + self.shop_prices, self.figurine_prices = shuffle_shop_prices(self) + else: + if slot_data := self.multiworld.re_gen_passthrough.get(self.game): + self.shop_prices, self.figurine_prices = reverse_shop_prices(slot_data["shop"], slot_data["figures"]) starting_portals = ["Autumn Hills", "Howling Grotto", "Glacial Peak", "Riviere Turquoise", "Sunken Shrine", "Searing Crags"] @@ -276,6 +280,10 @@ class MessengerWorld(World): self.multiworld.itempool += filler + if hasattr(self.multiworld, "re_gen_passthrough"): + if slot_data := self.multiworld.re_gen_passthrough.get(self.game): + self.total_shards = slot_data["max_price"] + def set_rules(self) -> None: logic = self.options.logic_level if logic == Logic.option_normal: diff --git a/worlds/messenger/universal_tracker.py b/worlds/messenger/universal_tracker.py index 9d752031bf..9da7af5f8e 100644 --- a/worlds/messenger/universal_tracker.py +++ b/worlds/messenger/universal_tracker.py @@ -1,9 +1,11 @@ from Options import PlandoConnection from .connections import RANDOMIZED_CONNECTIONS from .portals import REGION_ORDER, SHOP_POINTS, CHECKPOINTS +from .shop import FIGURINES, SHOP_ITEMS from .transitions import TRANSITIONS REVERSED_RANDOMIZED_CONNECTIONS = {v: k for k, v in RANDOMIZED_CONNECTIONS.items()} +REVERSED_SHOP_ITEMS = {v.internal_name: k for k, v in (SHOP_ITEMS | FIGURINES).items()} def find_spot(portal_key: int) -> str: @@ -26,6 +28,14 @@ def reverse_portal_exits_into_portal_plando(portal_exits: list[int]) -> list[Pla PlandoConnection("Glacial Peak", find_spot(portal_exits[5]), "both"), ] +def reverse_shop_prices( + shop_prices: dict[str, int], figures_prices: dict[str, int] +) -> tuple[dict[str, int], dict[str, int]]: + return ( + {REVERSED_SHOP_ITEMS[item_internal_name]: price for item_internal_name, price in shop_prices.items()}, + {REVERSED_SHOP_ITEMS[item_internal_name]: price for item_internal_name, price in figures_prices.items()}, + ) + def reverse_transitions_into_plando_connections(transitions: list[list[int]]) -> list[PlandoConnection]: plando_connections = [] diff --git a/worlds/sc2/__init__.py b/worlds/sc2/__init__.py index c57f54aa70..be27b5aa90 100644 --- a/worlds/sc2/__init__.py +++ b/worlds/sc2/__init__.py @@ -91,6 +91,7 @@ class SC2World(World): game = "Starcraft 2" web = Starcraft2WebWorld() settings: ClassVar[settings.Starcraft2Settings] + disable_ut = True item_name_to_id = {name: data.code for name, data in get_full_item_list().items()} location_name_to_id = {location.name: location.code for location in DEFAULT_LOCATION_LIST} @@ -433,7 +434,7 @@ def create_and_flag_explicit_item_locks_and_excludes(world: SC2World) -> List[Fi if max_count and count > max_count: return max_count return count - + auto_excludes = Counter({item_name: 1 for item_name in item_groups.legacy_items}) if world.options.exclude_overpowered_items.value == ExcludeOverpoweredItems.option_true: for item_name in item_groups.overpowered_items: @@ -1066,7 +1067,7 @@ def fill_pool_with_kerrigan_levels(world: SC2World, item_pool: List[StarcraftIte or (world.options.grant_story_levels and not kerrigan_build_missions) ): return - + def add_kerrigan_level_items(level_amount: int, item_amount: int): name = f"{level_amount} Kerrigan Level" if level_amount > 1: diff --git a/worlds/sc2/pool_filter.py b/worlds/sc2/pool_filter.py index abecf8264c..1087364e50 100644 --- a/worlds/sc2/pool_filter.py +++ b/worlds/sc2/pool_filter.py @@ -1,5 +1,6 @@ import logging from typing import Callable, Dict, List, Set, Tuple, TYPE_CHECKING, Iterable +from collections import Counter from BaseClasses import Location, ItemClassification from .item import StarcraftItem, ItemFilterFlags, item_names, item_parents, item_groups @@ -110,7 +111,7 @@ class ValidInventory: self.player = world.player self.world: 'SC2World' = world # Track all Progression items and those with complex rules for filtering - self.logical_inventory: Dict[str, int] = {} + self.logical_inventory: Counter[str] = Counter() for item in item_pool: if not item_table[item.name].is_important_for_filtering(): continue @@ -125,13 +126,13 @@ class ValidInventory: self.item_name_to_child_items.setdefault(parent_item, []).append(item) def has(self, item: str, player: int, count: int = 1) -> bool: - return self.logical_inventory.get(item, 0) >= count + return self.logical_inventory[item] >= count def has_any(self, items: Set[str], player: int) -> bool: - return any(self.logical_inventory.get(item) for item in items) + return any(self.logical_inventory[item] for item in items) def has_all(self, items: Set[str], player: int) -> bool: - return all(self.logical_inventory.get(item) for item in items) + return all(self.logical_inventory[item] for item in items) def has_group(self, item_group: str, player: int, count: int = 1) -> bool: return False # Deliberately fails here, as item pooling is not aware about mission layout @@ -140,13 +141,17 @@ class ValidInventory: return 0 # For item filtering assume no missions are beaten def count(self, item: str, player: int) -> int: - return self.logical_inventory.get(item, 0) + return self.logical_inventory[item] def count_from_list(self, items: Iterable[str], player: int) -> int: - return sum(self.logical_inventory.get(item, 0) for item in items) + return sum(self.logical_inventory[item] for item in items) def count_from_list_unique(self, items: Iterable[str], player: int) -> int: - return sum(item in self.logical_inventory for item in items) + result = 0 + for item in items: + if self.logical_inventory[item] > 0: + result += 1 + return result def generate_reduced_inventory(self, inventory_size: int, filler_amount: int, mission_requirements: List[Tuple[str, Callable]]) -> List[StarcraftItem]: """Attempts to generate a reduced inventory that can fulfill the mission requirements."""