Merge remote-tracking branch 'upstream/DoorDevUnstable' into OverworldShuffleDev

This commit is contained in:
codemann8
2021-05-01 12:57:41 -05:00
10 changed files with 162 additions and 40 deletions

2
CLI.py
View File

@@ -187,7 +187,7 @@ def parse_settings():
"quickswap": False, "quickswap": False,
"heartcolor": "red", "heartcolor": "red",
"heartbeep": "normal", "heartbeep": "normal",
"sprite": os.path.join(".", "data", "sprites", "official", "001.link.1.zspr"), "sprite": None,
"fastmenu": "normal", "fastmenu": "normal",
"ow_palettes": "default", "ow_palettes": "default",
"uw_palettes": "default", "uw_palettes": "default",

View File

@@ -555,7 +555,7 @@ def find_portal_candidates(door_list, dungeon, need_passage=False, dead_end_allo
if not dead_end_allowed: if not dead_end_allowed:
ret = [x for x in ret if not x.deadEnd] ret = [x for x in ret if not x.deadEnd]
if standard: if standard:
ret = [x for x in ret if not x.standard_restrict] ret = [x for x in ret if not x.standard_restricted]
return ret return ret

View File

@@ -1249,7 +1249,6 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda for r_name in ['Hyrule Dungeon Cellblock', 'Sanctuary']: # need to deliver zelda
assign_sector(find_sector(r_name, candidate_sectors), current_dungeon, assign_sector(find_sector(r_name, candidate_sectors), current_dungeon,
candidate_sectors, global_pole) candidate_sectors, global_pole)
standard_stair_check(dungeon_map, current_dungeon, candidate_sectors, global_pole)
entrances_map, potentials, connections = connections_tuple entrances_map, potentials, connections = connections_tuple
accessible_sectors, reverse_d_map = set(), {} accessible_sectors, reverse_d_map = set(), {}
for key in dungeon_entrances.keys(): for key in dungeon_entrances.keys():
@@ -1265,6 +1264,9 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
if not sector: if not sector:
sector = find_sector(r_name, all_sectors) sector = find_sector(r_name, all_sectors)
reverse_d_map[sector] = key reverse_d_map[sector] = key
if world.mode[player] == 'standard':
current_dungeon = dungeon_map['Hyrule Castle']
standard_stair_check(world, dungeon_map, current_dungeon, candidate_sectors, global_pole)
complete_dungeons = {x: y for x, y in dungeon_map.items() if sum(len(sector.outstanding_doors) for sector in y.sectors) <= 0} complete_dungeons = {x: y for x, y in dungeon_map.items() if sum(len(sector.outstanding_doors) for sector in y.sectors) <= 0}
[dungeon_map.pop(key) for key in complete_dungeons.keys()] [dungeon_map.pop(key) for key in complete_dungeons.keys()]

36
Fill.py
View File

@@ -534,6 +534,30 @@ def balance_money_progression(world):
'TR Rupees': 270, 'PoD Dark Basement': 270} 'TR Rupees': 270, 'PoD Dark Basement': 270}
acceptable_balancers = ['Bombs (3)', 'Arrows (10)', 'Bombs (10)'] 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): def get_sphere_locations(sphere_state, locations):
sphere_state.sweep_for_events(key_only=True, locations=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)] 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 = location.parent_region.shop
shop_item = shop.inventory[slot] shop_item = shop.inventory[slot]
if interesting_item(location, location.item, world, location.item.player): if interesting_item(location, location.item, world, location.item.player):
sphere_costs[loc_player] += shop_item['price'] if location.item.name.startswith('Rupee') and loc_player == location.item.player:
location_free = False if shop_item['price'] < rupee_chart[location.item.name]:
locked_by_money[loc_player].add(location) 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: elif location.name in pay_for_locations:
sphere_costs[loc_player] += pay_for_locations[location.name] sphere_costs[loc_player] += pay_for_locations[location.name]
location_free = False location_free = False

View File

@@ -3,12 +3,12 @@ import logging
import math import math
import random 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 Bosses import place_bosses
from Dungeons import get_dungeon_item_pool from Dungeons import get_dungeon_item_pool
from EntranceShuffle import connect_entrance from EntranceShuffle import connect_entrance
from Regions import shop_to_location_table, retro_shops, shop_table_by_location 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 from Items import ItemFactory
import source.classes.constants as CONST import source.classes.constants as CONST
@@ -575,6 +575,7 @@ def customize_shops(world, player):
loc.item = upgrade loc.item = upgrade
upgrade.location = loc upgrade.location = loc
change_shop_items_to_rupees(world, player, shops_to_customize) change_shop_items_to_rupees(world, player, shops_to_customize)
balance_prices(world, player)
def randomize_price(price): def randomize_price(price):
@@ -607,6 +608,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) 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', 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'] 'Blue Shield', 'Red Shield', 'Bee', 'Small Key (Universal)', 'Blue Potion', 'Green Potion']
@@ -622,6 +694,9 @@ shop_transfer = {'Red Potion': 'Rupees (50)', 'Bee': 'Rupees (5)', 'Blue Potion'
# 'Blue Shield': 'Rupees (50)', 'Red Shield': 'Rupees (300)', # '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): def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, door_shuffle):
pool = [] pool = []

View File

@@ -28,7 +28,7 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
__version__ = '0.3.1.7-u' __version__ = '0.3.1.8-u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):

View File

@@ -44,38 +44,44 @@ All items in the general item pool may appear in shops. This includes normal pro
#### Pricing Guide #### 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 | | Category | Items | Base Price | Typical Range |
| ----------------- | ------- |:----------:|:-------------:| | ----------------- | ------- |:----------:|:-------------:|
| Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-250 | Major Progression | Hammer, Hookshot, Mirror, Ocarina, Boots, Somaria, Fire Rod, Ice Rod | 250 | 125-500
| | Moon Pearl | 200 | 100-200 | | Moon Pearl | 200 | 100-400
| | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-150 | | Lamp, Progressive Bows, Gloves, & Swords | 150 | 75-300
| | Triforce Piece | 100 | 50-100 | | Triforce Piece | 100 | 50-200
| Medallions | Bombos, Ether, Quake | 100 | 50-100 | Medallions | Bombos, Ether, Quake | 100 | 50-200
| Safety/Fetch | Cape, Mushroom, Shovel, Powder, Bug Net, Byrna, Progressive Armor & Shields, Half Magic | 50 | 25-50 | 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-50 | Bottles | Empty Bottle or Bee Bottle | 50 | 25-100
| | Green Goo or Good Bee | 60 | 30-60 | | Green Goo or Good Bee | 60 | 30-120
| | Red Goo or Fairy | 70 | 35-70 | | Red Goo or Fairy | 70 | 35-140
| | Blue Goo | 80 | 40-80 | | Blue Goo | 80 | 40-160
| Health | Heart Container | 40 | 20-40 | Health | Heart Container | 40 | 20-80
| | Sanctuary Heart | 50 | 25-50 | | Sanctuary Heart | 50 | 25-100
| | Piece of Heart | 10 | 5-10 | | Piece of Heart | 10 | 5-20
| Dungeon | Big Keys | 60 | 30-60 | Dungeon | Big Keys | 60 | 30-120
| | Small Keys | 40 | 20-40 | | Small Keys | 40 | 20-80
| | Info Maps | 20 | 10-20 | | Info Maps | 20 | 10-40
| | Other Maps & Compasses | 10 | 5-10 | | Other Maps & Compasses | 10 | 5-20
| Rupees | Green | Free | Free | Rupees | Green | Free | Free
| | Blue | 2 | 2 | | Blue | 2 | 2-4
| | Red | 10 | 5-10 | | Red | 10 | 5-20
| | Fifty | 25 | 15-25 | | Fifty | 25 | 15-50
| | One Hundred | 50 | 25-50 | | One Hundred | 50 | 25-100
| | Three Hundred | 150 | 75-150 | | Three Hundred | 150 | 75-300
| Ammo | Three Bombs | 15 | 10-15 | Ammo | Three Bombs | 15 | 10-30
| | Single Arrow | 3 | 3 | | Single Arrow | 3 | 3-6
| Original Shop Items | Other Ammo, Refills, Non-Progressive Shields, Capacity Upgrades, Small Hearts, Retro Quiver, Universal Key | Original | Could be Discounted as Above | Original Shop Items | Other Ammo, Refills, Non-Progressive Shields, Capacity Upgrades, Small Hearts, Retro Quiver, Universal Key | Original | .5 - 2 * Original
~~In addition, 4-7 items are steeply discounted at random.~~ Sales are over.
#### Rupee Balancing Algorithm #### Rupee Balancing Algorithm
@@ -144,6 +150,12 @@ New item counter modified to show total
# Bug Fixes and Notes. # Bug Fixes and Notes.
* 0.3.1.9-u
* Generation improvements for standard
* Removed link sprite from repo
* 0.3.1.8-u
* Fix for retro generation
* Shopsanity - rebalance pricing - later prices can be are higher
* 0.3.1.7-u * 0.3.1.7-u
* TFH counter off in modes where it should be off * TFH counter off in modes where it should be off
* Fixed Big Bomb logic for inverted (bad merge) * Fixed Big Bomb logic for inverted (bad merge)

9
Rom.py
View File

@@ -16,7 +16,7 @@ from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Doo
from DoorShuffle import compass_data, DROptions, boss_indicator from DoorShuffle import compass_data, DROptions, boss_indicator
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses
from KeyDoorShuffle import count_locations_exclude_logic from KeyDoorShuffle import count_locations_exclude_logic
from Regions import location_table, shop_to_location_table from Regions import location_table, shop_to_location_table, retro_shops
from RoomData import DoorKind from RoomData import DoorKind
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts from Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
@@ -1588,8 +1588,11 @@ def write_custom_shops(rom, world, player):
break break
if world.shopsanity[player] or shop.type == ShopType.TakeAny: if world.shopsanity[player] or shop.type == ShopType.TakeAny:
rom.write_byte(0x186560 + shop.sram_address + index, 1) rom.write_byte(0x186560 + shop.sram_address + index, 1)
loc_item = world.get_location(shop_to_location_table[shop.region.name][index], player).item if world.shopsanity[player] and shop.region.name in shop_to_location_table:
if not loc_item: loc_item = world.get_location(shop_to_location_table[shop.region.name][index], player).item
elif world.shopsanity[player] and shop.region.name in retro_shops:
loc_item = world.get_location(retro_shops[shop.region.name][index], player).item
else:
loc_item = ItemFactory(item['item'], player) loc_item = ItemFactory(item['item'], player)
item_id = loc_item.code item_id = loc_item.code
price = int16_as_bytes(item['price']) price = int16_as_bytes(item['price'])