diff --git a/BaseClasses.py b/BaseClasses.py index dd7b24e2..9a91072d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1239,6 +1239,7 @@ class Spoiler(object): if item is None: continue shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item'] + if item['max'] == 0: continue shopdata['item_{}'.format(index)] += " x {}".format(item['max']) diff --git a/EntranceRandomizer.py b/EntranceRandomizer.py index 5c9ab56c..8f266d6d 100755 --- a/EntranceRandomizer.py +++ b/EntranceRandomizer.py @@ -326,7 +326,9 @@ def parse_arguments(argv, no_defaults=False): parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4)) parser.add_argument('--shop_shuffle', default='', help='''\ combine letters for options: - i: shuffle the inventories of the shops around + g: generate default inventories for light and dark world shops, and unique shops + f: generate default inventories for each shop individually + i: shuffle the default inventories of the shops around p: randomize the prices of the items in shop inventories u: shuffle capacity upgrades into the item pool ''') diff --git a/Fill.py b/Fill.py index 1212f6e0..67cfdf32 100644 --- a/Fill.py +++ b/Fill.py @@ -152,6 +152,7 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None fill_locations.remove(spot_to_fill) world.random.shuffle(fill_locations) + prioitempool, fill_locations = fast_fill(world, prioitempool, fill_locations) restitempool, fill_locations = fast_fill(world, restitempool, fill_locations) diff --git a/ItemPool.py b/ItemPool.py index 132785a1..7ec509a3 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -379,7 +379,8 @@ def shuffle_shops(world, items, player: int): if 'p' in option: def price_adjust(price: int) -> int: # it is important that a base price of 0 always returns 0 as new price! - return int(price * (0.5 + world.random.random() * 1.5)) + 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: @@ -394,6 +395,7 @@ def shuffle_shops(world, items, player: int): if 'i' in option: world.random.shuffle(total_inventory) + i = 0 for shop in shops: slots = shop.slots @@ -464,13 +466,14 @@ def create_dynamic_shop_locations(world, player): if item is None: continue if item['create_location']: - loc = Location(player, "{} Item {}".format(shop.region.name, i+1), parent=shop.region) + loc = Location(player, "{} Slot Item {}".format(shop.region.name, i+1), parent=shop.region) shop.region.locations.append(loc) world.dynamic_locations.append(loc) world.clear_location_cache() - world.push_item(loc, ItemFactory(item['item'], player), False) + world.push_item(loc, ItemFactory(item['item'], player), False) + loc.event = True loc.locked = True diff --git a/Regions.py b/Regions.py index bdef78ac..48014448 100644 --- a/Regions.py +++ b/Regions.py @@ -368,7 +368,27 @@ def create_shops(world, player: int): cls_mapping = {ShopType.UpgradeShop: UpgradeShop, ShopType.Shop: Shop, ShopType.TakeAny: TakeAny} - for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items(): + option = world.shop_shuffle[player] + my_shop_table = dict(shop_table) + if 'g' in option or 'f' in option: + new_basic_shop = world.random.sample(shop_generation_types['default'], k=3) + new_dark_shop = world.random.sample(shop_generation_types['default'], k=3) + for name, shop in my_shop_table.items(): + typ, shop_id, keeper, custom, locked, items = shop + new_items = world.random.sample(shop_generation_types['default'], k=3) + 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 + if name == 'Capacity Upgrade': + continue + if name == 'Potion Shop': + continue + keeper = world.random.choice([0xA0, 0xC1, 0xFF]) + my_shop_table[name] = (typ, shop_id, keeper, custom, locked, new_items) + + for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in my_shop_table.items(): if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop': locked = True inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)] @@ -379,6 +399,7 @@ def create_shops(world, player: int): for index, item in enumerate(inventory): shop.add_inventory(index, *item) + # (type, room_id, shopkeeper, custom, locked, [items]) # item = (item, price, max=0, replacement=None, replacement_price=0) _basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)] @@ -393,10 +414,18 @@ shop_table = { 'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults), 'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults), 'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults), - 'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]), + 'Potion Shop': (0x0109, ShopType.Shop, 0xA0, True, False, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]), 'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)]) } +shop_generation_types = { + 'default': _basic_shop_defaults + [('Bombs (3)', 20), ('Green Potion', 90), ('Blue Potion', 190), ('Bee', 10), ('Single Arrow', 5)] + [('Red Shield', 500), ('Blue Shield', 50)], + 'potion': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)], + 'discount_potion': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], + 'bottle': [('Bee', 10)], + 'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)], +} + old_location_address_to_new_location_address = { 0x2eb18: 0x18001b, # Bottle Merchant 0x33d68: 0x18001a, # Purple Chest diff --git a/Rom.py b/Rom.py index 47200459..e4e6d0c2 100644 --- a/Rom.py +++ b/Rom.py @@ -1,7 +1,7 @@ from __future__ import annotations JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = 'e3714804e3fae1c6ac6100b94d1aee62' +RANDOMIZERBASEHASH = '383b6f24e7c154de0f215e09f3cc937e' import io import json @@ -122,6 +122,9 @@ class LocalRom(object): if not os.path.exists(local_path('data', 'basepatch.bmbp')): Patch.create_patch_file(local_path('basepatch.sfc')) return + + if not os.path.isfile(local_path('data', 'basepatch.bmbp')): + raise RuntimeError('Base patch unverified. Unable to continue.') if os.path.isfile(local_path('data', 'basepatch.bmbp')): _, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.bmbp')) @@ -130,6 +133,7 @@ class LocalRom(object): with open(local_path('basepatch.sfc'), 'wb') as stream: stream.write(buffer) return + raise RuntimeError('Base patch unverified. Unable to continue.') raise RuntimeError('Could not find Base Patch. Unable to continue.') @@ -1513,10 +1517,10 @@ def write_custom_shops(rom, world, player): for item in shop.inventory: if item is None: break - item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [ - item['max'], - ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes( - item['replacement_price']) + item_data = [shop_id, ItemFactory(item['item'], player).code]\ + + int16_as_bytes(item['price']) +\ + [item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] +\ + int16_as_bytes(item['replacement_price']) + [0] items_data.extend(item_data) rom.write_bytes(0x184800, shop_data) diff --git a/Rules.py b/Rules.py index 04889a0f..3c12d9b3 100644 --- a/Rules.py +++ b/Rules.py @@ -85,7 +85,7 @@ def set_rules(world, player): add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or') set_bunny_rules(world, player, world.mode[player] == 'inverted') - + def mirrorless_path_to_castle_courtyard(world, player): # If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch. diff --git a/WebHostLib/static/static/playerSettings.json b/WebHostLib/static/static/playerSettings.json index e6ee539f..2d7633e2 100644 --- a/WebHostLib/static/static/playerSettings.json +++ b/WebHostLib/static/static/playerSettings.json @@ -1092,10 +1092,22 @@ "description": "Shop contents are left unchanged.", "defaultValue": 50 }, + "g": { + "keyString": "shop_shuffle.g", + "friendlyName": "Inventory Generate", + "description": "Generates new default base inventories of overworld and underworld shops.", + "defaultValue": 0 + }, + "f": { + "keyString": "shop_shuffle.f", + "friendlyName": "Full Inventory Generate", + "description": "Generates new base inventories of each individual shop.", + "defaultValue": 0 + }, "i": { "keyString": "shop_shuffle.i", "friendlyName": "Inventory Shuffle", - "description": "Randomizes the inventories of shops.", + "description": "Shuffles the inventories of shops between each other.", "defaultValue": 0 }, "p": { diff --git a/data/basepatch.bmbp b/data/basepatch.bmbp index 8ed7084b..78ca3f18 100644 Binary files a/data/basepatch.bmbp and b/data/basepatch.bmbp differ diff --git a/playerSettings.yaml b/playerSettings.yaml index 469a96de..03142b42 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -211,12 +211,16 @@ beemizer: # Remove items from the global item pool and replace them with single 2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees 3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees 4: 0 # 100% of the non-essential item pool is replaced with bee traps +### Item Shuffle (shop) shop_shuffle: none: 50 - i: 0 # Shuffle the inventories of the shops around + g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops + f: 0 # Generate new default inventories for every shop independently + i: 0 # Shuffle default inventories of the shops around p: 0 # Randomize the prices of the items in shop inventories u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld) ip: 0 # Shuffle inventories and randomize prices + fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool # You can add more combos shuffle_prizes: # aka drops