From 7a74ba8999f1b1cc54fa7d56773019d2964445ee Mon Sep 17 00:00:00 2001 From: aerinon Date: Tue, 27 Apr 2021 14:00:00 -0600 Subject: [PATCH] Shopsanity price re-work --- Fill.py | 36 ++++++++++++++++++++-- ItemList.py | 79 +++++++++++++++++++++++++++++++++++++++++++++++-- RELEASENOTES.md | 65 ++++++++++++++++++++++------------------ 3 files changed, 147 insertions(+), 33 deletions(-) diff --git a/Fill.py b/Fill.py index 3cdf8bf3..55c203a1 100644 --- a/Fill.py +++ b/Fill.py @@ -534,6 +534,30 @@ def balance_money_progression(world): 'TR Rupees': 270, 'PoD Dark Basement': 270} acceptable_balancers = ['Bombs (3)', 'Arrows (10)', 'Bombs (10)'] + base_value = sum(rupee_rooms.values()) + available_money = {player: base_value for player in range(1, world.players+1)} + for loc in world.get_locations(): + if loc.item.name in rupee_chart: + available_money[loc.item.player] += rupee_chart[loc.item.name] + + total_price = {player: 0 for player in range(1, world.players+1)} + for player in range(1, world.players+1): + for shop, loc_list in shop_to_location_table.items(): + for loc in loc_list: + loc = world.get_location(loc, player) + slot = shop_to_location_table[loc.parent_region.name].index(loc.name) + shop = loc.parent_region.shop + shop_item = shop.inventory[slot] + if shop_item: + total_price[player] += shop_item['price'] + total_price[player] += 110 + sum(pay_for_locations.values()) + # base needed: 830 + # base available: 765 + + for player in range(1, world.players+1): + logger.debug(f'Money balance for P{player}: Needed: {total_price[player]} Available: {available_money[player]}') + + def get_sphere_locations(sphere_state, locations): sphere_state.sweep_for_events(key_only=True, locations=locations) return [loc for loc in locations if sphere_state.can_reach(loc) and sphere_state.not_flooding_a_key(sphere_state.world, loc)] @@ -580,9 +604,15 @@ def balance_money_progression(world): shop = location.parent_region.shop shop_item = shop.inventory[slot] if interesting_item(location, location.item, world, location.item.player): - sphere_costs[loc_player] += shop_item['price'] - location_free = False - locked_by_money[loc_player].add(location) + if location.item.name.startswith('Rupee') and loc_player == location.item.player: + if shop_item['price'] < rupee_chart[location.item.name]: + wallet[loc_player] -= shop_item['price'] # will get picked up in the location_free block + else: + location_free = False + else: + location_free = False + sphere_costs[loc_player] += shop_item['price'] + locked_by_money[loc_player].add(location) elif location.name in pay_for_locations: sphere_costs[loc_player] += pay_for_locations[location.name] location_free = False diff --git a/ItemList.py b/ItemList.py index ccc10b28..27312851 100644 --- a/ItemList.py +++ b/ItemList.py @@ -3,12 +3,12 @@ import logging import math import random -from BaseClasses import Region, RegionType, Shop, ShopType, Location +from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState from Bosses import place_bosses from Dungeons import get_dungeon_item_pool from EntranceShuffle import connect_entrance from Regions import shop_to_location_table, retro_shops, shop_table_by_location -from Fill import FillError, fill_restrictive +from Fill import FillError, fill_restrictive, fast_fill from Items import ItemFactory import source.classes.constants as CONST @@ -574,6 +574,7 @@ def customize_shops(world, player): loc.item = upgrade upgrade.location = loc change_shop_items_to_rupees(world, player, shops_to_customize) + balance_prices(world, player) def randomize_price(price): @@ -606,6 +607,77 @@ def change_shop_items_to_rupees(world, player, shops): shop.add_inventory(slot, new_item.name, randomize_price(new_item.price), 1, player=new_item.player) +def balance_prices(world, player): + available_money = 765 # this base just counts the main rupee rooms. Could up it for houlihan by 225 + needed_money = 830 # this base is the pay for + for loc in world.get_filled_locations(player): + if loc.item.name in rupee_chart: + available_money += rupee_chart[loc.item.name] # rupee at locations + shop_locations = [] + for shop, loc_list in shop_to_location_table.items(): + for loc in loc_list: + loc = world.get_location(loc, player) + shop_locations.append(loc) + slot = shop_to_location_table[loc.parent_region.name].index(loc.name) + needed_money += loc.parent_region.shop.inventory[slot]['price'] + + target = available_money - needed_money + # remove the first set of shops from consideration (or used them for discounting) + state, done = CollectionState(world), False + unchecked_locations = world.get_locations().copy() + while not done: + state.sweep_for_events(key_only=True, locations=unchecked_locations) + sphere_loc = [l for l in unchecked_locations if state.can_reach(l) and state.not_flooding_a_key(state.world, l)] + if any(l in shop_locations for l in sphere_loc): + if target >= 0: + shop_locations = [l for l in shop_locations if l not in sphere_loc] + else: + shop_locations = [l for l in sphere_loc if l in shop_locations] + done = True + else: + for l in sphere_loc: + state.collect(l.item, True, l) + unchecked_locations.remove(l) + + while len(shop_locations) > 0: + adjustment = target // len(shop_locations) + adjustment = 5 * (adjustment // 5) + more_adjustment = [] + for loc in shop_locations: + slot = shop_to_location_table[loc.parent_region.name].index(loc.name) + price_max = loc.item.price * 2 + inventory = loc.parent_region.shop.inventory[slot] + flex = price_max - inventory['price'] + if flex <= adjustment: + inventory['price'] = price_max + target -= flex + elif adjustment <= 0: + old_price = inventory['price'] + new_price = max(0, inventory['price'] + adjustment) + inventory['price'] = new_price + target += (old_price - new_price) + else: + more_adjustment.append(loc) + if len(shop_locations) == len(more_adjustment): + for loc in shop_locations: + slot = shop_to_location_table[loc.parent_region.name].index(loc.name) + inventory = loc.parent_region.shop.inventory[slot] + new_price = inventory['price'] + adjustment + new_price = min(500, max(0, new_price)) # cap prices between 0--twice base price + inventory['price'] = new_price + target -= adjustment + more_adjustment = [] + shop_locations = more_adjustment + logging.getLogger('').debug(f'Price target is off by by {target} rupees') + + # for loc in shop_locations: + # slot = shop_to_location_table[loc.parent_region.name].index(loc.name) + # new_price = loc.parent_region.shop.inventory[slot]['price'] + adjustment + # + # new_price = min(500, max(0, new_price)) # cap prices between 0--twice base price + # loc.parent_region.shop.inventory[slot]['price'] = new_price + + repeatable_shop_items = ['Single Arrow', 'Arrows (10)', 'Bombs (3)', 'Bombs (10)', 'Red Potion', 'Small Heart', 'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion'] @@ -621,6 +693,9 @@ shop_transfer = {'Red Potion': 'Rupees (50)', 'Bee': 'Rupees (5)', 'Blue Potion' # 'Blue Shield': 'Rupees (50)', 'Red Shield': 'Rupees (300)', } +rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, + 'Rupees (100)': 100, 'Rupees (300)': 300} + def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, door_shuffle): pool = [] diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9b677253..214d470d 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -44,38 +44,44 @@ All items in the general item pool may appear in shops. This includes normal pro #### Pricing Guide -All prices range approx. from half the base price to the base price in increments of 5, the exact price is chosen randomly within the range. +#### Sphere effects + +Design goal: Shops in early spheres may be discounted below the base price while shops in later spheres will likely exceed the base price range. This is an attempt to balance out the rupees in the item pool vs. the prices the shops charges. Poorer item pools like Triforce Hunt may have early shop prices be adjusted downward while rupee rich item pools will have prices increased, but later in the game. + +Detailed explanation: It is calculated how much money is available in the item pool and various rupee sources. If this amount exceeds the total amount of money needed for shop prices for items, then shops that are not in sphere 1 will raise their prices by a calculated amount to help balance out the money. Conversely, if the amount is below the money needed, then shops in sphere 1 will be discounted by a calculated amount to help ensure everything is purchase-able with minimal grinding. + +#### Base prices + +All prices range approx. from half the base price to twice the base price (as a max) in increments of 5, the exact price is chosen randomly within the range subject to adjustments by the sphere effects above. | Category | Items | Base Price | Typical Range | | ----------------- | ------- |:----------:|:-------------:| -| Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250 -| | Moon Pearl | 200 | 100-200 -| | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-150 -| | Triforce Piece | 100 | 50-100 -| Medallions | Bombos, Ether, Quake | 100 | 50-100 -| Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-50 -| Bottles | Empty Bottle or Bee Bottle | 50 | 25-50 -| | Green Goo or Good Bee | 60 | 30-60 -| | Red Goo or Fairy | 70 | 35-70 -| | Blue Goo | 80 | 40-80 -| Health | Heart Container | 40 | 20-40 -| | Sanctuary Heart | 50 | 25-50 -| | Piece of Heart | 10 | 5-10 -| Dungeon | Big Keys | 60 | 30-60 -| | Small Keys | 40 | 20-40 -| | Info Maps | 20 | 10-20 -| | Other Maps & Compasses | 10 | 5-10 +| Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-500 +| | Moon Pearl | 200 | 100-400 +| | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-300 +| | Triforce Piece | 100 | 50-200 +| Medallions | Bombos, Ether, Quake | 100 | 50-200 +| Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-100 +| Bottles | Empty Bottle or Bee Bottle | 50 | 25-100 +| | Green Goo or Good Bee | 60 | 30-120 +| | Red Goo or Fairy | 70 | 35-140 +| | Blue Goo | 80 | 40-160 +| Health | Heart Container | 40 | 20-80 +| | Sanctuary Heart | 50 | 25-100 +| | Piece of Heart | 10 | 5-20 +| Dungeon | Big Keys | 60 | 30-120 +| | Small Keys | 40 | 20-80 +| | Info Maps | 20 | 10-40 +| | Other Maps & Compasses | 10 | 5-20 | Rupees | Green | Free | Free -| | Blue | 2 | 2 -| | Red | 10 | 5-10 -| | Fifty | 25 | 15-25 -| | One Hundred | 50 | 25-50 -| | Three Hundred | 150 | 75-150 -| Ammo | Three Bombs | 15 | 10-15 -| | Single Arrow | 3 | 3 -| Original Shop Items | Other Ammo, Refills, Non-Progressive Shields, Capacity Upgrades, Small Hearts, Retro Quiver, Universal Key | Original | Could be Discounted as Above - -~~In addition, 4-7 items are steeply discounted at random.~~ Sales are over. +| | Blue | 2 | 2-4 +| | Red | 10 | 5-20 +| | Fifty | 25 | 15-50 +| | One Hundred | 50 | 25-100 +| | Three Hundred | 150 | 75-300 +| Ammo | Three Bombs | 15 | 10-30 +| | Single Arrow | 3 | 3-6 +| Original Shop Items | Other Ammo, Refills, Non-Progressive Shields, Capacity Upgrades, Small Hearts, Retro Quiver, Universal Key | Original | .5 - 2 * Original #### Rupee Balancing Algorithm @@ -144,6 +150,9 @@ New item counter modified to show total # Bug Fixes and Notes. +* 0.3.1.8-u + * Fix for retro generation + * Shopsanity - rebalance pricing - later prices can be are higher * 0.3.1.7-u * TFH counter off in modes where it should be off * Fixed Big Bomb logic for inverted (bad merge)