From 21255b3b46c66f6f7392b5b5c6684ea89ee1c092 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Fri, 27 Aug 2021 14:52:33 +0200 Subject: [PATCH] LttP: Rename Shop Slot 1, 2, 3 to Shop Slot Left, Center, Right General: Move generic IDs from LttP to new Generic World Generate: ensure thread errors are collected before data from their completion may be referenced in playthrough/spoiler --- BaseClasses.py | 3 ++- Main.py | 26 +++++++++++++++++--------- worlds/alttp/ItemPool.py | 4 ++-- worlds/alttp/Regions.py | 6 ++---- worlds/alttp/Rom.py | 2 +- worlds/alttp/Shops.py | 31 ++++++++++++++++++------------- worlds/alttp/__init__.py | 4 +--- worlds/generic/__init__.py | 14 ++++++++++++++ worlds/hk/__init__.py | 2 ++ worlds/oribf/__init__.py | 2 ++ 10 files changed, 61 insertions(+), 33 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 5853bfbf..127bc367 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -911,7 +911,8 @@ class Boss(): return f"Boss({self.name})" class Location(): - shop_slot: bool = False + # If given as integer, then this is the shop's inventory index + shop_slot: Optional[int] = None shop_slot_disabled: bool = False event: bool = False locked: bool = False diff --git a/Main.py b/Main.py index b920f053..16f91ae6 100644 --- a/Main.py +++ b/Main.py @@ -147,11 +147,13 @@ def main(args, seed=None): longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types) numlength = 8 for name, cls in AutoWorld.AutoWorldRegister.world_types.items(): - logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} Items | {len(cls.location_names):3} Locations") - logger.info(f" Item IDs: {min(cls.item_id_to_name):{numlength}} - " - f"{max(cls.item_id_to_name):{numlength}} | " - f"Location IDs: {min(cls.location_id_to_name):{numlength}} - " - f"{max(cls.location_id_to_name):{numlength}}") + if not getattr(cls, "hidden", False): + logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} Items | " + f"{len(cls.location_names):3} Locations") + logger.info(f" Item IDs: {min(cls.item_id_to_name):{numlength}} - " + f"{max(cls.item_id_to_name):{numlength}} | " + f"Location IDs: {min(cls.location_id_to_name):{numlength}} - " + f"{max(cls.location_id_to_name):{numlength}}") logger.info('') for player in world.get_game_players("A Link to the Past"): @@ -383,22 +385,28 @@ def main(args, seed=None): raise Exception("Game appears as unbeatable. Aborting.") else: logger.warning("Location Accessibility requirements not fulfilled.") + + # retrieve exceptions via .result() if they occured. if multidata_task: - multidata_task.result() # retrieve exception if one exists + multidata_task.result() + for future in output_file_futures: + future.result() + pool.shutdown() # wait for all queued tasks to complete + if not args.skip_playthrough: logger.info('Calculating playthrough.') create_playthrough(world) + if args.create_spoiler: world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase)) - for future in output_file_futures: - future.result() + zipfilename = output_path(f"AP_{world.seed_name}.zip") logger.info(f'Creating final archive at {zipfilename}.') with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf: for file in os.scandir(temp_dir): - zf.write(os.path.join(temp_dir, file), arcname=file.name) + zf.write(file.path, arcname=file.name) logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start) return world diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py index 494ec01e..558fb61c 100644 --- a/worlds/alttp/ItemPool.py +++ b/worlds/alttp/ItemPool.py @@ -500,14 +500,14 @@ def create_dynamic_shop_locations(world, player): if item is None: continue if item['create_location']: - loc = ALttPLocation(player, "{} Slot {}".format(shop.region.name, i + 1), parent=shop.region) + loc = ALttPLocation(player, f"{shop.region.name} {shop.slot_names[i]}", parent=shop.region) shop.region.locations.append(loc) world.dynamic_locations.append(loc) world.clear_location_cache() world.push_item(loc, ItemFactory(item['item'], player), False) - loc.shop_slot = True + loc.shop_slot = i loc.event = True loc.locked = True diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py index 42df8c8e..a3f92bd2 100644 --- a/worlds/alttp/Regions.py +++ b/worlds/alttp/Regions.py @@ -676,12 +676,10 @@ location_table: typing.Dict[str, from worlds.alttp.Shops import shop_table_by_location_id, shop_table_by_location lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int} -lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, - -1: "Cheat Console", -2: "Server"} +lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}} lookup_id_to_name.update(shop_table_by_location_id) lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int} -lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, - "Cheat Console": -1, "Server": -2} +lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}} lookup_name_to_id.update(shop_table_by_location) lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks', diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index 29f94036..be22447d 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -763,7 +763,7 @@ def patch_rom(world, rom, player, enemized): # patch items for location in world.get_locations(): - if location.player != player or location.address is None or location.shop_slot: + if location.player != player or location.address is None or location.shop_slot is not None: continue itemid = location.item.code if location.item is not None else 0x5A diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 4f9ca4e9..f90f42fc 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -22,6 +22,11 @@ class Shop(): slots: int = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots blacklist: Set[str] = set() # items that don't work, todo: actually check against this type = ShopType.Shop + slot_names: Dict[int, str] = { + 0: "Left", + 1: "Center", + 2: "Right" + } def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool, sram_offset: int): self.region = region @@ -131,23 +136,22 @@ shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop, def FillDisabledShopSlots(world): shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops) - for location in shop_locations if location.shop_slot and location.shop_slot_disabled} + for location in shop_locations + if location.shop_slot is not None and location.shop_slot_disabled} for location in shop_slots: location.shop_slot_disabled = True - slot_num = int(location.name[-1]) - 1 shop: Shop = location.parent_region.shop - location.item = ItemFactory(shop.inventory[slot_num]['item'], location.player) + location.item = ItemFactory(shop.inventory[location.shop_slot]['item'], location.player) location.item_rule = lambda item: item.name == location.item.name and item.player == location.player def ShopSlotFill(world): shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops) - for location in shop_locations if location.shop_slot} + for location in shop_locations if location.shop_slot is not None} removed = set() for location in shop_slots: - slot_num = int(location.name[-1]) - 1 shop: Shop = location.parent_region.shop - if not shop.can_push_inventory(slot_num) or location.shop_slot_disabled: + if not shop.can_push_inventory(location.shop_slot) or location.shop_slot_disabled: location.shop_slot_disabled = True removed.add(location) @@ -179,7 +183,7 @@ def ShopSlotFill(world): shops_per_sphere.append(current_shops_slots) candidates_per_sphere.append(current_candidates) for location in sphere: - if location.shop_slot: + if location.shop_slot is not None: if not location.shop_slot_disabled: current_shops_slots.append(location) elif not location.locked and not location.item.name in blacklist_words: @@ -229,7 +233,7 @@ def ShopSlotFill(world): else: price = world.random.randrange(8, 56) - shop.push_inventory(int(location.name[-1]) - 1, item_name, price * 5, 1, + shop.push_inventory(location.shop_slot, item_name, price * 5, 1, location.item.player if location.item.player != location.player else 0) @@ -278,10 +282,10 @@ def create_shops(world, player: int): for index, item in enumerate(inventory): shop.add_inventory(index, *item) if not locked and num_slots: - slot_name = "{} Slot {}".format(region.name, index + 1) + slot_name = f"{region.name} {shop.slot_names[index]}" loc = ALttPLocation(player, slot_name, address=shop_table_by_location[slot_name], parent=region, hint_text="for sale") - loc.shop_slot = True + loc.shop_slot = index loc.locked = True if single_purchase_slots.pop(): if world.goal[player] != 'icerodhunt': @@ -337,9 +341,10 @@ total_shop_slots = len(shop_table) * 3 total_dynamic_shop_slots = sum(3 for shopname, data in shop_table.items() if not data[4]) # data[4] -> locked SHOP_ID_START = 0x400000 -shop_table_by_location_id = {cnt: s for cnt, s in enumerate( - (f"{name} Slot {num}" for name in [key for key, value in sorted(shop_table.items(), key=lambda item: item[1].sram_offset)] - for num in range(1, 4)), start=SHOP_ID_START)} +shop_table_by_location_id = dict(enumerate( + (f"{name} {Shop.slot_names[num]}" for name, shop_data in sorted(shop_table.items(), key=lambda item: item[1].sram_offset) + for num in range(3)), start=SHOP_ID_START)) + shop_table_by_location_id[(SHOP_ID_START + total_shop_slots)] = "Old Man Sword Cave" shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 1)] = "Take-Any #1" shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 2)] = "Take-Any #2" diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index cdea0d6d..a04b7557 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -26,14 +26,12 @@ class ALTTPWorld(World): options = alttp_options topology_present = True item_name_groups = item_name_groups - item_names = frozenset(item_table) - location_names = frozenset(lookup_name_to_id) hint_blacklist = {"Triforce"} item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int} location_name_to_id = lookup_name_to_id - data_version = 7 + data_version = 8 remote_items: bool = False set_rules = set_rules diff --git a/worlds/generic/__init__.py b/worlds/generic/__init__.py index 851c6be5..3693da47 100644 --- a/worlds/generic/__init__.py +++ b/worlds/generic/__init__.py @@ -1,6 +1,20 @@ from typing import NamedTuple, Union import logging +from ..AutoWorld import World + + +class GenericWorld(World): + game = "Archipelago" + topology_present = False + item_name_to_id = { + "Nothing": -1 + } + location_name_to_id = { + "Cheat Console" : -1, + "Server": -2 + } + hidden = True class PlandoItem(NamedTuple): item: str diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py index e441eaed..046ee6ea 100644 --- a/worlds/hk/__init__.py +++ b/worlds/hk/__init__.py @@ -19,6 +19,8 @@ class HKWorld(World): item_name_to_id = {name: data.id for name, data in item_table.items() if data.type != "Event"} location_name_to_id = lookup_name_to_id + hidden = True + def generate_basic(self): # Link regions self.world.get_entrance('Hollow Nest S&Q', self.player).connect(self.world.get_region('Hollow Nest', self.player)) diff --git a/worlds/oribf/__init__.py b/worlds/oribf/__init__.py index ad7c87c3..2ced5b95 100644 --- a/worlds/oribf/__init__.py +++ b/worlds/oribf/__init__.py @@ -19,6 +19,8 @@ class OriBlindForest(World): options = options + hidden = True + def generate_early(self): logic_sets = {"casual-core"} for logic_set in location_rules: