Shops: limit "funny_prices" to logic free choices

This commit is contained in:
Fabian Dill 2021-09-12 20:25:08 +02:00
parent 44b5423afc
commit 4d68000692
4 changed files with 72 additions and 45 deletions

View File

@ -1056,17 +1056,18 @@ class Spoiler():
listed_locations.update(other_locations) listed_locations.update(other_locations)
self.shops = [] self.shops = []
from worlds.alttp.Shops import ShopType from worlds.alttp.Shops import ShopType, price_type_display_name
for shop in self.world.shops: for shop in self.world.shops:
if not shop.custom: if not shop.custom:
continue continue
shopdata = {'location': str(shop.region), shopdata = {
'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop' 'location': str(shop.region),
} 'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop'
}
for index, item in enumerate(shop.inventory): for index, item in enumerate(shop.inventory):
if item is None: if item is None:
continue continue
shopdata['item_{}'.format(index)] = "{}{}".format(item['item'], item['price']) if item['price'] else item['item'] shopdata['item_{}'.format(index)] = f"{item['item']}{item['price']} {price_type_display_name[item['price_type']]}"
if item['player'] > 0: if item['player'] > 0:
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('', '(Player {}) — '.format(item['player'])) shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('', '(Player {}) — '.format(item['player']))

View File

@ -13,7 +13,7 @@ class Version(typing.NamedTuple):
build: int build: int
__version__ = "0.1.7" __version__ = "0.1.8"
version_tuple = tuplize_version(__version__) version_tuple = tuplize_version(__version__)
import builtins import builtins

View File

@ -1709,9 +1709,10 @@ def write_custom_shops(rom, world, player):
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high][player] # [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high][player]
for index, item in enumerate(shop.inventory): for index, item in enumerate(shop.inventory):
slot = 0 if shop.type == ShopType.TakeAny else index
if item is None: if item is None:
break break
price_data = int16_as_bytes(0x8000 | 0x100 * (item["price_type"] - 1) | item['price'])
slot = 0 if shop.type == ShopType.TakeAny else index
if not item['item'] in item_table: # item not native to ALTTP if not item['item'] in item_table: # item not native to ALTTP
item_code = get_nonnative_item_sprite(item['item']) item_code = get_nonnative_item_sprite(item['item'])
else: else:
@ -1719,7 +1720,7 @@ def write_custom_shops(rom, world, player):
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]: if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]:
rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask) rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask)
item_data = [shop_id, item_code] + int16_as_bytes(item['price']) + \ item_data = [shop_id, item_code] + price_data + \
[item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + \ [item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + \
int16_as_bytes(item['replacement_price']) + [0 if item['player'] == player else item['player']] int16_as_bytes(item['replacement_price']) + [0 if item['player'] == player else item['player']]
items_data.extend(item_data) items_data.extend(item_data)

View File

@ -1,5 +1,5 @@
from __future__ import annotations from __future__ import annotations
from enum import unique, Enum from enum import unique, IntEnum
from typing import List, Optional, Set, NamedTuple, Dict from typing import List, Optional, Set, NamedTuple, Dict
import logging import logging
@ -13,13 +13,14 @@ logger = logging.getLogger("Shops")
@unique @unique
class ShopType(Enum): class ShopType(IntEnum):
Shop = 0 Shop = 0
TakeAny = 1 TakeAny = 1
UpgradeShop = 2 UpgradeShop = 2
@unique @unique
class ShopPriceType(Enum): class ShopPriceType(IntEnum):
Rupees = 0 Rupees = 0
Hearts = 1 Hearts = 1
Magic = 2 Magic = 2
@ -32,6 +33,7 @@ class ShopPriceType(Enum):
Potion = 9 Potion = 9
Item = 10 Item = 10
class Shop(): class Shop():
slots: int = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots 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 blacklist: Set[str] = set() # items that don't work, todo: actually check against this
@ -112,7 +114,8 @@ class Shop():
'player': player 'player': player
} }
def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0, price_type: int = ShopPriceType.Rupees): def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0,
price_type: int = ShopPriceType.Rupees):
if not self.inventory[slot]: if not self.inventory[slot]:
raise ValueError("Inventory can't be pushed back if it doesn't exist") raise ValueError("Inventory can't be pushed back if it doesn't exist")
@ -185,7 +188,8 @@ def ShopSlotFill(world):
blacklist_word in item_name for blacklist_word in blacklist_words)} blacklist_word in item_name for blacklist_word in blacklist_words)}
blacklist_words.add("Bee") blacklist_words.add("Bee")
locations_per_sphere = list(sorted(sphere, key=lambda location: location.name) for sphere in world.get_spheres()) locations_per_sphere = list(
sorted(sphere, key=lambda location: location.name) for sphere in world.get_spheres())
# currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory # 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 # Potentially create Locations as needed and make inventory the only source, to prevent divergence
@ -241,7 +245,8 @@ def ShopSlotFill(world):
item_name = location.item.name item_name = location.item.name
if location.item.game != "A Link to the Past": if location.item.game != "A Link to the Past":
price = world.random.randrange(1, 28) price = world.random.randrange(1, 28)
elif any(x in item_name for x in ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']): elif any(x in item_name for x in
['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']):
price = world.random.randrange(1, 7) price = world.random.randrange(1, 7)
elif any(x in item_name for x in ['Arrow', 'Bomb', 'Clock']): elif any(x in item_name for x in ['Arrow', 'Bomb', 'Clock']):
price = world.random.randrange(2, 14) price = world.random.randrange(2, 14)
@ -269,7 +274,9 @@ def create_shops(world, player: int):
world.random.shuffle(single_purchase_slots) world.random.shuffle(single_purchase_slots)
if 'g' in option or 'f' in option: if 'g' in option or 'f' in option:
default_shop_table = [i for l in [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if not world.retro[player] or x != 'arrows'] for i in l] default_shop_table = [i for l in
[shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if
not world.retro[player] or x != 'arrows'] for i in l]
new_basic_shop = world.random.sample(default_shop_table, k=3) new_basic_shop = world.random.sample(default_shop_table, k=3)
new_dark_shop = world.random.sample(default_shop_table, k=3) new_dark_shop = world.random.sample(default_shop_table, k=3)
for name, shop in player_shop_table.items(): for name, shop in player_shop_table.items():
@ -287,7 +294,8 @@ def create_shops(world, player: int):
# make sure that blue potion is available in inverted, special case locked = None; lock when done. # make sure that blue potion is available in inverted, special case locked = None; lock when done.
player_shop_table["Dark Lake Hylia Shop"] = \ player_shop_table["Dark Lake Hylia Shop"] = \
player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None) player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
chance_100 = int(world.retro[player])*0.25+int(world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5 chance_100 = int(world.retro[player]) * 0.25 + int(
world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items(): for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items():
region = world.get_region(region_name, player) region = world.get_region(region_name, player)
shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset) shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset)
@ -359,7 +367,8 @@ total_dynamic_shop_slots = sum(3 for shopname, data in shop_table.items() if not
SHOP_ID_START = 0x400000 SHOP_ID_START = 0x400000
shop_table_by_location_id = dict(enumerate( shop_table_by_location_id = dict(enumerate(
(f"{name} {Shop.slot_names[num]}" for name, shop_data in sorted(shop_table.items(), key=lambda item: item[1].sram_offset) (f"{name} {Shop.slot_names[num]}" for name, shop_data in
sorted(shop_table.items(), key=lambda item: item[1].sram_offset)
for num in range(3)), start=SHOP_ID_START)) for num in range(3)), start=SHOP_ID_START))
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)] = "Old Man Sword Cave"
@ -386,7 +395,8 @@ def set_up_shops(world, player: int):
if world.retro[player]: if world.retro[player]:
rss = world.get_region('Red Shield Shop', player).shop rss = world.get_region('Red Shield Shop', player).shop
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50], 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. ['Blue Shield', 50], ['Small Heart',
10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
replacement_items.append(['Small Key (Universal)', 100]) replacement_items.append(['Small Key (Universal)', 100])
replacement_item = world.random.choice(replacement_items) replacement_item = world.random.choice(replacement_items)
@ -407,7 +417,6 @@ def set_up_shops(world, player: int):
shop.push_inventory(next(slots), 'Single Arrow', 80) shop.push_inventory(next(slots), 'Single Arrow', 80)
def shuffle_shops(world, items, player: int): def shuffle_shops(world, items, player: int):
option = world.shop_shuffle[player] option = world.shop_shuffle[player]
if 'u' in option: if 'u' in option:
@ -437,7 +446,8 @@ def shuffle_shops(world, items, player: int):
if not new_items: if not new_items:
break break
else: else:
logging.warning(f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.") 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) 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) arrowupgrades = sum(1 for item in new_items if 'Arrow Upgrade' in item)
if bombupgrades: if bombupgrades:
@ -478,7 +488,6 @@ def shuffle_shops(world, items, player: int):
adjust_item(item) adjust_item(item)
if 'P' in option: if 'P' in option:
print('Making funny prices.')
for item in total_inventory: for item in total_inventory:
price_to_funny_price(item, world, player) price_to_funny_price(item, world, player)
for shop in upgrade_shops: for shop in upgrade_shops:
@ -494,6 +503,7 @@ def shuffle_shops(world, items, player: int):
shop.inventory = total_inventory[i:i + slots] shop.inventory = total_inventory[i:i + slots]
i += slots i += slots
price_blacklist = { price_blacklist = {
ShopPriceType.Rupees: {'Rupees'}, ShopPriceType.Rupees: {'Rupees'},
ShopPriceType.Hearts: {'Small Heart', 'Apple'}, ShopPriceType.Hearts: {'Small Heart', 'Apple'},
@ -501,37 +511,52 @@ price_blacklist = {
ShopPriceType.Bombs: {'Bombs', 'Single Bomb'}, ShopPriceType.Bombs: {'Bombs', 'Single Bomb'},
ShopPriceType.Arrows: {'Arrows', 'Single Arrow'}, ShopPriceType.Arrows: {'Arrows', 'Single Arrow'},
ShopPriceType.HeartContainer: {}, ShopPriceType.HeartContainer: {},
ShopPriceType.BombUpgrade: {}, ShopPriceType.BombUpgrade: {"Bomb Upgrade"},
ShopPriceType.ArrowUpgrade: {}, ShopPriceType.ArrowUpgrade: {"Arrow Upgrade"},
ShopPriceType.Keys: {}, ShopPriceType.Keys: {"Small Key"},
ShopPriceType.Potion: {}, ShopPriceType.Potion: {},
# ShopPriceType.Item: {}
} }
price_chart = { price_chart = {
ShopPriceType.Rupees: lambda p: p, ShopPriceType.Rupees: lambda p: p,
ShopPriceType.Hearts: lambda p: min(1, p//5)*4, ShopPriceType.Hearts: lambda p: min(1, p // 5) * 4,
ShopPriceType.Magic: lambda p: min(8, p//5)*4, ShopPriceType.Magic: lambda p: min(8, p // 5) * 4,
ShopPriceType.Bombs: lambda p: min(10, p//5), ShopPriceType.Bombs: lambda p: min(10, p // 5),
ShopPriceType.Arrows: lambda p: min(30, p//5), ShopPriceType.Arrows: lambda p: min(30, p // 5),
ShopPriceType.HeartContainer: lambda p: 0x8, ShopPriceType.HeartContainer: lambda p: 0x8,
ShopPriceType.BombUpgrade: lambda p: 0x1, ShopPriceType.BombUpgrade: lambda p: 0x1,
ShopPriceType.ArrowUpgrade: lambda p: 0x1, ShopPriceType.ArrowUpgrade: lambda p: 0x1,
ShopPriceType.Keys: lambda p: min(3, (p//100)+1), ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1),
ShopPriceType.Potion: lambda p: (p//5)%5, ShopPriceType.Potion: lambda p: (p // 5) % 5,
# ShopPriceType.Item: lambda p: 0,
} }
def price_to_funny_price(item, world, player): price_type_display_name = {
if item is None: ShopPriceType.Rupees: "Rupees",
return ShopPriceType.Hearts: "Hearts",
my_price_types = [x for x in price_blacklist] ShopPriceType.Bombs: "Bombs",
my_choices = world.random.sample(my_price_types, len(my_price_types)) ShopPriceType.Arrows: "Arrows",
for p in my_choices: }
if p in [ShopPriceType.Rupees, ShopPriceType.BombUpgrade, ShopPriceType.ArrowUpgrade] or (p in [ShopPriceType.Keys] and world.smallkey_shuffle[player] == smallkey_shuffle.option_universal):
return # prices with no? logic requirements
if any(x in item['item'] for x in price_blacklist[p]): simple_price_types = [
continue ShopPriceType.Rupees,
else: ShopPriceType.Hearts,
item['price'] = 0x8000 | 0x100*(p.value-1) | price_chart[p](item['price']) ShopPriceType.Bombs,
break ShopPriceType.Arrows,
]
def price_to_funny_price(item: dict, world, player: int):
if item:
my_price_types = simple_price_types.copy()
world.random.shuffle(my_price_types)
for p in my_price_types:
if p in [ShopPriceType.Rupees, ShopPriceType.BombUpgrade, ShopPriceType.ArrowUpgrade] or (
p in [ShopPriceType.Keys] and world.smallkey_shuffle[player] == smallkey_shuffle.option_universal):
return
if any(x in item['item'] for x in price_blacklist[p]):
continue
else:
item['price'] = price_chart[p](item['price'])
item['price_type'] = p
break