General coding pass on Shops
This commit is contained in:
parent
a67d657882
commit
478e1f3a82
6
Rom.py
6
Rom.py
|
@ -1572,9 +1572,9 @@ def write_custom_shops(rom, world, player):
|
||||||
for item in shop.inventory:
|
for item in shop.inventory:
|
||||||
if item is None:
|
if item is None:
|
||||||
break
|
break
|
||||||
item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [ item['max'],\
|
item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + \
|
||||||
ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes(
|
[item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + \
|
||||||
item['replacement_price']) + [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)
|
||||||
|
|
||||||
rom.write_bytes(0x184800, shop_data)
|
rom.write_bytes(0x184800, shop_data)
|
||||||
|
|
130
Shops.py
130
Shops.py
|
@ -1,11 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from enum import unique, Enum
|
from enum import unique, Enum
|
||||||
from typing import List, Union, Optional, Set
|
from typing import List, Union, Optional, Set, NamedTuple, Dict
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from BaseClasses import Location
|
from BaseClasses import Location
|
||||||
from EntranceShuffle import door_addresses
|
from EntranceShuffle import door_addresses
|
||||||
from Items import item_name_groups, item_table
|
from Items import item_name_groups, item_table, ItemFactory
|
||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes
|
||||||
|
|
||||||
logger = logging.getLogger("Shops")
|
logger = logging.getLogger("Shops")
|
||||||
|
@ -19,14 +19,14 @@ class ShopType(Enum):
|
||||||
|
|
||||||
|
|
||||||
class Shop():
|
class Shop():
|
||||||
slots = 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() # items that don't work, todo: actually check against this
|
blacklist: Set[str] = set() # items that don't work, todo: actually check against this
|
||||||
type = ShopType.Shop
|
type = ShopType.Shop
|
||||||
|
|
||||||
def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool):
|
def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool):
|
||||||
self.region = region
|
self.region = region
|
||||||
self.room_id = room_id
|
self.room_id = room_id
|
||||||
self.inventory: List[Union[None, dict]] = [None] * self.slots
|
self.inventory: List[Optional[dict]] = [None] * self.slots
|
||||||
self.shopkeeper_config = shopkeeper_config
|
self.shopkeeper_config = shopkeeper_config
|
||||||
self.custom = custom
|
self.custom = custom
|
||||||
self.locked = locked
|
self.locked = locked
|
||||||
|
@ -57,10 +57,12 @@ class Shop():
|
||||||
for inv in self.inventory:
|
for inv in self.inventory:
|
||||||
if inv is None:
|
if inv is None:
|
||||||
continue
|
continue
|
||||||
if inv['item'] == item:
|
if inv['max']:
|
||||||
return True
|
if inv['replacement'] == item:
|
||||||
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == item:
|
return True
|
||||||
|
elif inv['item'] == item:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def has(self, item: str) -> bool:
|
def has(self, item: str) -> bool:
|
||||||
|
@ -69,7 +71,7 @@ class Shop():
|
||||||
continue
|
continue
|
||||||
if inv['item'] == item:
|
if inv['item'] == item:
|
||||||
return True
|
return True
|
||||||
if inv['max'] != 0 and inv['replacement'] == item:
|
if inv['replacement'] == item:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -118,6 +120,11 @@ class UpgradeShop(Shop):
|
||||||
blacklist = item_name_groups["Potions"]
|
blacklist = item_name_groups["Potions"]
|
||||||
|
|
||||||
|
|
||||||
|
shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||||
|
ShopType.Shop: Shop,
|
||||||
|
ShopType.TakeAny: TakeAny}
|
||||||
|
|
||||||
|
|
||||||
def ShopSlotFill(world):
|
def ShopSlotFill(world):
|
||||||
shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||||
for location in shop_locations if location.shop_slot}
|
for location in shop_locations if location.shop_slot}
|
||||||
|
@ -194,29 +201,25 @@ def ShopSlotFill(world):
|
||||||
|
|
||||||
|
|
||||||
def create_shops(world, player: int):
|
def create_shops(world, player: int):
|
||||||
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
|
||||||
ShopType.Shop: Shop,
|
|
||||||
ShopType.TakeAny: TakeAny}
|
|
||||||
option = world.shop_shuffle[player]
|
option = world.shop_shuffle[player]
|
||||||
my_shop_table = dict(shop_table)
|
|
||||||
|
|
||||||
num_slots = int(world.shop_shuffle_slots[player])
|
player_shop_table = shop_table.copy()
|
||||||
|
if "w" in option:
|
||||||
|
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
|
||||||
|
|
||||||
my_shop_slots = ([True] * num_slots + [False] * (len(shop_table) * 3))[:len(shop_table) * 3 - 2]
|
num_slots = min(dynamic_shop_slots, max(0, int(world.shop_shuffle_slots[player]))) # 0 to 30
|
||||||
|
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
|
||||||
|
world.random.shuffle(single_purchase_slots)
|
||||||
|
|
||||||
world.random.shuffle(my_shop_slots)
|
|
||||||
|
|
||||||
from Items import ItemFactory
|
|
||||||
if 'g' in option or 'f' in option:
|
if 'g' in option or 'f' in option:
|
||||||
new_basic_shop = world.random.sample(shop_generation_types['default'], k=3)
|
new_basic_shop = world.random.sample(shop_generation_types['default'], k=3)
|
||||||
new_dark_shop = world.random.sample(shop_generation_types['default'], k=3)
|
new_dark_shop = world.random.sample(shop_generation_types['default'], k=3)
|
||||||
for name, shop in my_shop_table.items():
|
for name, shop in player_shop_table.items():
|
||||||
typ, shop_id, keeper, custom, locked, items = shop
|
typ, shop_id, keeper, custom, locked, items = shop
|
||||||
if name == 'Capacity Upgrade':
|
if not locked:
|
||||||
pass
|
|
||||||
elif name == 'Potion Shop' and not "w" in option:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
new_items = world.random.sample(shop_generation_types['default'], k=3)
|
new_items = world.random.sample(shop_generation_types['default'], k=3)
|
||||||
if 'f' not in option:
|
if 'f' not in option:
|
||||||
if items == _basic_shop_defaults:
|
if items == _basic_shop_defaults:
|
||||||
|
@ -224,55 +227,64 @@ def create_shops(world, player: int):
|
||||||
elif items == _dark_world_shop_defaults:
|
elif items == _dark_world_shop_defaults:
|
||||||
new_items = new_dark_shop
|
new_items = new_dark_shop
|
||||||
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
|
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
|
||||||
my_shop_table[name] = (typ, shop_id, keeper, custom, locked, new_items)
|
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items)
|
||||||
|
if world.mode[player] == "inverted":
|
||||||
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in my_shop_table.items():
|
player_shop_table["Dark Lake Hylia Shop"] = \
|
||||||
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
player_shop_table["Dark Lake Hylia Shop"]._replace(locked=True, items=_inverted_hylia_shop_defaults)
|
||||||
locked = True
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in player_shop_table.items():
|
||||||
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
|
||||||
region = world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
shop = cls_mapping[type](region, room_id, shopkeeper, custom, locked)
|
shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked)
|
||||||
region.shop = shop
|
region.shop = shop
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
for index, item in enumerate(inventory):
|
for index, item in enumerate(inventory):
|
||||||
shop.add_inventory(index, *item)
|
shop.add_inventory(index, *item)
|
||||||
if region_name == 'Potion Shop' and 'w' not in option:
|
if not locked and single_purchase_slots.pop():
|
||||||
pass
|
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
|
||||||
elif region_name == 'Capacity Upgrade':
|
slot_name = "{} Slot {}".format(region.name, index + 1)
|
||||||
pass
|
loc = Location(player, slot_name, address=shop_table_by_location[slot_name],
|
||||||
else:
|
parent=region, hint_text="for sale")
|
||||||
if my_shop_slots.pop():
|
loc.shop_slot = True
|
||||||
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
|
loc.locked = True
|
||||||
slot_name = "{} Slot {}".format(shop.region.name, index + 1)
|
loc.item = ItemFactory(additional_item, player)
|
||||||
loc = Location(player, slot_name, address=shop_table_by_location[slot_name],
|
shop.region.locations.append(loc)
|
||||||
parent=shop.region, hint_text="for sale")
|
world.dynamic_locations.append(loc)
|
||||||
loc.shop_slot = True
|
world.clear_location_cache()
|
||||||
loc.locked = True
|
|
||||||
loc.item = ItemFactory(additional_item, player)
|
|
||||||
shop.region.locations.append(loc)
|
|
||||||
world.dynamic_locations.append(loc)
|
|
||||||
|
|
||||||
world.clear_location_cache()
|
|
||||||
|
class ShopData(NamedTuple):
|
||||||
|
room: int
|
||||||
|
type: ShopType
|
||||||
|
shopkeeper: int
|
||||||
|
custom: bool
|
||||||
|
locked: bool
|
||||||
|
items: List
|
||||||
|
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
_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)]
|
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
shop_table = {
|
_inverted_hylia_shop_defaults = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
|
shop_table: Dict[str, ShopData] = {
|
||||||
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, True, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
|
'Cave Shop (Dark Death Mountain)': ShopData(0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
|
||||||
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
'Red Shield Shop': ShopData(0x0110, ShopType.Shop, 0xC1, True, False,
|
||||||
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
[('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
|
||||||
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
'Dark Lake Hylia Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
'Dark World Lumberjack Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Village of Outcasts Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Dark World Potion Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
'Light World Death Mountain Shop': ShopData(0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Potion Shop': (0x0109, ShopType.Shop, 0xA0, True, False, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
'Kakariko Shop': ShopData(0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
'Cave Shop (Lake Hylia)': ShopData(0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
||||||
|
'Potion Shop': ShopData(0x0109, ShopType.Shop, 0xA0, True, True,
|
||||||
|
[('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
||||||
|
'Capacity Upgrade': ShopData(0x0115, ShopType.UpgradeShop, 0x04, True, True,
|
||||||
|
[('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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_ID_START = 0x400000
|
||||||
shop_table_by_location_id = {SHOP_ID_START + cnt: s for cnt, s in enumerate(
|
shop_table_by_location_id = {SHOP_ID_START + cnt: s for cnt, s in enumerate(
|
||||||
[item for sublist in [["{} Slot {}".format(name, num + 1) for num in range(3)] for name in shop_table] for item in
|
[item for sublist in [["{} Slot {}".format(name, num + 1) for num in range(3)] for name in shop_table] for item in
|
||||||
|
|
Loading…
Reference in New Issue