From de567cc7016ebd3804c4f53250074ebacfd16d7c Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sat, 28 Aug 2021 12:56:52 +0200 Subject: [PATCH] LttP: Move more functionality into ALttPItem from Item LttP: More efficiently build !hint entrance info LttP: More efficiently check for and build Big Bomb Shop playthrough path --- BaseClasses.py | 62 +++++++------------------------------- Main.py | 46 ++++++++++++++-------------- Utils.py | 2 +- worlds/alttp/SubClasses.py | 47 ++++++++++++++++++++++++++++- worlds/alttp/__init__.py | 1 + 5 files changed, 83 insertions(+), 75 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 127bc367..62eb8d98 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -808,10 +808,6 @@ class Region(object): return False def can_fill(self, item: Item): - inside_dungeon_item = item.locked_dungeon_item - if inside_dungeon_item: - return self.dungeon.is_dungeon_item(item) and item.player == self.player - return True def __repr__(self): @@ -973,15 +969,24 @@ class Location(): class Item(): location: Optional[Location] = None world: Optional[MultiWorld] = None + code: Optional[str] = None # an item with ID None is called an Event, and does not get written to multidata game: str = "Generic" type: str = None - never_exclude = False # change manually to ensure that a specific nonprogression item never goes on an excluded location + # change manually to ensure that a specific non-progression item never goes on an excluded location + never_exclude = False + + # need to find a decent place for these to live and to allow other games to register texts if they want. pedestal_credit_text: str = "and the Unknown Item" sickkid_credit_text: Optional[str] = None magicshop_credit_text: Optional[str] = None zora_credit_text: Optional[str] = None fluteboy_credit_text: Optional[str] = None - code: Optional[str] = None # an item with ID None is called an Event, and does not get written to multidata + + # hopefully temporary attributes to satisfy legacy LttP code, proper implementation in subclass ALttPItem + smallkey: bool = False + bigkey: bool = False + map: bool = False + compass: bool = False def __init__(self, name: str, advancement: bool, code: Optional[int], player: int): self.name = name @@ -1008,51 +1013,6 @@ class Item(): def __hash__(self): return hash((self.name, self.player)) - @property - def crystal(self) -> bool: - return self.type == 'Crystal' - - @property - def smallkey(self) -> bool: - return self.type == 'SmallKey' - - @property - def bigkey(self) -> bool: - return self.type == 'BigKey' - - @property - def map(self) -> bool: - return self.type == 'Map' - - @property - def compass(self) -> bool: - return self.type == 'Compass' - - @property - def dungeon_item(self) -> Optional[str]: - if self.game == "A Link to the Past" and self.type in {"SmallKey", "BigKey", "Map", "Compass"}: - return self.type - - @property - def shuffled_dungeon_item(self) -> bool: - dungeon_item_type = self.dungeon_item - if dungeon_item_type: - return {"SmallKey" : self.world.keyshuffle, - "BigKey": self.world.bigkeyshuffle, - "Map": self.world.mapshuffle, - "Compass": self.world.compassshuffle}[dungeon_item_type][self.player] - return False - - @property - def locked_dungeon_item(self) -> bool: - dungeon_item_type = self.dungeon_item - if dungeon_item_type: - return not {"SmallKey" : self.world.keyshuffle, - "BigKey": self.world.bigkeyshuffle, - "Map": self.world.mapshuffle, - "Compass": self.world.compassshuffle}[dungeon_item_type][self.player] - return False - def __repr__(self): return self.__str__() diff --git a/Main.py b/Main.py index 47e160cc..85275ca4 100644 --- a/Main.py +++ b/Main.py @@ -249,20 +249,21 @@ def main(args, seed=None): for player in range(1, world.players + 1): checks_in_area[player]["Total"] = 0 - for location in [loc for loc in world.get_filled_locations() if type(loc.address) is int]: - main_entrance = get_entrance_to_region(location.parent_region) - if location.game != "A Link to the Past": - checks_in_area[location.player]["Light World"].append(location.address) - elif location.parent_region.dungeon: - dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', - 'Inverted Ganons Tower': 'Ganons Tower'} \ - .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) - checks_in_area[location.player][dungeonname].append(location.address) - elif main_entrance.parent_region.type == RegionType.LightWorld: - checks_in_area[location.player]["Light World"].append(location.address) - elif main_entrance.parent_region.type == RegionType.DarkWorld: - checks_in_area[location.player]["Dark World"].append(location.address) - checks_in_area[location.player]["Total"] += 1 + for location in world.get_filled_locations(): + if type(location.address) is int: + main_entrance = get_entrance_to_region(location.parent_region) + if location.game != "A Link to the Past": + checks_in_area[location.player]["Light World"].append(location.address) + elif location.parent_region.dungeon: + dungeonname = {'Inverted Agahnims Tower': 'Agahnims Tower', + 'Inverted Ganons Tower': 'Ganons Tower'} \ + .get(location.parent_region.dungeon.name, location.parent_region.dungeon.name) + checks_in_area[location.player][dungeonname].append(location.address) + elif main_entrance.parent_region.type == RegionType.LightWorld: + checks_in_area[location.player]["Light World"].append(location.address) + elif main_entrance.parent_region.type == RegionType.DarkWorld: + checks_in_area[location.player]["Dark World"].append(location.address) + checks_in_area[location.player]["Total"] += 1 oldmancaves = [] takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"] @@ -497,14 +498,15 @@ def create_playthrough(world): {str(location): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player}) if player in world.get_game_players("A Link to the Past"): - for path in dict(world.spoiler.paths).values(): - if any(exit_path == 'Pyramid Fairy' for (_, exit_path) in path): - if world.mode[player] != 'inverted': - world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = \ - get_path(state,world.get_region('Big Bomb Shop', player)) - else: - world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = \ - get_path(state,world.get_region('Inverted Big Bomb Shop', player)) + # If Pyramid Fairy Entrance needs to be reached, also path to Big Bomb Shop + # Maybe move the big bomb over to the Event system instead? + if any(exit_path == 'Pyramid Fairy' for path in world.spoiler.paths.values() for (_, exit_path) in path): + if world.mode[player] != 'inverted': + world.spoiler.paths[str(world.get_region('Big Bomb Shop', player))] = \ + get_path(state, world.get_region('Big Bomb Shop', player)) + else: + world.spoiler.paths[str(world.get_region('Inverted Big Bomb Shop', player))] = \ + get_path(state, world.get_region('Inverted Big Bomb Shop', player)) # we can finally output our playthrough world.spoiler.playthrough = {"0": sorted([str(item) for item in world.precollected_items if item.advancement])} diff --git a/Utils.py b/Utils.py index af62634f..66bc214e 100644 --- a/Utils.py +++ b/Utils.py @@ -13,7 +13,7 @@ class Version(typing.NamedTuple): build: int -__version__ = "0.1.6" +__version__ = "0.1.7" version_tuple = tuplize_version(__version__) import builtins diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py index aa349799..4b87c698 100644 --- a/worlds/alttp/SubClasses.py +++ b/worlds/alttp/SubClasses.py @@ -29,4 +29,49 @@ class ALttPItem(Item): self.zora_credit_text = zora_credit self.magicshop_credit_text = witch_credit self.fluteboy_credit_text = flute_boy_credit - self._hint_text = hint_text \ No newline at end of file + self._hint_text = hint_text + + @property + def crystal(self) -> bool: + return self.type == 'Crystal' + + @property + def smallkey(self) -> bool: + return self.type == 'SmallKey' + + @property + def bigkey(self) -> bool: + return self.type == 'BigKey' + + @property + def map(self) -> bool: + return self.type == 'Map' + + @property + def compass(self) -> bool: + return self.type == 'Compass' + + @property + def dungeon_item(self) -> Optional[str]: + if self.game == "A Link to the Past" and self.type in {"SmallKey", "BigKey", "Map", "Compass"}: + return self.type + + @property + def shuffled_dungeon_item(self) -> bool: + dungeon_item_type = self.dungeon_item + if dungeon_item_type: + return {"SmallKey" : self.world.keyshuffle, + "BigKey": self.world.bigkeyshuffle, + "Map": self.world.mapshuffle, + "Compass": self.world.compassshuffle}[dungeon_item_type][self.player] + return False + + @property + def locked_dungeon_item(self) -> bool: + dungeon_item_type = self.dungeon_item + if dungeon_item_type: + return not {"SmallKey" : self.world.keyshuffle, + "BigKey": self.world.bigkeyshuffle, + "Map": self.world.mapshuffle, + "Compass": self.world.compassshuffle}[dungeon_item_type][self.player] + return False \ No newline at end of file diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 843ab93e..98199e07 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -62,6 +62,7 @@ class ALTTPWorld(World): world.difficulty_requirements[player] = difficulties[world.difficulty[player]] def create_regions(self): + # noinspection PyAttributeOutsideInit self.rom_name_available_event = threading.Event() player = self.player