diff --git a/BaseClasses.py b/BaseClasses.py index e3440586..42502b58 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1056,38 +1056,39 @@ class ShopType(Enum): TakeAny = 1 UpgradeShop = 2 -class Shop(object): - slots = 3 +class Shop(): + slots = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots + blacklist = set() # items that don't work, todo: actually check against this + type = ShopType.Shop - def __init__(self, region, room_id, type, shopkeeper_config, custom, locked: bool): + def __init__(self, region: Region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool): self.region = region self.room_id = room_id - self.type = type - self.inventory: List[Union[None, dict]] = [None, None, None] + self.inventory: List[Union[None, dict]] = [None] * self.slots self.shopkeeper_config = shopkeeper_config self.custom = custom self.locked = locked @property - def item_count(self): - return (3 if self.inventory[2] else - 2 if self.inventory[1] else - 1 if self.inventory[0] else - 0) + def item_count(self) -> int: + for x in range(self.slots - 1, -1, -1): # last x is 0 + if self.inventory[x]: + return x + 1 + return 0 - def get_bytes(self): + def get_bytes(self) -> List[int]: # [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index] entrances = self.region.entrances config = self.item_count if len(entrances) == 1 and entrances[0].name in door_addresses: - door_id = door_addresses[entrances[0].name][0]+1 + door_id = door_addresses[entrances[0].name][0] + 1 else: door_id = 0 - config |= 0x40 # ignore door id + config |= 0x40 # ignore door id if self.type == ShopType.TakeAny: config |= 0x80 - if self.type == ShopType.UpgradeShop: - config |= 0x10 # Alt. VRAM + elif self.type == ShopType.UpgradeShop: + config |= 0x10 # Alt. VRAM return [0x00]+int16_as_bytes(self.room_id)+[door_id, 0x00, config, self.shopkeeper_config, 0x00] def has_unlimited(self, item: str) -> bool: @@ -1111,7 +1112,7 @@ class Shop(object): return False def clear_inventory(self): - self.inventory = [None, None, None] + self.inventory = [None] * self.slots def add_inventory(self, slot: int, item: str, price: int, max: int = 0, replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False): @@ -1137,8 +1138,21 @@ class Shop(object): 'create_location': self.inventory[slot]["create_location"] } + +class TakeAny(Shop): + type = ShopType.TakeAny + + +class UpgradeShop(Shop): + type = ShopType.UpgradeShop + # Potions break due to VRAM flags set in UpgradeShop. + # Didn't check for more things breaking as not much else can be shuffled here currently + blacklist = item_name_groups["Potions"] + + class Spoiler(object): world: World + def __init__(self, world): self.world = world self.hashes = {} diff --git a/ItemPool.py b/ItemPool.py index ca186630..b4f116b9 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -1,7 +1,7 @@ from collections import namedtuple import logging -from BaseClasses import Region, RegionType, Shop, ShopType, Location +from BaseClasses import Region, RegionType, ShopType, Location, TakeAny from Bosses import place_bosses from Dungeons import get_dungeon_item_pool from EntranceShuffle import connect_entrance @@ -350,27 +350,31 @@ def shuffle_shops(world, items, player: int): if 'p' in option or 'i' in option: shops = [] + upgrade_shops = [] total_inventory = [] for shop in world.shops: - if shop.type == ShopType.Shop and shop.region.player == player and shop.region.name != 'Potion Shop': - shops.append(shop) - total_inventory.extend(shop.inventory) + if shop.region.player == player: + if shop.type == ShopType.UpgradeShop: + upgrade_shops.append(shop) + elif shop.type == ShopType.Shop and shop.region.name != 'Potion Shop': + shops.append(shop) + total_inventory.extend(shop.inventory) if 'p' in option: def price_adjust(price: int) -> int: # it is important that a base price of 0 always returns 0 as new price! return int(price * (0.5 + world.random.random() * 2)) - for item in total_inventory: + def adjust_item(item): if item: item["price"] = price_adjust(item["price"]) item['replacement_price'] = price_adjust(item["price"]) - for shop in world.shops: - if shop.type == ShopType.UpgradeShop and shop.region.player == player: - for item in shop.inventory: - if item: - item['price'] = price_adjust(item["price"]) - item['replacement_price'] = price_adjust(item["price"]) + + for item in total_inventory: + adjust_item(item) + for shop in upgrade_shops: + for item in shop.inventory: + adjust_item(item) if 'i' in option: world.random.shuffle(total_inventory) @@ -405,7 +409,7 @@ def set_up_take_anys(world, player): entrance = world.get_region(reg, player).entrances[0] connect_entrance(world, entrance.name, old_man_take_any.name, player) entrance.target = 0x58 - old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True, True) + old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True) world.shops.append(old_man_take_any.shop) swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player] @@ -427,7 +431,7 @@ def set_up_take_anys(world, player): entrance = world.get_region(reg, player).entrances[0] connect_entrance(world, entrance.name, take_any.name, player) entrance.target = target - take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True, True) + take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True) world.shops.append(take_any.shop) take_any.shop.add_inventory(0, 'Blue Potion', 0, 0) take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0) diff --git a/Main.py b/Main.py index 55320328..cf0594b1 100644 --- a/Main.py +++ b/Main.py @@ -425,7 +425,8 @@ def copy_dynamic_regions_and_locations(world, ret): # Note: ideally exits should be copied here, but the current use case (Take anys) do not require this if region.shop: - new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.custom, region.shop.locked) + new_reg.shop = region.shop.__class__(new_reg, region.shop.room_id, region.shop.shopkeeper_config, + region.shop.custom, region.shop.locked) ret.shops.append(new_reg.shop) for location in world.dynamic_locations: diff --git a/Regions.py b/Regions.py index a3ed9fec..aedc2604 100644 --- a/Regions.py +++ b/Regions.py @@ -1,6 +1,6 @@ import collections -from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType +from BaseClasses import Region, Location, Entrance, RegionType, Shop, TakeAny, UpgradeShop, ShopType def create_regions(world, player): @@ -365,12 +365,15 @@ def mark_light_world_regions(world, player: int): def create_shops(world, player: int): + cls_mapping = {ShopType.UpgradeShop: UpgradeShop, + ShopType.Shop: Shop, + ShopType.TakeAny: TakeAny} for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items(): if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop': locked = True inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] region = world.get_region(region_name, player) - shop = Shop(region, room_id, type, shopkeeper, custom, locked) + shop = cls_mapping[type](region, room_id, shopkeeper, custom, locked) region.shop = shop world.shops.append(shop) for index, item in enumerate(inventory):