mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00

## What is this fixing or adding? Adds Bombless Start option, along with proper bomb logic. This involves updating `can_kill_most_things` to include checking how many bombs can be held. Many places where the ability to kill enemies was assumed, now have logic. This fixes some possible existing logic issues, for example: Mini Moldorm cave checks currently are always in logic despite the fact that on expert enemy health it would require 12 bombs to kill each mini moldorm. Overhauls options, pulling them out of core and in particular making large changes to how the shop options work. Co-authored-by: espeon65536 <81029175+espeon65536@users.noreply.github.com> Co-authored-by: black-sliver <59490463+black-sliver@users.noreply.github.com> Co-authored-by: Bondo <38083232+BadmoonzZ@users.noreply.github.com> Co-authored-by: espeon65536 <espeon65536@gmail.com> Co-authored-by: Fabian Dill <Berserker66@users.noreply.github.com>
474 lines
21 KiB
Python
474 lines
21 KiB
Python
from __future__ import annotations
|
|
from enum import unique, IntEnum
|
|
from typing import List, Optional, Set, NamedTuple, Dict
|
|
import logging
|
|
|
|
from Utils import int16_as_bytes
|
|
|
|
from worlds.generic.Rules import add_rule
|
|
|
|
from BaseClasses import CollectionState
|
|
from .SubClasses import ALttPLocation
|
|
from .EntranceShuffle import door_addresses
|
|
from .Items import item_name_groups
|
|
from .Options import small_key_shuffle, RandomizeShopInventories
|
|
from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
|
|
|
|
logger = logging.getLogger("Shops")
|
|
|
|
|
|
@unique
|
|
class ShopType(IntEnum):
|
|
Shop = 0
|
|
TakeAny = 1
|
|
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:
|
|
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
|
|
type = ShopType.Shop
|
|
slot_names: Dict[int, str] = {
|
|
0: " Left",
|
|
1: " Center",
|
|
2: " Right"
|
|
}
|
|
|
|
def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool, sram_offset: int):
|
|
self.region = region
|
|
self.room_id = room_id
|
|
self.inventory: List[Optional[dict]] = [None] * self.slots
|
|
self.shopkeeper_config = shopkeeper_config
|
|
self.custom = custom
|
|
self.locked = locked
|
|
self.sram_offset = sram_offset
|
|
|
|
@property
|
|
def item_count(self) -> int:
|
|
for x in range(self.slots - 1, -1, -1): # last x is 0
|
|
if self.inventory[x]:
|
|
return x + 1
|
|
return 0
|
|
|
|
def get_bytes(self) -> List[int]:
|
|
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
|
entrances = self.region.entrances
|
|
config = self.item_count
|
|
if len(entrances) == 1 and entrances[0].name in door_addresses:
|
|
door_id = door_addresses[entrances[0].name][0] + 1
|
|
else:
|
|
door_id = 0
|
|
config |= 0x40 # ignore door id
|
|
if self.type == ShopType.TakeAny:
|
|
config |= 0x80
|
|
elif self.type == ShopType.UpgradeShop:
|
|
config |= 0x10 # Alt. VRAM
|
|
return [0x00] + int16_as_bytes(self.room_id) + [door_id, 0x00, config, self.shopkeeper_config, 0x00]
|
|
|
|
def has_unlimited(self, item: str) -> bool:
|
|
for inv in self.inventory:
|
|
if inv is None:
|
|
continue
|
|
if inv['max']:
|
|
if inv['replacement'] == item:
|
|
return True
|
|
elif inv['item'] == item:
|
|
return True
|
|
|
|
return False
|
|
|
|
def has(self, item: str) -> bool:
|
|
for inv in self.inventory:
|
|
if inv is None:
|
|
continue
|
|
if inv['item'] == item:
|
|
return True
|
|
if inv['replacement'] == item:
|
|
return True
|
|
return False
|
|
|
|
def clear_inventory(self):
|
|
self.inventory = [None] * self.slots
|
|
|
|
def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
|
|
replacement: Optional[str] = None, replacement_price: int = 0,
|
|
player: int = 0, price_type: int = ShopPriceType.Rupees,
|
|
replacement_price_type: int = ShopPriceType.Rupees):
|
|
self.inventory[slot] = {
|
|
'item': item,
|
|
'price': price,
|
|
'price_type': price_type,
|
|
'max': max,
|
|
'replacement': replacement,
|
|
'replacement_price': replacement_price,
|
|
'replacement_price_type': replacement_price_type,
|
|
'player': player
|
|
}
|
|
|
|
def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0,
|
|
price_type: int = ShopPriceType.Rupees):
|
|
|
|
self.inventory[slot] = {
|
|
'item': item,
|
|
'price': price,
|
|
'price_type': price_type,
|
|
'max': max,
|
|
'replacement': self.inventory[slot]["item"] if self.inventory[slot] else None,
|
|
'replacement_price': self.inventory[slot]["price"] if self.inventory[slot] else 0,
|
|
'replacement_price_type': self.inventory[slot]["price_type"] if self.inventory[slot] else ShopPriceType.Rupees,
|
|
'player': player
|
|
}
|
|
|
|
|
|
class TakeAny(Shop):
|
|
type = ShopType.TakeAny
|
|
slot_names: Dict[int, str] = {
|
|
0: "",
|
|
1: "",
|
|
2: ""
|
|
}
|
|
|
|
|
|
class UpgradeShop(Shop):
|
|
type = ShopType.UpgradeShop
|
|
# Potions break due to VRAM flags set in UpgradeShop.
|
|
# Didn't check for more things breaking as not much else can be shuffled here currently
|
|
blacklist = item_name_groups["Potions"]
|
|
slot_names: Dict[int, str] = {
|
|
0: " Left",
|
|
1: " Right"
|
|
}
|
|
|
|
|
|
shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
|
ShopType.Shop: Shop,
|
|
ShopType.TakeAny: TakeAny}
|
|
|
|
|
|
def push_shop_inventories(multiworld):
|
|
shop_slots = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop.type
|
|
!= ShopType.TakeAny) for location in shop_locations if location.shop_slot is not None]
|
|
|
|
for location in shop_slots:
|
|
item_name = location.item.name
|
|
# Retro Bow arrows will already have been pushed
|
|
if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player)
|
|
!= ("Single Arrow", location.player)):
|
|
location.shop.push_inventory(location.shop_slot, item_name, location.shop_price,
|
|
1, location.item.player if location.item.player != location.player else 0,
|
|
location.shop_price_type)
|
|
location.shop_price = location.shop.inventory[location.shop_slot]["price"] = min(location.shop_price,
|
|
get_price(multiworld, location.shop.inventory[location.shop_slot], location.player,
|
|
location.shop_price_type)[1])
|
|
|
|
|
|
def create_shops(multiworld, player: int):
|
|
|
|
player_shop_table = shop_table.copy()
|
|
if multiworld.include_witch_hut[player]:
|
|
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
|
|
dynamic_shop_slots = total_dynamic_shop_slots + 3
|
|
else:
|
|
dynamic_shop_slots = total_dynamic_shop_slots
|
|
if multiworld.shuffle_capacity_upgrades[player]:
|
|
player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False)
|
|
|
|
num_slots = min(dynamic_shop_slots, multiworld.shop_item_slots[player])
|
|
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
|
|
multiworld.random.shuffle(single_purchase_slots)
|
|
|
|
if multiworld.randomize_shop_inventories[player]:
|
|
default_shop_table = [i for l in
|
|
[shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if
|
|
not multiworld.retro_bow[player] or x != 'arrows'] for i in l]
|
|
new_basic_shop = multiworld.random.sample(default_shop_table, k=3)
|
|
new_dark_shop = multiworld.random.sample(default_shop_table, k=3)
|
|
for name, shop in player_shop_table.items():
|
|
typ, shop_id, keeper, custom, locked, items, sram_offset = shop
|
|
if not locked:
|
|
new_items = multiworld.random.sample(default_shop_table, k=len(items))
|
|
if multiworld.randomize_shop_inventories[player] == RandomizeShopInventories.option_randomize_by_shop_type:
|
|
if items == _basic_shop_defaults:
|
|
new_items = new_basic_shop
|
|
elif items == _dark_world_shop_defaults:
|
|
new_items = new_dark_shop
|
|
keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF])
|
|
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
|
|
if multiworld.mode[player] == "inverted":
|
|
# 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"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
|
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items():
|
|
region = multiworld.get_region(region_name, player)
|
|
shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset)
|
|
# special case: allow shop slots, but do not allow overwriting of base inventory behind them
|
|
if locked is None:
|
|
shop.locked = True
|
|
region.shop = shop
|
|
multiworld.shops.append(shop)
|
|
for index, item in enumerate(inventory):
|
|
shop.add_inventory(index, *item)
|
|
if not locked and (num_slots or type == ShopType.UpgradeShop):
|
|
slot_name = f"{region.name}{shop.slot_names[index]}"
|
|
loc = ALttPLocation(player, slot_name, address=shop_table_by_location[slot_name],
|
|
parent=region, hint_text="for sale")
|
|
loc.shop_price_type, loc.shop_price = get_price(multiworld, None, player)
|
|
loc.item_rule = lambda item, spot=loc: not any(i for i in price_blacklist[spot.shop_price_type] if i in item.name)
|
|
add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot))
|
|
loc.shop = shop
|
|
loc.shop_slot = index
|
|
if ((not (multiworld.shuffle_capacity_upgrades[player] and type == ShopType.UpgradeShop))
|
|
and not single_purchase_slots.pop()):
|
|
loc.shop_slot_disabled = True
|
|
loc.locked = True
|
|
else:
|
|
shop.region.locations.append(loc)
|
|
|
|
|
|
class ShopData(NamedTuple):
|
|
room: int
|
|
type: ShopType
|
|
shopkeeper: int
|
|
custom: bool
|
|
locked: Optional[bool]
|
|
items: List
|
|
sram_offset: int
|
|
|
|
|
|
# (type, room_id, shopkeeper, custom, locked, [items], sram_offset)
|
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
|
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
|
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
|
_inverted_hylia_shop_defaults = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
|
shop_table: Dict[str, ShopData] = {
|
|
'Cave Shop (Dark Death Mountain)': ShopData(0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults, 0),
|
|
'Red Shield Shop': ShopData(0x0110, ShopType.Shop, 0xC1, True, False,
|
|
[('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)], 3),
|
|
'Dark Lake Hylia Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 6),
|
|
'Dark World Lumberjack Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 9),
|
|
'Village of Outcasts Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 12),
|
|
'Dark World Potion Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 15),
|
|
'Light World Death Mountain Shop': ShopData(0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 18),
|
|
'Kakariko Shop': ShopData(0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 21),
|
|
'Cave Shop (Lake Hylia)': ShopData(0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 24),
|
|
'Potion Shop': ShopData(0x0109, ShopType.Shop, 0xA0, True, True,
|
|
[('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], 27),
|
|
'Capacity Upgrade': ShopData(0x0115, ShopType.UpgradeShop, 0x04, True, True,
|
|
[('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)], 30)
|
|
}
|
|
|
|
total_shop_slots = len(shop_table) * 3
|
|
total_dynamic_shop_slots = sum(3 for shopname, data in shop_table.items() if not data[4]) # data[4] -> locked
|
|
|
|
SHOP_ID_START = 0x400000
|
|
shop_table_by_location_id = dict(enumerate(
|
|
(f"{name}{UpgradeShop.slot_names[num]}" if shop_data.type == ShopType.UpgradeShop else
|
|
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(2 if shop_data.type == ShopType.UpgradeShop else 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 + 1)] = "Take-Any #1"
|
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 2)] = "Take-Any #2"
|
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 3)] = "Take-Any #3"
|
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 4)] = "Take-Any #4"
|
|
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
|
|
|
|
shop_generation_types = {
|
|
'arrows': [('Single Arrow', 5), ('Arrows (10)', 50)],
|
|
'bombs': [('Single Bomb', 10), ('Bombs (3)', 30), ('Bombs (10)', 50)],
|
|
'shields': [('Red Shield', 500), ('Blue Shield', 50)],
|
|
'potions': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)],
|
|
'discount_potions': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
|
'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(multiworld, player: int):
|
|
# TODO: move hard+ mode changes for shields here, utilizing the new shops
|
|
|
|
if multiworld.retro_bow[player]:
|
|
rss = multiworld.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 multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
|
replacement_items.append(['Small Key (Universal)', 100])
|
|
replacement_item = multiworld.random.choice(replacement_items)
|
|
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
|
|
rss.locked = True
|
|
|
|
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal or multiworld.retro_bow[player]:
|
|
for shop in multiworld.random.sample([s for s in multiworld.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]
|
|
multiworld.random.shuffle(slots)
|
|
slots = iter(slots)
|
|
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
|
shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
|
|
if multiworld.retro_bow[player]:
|
|
shop.push_inventory(next(slots), 'Single Arrow', 80)
|
|
|
|
if multiworld.shuffle_capacity_upgrades[player]:
|
|
for shop in multiworld.shops:
|
|
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
|
|
shop.region.name == "Capacity Upgrade":
|
|
shop.clear_inventory()
|
|
|
|
if (multiworld.shuffle_shop_inventories[player] or multiworld.randomize_shop_prices[player]
|
|
or multiworld.randomize_cost_types[player]):
|
|
shops = []
|
|
total_inventory = []
|
|
for shop in multiworld.shops:
|
|
if shop.region.player == player:
|
|
if shop.type == ShopType.Shop and not shop.locked:
|
|
shops.append(shop)
|
|
total_inventory.extend(shop.inventory)
|
|
|
|
for item in total_inventory:
|
|
item["price_type"], item["price"] = get_price(multiworld, item, player)
|
|
|
|
if multiworld.shuffle_shop_inventories[player]:
|
|
multiworld.random.shuffle(total_inventory)
|
|
|
|
i = 0
|
|
for shop in shops:
|
|
slots = shop.slots
|
|
shop.inventory = total_inventory[i: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, d: p,
|
|
# Each heart is 0x8 in memory, Max of 19 hearts on easy/normal, 9 on hard, 7 on expert
|
|
ShopPriceType.Hearts: lambda p, d: max(8, min([19, 19, 9, 7][d], p // 14) * 8),
|
|
# Each pip is 0x8 in memory, Max of 15 pips (16 total)
|
|
ShopPriceType.Magic: lambda p, d: max(8, min(15, p // 18) * 8),
|
|
ShopPriceType.Bombs: lambda p, d: max(1, min(50, p // 5)), # 50 Bombs max
|
|
ShopPriceType.Arrows: lambda p, d: max(1, min(70, p // 4)), # 70 Arrows Max
|
|
ShopPriceType.HeartContainer: lambda p, d: 0x8,
|
|
ShopPriceType.BombUpgrade: lambda p, d: 0x1,
|
|
ShopPriceType.ArrowUpgrade: lambda p, d: 0x1,
|
|
ShopPriceType.Keys: lambda p, d: max(1, min(3, (p // 90) + 1)), # Max of 3 keys for a price
|
|
ShopPriceType.Potion: lambda p, d: (p // 5) % 5,
|
|
}
|
|
|
|
price_type_display_name = {
|
|
ShopPriceType.Rupees: "Rupees",
|
|
ShopPriceType.Hearts: "Hearts",
|
|
ShopPriceType.Bombs: "Bombs",
|
|
ShopPriceType.Arrows: "Arrows",
|
|
ShopPriceType.Keys: "Keys",
|
|
ShopPriceType.Item: "Item",
|
|
ShopPriceType.Magic: "Magic"
|
|
}
|
|
|
|
# price division
|
|
price_rate_display = {
|
|
ShopPriceType.Hearts: 8,
|
|
ShopPriceType.Magic: 8,
|
|
}
|
|
|
|
|
|
def get_price_modifier(item):
|
|
if item.game == "A Link to the Past":
|
|
if any(x in item.name for x in
|
|
['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']):
|
|
return 0.125
|
|
elif any(x in item.name for x in
|
|
['Arrow', 'Bomb', 'Clock']) and item.name != "Bombos" and "(50)" not in item.name:
|
|
return 0.25
|
|
elif any(x in item.name for x in ['Small Key', 'Heart']):
|
|
return 0.5
|
|
else:
|
|
return 1
|
|
if item.advancement:
|
|
return 1
|
|
elif item.useful:
|
|
return 0.5
|
|
else:
|
|
return 0.25
|
|
|
|
|
|
def get_price(multiworld, item, player: int, price_type=None):
|
|
"""Converts a raw Rupee price into a special price type"""
|
|
|
|
if price_type:
|
|
price_types = [price_type]
|
|
else:
|
|
price_types = [ShopPriceType.Rupees] # included as a chance to not change price
|
|
if multiworld.randomize_cost_types[player]:
|
|
price_types += [
|
|
ShopPriceType.Hearts,
|
|
ShopPriceType.Bombs,
|
|
ShopPriceType.Magic,
|
|
]
|
|
if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
|
|
if item and item["item"] == "Small Key (Universal)":
|
|
price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys
|
|
else:
|
|
price_types.append(ShopPriceType.Keys)
|
|
if multiworld.retro_bow[player]:
|
|
if item and item["item"] == "Single Arrow":
|
|
price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows
|
|
else:
|
|
price_types.append(ShopPriceType.Arrows)
|
|
diff = multiworld.item_pool[player].value
|
|
if item:
|
|
# This is for a shop's regular inventory, the item is already determined, and we will decide the price here
|
|
price = item["price"]
|
|
if multiworld.randomize_shop_prices[player]:
|
|
adjust = 2 if price < 100 else 5
|
|
price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust
|
|
multiworld.random.shuffle(price_types)
|
|
for p_type in price_types:
|
|
if any(x in item['item'] for x in price_blacklist[p_type]):
|
|
continue
|
|
return p_type, price_chart[p_type](price, diff)
|
|
else:
|
|
# This is an AP location and the price will be adjusted after an item is shuffled into it
|
|
p_type = multiworld.random.choice(price_types)
|
|
return p_type, price_chart[p_type](min(int(multiworld.random.randint(8, 56)
|
|
* multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff)
|
|
|
|
|
|
def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation):
|
|
if location.shop_price_type == ShopPriceType.Hearts:
|
|
return has_hearts(state, player, (location.shop_price / 8) + 1)
|
|
elif location.shop_price_type == ShopPriceType.Bombs:
|
|
return can_use_bombs(state, player, location.shop_price)
|
|
elif location.shop_price_type == ShopPriceType.Arrows:
|
|
return can_hold_arrows(state, player, location.shop_price)
|
|
return True
|