diff --git a/BaseClasses.py b/BaseClasses.py index 855e69c5d4..278b227d97 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1018,10 +1018,9 @@ class Location: self.parent_region = parent def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool: - return ((self.always_allow(state, item) and item.name not in state.multiworld.non_local_items[item.player]) - or ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful)) - and self.item_rule(item) - and (not check_access or self.can_reach(state)))) + return ((self.progress_type != LocationProgressType.EXCLUDED or not (item.advancement or item.useful)) + and self.item_rule(item) + and (not check_access or self.can_reach(state))) def can_reach(self, state: CollectionState) -> bool: # self.access_rule computes faster on average, so placing it first for faster abort @@ -1062,6 +1061,15 @@ class Location: return "at " + self.name.replace("_", " ").replace("-", " ") +class AlwaysAllowLocation(Location): + """Subclass of Location that allows an always_allow item rule, which overrides all other requirements.""" + always_allow: Callable[[Item], bool] = staticmethod(lambda item, state: False) + + def can_fill(self, state: CollectionState, item: Item, check_access=True) -> bool: + return ((self.always_allow(state, item) and item.name not in state.multiworld.non_local_items[item.player]) + or super().can_fill(state, item, check_access)) + + class ItemClassification(IntFlag): filler = 0b0000 # aka trash, as in filler items like ammo, currency etc, progression = 0b0001 # Item that is logically relevant diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py index 64e4adaec9..1802f4dca4 100644 --- a/worlds/alttp/SubClasses.py +++ b/worlds/alttp/SubClasses.py @@ -2,14 +2,14 @@ from typing import Optional, TYPE_CHECKING from enum import IntEnum -from BaseClasses import Location, Item, ItemClassification, Region, MultiWorld +from BaseClasses import AlwaysAllowLocation, Item, ItemClassification, Region, MultiWorld if TYPE_CHECKING: from .Dungeons import Dungeon from .Regions import LTTPRegion -class ALttPLocation(Location): +class ALttPLocation(AlwaysAllowLocation): game: str = "A Link to the Past" crystal: bool player_address: Optional[int] diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py index 520ad22525..f2cf0e61a2 100644 --- a/worlds/generic/Rules.py +++ b/worlds/generic/Rules.py @@ -168,7 +168,8 @@ def allow_self_locking_items(spot: typing.Union[Location, Region], *item_names: """ This function sets rules on the supplied spot, such that the supplied item_name(s) can possibly be placed there. - spot: Location or Region that the item(s) are allowed to be placed in + spot: Location or Region that the item(s) are allowed to be placed in. + Affected Locations must subclass AlwaysAllowLocation. item_names: item name or names that are allowed to be placed in the Location or Region """ player = spot.player diff --git a/worlds/messenger/subclasses.py b/worlds/messenger/subclasses.py index b6a0b80b21..1533f5eec5 100644 --- a/worlds/messenger/subclasses.py +++ b/worlds/messenger/subclasses.py @@ -1,7 +1,7 @@ from functools import cached_property from typing import Optional, TYPE_CHECKING, cast -from BaseClasses import CollectionState, Item, ItemClassification, Location, Region +from BaseClasses import CollectionState, Item, ItemClassification, AlwaysAllowLocation, Region from .constants import NOTES, PHOBEKINS, PROG_ITEMS, USEFUL_ITEMS from .regions import MEGA_SHARDS, REGIONS, SEALS from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS @@ -33,7 +33,7 @@ class MessengerRegion(Region): world.multiworld.regions.append(self) -class MessengerLocation(Location): +class MessengerLocation(AlwaysAllowLocation): game = "The Messenger" def __init__(self, player: int, name: str, loc_id: Optional[int], parent: MessengerRegion) -> None: diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py index 3fff3b88c1..c1af58842c 100644 --- a/worlds/pokemon_rb/locations.py +++ b/worlds/pokemon_rb/locations.py @@ -1,5 +1,5 @@ -from BaseClasses import Location +from BaseClasses import AlwaysAllowLocation from .rom_addresses import rom_addresses from . import poke_data loc_id_start = 172000000 @@ -2767,7 +2767,7 @@ for mon in poke_data.evolves_from: location_data.append(LocationData("Evolution", mon, mon, event=True)) -class PokemonRBLocation(Location): +class PokemonRBLocation(AlwaysAllowLocation): game = "Pokemon Red and Blue" def __init__(self, player, name, address, rom_address, type, level, level_address): diff --git a/worlds/smz3/__init__.py b/worlds/smz3/__init__.py index 39aa42c07a..cbb9b1830d 100644 --- a/worlds/smz3/__init__.py +++ b/worlds/smz3/__init__.py @@ -5,7 +5,7 @@ import random import threading from typing import Dict, Set, TextIO -from BaseClasses import Region, Entrance, Location, MultiWorld, Item, ItemClassification, CollectionState, \ +from BaseClasses import Region, Entrance, AlwaysAllowLocation, MultiWorld, Item, ItemClassification, CollectionState, \ Tutorial from worlds.generic.Rules import set_rule from .TotalSMZ3.Item import ItemType @@ -652,7 +652,7 @@ class SMZ3World(World): return ret -class SMZ3Location(Location): +class SMZ3Location(AlwaysAllowLocation): game: str = "SMZ3" def __init__(self, player: int, name: str, address=None, parent=None):