From 4d68000692e04936695353c5abb95899f25e4ff5 Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Sun, 12 Sep 2021 20:25:08 +0200 Subject: [PATCH] Shops: limit "funny_prices" to logic free choices --- BaseClasses.py | 11 ++--- Utils.py | 2 +- worlds/alttp/Rom.py | 5 ++- worlds/alttp/Shops.py | 99 +++++++++++++++++++++++++++---------------- 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index ba14dcda..130a7657 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1056,17 +1056,18 @@ class Spoiler(): listed_locations.update(other_locations) self.shops = [] - from worlds.alttp.Shops import ShopType + from worlds.alttp.Shops import ShopType, price_type_display_name for shop in self.world.shops: if not shop.custom: continue - shopdata = {'location': str(shop.region), - 'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop' - } + shopdata = { + 'location': str(shop.region), + 'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop' + } for index, item in enumerate(shop.inventory): if item is None: continue - shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item'] + shopdata['item_{}'.format(index)] = f"{item['item']} — {item['price']} {price_type_display_name[item['price_type']]}" if item['player'] > 0: shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('—', '(Player {}) — '.format(item['player'])) diff --git a/Utils.py b/Utils.py index 5f3cc0cb..d853513b 100644 --- a/Utils.py +++ b/Utils.py @@ -13,7 +13,7 @@ class Version(typing.NamedTuple): build: int -__version__ = "0.1.7" +__version__ = "0.1.8" version_tuple = tuplize_version(__version__) import builtins diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py index cb2ba14c..e8a697e3 100644 --- a/worlds/alttp/Rom.py +++ b/worlds/alttp/Rom.py @@ -1709,9 +1709,10 @@ def write_custom_shops(rom, world, player): # [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high][player] for index, item in enumerate(shop.inventory): - slot = 0 if shop.type == ShopType.TakeAny else index if item is None: break + price_data = int16_as_bytes(0x8000 | 0x100 * (item["price_type"] - 1) | item['price']) + slot = 0 if shop.type == ShopType.TakeAny else index if not item['item'] in item_table: # item not native to ALTTP item_code = get_nonnative_item_sprite(item['item']) else: @@ -1719,7 +1720,7 @@ def write_custom_shops(rom, world, player): if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]: rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask) - item_data = [shop_id, item_code] + int16_as_bytes(item['price']) + \ + item_data = [shop_id, item_code] + price_data + \ [item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + \ int16_as_bytes(item['replacement_price']) + [0 if item['player'] == player else item['player']] items_data.extend(item_data) diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py index 29c1d639..e62e8402 100644 --- a/worlds/alttp/Shops.py +++ b/worlds/alttp/Shops.py @@ -1,5 +1,5 @@ from __future__ import annotations -from enum import unique, Enum +from enum import unique, IntEnum from typing import List, Optional, Set, NamedTuple, Dict import logging @@ -13,13 +13,14 @@ logger = logging.getLogger("Shops") @unique -class ShopType(Enum): +class ShopType(IntEnum): Shop = 0 TakeAny = 1 UpgradeShop = 2 + @unique -class ShopPriceType(Enum): +class ShopPriceType(IntEnum): Rupees = 0 Hearts = 1 Magic = 2 @@ -32,6 +33,7 @@ class ShopPriceType(Enum): Potion = 9 Item = 10 + 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 @@ -112,7 +114,8 @@ class Shop(): 'player': player } - def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0, price_type: int = ShopPriceType.Rupees): + def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0, + price_type: int = ShopPriceType.Rupees): if not self.inventory[slot]: raise ValueError("Inventory can't be pushed back if it doesn't exist") @@ -185,7 +188,8 @@ def ShopSlotFill(world): blacklist_word in item_name for blacklist_word in blacklist_words)} blacklist_words.add("Bee") - locations_per_sphere = list(sorted(sphere, key=lambda location: location.name) for sphere in world.get_spheres()) + locations_per_sphere = list( + sorted(sphere, key=lambda location: location.name) for sphere in world.get_spheres()) # currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory # Potentially create Locations as needed and make inventory the only source, to prevent divergence @@ -241,7 +245,8 @@ def ShopSlotFill(world): item_name = location.item.name if location.item.game != "A Link to the Past": price = world.random.randrange(1, 28) - elif any(x in item_name for x in ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): + elif any(x in item_name for x in + ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): price = world.random.randrange(1, 7) elif any(x in item_name for x in ['Arrow', 'Bomb', 'Clock']): price = world.random.randrange(2, 14) @@ -269,7 +274,9 @@ def create_shops(world, player: int): world.random.shuffle(single_purchase_slots) if 'g' in option or 'f' in option: - default_shop_table = [i for l in [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if not world.retro[player] or x != 'arrows'] for i in l] + default_shop_table = [i for l in + [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if + not world.retro[player] or x != 'arrows'] for i in l] new_basic_shop = world.random.sample(default_shop_table, k=3) new_dark_shop = world.random.sample(default_shop_table, k=3) for name, shop in player_shop_table.items(): @@ -287,7 +294,8 @@ def create_shops(world, player: int): # make sure that blue potion is available in inverted, special case locked = None; lock when done. player_shop_table["Dark Lake Hylia Shop"] = \ player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None) - chance_100 = int(world.retro[player])*0.25+int(world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5 + chance_100 = int(world.retro[player]) * 0.25 + int( + world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5 for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items(): region = world.get_region(region_name, player) shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset) @@ -359,7 +367,8 @@ total_dynamic_shop_slots = sum(3 for shopname, data in shop_table.items() if not SHOP_ID_START = 0x400000 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) + (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" @@ -386,7 +395,8 @@ def set_up_shops(world, player: int): if world.retro[player]: rss = world.get_region('Red Shield Shop', player).shop replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], - ['Blue Shield', 50], ['Small Heart', 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them. + ['Blue Shield', 50], ['Small Heart', + 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them. if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: replacement_items.append(['Small Key (Universal)', 100]) replacement_item = world.random.choice(replacement_items) @@ -407,7 +417,6 @@ def set_up_shops(world, player: int): shop.push_inventory(next(slots), 'Single Arrow', 80) - def shuffle_shops(world, items, player: int): option = world.shop_shuffle[player] if 'u' in option: @@ -437,7 +446,8 @@ def shuffle_shops(world, items, player: int): if not new_items: break else: - logging.warning(f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.") + logging.warning( + f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.") bombupgrades = sum(1 for item in new_items if 'Bomb Upgrade' in item) arrowupgrades = sum(1 for item in new_items if 'Arrow Upgrade' in item) if bombupgrades: @@ -478,7 +488,6 @@ def shuffle_shops(world, items, player: int): adjust_item(item) if 'P' in option: - print('Making funny prices.') for item in total_inventory: price_to_funny_price(item, world, player) for shop in upgrade_shops: @@ -494,6 +503,7 @@ def shuffle_shops(world, items, player: int): shop.inventory = total_inventory[i:i + slots] i += slots + price_blacklist = { ShopPriceType.Rupees: {'Rupees'}, ShopPriceType.Hearts: {'Small Heart', 'Apple'}, @@ -501,37 +511,52 @@ price_blacklist = { ShopPriceType.Bombs: {'Bombs', 'Single Bomb'}, ShopPriceType.Arrows: {'Arrows', 'Single Arrow'}, ShopPriceType.HeartContainer: {}, - ShopPriceType.BombUpgrade: {}, - ShopPriceType.ArrowUpgrade: {}, - ShopPriceType.Keys: {}, + ShopPriceType.BombUpgrade: {"Bomb Upgrade"}, + ShopPriceType.ArrowUpgrade: {"Arrow Upgrade"}, + ShopPriceType.Keys: {"Small Key"}, ShopPriceType.Potion: {}, - # ShopPriceType.Item: {} } price_chart = { ShopPriceType.Rupees: lambda p: p, - ShopPriceType.Hearts: lambda p: min(1, p//5)*4, - ShopPriceType.Magic: lambda p: min(8, p//5)*4, - ShopPriceType.Bombs: lambda p: min(10, p//5), - ShopPriceType.Arrows: lambda p: min(30, p//5), + ShopPriceType.Hearts: lambda p: min(1, p // 5) * 4, + ShopPriceType.Magic: lambda p: min(8, p // 5) * 4, + ShopPriceType.Bombs: lambda p: min(10, p // 5), + ShopPriceType.Arrows: lambda p: min(30, p // 5), ShopPriceType.HeartContainer: lambda p: 0x8, ShopPriceType.BombUpgrade: lambda p: 0x1, ShopPriceType.ArrowUpgrade: lambda p: 0x1, - ShopPriceType.Keys: lambda p: min(3, (p//100)+1), - ShopPriceType.Potion: lambda p: (p//5)%5, - # ShopPriceType.Item: lambda p: 0, + ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1), + ShopPriceType.Potion: lambda p: (p // 5) % 5, } -def price_to_funny_price(item, world, player): - if item is None: - return - my_price_types = [x for x in price_blacklist] - my_choices = world.random.sample(my_price_types, len(my_price_types)) - for p in my_choices: - if p in [ShopPriceType.Rupees, ShopPriceType.BombUpgrade, ShopPriceType.ArrowUpgrade] or (p in [ShopPriceType.Keys] and world.smallkey_shuffle[player] == smallkey_shuffle.option_universal): - return - if any(x in item['item'] for x in price_blacklist[p]): - continue - else: - item['price'] = 0x8000 | 0x100*(p.value-1) | price_chart[p](item['price']) - break \ No newline at end of file +price_type_display_name = { + ShopPriceType.Rupees: "Rupees", + ShopPriceType.Hearts: "Hearts", + ShopPriceType.Bombs: "Bombs", + ShopPriceType.Arrows: "Arrows", +} + +# prices with no? logic requirements +simple_price_types = [ + ShopPriceType.Rupees, + ShopPriceType.Hearts, + ShopPriceType.Bombs, + ShopPriceType.Arrows, +] + + +def price_to_funny_price(item: dict, world, player: int): + if item: + my_price_types = simple_price_types.copy() + world.random.shuffle(my_price_types) + for p in my_price_types: + if p in [ShopPriceType.Rupees, ShopPriceType.BombUpgrade, ShopPriceType.ArrowUpgrade] or ( + p in [ShopPriceType.Keys] and world.smallkey_shuffle[player] == smallkey_shuffle.option_universal): + return + if any(x in item['item'] for x in price_blacklist[p]): + continue + else: + item['price'] = price_chart[p](item['price']) + item['price_type'] = p + break