diff --git a/ItemPool.py b/ItemPool.py index 44d0df31..5051d5cd 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -2,12 +2,12 @@ from collections import namedtuple import logging from BaseClasses import Region, RegionType, Location -from Shops import ShopType, Shop, TakeAny, total_shop_slots +from Shops import TakeAny, total_shop_slots, set_up_shops, shuffle_shops from Bosses import place_bosses from Dungeons import get_dungeon_item_pool from EntranceShuffle import connect_entrance from Fill import FillError, fill_restrictive -from Items import ItemFactory +from Items import ItemFactory, trap_replaceable from Rules import forbid_items_for_player # This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space. @@ -390,27 +390,22 @@ def generate_itempool(world, player: int): for i in range(4): next(adv_heart_pieces).advancement = True - beeweights = {0: {None: 100}, - 1: {None: 75, 'trap': 25}, - 2: {None: 40, 'trap': 40, 'bee': 20}, - 3: {'trap': 50, 'bee': 50}, - 4: {'trap': 100}} - - def beemizer(item): - if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type: - choice = world.random.choices(list(beeweights[world.beemizer[item.player]].keys()), - weights=list(beeweights[world.beemizer[item.player]].values()))[0] - return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee", - player) - return item progressionitems = [] nonprogressionitems = [] for item in items: if item.advancement or item.priority or item.type: progressionitems.append(item) + elif world.beemizer[player] and item.name in trap_replaceable: + if world.random.random() < world.beemizer[item.player] * 0.25: + if world.random.random() < (0.5 + world.beemizer[item.player] * 0.1): + nonprogressionitems.append(ItemFactory("Bee Trap", player)) + else: + nonprogressionitems.append(ItemFactory("Bee", player)) + else: + nonprogressionitems.append(item) else: - nonprogressionitems.append(beemizer(item)) + nonprogressionitems.append(item) world.random.shuffle(nonprogressionitems) if additional_triforce_pieces: @@ -446,89 +441,6 @@ def generate_itempool(world, player: int): set_up_take_anys(world, player) # depends on world.itempool to be set -def shuffle_shops(world, items, player: int): - option = world.shop_shuffle[player] - if 'u' in option: - progressive = world.progressive[player] - progressive = world.random.choice([True, False]) if progressive == 'random' else progressive == 'on' - progressive &= world.goal == 'icerodhunt' - new_items = ["Bomb Upgrade (+5)"] * 6 - new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") - - if not world.retro[player]: - 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: 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 "Heart" not in item.name: - items[i] = ItemFactory(new_items.pop(), player) - 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.") - 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: - capacityshop.add_inventory(1, 'Bomb Upgrade (+5)', 100, bombupgrades) - if arrowupgrades: - capacityshop.add_inventory(1, 'Arrow Upgrade (+5)', 100, arrowupgrades) - else: - for item in new_items: - world.push_precollected(ItemFactory(item, player)) - - if 'p' in option or 'i' in option: - shops = [] - upgrade_shops = [] - total_inventory = [] - for shop in world.shops: - if shop.region.player == player: - if shop.type == ShopType.UpgradeShop: - upgrade_shops.append(shop) - elif shop.type == ShopType.Shop: - if shop.region.name == 'Potion Shop' and not 'w' in option: - # don't modify potion shop - pass - else: - shops.append(shop) - total_inventory.extend(shop.inventory) - - if 'p' in option: - def price_adjust(price: int) -> int: - # it is important that a base price of 0 always returns 0 as new price! - 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) - - 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 - - take_any_locations = { 'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut', 'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)', @@ -639,32 +551,6 @@ def fill_prizes(world, attempts=15): raise FillError('Unable to place dungeon prizes') -def set_up_shops(world, player: int): - # TODO: move hard+ mode changes for shields here, utilizing the new shops - - 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. - if world.keyshuffle[player] == "universal": - 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 - - if world.keyshuffle[player] == "universal" or world.retro[player]: - 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, 0, 1, 1, 2, 2] - world.random.shuffle(slots) - slots = iter(slots) - if world.keyshuffle[player] == "universal": - shop.add_inventory(next(slots), 'Small Key (Universal)', 100) - if world.retro[player]: - shop.push_inventory(next(slots), 'Single Arrow', 80) - def get_pool_core(world, player: int): progressive = world.progressive[player] shuffle = world.shuffle[player] diff --git a/Items.py b/Items.py index cac7db1e..94b24643 100644 --- a/Items.py +++ b/Items.py @@ -234,3 +234,5 @@ progression_items = {name for name, data in item_table.items() if type(data[3]) item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data[3]) == int} item_name_groups['Progression Items'] = progression_items item_name_groups['Non Progression Items'] = item_name_groups['Everything'] - progression_items + +trap_replaceable = item_name_groups['Rupees'] | {'Arrows (10)', 'Single Bomb', 'Bombs (3)', 'Bombs (10)'} diff --git a/Shops.py b/Shops.py index d9611039..a3c09c88 100644 --- a/Shops.py +++ b/Shops.py @@ -5,7 +5,7 @@ import logging from BaseClasses import Location from EntranceShuffle import door_addresses -from Items import item_name_groups, item_table, ItemFactory +from Items import item_name_groups, item_table, ItemFactory, trap_replaceable from Utils import int16_as_bytes logger = logging.getLogger("Shops") @@ -328,3 +328,113 @@ shop_generation_types = { 'bottle': [('Small Heart', 10), ('Apple', 50), ('Bee', 10), ('Good Bee', 100), ('Faerie', 100), ('Magic Jar', 100)], 'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)], } + + +def set_up_shops(world, player: int): + # TODO: move hard+ mode changes for shields here, utilizing the new shops + + 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. + if world.keyshuffle[player] == "universal": + 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 + + if world.keyshuffle[player] == "universal" or world.retro[player]: + 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) + if world.keyshuffle[player] == "universal": + shop.add_inventory(next(slots), 'Small Key (Universal)', 100) + if world.retro[player]: + 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] + progressive = world.random.choice([True, False]) if progressive == 'random' else progressive == 'on' + progressive &= world.goal == 'icerodhunt' + new_items = ["Bomb Upgrade (+5)"] * 6 + new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)") + + if not world.retro[player]: + 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: + 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: + capacityshop.add_inventory(1, 'Bomb Upgrade (+5)', 100, bombupgrades) + if arrowupgrades: + capacityshop.add_inventory(1, 'Arrow Upgrade (+5)', 100, arrowupgrades) + else: + for item in new_items: + world.push_precollected(ItemFactory(item, player)) + + if 'p' in option or 'i' in option: + shops = [] + upgrade_shops = [] + total_inventory = [] + for shop in world.shops: + if shop.region.player == player: + if shop.type == ShopType.UpgradeShop: + upgrade_shops.append(shop) + elif shop.type == ShopType.Shop: + if shop.region.name == 'Potion Shop' and not 'w' in option: + # don't modify potion shop + pass + else: + shops.append(shop) + total_inventory.extend(shop.inventory) + + if 'p' in option: + def price_adjust(price: int) -> int: + # it is important that a base price of 0 always returns 0 as new price! + 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) + + 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 diff --git a/playerSettings.yaml b/playerSettings.yaml index 5b1008b7..84fbf192 100644 --- a/playerSettings.yaml +++ b/playerSettings.yaml @@ -221,12 +221,12 @@ pot_shuffle: 'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile 'off': 50 # Default pot item locations ### End of Enemizer Section ### -beemizer: # Remove items from the global item pool and replace them with single bees and bee traps +beemizer: # Remove items from the global item pool and replace them with single bees (fill bottles) and bee traps 0: 50 # No bee traps are placed - 1: 0 # 25% of the non-essential item pool is replaced with bee traps - 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 + 1: 0 # 25% of rupees, bombs and arrows are replaced with bees, of which 60% are traps and 40% single bees + 2: 0 # 50% of rupees, bombs and arrows are replaced with bees, of which 70% are traps and 30% single bees + 3: 0 # 75% of rupees, bombs and arrows are replaced with bees, of which 80% are traps and 20% single bees + 4: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 90% are traps and 10% single bees ### Shop Settings ### shop_shuffle_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl) 0: 50