Merge branch 'new_shops' into Archipelago_Main
This commit is contained in:
commit
d1709764ef
|
@ -1057,17 +1057,19 @@ 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, price_rate_display
|
||||||
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']
|
my_price = item['price'] // price_rate_display.get(item['price_type'], 1)
|
||||||
|
shopdata['item_{}'.format(index)] = f"{item['item']} — {my_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']))
|
||||||
|
|
Binary file not shown.
|
@ -4,7 +4,7 @@ import Utils
|
||||||
from Patch import read_rom
|
from Patch import read_rom
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '13a75c5dd28055fbcf8f69bd8161871d'
|
RANDOMIZERBASEHASH = 'e397fef0e947d1bd760c68c4fe99a600'
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
@ -22,7 +22,7 @@ from typing import Optional
|
||||||
|
|
||||||
from BaseClasses import CollectionState, Region
|
from BaseClasses import CollectionState, Region
|
||||||
from worlds.alttp.SubClasses import ALttPLocation
|
from worlds.alttp.SubClasses import ALttPLocation
|
||||||
from worlds.alttp.Shops import ShopType
|
from worlds.alttp.Shops import ShopType, ShopPriceType
|
||||||
from worlds.alttp.Dungeons import dungeon_music_addresses
|
from worlds.alttp.Dungeons import dungeon_music_addresses
|
||||||
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
|
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
|
||||||
from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable
|
from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable
|
||||||
|
@ -1707,9 +1707,16 @@ 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
|
||||||
|
if item['price_type'] != ShopPriceType.Rupees:
|
||||||
|
# Set special price flag 0x8000
|
||||||
|
# Then set the type of price we're setting 0x7F00 (this starts from Hearts, not Rupees, subtract 1)
|
||||||
|
# Then append the price/index into the second byte 0x00FF
|
||||||
|
price_data = int16_as_bytes(0x8000 | 0x100 * (item["price_type"] - 1) | item['price'])
|
||||||
|
else:
|
||||||
|
price_data = int16_as_bytes(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:
|
||||||
|
@ -1717,7 +1724,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)
|
||||||
|
|
|
@ -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,12 +13,27 @@ 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
|
||||||
|
class ShopPriceType(IntEnum):
|
||||||
|
Rupees = 0
|
||||||
|
Hearts = 1
|
||||||
|
Magic = 2
|
||||||
|
Bombs = 3
|
||||||
|
Arrows = 4
|
||||||
|
HeartContainer = 5
|
||||||
|
BombUpgrade = 6
|
||||||
|
ArrowUpgrade = 7
|
||||||
|
Keys = 8
|
||||||
|
Potion = 9
|
||||||
|
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
|
||||||
|
@ -87,10 +102,11 @@ class Shop():
|
||||||
|
|
||||||
def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
|
def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
|
||||||
replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False,
|
replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False,
|
||||||
player: int = 0):
|
player: int = 0, price_type: int = ShopPriceType.Rupees):
|
||||||
self.inventory[slot] = {
|
self.inventory[slot] = {
|
||||||
'item': item,
|
'item': item,
|
||||||
'price': price,
|
'price': price,
|
||||||
|
'price_type': price_type,
|
||||||
'max': max,
|
'max': max,
|
||||||
'replacement': replacement,
|
'replacement': replacement,
|
||||||
'replacement_price': replacement_price,
|
'replacement_price': replacement_price,
|
||||||
|
@ -98,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):
|
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")
|
||||||
|
|
||||||
|
@ -108,6 +125,7 @@ class Shop():
|
||||||
self.inventory[slot] = {
|
self.inventory[slot] = {
|
||||||
'item': item,
|
'item': item,
|
||||||
'price': price,
|
'price': price,
|
||||||
|
'price_type': price_type,
|
||||||
'max': max,
|
'max': max,
|
||||||
'replacement': self.inventory[slot]["item"],
|
'replacement': self.inventory[slot]["item"],
|
||||||
'replacement_price': self.inventory[slot]["price"],
|
'replacement_price': self.inventory[slot]["price"],
|
||||||
|
@ -170,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
|
||||||
|
@ -226,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)
|
||||||
|
@ -254,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():
|
||||||
|
@ -272,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)
|
||||||
|
@ -344,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"
|
||||||
|
@ -371,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)
|
||||||
|
@ -421,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:
|
||||||
|
@ -432,7 +458,7 @@ def shuffle_shops(world, items, player: int):
|
||||||
for item in new_items:
|
for item in new_items:
|
||||||
world.push_precollected(ItemFactory(item, player))
|
world.push_precollected(ItemFactory(item, player))
|
||||||
|
|
||||||
if 'p' in option or 'i' in option:
|
if any(setting in option for setting in 'ipP'):
|
||||||
shops = []
|
shops = []
|
||||||
upgrade_shops = []
|
upgrade_shops = []
|
||||||
total_inventory = []
|
total_inventory = []
|
||||||
|
@ -461,6 +487,13 @@ def shuffle_shops(world, items, player: int):
|
||||||
for item in shop.inventory:
|
for item in shop.inventory:
|
||||||
adjust_item(item)
|
adjust_item(item)
|
||||||
|
|
||||||
|
if 'P' in option:
|
||||||
|
for item in total_inventory:
|
||||||
|
price_to_funny_price(item, world, player)
|
||||||
|
# Don't apply to upgrade shops
|
||||||
|
# Upgrade shop is only one place, and will generally be too easy to
|
||||||
|
# replenish hearts and bombs
|
||||||
|
|
||||||
if 'i' in option:
|
if 'i' in option:
|
||||||
world.random.shuffle(total_inventory)
|
world.random.shuffle(total_inventory)
|
||||||
|
|
||||||
|
@ -469,3 +502,82 @@ def shuffle_shops(world, items, player: int):
|
||||||
slots = shop.slots
|
slots = shop.slots
|
||||||
shop.inventory = total_inventory[i:i + slots]
|
shop.inventory = total_inventory[i:i + slots]
|
||||||
i += slots
|
i += slots
|
||||||
|
|
||||||
|
|
||||||
|
price_blacklist = {
|
||||||
|
ShopPriceType.Rupees: {'Rupees'},
|
||||||
|
ShopPriceType.Hearts: {'Small Heart', 'Apple'},
|
||||||
|
ShopPriceType.Magic: {'Magic Jar'},
|
||||||
|
ShopPriceType.Bombs: {'Bombs', 'Single Bomb'},
|
||||||
|
ShopPriceType.Arrows: {'Arrows', 'Single Arrow'},
|
||||||
|
ShopPriceType.HeartContainer: {},
|
||||||
|
ShopPriceType.BombUpgrade: {"Bomb Upgrade"},
|
||||||
|
ShopPriceType.ArrowUpgrade: {"Arrow Upgrade"},
|
||||||
|
ShopPriceType.Keys: {"Small Key"},
|
||||||
|
ShopPriceType.Potion: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
price_chart = {
|
||||||
|
ShopPriceType.Rupees: lambda p: p,
|
||||||
|
ShopPriceType.Hearts: lambda p: min(5, p // 5) * 8, # Each heart is 0x8 in memory, Max of 5 hearts (20 total??)
|
||||||
|
ShopPriceType.Magic: lambda p: min(15, p // 5) * 8, # Each pip is 0x8 in memory, Max of 15 pips (16 total...)
|
||||||
|
ShopPriceType.Bombs: lambda p: max(1, min(10, p // 5)), # 10 Bombs max
|
||||||
|
ShopPriceType.Arrows: lambda p: max(1, min(30, p // 5)), # 30 Arrows Max
|
||||||
|
ShopPriceType.HeartContainer: lambda p: 0x8,
|
||||||
|
ShopPriceType.BombUpgrade: lambda p: 0x1,
|
||||||
|
ShopPriceType.ArrowUpgrade: lambda p: 0x1,
|
||||||
|
ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1), # Max of 3 keys for a price
|
||||||
|
ShopPriceType.Potion: lambda p: (p // 5) % 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
price_type_display_name = {
|
||||||
|
ShopPriceType.Rupees: "Rupees",
|
||||||
|
ShopPriceType.Hearts: "Hearts",
|
||||||
|
ShopPriceType.Bombs: "Bombs",
|
||||||
|
ShopPriceType.Arrows: "Arrows",
|
||||||
|
ShopPriceType.Keys: "Keys",
|
||||||
|
}
|
||||||
|
|
||||||
|
# price division
|
||||||
|
price_rate_display = {
|
||||||
|
ShopPriceType.Hearts: 8,
|
||||||
|
ShopPriceType.Magic: 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
# prices with no? logic requirements
|
||||||
|
simple_price_types = [
|
||||||
|
ShopPriceType.Rupees,
|
||||||
|
ShopPriceType.Hearts,
|
||||||
|
ShopPriceType.Bombs,
|
||||||
|
ShopPriceType.Arrows,
|
||||||
|
ShopPriceType.Keys
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def price_to_funny_price(item: dict, world, player: int):
|
||||||
|
"""
|
||||||
|
Converts a raw Rupee price into a special price type
|
||||||
|
"""
|
||||||
|
if item:
|
||||||
|
my_price_types = simple_price_types.copy()
|
||||||
|
world.random.shuffle(my_price_types)
|
||||||
|
for p_type in my_price_types:
|
||||||
|
# Ignore rupee prices, logic-based prices or Keys (if we're not on universal keys)
|
||||||
|
if p_type in [ShopPriceType.Rupees, ShopPriceType.BombUpgrade, ShopPriceType.ArrowUpgrade]:
|
||||||
|
return
|
||||||
|
# If we're using keys...
|
||||||
|
# Check if we're in universal, check if our replacement isn't a Small Key
|
||||||
|
# Check if price isn't super small... (this will ideally be handled in a future table)
|
||||||
|
if p_type in [ShopPriceType.Keys]:
|
||||||
|
if world.smallkey_shuffle[player] != smallkey_shuffle.option_universal:
|
||||||
|
continue
|
||||||
|
elif item['replacement'] and 'Small Key' in item['replacement']:
|
||||||
|
continue
|
||||||
|
if item['price'] < 50:
|
||||||
|
continue
|
||||||
|
if any(x in item['item'] for x in price_blacklist[p_type]):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
item['price'] = min(price_chart[p_type](item['price']) , 255)
|
||||||
|
item['price_type'] = p_type
|
||||||
|
break
|
||||||
|
|
Loading…
Reference in New Issue