Merge branch 'new_shops' into Archipelago_Main
This commit is contained in:
		
						commit
						d1709764ef
					
				|  | @ -1057,17 +1057,19 @@ 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, price_rate_display | ||||
|         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'] | ||||
|                 my_price = item['price'] // price_rate_display.get(item['price_type'], 1) | ||||
|                 shopdata['item_{}'.format(index)] = f"{item['item']} — {my_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'])) | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							|  | @ -4,7 +4,7 @@ import Utils | |||
| from Patch import read_rom | ||||
| 
 | ||||
| JAP10HASH = '03a63945398191337e896e5771f77173' | ||||
| RANDOMIZERBASEHASH = '13a75c5dd28055fbcf8f69bd8161871d' | ||||
| RANDOMIZERBASEHASH = 'e397fef0e947d1bd760c68c4fe99a600' | ||||
| 
 | ||||
| import io | ||||
| import json | ||||
|  | @ -22,7 +22,7 @@ from typing import Optional | |||
| 
 | ||||
| from BaseClasses import CollectionState, Region | ||||
| from worlds.alttp.SubClasses import ALttPLocation | ||||
| from worlds.alttp.Shops import ShopType | ||||
| from worlds.alttp.Shops import ShopType, ShopPriceType | ||||
| from worlds.alttp.Dungeons import dungeon_music_addresses | ||||
| from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address | ||||
| from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable | ||||
|  | @ -1707,9 +1707,16 @@ 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 | ||||
|             if item['price_type'] != ShopPriceType.Rupees: | ||||
|                 # Set special price flag 0x8000 | ||||
|                 # Then set the type of price we're setting 0x7F00 (this starts from Hearts, not Rupees, subtract 1) | ||||
|                 # Then append the price/index into the second byte 0x00FF | ||||
|                 price_data = int16_as_bytes(0x8000 | 0x100 * (item["price_type"] - 1) | item['price']) | ||||
|             else: | ||||
|                 price_data = int16_as_bytes(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: | ||||
|  | @ -1717,7 +1724,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) | ||||
|  |  | |||
|  | @ -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,12 +13,27 @@ logger = logging.getLogger("Shops") | |||
| 
 | ||||
| 
 | ||||
| @unique | ||||
| class ShopType(Enum): | ||||
| class ShopType(IntEnum): | ||||
|     Shop = 0 | ||||
|     TakeAny = 1 | ||||
|     UpgradeShop = 2 | ||||
| 
 | ||||
| 
 | ||||
| @unique | ||||
| class ShopPriceType(IntEnum): | ||||
|     Rupees = 0 | ||||
|     Hearts = 1 | ||||
|     Magic = 2 | ||||
|     Bombs = 3 | ||||
|     Arrows = 4 | ||||
|     HeartContainer = 5 | ||||
|     BombUpgrade = 6 | ||||
|     ArrowUpgrade = 7 | ||||
|     Keys = 8 | ||||
|     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 | ||||
|  | @ -87,10 +102,11 @@ class Shop(): | |||
| 
 | ||||
|     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, | ||||
|                       player: int = 0): | ||||
|                       player: int = 0, price_type: int = ShopPriceType.Rupees): | ||||
|         self.inventory[slot] = { | ||||
|             'item': item, | ||||
|             'price': price, | ||||
|             'price_type': price_type, | ||||
|             'max': max, | ||||
|             'replacement': replacement, | ||||
|             'replacement_price': replacement_price, | ||||
|  | @ -98,7 +114,8 @@ class Shop(): | |||
|             'player': player | ||||
|         } | ||||
| 
 | ||||
|     def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0): | ||||
|     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") | ||||
| 
 | ||||
|  | @ -108,6 +125,7 @@ class Shop(): | |||
|         self.inventory[slot] = { | ||||
|             'item': item, | ||||
|             'price': price, | ||||
|             'price_type': price_type, | ||||
|             'max': max, | ||||
|             'replacement': self.inventory[slot]["item"], | ||||
|             'replacement_price': self.inventory[slot]["price"], | ||||
|  | @ -170,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 | ||||
|  | @ -226,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) | ||||
|  | @ -254,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(): | ||||
|  | @ -272,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) | ||||
|  | @ -344,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" | ||||
|  | @ -371,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) | ||||
|  | @ -421,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: | ||||
|  | @ -432,7 +458,7 @@ def shuffle_shops(world, items, player: int): | |||
|             for item in new_items: | ||||
|                 world.push_precollected(ItemFactory(item, player)) | ||||
| 
 | ||||
|     if 'p' in option or 'i' in option: | ||||
|     if any(setting in option for setting in 'ipP'): | ||||
|         shops = [] | ||||
|         upgrade_shops = [] | ||||
|         total_inventory = [] | ||||
|  | @ -461,6 +487,13 @@ def shuffle_shops(world, items, player: int): | |||
|                 for item in shop.inventory: | ||||
|                     adjust_item(item) | ||||
| 
 | ||||
|         if 'P' in option: | ||||
|             for item in total_inventory: | ||||
|                 price_to_funny_price(item, world, player) | ||||
|             # Don't apply to upgrade shops | ||||
|             # Upgrade shop is only one place, and will generally be too easy to | ||||
|             # replenish hearts and bombs | ||||
| 
 | ||||
|         if 'i' in option: | ||||
|             world.random.shuffle(total_inventory) | ||||
| 
 | ||||
|  | @ -469,3 +502,82 @@ def shuffle_shops(world, items, player: int): | |||
|                 slots = shop.slots | ||||
|                 shop.inventory = total_inventory[i:i + slots] | ||||
|                 i += slots | ||||
| 
 | ||||
| 
 | ||||
| price_blacklist = { | ||||
|     ShopPriceType.Rupees: {'Rupees'}, | ||||
|     ShopPriceType.Hearts: {'Small Heart', 'Apple'}, | ||||
|     ShopPriceType.Magic: {'Magic Jar'}, | ||||
|     ShopPriceType.Bombs: {'Bombs', 'Single Bomb'}, | ||||
|     ShopPriceType.Arrows: {'Arrows', 'Single Arrow'}, | ||||
|     ShopPriceType.HeartContainer: {}, | ||||
|     ShopPriceType.BombUpgrade: {"Bomb Upgrade"}, | ||||
|     ShopPriceType.ArrowUpgrade: {"Arrow Upgrade"}, | ||||
|     ShopPriceType.Keys: {"Small Key"}, | ||||
|     ShopPriceType.Potion: {}, | ||||
| } | ||||
| 
 | ||||
| price_chart = { | ||||
|     ShopPriceType.Rupees: lambda p: p, | ||||
|     ShopPriceType.Hearts: lambda p: min(5, p // 5) * 8, # Each heart is 0x8 in memory, Max of 5 hearts (20 total??) | ||||
|     ShopPriceType.Magic: lambda p: min(15, p // 5) * 8, # Each pip is 0x8 in memory, Max of 15 pips (16 total...) | ||||
|     ShopPriceType.Bombs: lambda p: max(1, min(10, p // 5)), # 10 Bombs max | ||||
|     ShopPriceType.Arrows: lambda p: max(1, min(30, p // 5)), # 30 Arrows Max | ||||
|     ShopPriceType.HeartContainer: lambda p: 0x8, | ||||
|     ShopPriceType.BombUpgrade: lambda p: 0x1, | ||||
|     ShopPriceType.ArrowUpgrade: lambda p: 0x1, | ||||
|     ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1), # Max of 3 keys for a price | ||||
|     ShopPriceType.Potion: lambda p: (p // 5) % 5, | ||||
| } | ||||
| 
 | ||||
| price_type_display_name = { | ||||
|     ShopPriceType.Rupees: "Rupees", | ||||
|     ShopPriceType.Hearts: "Hearts", | ||||
|     ShopPriceType.Bombs: "Bombs", | ||||
|     ShopPriceType.Arrows: "Arrows", | ||||
|     ShopPriceType.Keys: "Keys", | ||||
| } | ||||
| 
 | ||||
| # price division | ||||
| price_rate_display = { | ||||
|     ShopPriceType.Hearts: 8, | ||||
|     ShopPriceType.Magic: 8, | ||||
| } | ||||
| 
 | ||||
| # prices with no? logic requirements | ||||
| simple_price_types = [ | ||||
|     ShopPriceType.Rupees, | ||||
|     ShopPriceType.Hearts, | ||||
|     ShopPriceType.Bombs, | ||||
|     ShopPriceType.Arrows, | ||||
|     ShopPriceType.Keys | ||||
| ] | ||||
| 
 | ||||
| 
 | ||||
| def price_to_funny_price(item: dict, world, player: int): | ||||
|     """ | ||||
|     Converts a raw Rupee price into a special price type | ||||
|     """ | ||||
|     if item: | ||||
|         my_price_types = simple_price_types.copy() | ||||
|         world.random.shuffle(my_price_types) | ||||
|         for p_type in my_price_types: | ||||
|             # Ignore rupee prices, logic-based prices or Keys (if we're not on universal keys) | ||||
|             if p_type in [ShopPriceType.Rupees, ShopPriceType.BombUpgrade, ShopPriceType.ArrowUpgrade]: | ||||
|                 return | ||||
|             # If we're using keys... | ||||
|             # Check if we're in universal, check if our replacement isn't a Small Key | ||||
|             # Check if price isn't super small... (this will ideally be handled in a future table) | ||||
|             if p_type in [ShopPriceType.Keys]: | ||||
|                 if world.smallkey_shuffle[player] != smallkey_shuffle.option_universal: | ||||
|                     continue | ||||
|                 elif item['replacement'] and 'Small Key' in item['replacement']: | ||||
|                     continue | ||||
|                 if item['price'] < 50: | ||||
|                     continue | ||||
|             if any(x in item['item'] for x in price_blacklist[p_type]): | ||||
|                 continue | ||||
|             else: | ||||
|                 item['price'] = min(price_chart[p_type](item['price']) , 255) | ||||
|                 item['price_type'] = p_type | ||||
|             break | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue