| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | from __future__ import annotations | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | from enum import unique, IntEnum | 
					
						
							| 
									
										
										
										
											2021-07-12 15:11:48 +02:00
										 |  |  | from typing import List, Optional, Set, NamedTuple, Dict | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | import logging | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-12 15:11:48 +02:00
										 |  |  | from worlds.alttp.SubClasses import ALttPLocation | 
					
						
							| 
									
										
										
										
											2021-01-30 23:37:35 +01:00
										 |  |  | from worlds.alttp.EntranceShuffle import door_addresses | 
					
						
							| 
									
										
										
										
											2021-02-19 13:45:50 +01:00
										 |  |  | from worlds.alttp.Items import item_name_groups, item_table, ItemFactory, trap_replaceable, GetBeemizerItem | 
					
						
							| 
									
										
										
										
											2021-08-30 09:59:20 -07:00
										 |  |  | from worlds.alttp.Options import smallkey_shuffle | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | from Utils import int16_as_bytes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | logger = logging.getLogger("Shops") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | @unique | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | class ShopType(IntEnum): | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |     Shop = 0 | 
					
						
							|  |  |  |     TakeAny = 1 | 
					
						
							|  |  |  |     UpgradeShop = 2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  | @unique | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | class ShopPriceType(IntEnum): | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  |     Rupees = 0 | 
					
						
							|  |  |  |     Hearts = 1 | 
					
						
							|  |  |  |     Magic = 2 | 
					
						
							|  |  |  |     Bombs = 3 | 
					
						
							|  |  |  |     Arrows = 4 | 
					
						
							|  |  |  |     HeartContainer = 5 | 
					
						
							|  |  |  |     BombUpgrade = 6 | 
					
						
							|  |  |  |     ArrowUpgrade = 7 | 
					
						
							|  |  |  |     Keys = 8 | 
					
						
							|  |  |  |     Potion = 9 | 
					
						
							|  |  |  |     Item = 10 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | class Shop(): | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     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 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |     type = ShopType.Shop | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |     slot_names: Dict[int, str] = { | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  |         0: " Left", | 
					
						
							|  |  |  |         1: " Center", | 
					
						
							|  |  |  |         2: " Right" | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |     def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool, sram_offset: int): | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         self.region = region | 
					
						
							|  |  |  |         self.room_id = room_id | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |         self.inventory: List[Optional[dict]] = [None] * self.slots | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         self.shopkeeper_config = shopkeeper_config | 
					
						
							|  |  |  |         self.custom = custom | 
					
						
							|  |  |  |         self.locked = locked | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |         self.sram_offset = sram_offset | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     @property | 
					
						
							|  |  |  |     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) -> 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 | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             door_id = 0 | 
					
						
							|  |  |  |             config |= 0x40  # ignore door id | 
					
						
							|  |  |  |         if self.type == ShopType.TakeAny: | 
					
						
							|  |  |  |             config |= 0x80 | 
					
						
							|  |  |  |         elif self.type == ShopType.UpgradeShop: | 
					
						
							|  |  |  |             config |= 0x10  # Alt. VRAM | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  |         return [0x00] + int16_as_bytes(self.room_id) + [door_id, 0x00, config, self.shopkeeper_config, 0x00] | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def has_unlimited(self, item: str) -> bool: | 
					
						
							|  |  |  |         for inv in self.inventory: | 
					
						
							|  |  |  |             if inv is None: | 
					
						
							|  |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |             if inv['max']: | 
					
						
							|  |  |  |                 if inv['replacement'] == item: | 
					
						
							|  |  |  |                     return True | 
					
						
							|  |  |  |             elif inv['item'] == item: | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def has(self, item: str) -> bool: | 
					
						
							|  |  |  |         for inv in self.inventory: | 
					
						
							|  |  |  |             if inv is None: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if inv['item'] == item: | 
					
						
							|  |  |  |                 return True | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |             if inv['replacement'] == item: | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |                 return True | 
					
						
							|  |  |  |         return False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def clear_inventory(self): | 
					
						
							|  |  |  |         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, | 
					
						
							| 
									
										
										
										
											2021-10-02 10:15:00 +02:00
										 |  |  |                       player: int = 0, price_type: int = ShopPriceType.Rupees, | 
					
						
							|  |  |  |                       replacement_price_type: int = ShopPriceType.Rupees): | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         self.inventory[slot] = { | 
					
						
							|  |  |  |             'item': item, | 
					
						
							|  |  |  |             'price': price, | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  |             'price_type': price_type, | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |             'max': max, | 
					
						
							|  |  |  |             'replacement': replacement, | 
					
						
							|  |  |  |             'replacement_price': replacement_price, | 
					
						
							| 
									
										
										
										
											2021-10-02 10:15:00 +02:00
										 |  |  |             'replacement_price_type': replacement_price_type, | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |             'create_location': create_location, | 
					
						
							|  |  |  |             'player': player | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |     def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0, | 
					
						
							|  |  |  |                        price_type: int = ShopPriceType.Rupees): | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         if not self.inventory[slot]: | 
					
						
							|  |  |  |             raise ValueError("Inventory can't be pushed back if it doesn't exist") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-29 00:21:42 -08:00
										 |  |  |         if not self.can_push_inventory(slot): | 
					
						
							|  |  |  |             logging.warning(f'Warning, there is already an item pushed into this slot.') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         self.inventory[slot] = { | 
					
						
							|  |  |  |             'item': item, | 
					
						
							|  |  |  |             'price': price, | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  |             'price_type': price_type, | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |             'max': max, | 
					
						
							|  |  |  |             'replacement': self.inventory[slot]["item"], | 
					
						
							|  |  |  |             'replacement_price': self.inventory[slot]["price"], | 
					
						
							| 
									
										
										
										
											2021-10-02 10:15:00 +02:00
										 |  |  |             'replacement_price_type': self.inventory[slot]["price_type"], | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |             'create_location': self.inventory[slot]["create_location"], | 
					
						
							|  |  |  |             'player': player | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def can_push_inventory(self, slot: int): | 
					
						
							|  |  |  |         return self.inventory[slot] and not self.inventory[slot]["replacement"] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class TakeAny(Shop): | 
					
						
							|  |  |  |     type = ShopType.TakeAny | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  |     slot_names: Dict[int, str] = { | 
					
						
							|  |  |  |         0: "", | 
					
						
							|  |  |  |         1: "", | 
					
						
							|  |  |  |         2: "" | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 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"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  | shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop, | 
					
						
							|  |  |  |                       ShopType.Shop: Shop, | 
					
						
							|  |  |  |                       ShopType.TakeAny: TakeAny} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  | def FillDisabledShopSlots(world): | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |     shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops) | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |                                       for location in shop_locations | 
					
						
							|  |  |  |                                       if location.shop_slot is not None and location.shop_slot_disabled} | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |     for location in shop_slots: | 
					
						
							|  |  |  |         location.shop_slot_disabled = True | 
					
						
							|  |  |  |         shop: Shop = location.parent_region.shop | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |         location.item = ItemFactory(shop.inventory[location.shop_slot]['item'], location.player) | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |         location.item_rule = lambda item: item.name == location.item.name and item.player == location.player | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | def ShopSlotFill(world): | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  |     shop_slots: Set[ALttPLocation] = {location for shop_locations in | 
					
						
							|  |  |  |                                       (shop.region.locations for shop in world.shops if shop.type != ShopType.TakeAny) | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |                                       for location in shop_locations if location.shop_slot is not None} | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |     removed = set() | 
					
						
							|  |  |  |     for location in shop_slots: | 
					
						
							|  |  |  |         shop: Shop = location.parent_region.shop | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |         if not shop.can_push_inventory(location.shop_slot) or location.shop_slot_disabled: | 
					
						
							| 
									
										
										
										
											2021-01-29 00:21:42 -08:00
										 |  |  |             location.shop_slot_disabled = True | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |             removed.add(location) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if removed: | 
					
						
							|  |  |  |         shop_slots -= removed | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if shop_slots: | 
					
						
							| 
									
										
										
										
											2021-08-30 01:16:04 +02:00
										 |  |  |         logger.info("Filling LttP Shop Slots") | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |         del shop_slots | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         from Fill import swap_location_item | 
					
						
							|  |  |  |         # TODO: allow each game to register a blacklist to be used here? | 
					
						
							|  |  |  |         blacklist_words = {"Rupee"} | 
					
						
							|  |  |  |         blacklist_words = {item_name for item_name in item_table if any( | 
					
						
							|  |  |  |             blacklist_word in item_name for blacklist_word in blacklist_words)} | 
					
						
							|  |  |  |         blacklist_words.add("Bee") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |         locations_per_sphere = list( | 
					
						
							|  |  |  |             sorted(sphere, key=lambda location: location.name) for sphere in world.get_spheres()) | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # 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 | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  |         cumu_weights = [] | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |         shops_per_sphere = [] | 
					
						
							|  |  |  |         candidates_per_sphere = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # sort spheres into piles of valid candidates and shops | 
					
						
							|  |  |  |         for sphere in locations_per_sphere: | 
					
						
							|  |  |  |             current_shops_slots = [] | 
					
						
							|  |  |  |             current_candidates = [] | 
					
						
							|  |  |  |             shops_per_sphere.append(current_shops_slots) | 
					
						
							|  |  |  |             candidates_per_sphere.append(current_candidates) | 
					
						
							|  |  |  |             for location in sphere: | 
					
						
							| 
									
										
										
										
											2022-08-04 14:10:58 +02:00
										 |  |  |                 if isinstance(location, ALttPLocation) and location.shop_slot is not None: | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |                     if not location.shop_slot_disabled: | 
					
						
							|  |  |  |                         current_shops_slots.append(location) | 
					
						
							| 
									
										
										
										
											2022-08-04 14:10:58 +02:00
										 |  |  |                 elif not location.locked and location.item.name not in blacklist_words: | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |                     current_candidates.append(location) | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  |             if cumu_weights: | 
					
						
							|  |  |  |                 x = cumu_weights[-1] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 x = 0 | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |             cumu_weights.append(len(current_candidates) + x) | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |             world.random.shuffle(current_candidates) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-07 09:45:30 +01:00
										 |  |  |         del locations_per_sphere | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |         for i, current_shop_slots in enumerate(shops_per_sphere): | 
					
						
							|  |  |  |             if current_shop_slots: | 
					
						
							| 
									
										
										
										
											2021-03-26 13:23:17 +01:00
										 |  |  |                 # getting all candidates and shuffling them feels cpu expensive, there may be a better method | 
					
						
							|  |  |  |                 candidates = [(location, i) for i, candidates in enumerate(candidates_per_sphere[i:], start=i) | 
					
						
							|  |  |  |                               for location in candidates] | 
					
						
							|  |  |  |                 world.random.shuffle(candidates) | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  |                 for location in current_shop_slots: | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                     shop: Shop = location.parent_region.shop | 
					
						
							| 
									
										
										
										
											2021-03-26 13:23:17 +01:00
										 |  |  |                     for index, (c, swapping_sphere_id) in enumerate(candidates):  # chosen item locations | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |                         if c.item_rule(location.item) and location.item_rule(c.item): | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |                             swap_location_item(c, location, check_locked=False) | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  |                             logger.debug(f'Swapping {c} into {location}:: {location.item}') | 
					
						
							| 
									
										
										
										
											2021-03-26 13:23:17 +01:00
										 |  |  |                             # remove candidate | 
					
						
							|  |  |  |                             candidates_per_sphere[swapping_sphere_id].remove(c) | 
					
						
							|  |  |  |                             candidates.pop(index) | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                             break | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         # This *should* never happen. But let's fail safely just in case. | 
					
						
							|  |  |  |                         logger.warning("Ran out of ShopShuffle Item candidate locations.") | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |                         location.shop_slot_disabled = True | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                         continue | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                     item_name = location.item.name | 
					
						
							| 
									
										
										
										
											2021-04-16 21:41:19 +02:00
										 |  |  |                     if location.item.game != "A Link to the Past": | 
					
						
							| 
									
										
										
										
											2022-01-02 21:07:43 -05:00
										 |  |  |                         if location.item.advancement: | 
					
						
							|  |  |  |                             price = world.random.randrange(8, 56) | 
					
						
							| 
									
										
										
										
											2022-06-17 03:23:27 +02:00
										 |  |  |                         elif location.item.useful: | 
					
						
							| 
									
										
										
										
											2022-01-02 21:07:43 -05:00
										 |  |  |                             price = world.random.randrange(4, 28) | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             price = world.random.randrange(2, 14) | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |                     elif any(x in item_name for x in | 
					
						
							|  |  |  |                              ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                         price = world.random.randrange(1, 7) | 
					
						
							| 
									
										
										
										
											2021-01-31 06:47:57 +01:00
										 |  |  |                     elif any(x in item_name for x in ['Arrow', 'Bomb', 'Clock']): | 
					
						
							|  |  |  |                         price = world.random.randrange(2, 14) | 
					
						
							| 
									
										
										
										
											2021-02-07 10:05:25 +01:00
										 |  |  |                     elif any(x in item_name for x in ['Small Key', 'Heart']): | 
					
						
							| 
									
										
										
										
											2021-01-31 06:47:57 +01:00
										 |  |  |                         price = world.random.randrange(4, 28) | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2021-01-31 06:47:57 +01:00
										 |  |  |                         price = world.random.randrange(8, 56) | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-02 21:07:43 -05:00
										 |  |  |                     shop.push_inventory(location.shop_slot, item_name, | 
					
						
							| 
									
										
										
										
											2022-01-06 23:42:55 -05:00
										 |  |  |                                         min(int(price * world.shop_price_modifier[location.player] / 100) * 5, 9999), 1, | 
					
						
							| 
									
										
										
										
											2021-01-17 22:58:52 +01:00
										 |  |  |                                         location.item.player if location.item.player != location.player else 0) | 
					
						
							| 
									
										
										
										
											2021-10-02 10:15:00 +02:00
										 |  |  |                     if 'P' in world.shop_shuffle[location.player]: | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  |                         price_to_funny_price(world, shop.inventory[location.shop_slot], location.player) | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | def create_shops(world, player: int): | 
					
						
							|  |  |  |     option = world.shop_shuffle[player] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     player_shop_table = shop_table.copy() | 
					
						
							|  |  |  |     if "w" in option: | 
					
						
							|  |  |  |         player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False) | 
					
						
							|  |  |  |         dynamic_shop_slots = total_dynamic_shop_slots + 3 | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         dynamic_shop_slots = total_dynamic_shop_slots | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-08 21:58:11 +02:00
										 |  |  |     num_slots = min(dynamic_shop_slots, world.shop_item_slots[player]) | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots) | 
					
						
							|  |  |  |     world.random.shuffle(single_purchase_slots) | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if 'g' in option or 'f' in option: | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |         default_shop_table = [i for l in | 
					
						
							|  |  |  |                               [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |                                not world.retro_bow[player] or x != 'arrows'] for i in l] | 
					
						
							| 
									
										
										
										
											2021-01-20 04:22:01 -06:00
										 |  |  |         new_basic_shop = world.random.sample(default_shop_table, k=3) | 
					
						
							|  |  |  |         new_dark_shop = world.random.sample(default_shop_table, k=3) | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |         for name, shop in player_shop_table.items(): | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |             typ, shop_id, keeper, custom, locked, items, sram_offset = shop | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |             if not locked: | 
					
						
							| 
									
										
										
										
											2021-01-20 04:22:01 -06:00
										 |  |  |                 new_items = world.random.sample(default_shop_table, k=3) | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |                 if 'f' not in option: | 
					
						
							|  |  |  |                     if items == _basic_shop_defaults: | 
					
						
							|  |  |  |                         new_items = new_basic_shop | 
					
						
							|  |  |  |                     elif items == _dark_world_shop_defaults: | 
					
						
							|  |  |  |                         new_items = new_dark_shop | 
					
						
							|  |  |  |                 keeper = world.random.choice([0xA0, 0xC1, 0xFF]) | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |                 player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset) | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     if world.mode[player] == "inverted": | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |         # make sure that blue potion is available in inverted, special case locked = None; lock when done. | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |         player_shop_table["Dark Lake Hylia Shop"] = \ | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |             player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None) | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |     chance_100 = int(world.retro_bow[player]) * 0.25 + int( | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |         world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5 | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |     for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items(): | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         region = world.get_region(region_name, player) | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |         shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset) | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |         # special case: allow shop slots, but do not allow overwriting of base inventory behind them | 
					
						
							|  |  |  |         if locked is None: | 
					
						
							|  |  |  |             shop.locked = True | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |         region.shop = shop | 
					
						
							|  |  |  |         world.shops.append(shop) | 
					
						
							|  |  |  |         for index, item in enumerate(inventory): | 
					
						
							|  |  |  |             shop.add_inventory(index, *item) | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |             if not locked and num_slots: | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  |                 slot_name = f"{region.name}{shop.slot_names[index]}" | 
					
						
							| 
									
										
										
										
											2021-02-21 20:17:24 +01:00
										 |  |  |                 loc = ALttPLocation(player, slot_name, address=shop_table_by_location[slot_name], | 
					
						
							|  |  |  |                                     parent=region, hint_text="for sale") | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |                 loc.shop_slot = index | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |                 loc.locked = True | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |                 if single_purchase_slots.pop(): | 
					
						
							| 
									
										
										
										
											2021-01-26 07:06:33 -08:00
										 |  |  |                     if world.goal[player] != 'icerodhunt': | 
					
						
							| 
									
										
										
										
											2021-02-07 09:45:30 +01:00
										 |  |  |                         if world.random.random() < chance_100: | 
					
						
							|  |  |  |                             additional_item = 'Rupees (100)' | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             additional_item = 'Rupees (50)' | 
					
						
							| 
									
										
										
										
											2021-01-26 07:06:33 -08:00
										 |  |  |                     else: | 
					
						
							| 
									
										
										
										
											2021-02-07 06:34:42 -08:00
										 |  |  |                         additional_item = GetBeemizerItem(world, player, 'Nothing') | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |                     loc.item = ItemFactory(additional_item, player) | 
					
						
							|  |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2021-02-07 06:34:42 -08:00
										 |  |  |                     loc.item = ItemFactory(GetBeemizerItem(world, player, 'Nothing'), player) | 
					
						
							| 
									
										
										
										
											2021-01-22 05:40:50 -08:00
										 |  |  |                     loc.shop_slot_disabled = True | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |                 shop.region.locations.append(loc) | 
					
						
							|  |  |  |                 world.clear_location_cache() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ShopData(NamedTuple): | 
					
						
							|  |  |  |     room: int | 
					
						
							|  |  |  |     type: ShopType | 
					
						
							|  |  |  |     shopkeeper: int | 
					
						
							|  |  |  |     custom: bool | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |     locked: Optional[bool] | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     items: List | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |     sram_offset: int | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-17 23:17:14 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  | # (type, room_id, shopkeeper, custom, locked, [items], sram_offset) | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | # item = (item, price, max=0, replacement=None, replacement_price=0) | 
					
						
							|  |  |  | _basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)] | 
					
						
							|  |  |  | _dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)] | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  | _inverted_hylia_shop_defaults = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] | 
					
						
							|  |  |  | shop_table: Dict[str, ShopData] = { | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |     'Cave Shop (Dark Death Mountain)': ShopData(0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults, 0), | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     'Red Shield Shop': ShopData(0x0110, ShopType.Shop, 0xC1, True, False, | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |                                 [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)], 3), | 
					
						
							|  |  |  |     'Dark Lake Hylia Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 6), | 
					
						
							|  |  |  |     'Dark World Lumberjack Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 9), | 
					
						
							|  |  |  |     'Village of Outcasts Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 12), | 
					
						
							|  |  |  |     'Dark World Potion Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 15), | 
					
						
							|  |  |  |     'Light World Death Mountain Shop': ShopData(0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 18), | 
					
						
							|  |  |  |     'Kakariko Shop': ShopData(0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 21), | 
					
						
							|  |  |  |     'Cave Shop (Lake Hylia)': ShopData(0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 24), | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     'Potion Shop': ShopData(0x0109, ShopType.Shop, 0xA0, True, True, | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |                             [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], 27), | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  |     'Capacity Upgrade': ShopData(0x0115, ShopType.UpgradeShop, 0x04, True, True, | 
					
						
							| 
									
										
										
										
											2021-01-22 07:08:50 -08:00
										 |  |  |                                  [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)], 30) | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-18 04:48:20 +01:00
										 |  |  | 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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | SHOP_ID_START = 0x400000 | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  | shop_table_by_location_id = dict(enumerate( | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  |     (f"{name}{Shop.slot_names[num]}" for name, shop_data in | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |      sorted(shop_table.items(), key=lambda item: item[1].sram_offset) | 
					
						
							| 
									
										
										
										
											2021-08-27 14:52:33 +02:00
										 |  |  |      for num in range(3)), start=SHOP_ID_START)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-19 06:03:44 +01:00
										 |  |  | 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" | 
					
						
							|  |  |  | shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 3)] = "Take-Any #3" | 
					
						
							|  |  |  | shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 4)] = "Take-Any #4" | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  | shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | shop_generation_types = { | 
					
						
							| 
									
										
										
										
											2021-01-20 04:22:01 -06:00
										 |  |  |     'arrows': [('Single Arrow', 5), ('Arrows (10)', 50)], | 
					
						
							|  |  |  |     'bombs': [('Single Bomb', 10), ('Bombs (3)', 30), ('Bombs (10)', 50)], | 
					
						
							|  |  |  |     'shields': [('Red Shield', 500), ('Blue Shield', 50)], | 
					
						
							|  |  |  |     'potions': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)], | 
					
						
							|  |  |  |     'discount_potions': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], | 
					
						
							|  |  |  |     'bottle': [('Small Heart', 10), ('Apple', 50), ('Bee', 10), ('Good Bee', 100), ('Faerie', 100), ('Magic Jar', 100)], | 
					
						
							| 
									
										
										
										
											2021-01-16 02:23:23 +01:00
										 |  |  |     'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)], | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def set_up_shops(world, player: int): | 
					
						
							|  |  |  |     # TODO: move hard+ mode changes for shields here, utilizing the new shops | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |     if world.retro_bow[player]: | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |         rss = world.get_region('Red Shield Shop', player).shop | 
					
						
							|  |  |  |         replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |                              ['Blue Shield', 50], ['Small Heart', | 
					
						
							|  |  |  |                                                    10]]  # Can't just replace the single arrow with 10 arrows as retro doesn't need them. | 
					
						
							| 
									
										
										
										
											2021-08-30 09:59:20 -07:00
										 |  |  |         if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |             replacement_items.append(['Small Key (Universal)', 100]) | 
					
						
							|  |  |  |         replacement_item = world.random.choice(replacement_items) | 
					
						
							|  |  |  |         rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1]) | 
					
						
							|  |  |  |         rss.locked = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |     if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal or world.retro_bow[player]: | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |         for shop in world.random.sample([s for s in world.shops if | 
					
						
							|  |  |  |                                          s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player], | 
					
						
							|  |  |  |                                         5): | 
					
						
							|  |  |  |             shop.locked = True | 
					
						
							|  |  |  |             slots = [0, 1, 2] | 
					
						
							|  |  |  |             world.random.shuffle(slots) | 
					
						
							|  |  |  |             slots = iter(slots) | 
					
						
							| 
									
										
										
										
											2021-08-30 09:59:20 -07:00
										 |  |  |             if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |                 shop.add_inventory(next(slots), 'Small Key (Universal)', 100) | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |             if world.retro_bow[player]: | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |                 shop.push_inventory(next(slots), 'Single Arrow', 80) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def shuffle_shops(world, items, player: int): | 
					
						
							|  |  |  |     option = world.shop_shuffle[player] | 
					
						
							|  |  |  |     if 'u' in option: | 
					
						
							|  |  |  |         progressive = world.progressive[player] | 
					
						
							| 
									
										
										
										
											2021-09-26 10:09:40 +02:00
										 |  |  |         progressive = world.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on' | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |         progressive &= world.goal == 'icerodhunt' | 
					
						
							|  |  |  |         new_items = ["Bomb Upgrade (+5)"] * 6 | 
					
						
							|  |  |  |         new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |         if not world.retro_bow[player]: | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |             new_items += ["Arrow Upgrade (+5)"] * 6 | 
					
						
							|  |  |  |             new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         world.random.shuffle(new_items)  # Decide what gets tossed randomly if it can't insert everything. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         capacityshop: Optional[Shop] = None | 
					
						
							|  |  |  |         for shop in world.shops: | 
					
						
							|  |  |  |             if shop.type == ShopType.UpgradeShop and shop.region.player == player and \ | 
					
						
							|  |  |  |                     shop.region.name == "Capacity Upgrade": | 
					
						
							|  |  |  |                 shop.clear_inventory() | 
					
						
							|  |  |  |                 capacityshop = shop | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if world.goal[player] != 'icerodhunt': | 
					
						
							|  |  |  |             for i, item in enumerate(items): | 
					
						
							|  |  |  |                 if item.name in trap_replaceable: | 
					
						
							|  |  |  |                     items[i] = ItemFactory(new_items.pop(), player) | 
					
						
							|  |  |  |                     if not new_items: | 
					
						
							|  |  |  |                         break | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |                 logging.warning( | 
					
						
							|  |  |  |                     f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.") | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |                 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) | 
					
						
							| 
									
										
										
										
											2022-07-04 19:09:03 +02:00
										 |  |  |                 slots = iter(range(2)) | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |                 if bombupgrades: | 
					
						
							| 
									
										
										
										
											2022-07-04 19:09:03 +02:00
										 |  |  |                     capacityshop.add_inventory(next(slots), 'Bomb Upgrade (+5)', 100, bombupgrades) | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |                 if arrowupgrades: | 
					
						
							| 
									
										
										
										
											2022-07-04 19:09:03 +02:00
										 |  |  |                     capacityshop.add_inventory(next(slots), 'Arrow Upgrade (+5)', 100, arrowupgrades) | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |         else: | 
					
						
							|  |  |  |             for item in new_items: | 
					
						
							|  |  |  |                 world.push_precollected(ItemFactory(item, player)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |     if any(setting in option for setting in 'ipP'): | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |         shops = [] | 
					
						
							|  |  |  |         upgrade_shops = [] | 
					
						
							|  |  |  |         total_inventory = [] | 
					
						
							|  |  |  |         for shop in world.shops: | 
					
						
							|  |  |  |             if shop.region.player == player: | 
					
						
							|  |  |  |                 if shop.type == ShopType.UpgradeShop: | 
					
						
							|  |  |  |                     upgrade_shops.append(shop) | 
					
						
							| 
									
										
										
										
											2021-02-03 14:24:29 +01:00
										 |  |  |                 elif shop.type == ShopType.Shop and not shop.locked: | 
					
						
							|  |  |  |                     shops.append(shop) | 
					
						
							|  |  |  |                     total_inventory.extend(shop.inventory) | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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! | 
					
						
							|  |  |  |                 adjust = 2 if price < 100 else 5 | 
					
						
							|  |  |  |                 return int((price / adjust) * (0.5 + world.random.random() * 1.5)) * adjust | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             def adjust_item(item): | 
					
						
							|  |  |  |                 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) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  |         if 'P' in option: | 
					
						
							|  |  |  |             for item in total_inventory: | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  |                 price_to_funny_price(world, item, player) | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |             # Don't apply to upgrade shops | 
					
						
							|  |  |  |             # Upgrade shop is only one place, and will generally be too easy to | 
					
						
							|  |  |  |             # replenish hearts and bombs | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-30 06:46:17 +01:00
										 |  |  |         if 'i' in option: | 
					
						
							|  |  |  |             world.random.shuffle(total_inventory) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             i = 0 | 
					
						
							|  |  |  |             for shop in shops: | 
					
						
							|  |  |  |                 slots = shop.slots | 
					
						
							|  |  |  |                 shop.inventory = total_inventory[i:i + slots] | 
					
						
							|  |  |  |                 i += slots | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  | 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: {}, | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |     ShopPriceType.BombUpgrade: {"Bomb Upgrade"}, | 
					
						
							|  |  |  |     ShopPriceType.ArrowUpgrade: {"Arrow Upgrade"}, | 
					
						
							|  |  |  |     ShopPriceType.Keys: {"Small Key"}, | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  |     ShopPriceType.Potion: {}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | price_chart = { | 
					
						
							|  |  |  |     ShopPriceType.Rupees: lambda p: p, | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  |     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 | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  |     ShopPriceType.HeartContainer: lambda p: 0x8, | 
					
						
							|  |  |  |     ShopPriceType.BombUpgrade: lambda p: 0x1, | 
					
						
							|  |  |  |     ShopPriceType.ArrowUpgrade: lambda p: 0x1, | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  |     ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1),  # Max of 3 keys for a price | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |     ShopPriceType.Potion: lambda p: (p // 5) % 5, | 
					
						
							| 
									
										
										
										
											2021-09-03 13:17:50 -05:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | price_type_display_name = { | 
					
						
							|  |  |  |     ShopPriceType.Rupees: "Rupees", | 
					
						
							|  |  |  |     ShopPriceType.Hearts: "Hearts", | 
					
						
							|  |  |  |     ShopPriceType.Bombs: "Bombs", | 
					
						
							|  |  |  |     ShopPriceType.Arrows: "Arrows", | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |     ShopPriceType.Keys: "Keys", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # price division | 
					
						
							|  |  |  | price_rate_display = { | 
					
						
							|  |  |  |     ShopPriceType.Hearts: 8, | 
					
						
							|  |  |  |     ShopPriceType.Magic: 8, | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # prices with no? logic requirements | 
					
						
							|  |  |  | simple_price_types = [ | 
					
						
							|  |  |  |     ShopPriceType.Rupees, | 
					
						
							|  |  |  |     ShopPriceType.Hearts, | 
					
						
							|  |  |  |     ShopPriceType.Bombs, | 
					
						
							|  |  |  |     ShopPriceType.Arrows, | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |     ShopPriceType.Keys | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  | ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  | def price_to_funny_price(world, item: dict, player: int): | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Converts a raw Rupee price into a special price type | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |     if item: | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  |         price_types = [ | 
					
						
							|  |  |  |             ShopPriceType.Rupees,  # included as a chance to not change price type | 
					
						
							|  |  |  |             ShopPriceType.Hearts, | 
					
						
							|  |  |  |             ShopPriceType.Bombs, | 
					
						
							|  |  |  |         ] | 
					
						
							|  |  |  |         # don't pay in universal keys to get access to universal keys | 
					
						
							|  |  |  |         if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal \ | 
					
						
							|  |  |  |                 and not "Small Key (Universal)" == item['replacement']: | 
					
						
							|  |  |  |             price_types.append(ShopPriceType.Keys) | 
					
						
							| 
									
										
										
										
											2022-06-01 17:29:21 +02:00
										 |  |  |         if not world.retro_bow[player]: | 
					
						
							| 
									
										
										
										
											2022-04-22 09:12:51 +02:00
										 |  |  |             price_types.append(ShopPriceType.Arrows) | 
					
						
							|  |  |  |         world.random.shuffle(price_types) | 
					
						
							|  |  |  |         for p_type in price_types: | 
					
						
							|  |  |  |             # Ignore rupee prices | 
					
						
							|  |  |  |             if p_type == ShopPriceType.Rupees: | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |             if any(x in item['item'] for x in price_blacklist[p_type]): | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             else: | 
					
						
							| 
									
										
										
										
											2021-10-21 08:15:47 +02:00
										 |  |  |                 item['price'] = min(price_chart[p_type](item['price']), 255) | 
					
						
							| 
									
										
										
										
											2021-09-12 16:09:13 -05:00
										 |  |  |                 item['price_type'] = p_type | 
					
						
							| 
									
										
										
										
											2021-09-12 20:25:08 +02:00
										 |  |  |             break | 
					
						
							| 
									
										
										
										
											2023-01-19 16:17:43 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def create_dynamic_shop_locations(world, player): | 
					
						
							|  |  |  |     for shop in world.shops: | 
					
						
							|  |  |  |         if shop.region.player == player: | 
					
						
							|  |  |  |             for i, item in enumerate(shop.inventory): | 
					
						
							|  |  |  |                 if item is None: | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 if item['create_location']: | 
					
						
							|  |  |  |                     slot_name = f"{shop.region.name}{shop.slot_names[i]}" | 
					
						
							|  |  |  |                     loc = ALttPLocation(player, slot_name, | 
					
						
							|  |  |  |                                         address=shop_table_by_location[slot_name], parent=shop.region) | 
					
						
							|  |  |  |                     loc.place_locked_item(ItemFactory(item['item'], player)) | 
					
						
							|  |  |  |                     if shop.type == ShopType.TakeAny: | 
					
						
							|  |  |  |                         loc.shop_slot_disabled = True | 
					
						
							|  |  |  |                     shop.region.locations.append(loc) | 
					
						
							|  |  |  |                     world.clear_location_cache() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     loc.shop_slot = i |