Move Shop related stuff to a Shops module

This commit is contained in:
Fabian Dill 2021-01-16 02:23:23 +01:00
parent f046ca806c
commit f3e686ba9a
14 changed files with 325 additions and 292 deletions

View File

@ -5,12 +5,11 @@ from enum import Enum, unique
import logging
import json
from collections import OrderedDict, Counter, deque
from typing import Union, Optional, List, Set, Dict, NamedTuple, Iterable
from typing import Union, Optional, List, Dict, NamedTuple, Iterable
import secrets
import random
from EntranceShuffle import door_addresses, indirect_connections
from Utils import int16_as_bytes
from EntranceShuffle import indirect_connections
from Items import item_name_groups
@ -1148,110 +1147,6 @@ class Item(object):
class Crystal(Item):
pass
@unique
class ShopType(Enum):
Shop = 0
TakeAny = 1
UpgradeShop = 2
class Shop():
slots = 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
type = ShopType.Shop
def __init__(self, region: Region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool):
self.region = region
self.room_id = room_id
self.inventory: List[Union[None, dict]] = [None] * self.slots
self.shopkeeper_config = shopkeeper_config
self.custom = custom
self.locked = locked
@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['item'] == item:
return True
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == 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['max'] != 0 and 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, create_location: bool = False,
player: int = 0):
self.inventory[slot] = {
'item': item,
'price': price,
'max': max,
'replacement': replacement,
'replacement_price': replacement_price,
'create_location': create_location,
'player': player
}
def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0):
if not self.inventory[slot]:
raise ValueError("Inventory can't be pushed back if it doesn't exist")
self.inventory[slot] = {
'item': item,
'price': price,
'max': max,
'replacement': self.inventory[slot]["item"],
'replacement_price': self.inventory[slot]["price"],
'create_location': self.inventory[slot]["create_location"],
'player': player
}
def can_push_inventory(self, slot: int):
return self.inventory[slot] and not self.inventory[slot]["replacement"]
class TakeAny(Shop):
type = ShopType.TakeAny
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"]
class Spoiler(object):
world: World
@ -1314,6 +1209,7 @@ class Spoiler(object):
listed_locations.update(other_locations)
self.shops = []
from Shops import ShopType
for shop in self.world.shops:
if not shop.custom:
continue

View File

@ -1,7 +1,8 @@
from collections import namedtuple
import logging
from BaseClasses import Region, RegionType, ShopType, Shop, Location, TakeAny
from BaseClasses import Region, RegionType, Location
from Shops import ShopType, Shop, TakeAny
from Bosses import place_bosses
from Dungeons import get_dungeon_item_pool
from EntranceShuffle import connect_entrance

80
Main.py
View File

@ -8,19 +8,17 @@ import random
import time
import zlib
import concurrent.futures
import typing
from BaseClasses import World, CollectionState, Item, Region, Location, Shop
from BaseClasses import World, CollectionState, Item, Region, Location
from Shops import ShopSlotFill, create_shops, SHOP_ID_START
from Items import ItemFactory, item_table, item_name_groups
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
SHOP_ID_START
from Regions import create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
from EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
from Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned, \
swap_location_item
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
from ItemPool import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
import Patch
@ -213,77 +211,13 @@ def main(args, seed=None):
if world.players > 1:
balance_multiworld_progression(world)
shop_slots: typing.List[Location] = [location for shop_locations in (shop.region.locations for shop in world.shops)
for location in shop_locations if location.shop_slot]
logger.info("Filling Shop Slots")
if shop_slots:
# TODO: allow each game to register a blacklist to be used here?
blacklist_words = {"Rupee"}
blacklist_words = {item_name for item_name in item_table if any(
blacklist_word in item_name for blacklist_word in blacklist_words)}
blacklist_words.add("Bee")
candidates: typing.List[Location] = [location for location in world.get_locations() if
not location.locked and
not location.shop_slot and
not location.item.name in blacklist_words]
world.random.shuffle(candidates)
if not world.fulfills_accessibility():
logger.warning("World does not fulfill accessibility rules as is, "
"only using \"beatable only\" for shop logic.")
shuffle_condition = world.can_beat_game
else:
shuffle_condition = world.fulfills_accessibility
# 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
for location in shop_slots:
slot_num = int(location.name[-1]) - 1
shop: Shop = location.parent_region.shop
if shop.can_push_inventory(slot_num):
for c in candidates: # chosen item locations
if c.item_rule(location.item) and location.item_rule(c.item): # if rule is good...
swap_location_item(c, location, check_locked=False)
candidates.remove(c)
if not shuffle_condition():
swap_location_item(c, location, check_locked=False)
continue
logger.debug(f'Swapping {c} into {location}:: {location.item}')
break
else:
# This *should* never happen. But let's fail safely just in case.
logger.warning("Ran out of ShopShuffle Item candidate locations.")
shop.region.locations.remove(location)
continue
item_name = location.item.name
if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
price = world.random.randrange(1, 7)
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
price = world.random.randrange(4, 24)
elif any(x in item_name for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
price = world.random.randrange(10, 30)
else:
price = world.random.randrange(10, 60)
price *= 5
shop.push_inventory(slot_num, item_name, price, 1,
location.item.player if location.item.player != location.player else 0)
else:
shop.region.locations.remove(location)
ShopSlotFill(world)
logger.info('Patching ROM.')
# remove locations that may no longer exist from caches, by flushing them entirely
if shop_slots:
world.clear_location_cache()
world._location_cache = {}
outfilebase = 'BM_%s' % (args.outputname if args.outputname else world.seed)

View File

@ -16,6 +16,7 @@ import subprocess
from random import randrange
import Shops
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem
exit_func = atexit.register(input, "Press enter to close.")
@ -159,8 +160,8 @@ SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes
location_shop_order = [ name for name, info in Regions.shop_table.items() ] # probably don't leave this here. This relies on python 3.6+ dictionary keys having defined order
location_shop_ids = set([info[0] for name, info in Regions.shop_table.items()])
location_shop_order = [name for name, info in Shops.shop_table.items()] # probably don't leave this here. This relies on python 3.6+ dictionary keys having defined order
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
"Blind's Hideout - Left": (0x11d, 0x20),
@ -1154,7 +1155,7 @@ async def track_locations(ctx : Context, roomid, roomdata):
if roomid in location_shop_ids:
misc_data = await snes_read(ctx, SHOP_ADDR, (len(location_shop_order)*3)+5)
for cnt, b in enumerate(misc_data):
my_check = Regions.shop_table_by_location_id[Regions.SHOP_ID_START + cnt]
my_check = Shops.shop_table_by_location_id[Shops.SHOP_ID_START + cnt]
if int(b) > 0 and my_check not in ctx.locations_checked:
new_check(my_check)
except Exception as e:
@ -1219,7 +1220,7 @@ async def track_locations(ctx : Context, roomid, roomdata):
for location in ctx.locations_checked:
try:
my_id = Regions.lookup_name_to_id.get(location, Regions.shop_table_by_location.get(location, -1))
my_id = Regions.lookup_name_to_id.get(location, Shops.shop_table_by_location.get(location, -1))
new_locations.append(my_id)
except Exception as e:
print(e)

View File

@ -1,7 +1,8 @@
import collections
import typing
from BaseClasses import Region, Location, Entrance, RegionType, Shop, TakeAny, UpgradeShop, ShopType
from BaseClasses import Region, Location, Entrance, RegionType
def create_regions(world, player):
@ -365,104 +366,6 @@ def mark_light_world_regions(world, player: int):
queue.append(exit.connected_region)
def create_shops(world, player: int):
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
ShopType.Shop: Shop,
ShopType.TakeAny: TakeAny}
option = world.shop_shuffle[player]
my_shop_table = dict(shop_table)
num_slots = int(world.shop_shuffle_slots[player])
my_shop_slots = ([True] * num_slots + [False] * (len(shop_table) * 3))[:len(shop_table)*3 - 2]
world.random.shuffle(my_shop_slots)
from Items import ItemFactory
if 'g' in option or 'f' in option:
new_basic_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():
typ, shop_id, keeper, custom, locked, items = shop
if name == 'Capacity Upgrade':
pass
elif name == 'Potion Shop' and not "w" in option:
pass
else:
new_items = world.random.sample(shop_generation_types['default'], k=3)
if 'f' not in option:
if items == _basic_shop_defaults:
new_items = new_basic_shop
elif items == _dark_world_shop_defaults:
new_items = new_dark_shop
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
my_shop_table[name] = (typ, shop_id, keeper, custom, locked, new_items)
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in my_shop_table.items():
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
locked = True
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
region = world.get_region(region_name, player)
shop = cls_mapping[type](region, room_id, shopkeeper, custom, locked)
region.shop = shop
world.shops.append(shop)
for index, item in enumerate(inventory):
shop.add_inventory(index, *item)
if region_name == 'Potion Shop' and 'w' not in option:
pass
elif region_name == 'Capacity Upgrade':
pass
else:
if my_shop_slots.pop():
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
slot_name = "{} Slot {}".format(shop.region.name, index + 1)
loc = Location(player, slot_name, address=shop_table_by_location[slot_name],
parent=shop.region, hint_text="for sale")
loc.shop_slot = True
loc.locked = True
loc.item = ItemFactory(additional_item, player)
shop.region.locations.append(loc)
world.dynamic_locations.append(loc)
world.clear_location_cache()
# (type, room_id, shopkeeper, custom, locked, [items])
# 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)]
shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, True, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
'Cave Shop (Lake Hylia)': (0x0112, 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)]),
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
}
SHOP_ID_START = 0x400000
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
sublist])}
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3)] = "Old Man Sword Cave"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 1)] = "Take-Any #1"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 2)] = "Take-Any #2"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 3)] = "Take-Any #3"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 4)] = "Take-Any #4"
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
shop_generation_types = {
'default': _basic_shop_defaults + [('Bombs (3)', 20), ('Green Potion', 90), ('Blue Potion', 190), ('Bee', 10), ('Single Arrow', 5), ('Single Bomb', 10)] + [('Red Shield', 500), ('Blue Shield', 50)],
'potion': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)],
'discount_potion': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
'bottle': [('Bee', 10)],
'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)],
}
old_location_address_to_new_location_address = {
0x2eb18: 0x18001b, # Bottle Merchant
@ -769,6 +672,7 @@ location_table: typing.Dict[str,
'Turtle Rock - Prize': (
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
from Shops import shop_table_by_location_id, shop_table_by_location
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
lookup_id_to_name.update(shop_table_by_location_id)

3
Rom.py
View File

@ -17,7 +17,8 @@ import xxtea
import concurrent.futures
from typing import Optional
from BaseClasses import CollectionState, ShopType, Region, Location
from BaseClasses import CollectionState, Region, Location
from Shops import ShopType
from Dungeons import dungeon_music_addresses
from Regions import location_table, old_location_address_to_new_location_address
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable

289
Shops.py Normal file
View File

@ -0,0 +1,289 @@
from __future__ import annotations
from enum import unique, Enum
from typing import List, Union, Optional
import logging
from BaseClasses import Location
from EntranceShuffle import door_addresses
from Items import item_name_groups, item_table
from Utils import int16_as_bytes
logger = logging.getLogger("Shops")
@unique
class ShopType(Enum):
Shop = 0
TakeAny = 1
UpgradeShop = 2
class Shop():
slots = 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
type = ShopType.Shop
def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool):
self.region = region
self.room_id = room_id
self.inventory: List[Union[None, dict]] = [None] * self.slots
self.shopkeeper_config = shopkeeper_config
self.custom = custom
self.locked = locked
@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['item'] == item:
return True
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == 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['max'] != 0 and 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, create_location: bool = False,
player: int = 0):
self.inventory[slot] = {
'item': item,
'price': price,
'max': max,
'replacement': replacement,
'replacement_price': replacement_price,
'create_location': create_location,
'player': player
}
def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0):
if not self.inventory[slot]:
raise ValueError("Inventory can't be pushed back if it doesn't exist")
self.inventory[slot] = {
'item': item,
'price': price,
'max': max,
'replacement': self.inventory[slot]["item"],
'replacement_price': self.inventory[slot]["price"],
'create_location': self.inventory[slot]["create_location"],
'player': player
}
def can_push_inventory(self, slot: int):
return self.inventory[slot] and not self.inventory[slot]["replacement"]
class TakeAny(Shop):
type = ShopType.TakeAny
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"]
def ShopSlotFill(world):
shop_slots: List[Location] = [location for shop_locations in (shop.region.locations for shop in world.shops)
for location in shop_locations if location.shop_slot]
if shop_slots:
from Fill import swap_location_item
# TODO: allow each game to register a blacklist to be used here?
blacklist_words = {"Rupee"}
blacklist_words = {item_name for item_name in item_table if any(
blacklist_word in item_name for blacklist_word in blacklist_words)}
blacklist_words.add("Bee")
candidates: List[Location] = [location for location in world.get_locations() if
not location.locked and
not location.shop_slot and
not location.item.name in blacklist_words]
world.random.shuffle(candidates)
if not world.fulfills_accessibility():
logger.warning("World does not fulfill accessibility rules as is, "
"only using \"beatable only\" for shop logic.")
shuffle_condition = world.can_beat_game
else:
shuffle_condition = world.fulfills_accessibility
# 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
for location in shop_slots:
slot_num = int(location.name[-1]) - 1
shop: Shop = location.parent_region.shop
if shop.can_push_inventory(slot_num):
for c in candidates: # chosen item locations
if c.item_rule(location.item) and location.item_rule(c.item): # if rule is good...
swap_location_item(c, location, check_locked=False)
candidates.remove(c)
if not shuffle_condition():
swap_location_item(c, location, check_locked=False)
continue
logger.debug(f'Swapping {c} into {location}:: {location.item}')
break
else:
# This *should* never happen. But let's fail safely just in case.
logger.warning("Ran out of ShopShuffle Item candidate locations.")
shop.region.locations.remove(location)
continue
item_name = location.item.name
if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
price = world.random.randrange(1, 7)
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
price = world.random.randrange(4, 24)
elif any(x in item_name for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
price = world.random.randrange(10, 30)
else:
price = world.random.randrange(10, 60)
price *= 5
shop.push_inventory(slot_num, item_name, price, 1,
location.item.player if location.item.player != location.player else 0)
else:
shop.region.locations.remove(location)
# remove locations that may no longer exist from caches, by flushing them entirely
if shop_slots:
world.clear_location_cache()
world._location_cache = {}
def create_shops(world, player: int):
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
ShopType.Shop: Shop,
ShopType.TakeAny: TakeAny}
option = world.shop_shuffle[player]
my_shop_table = dict(shop_table)
num_slots = int(world.shop_shuffle_slots[player])
my_shop_slots = ([True] * num_slots + [False] * (len(shop_table) * 3))[:len(shop_table)*3 - 2]
world.random.shuffle(my_shop_slots)
from Items import ItemFactory
if 'g' in option or 'f' in option:
new_basic_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():
typ, shop_id, keeper, custom, locked, items = shop
if name == 'Capacity Upgrade':
pass
elif name == 'Potion Shop' and not "w" in option:
pass
else:
new_items = world.random.sample(shop_generation_types['default'], k=3)
if 'f' not in option:
if items == _basic_shop_defaults:
new_items = new_basic_shop
elif items == _dark_world_shop_defaults:
new_items = new_dark_shop
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
my_shop_table[name] = (typ, shop_id, keeper, custom, locked, new_items)
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in my_shop_table.items():
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
locked = True
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
region = world.get_region(region_name, player)
shop = cls_mapping[type](region, room_id, shopkeeper, custom, locked)
region.shop = shop
world.shops.append(shop)
for index, item in enumerate(inventory):
shop.add_inventory(index, *item)
if region_name == 'Potion Shop' and 'w' not in option:
pass
elif region_name == 'Capacity Upgrade':
pass
else:
if my_shop_slots.pop():
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
slot_name = "{} Slot {}".format(shop.region.name, index + 1)
loc = Location(player, slot_name, address=shop_table_by_location[slot_name],
parent=shop.region, hint_text="for sale")
loc.shop_slot = True
loc.locked = True
loc.item = ItemFactory(additional_item, player)
shop.region.locations.append(loc)
world.dynamic_locations.append(loc)
world.clear_location_cache()
# (type, room_id, shopkeeper, custom, locked, [items])
# 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)]
shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, True, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
'Cave Shop (Lake Hylia)': (0x0112, 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)]),
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
}
SHOP_ID_START = 0x400000
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
sublist])}
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3)] = "Old Man Sword Cave"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 1)] = "Take-Any #1"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 2)] = "Take-Any #2"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 3)] = "Take-Any #3"
shop_table_by_location_id[(SHOP_ID_START + len(shop_table)*3 + 4)] = "Take-Any #4"
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
shop_generation_types = {
'default': _basic_shop_defaults + [('Bombs (3)', 20), ('Green Potion', 90), ('Blue Potion', 190), ('Bee', 10), ('Single Arrow', 5), ('Single Bomb', 10)] + [('Red Shield', 500), ('Blue Shield', 50)],
'potion': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)],
'discount_potion': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
'bottle': [('Bee', 10)],
'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)],
}

View File

@ -5,7 +5,8 @@ from Dungeons import create_dungeons, get_dungeon_item_pool
from EntranceShuffle import mandatory_connections, connect_simple
from ItemPool import difficulties, generate_itempool
from Items import ItemFactory
from Regions import create_regions, create_shops
from Regions import create_regions
from Shops import create_shops
from Rules import set_rules

View File

@ -4,7 +4,8 @@ from EntranceShuffle import link_inverted_entrances
from InvertedRegions import create_inverted_regions
from ItemPool import generate_itempool, difficulties
from Items import ItemFactory
from Regions import mark_light_world_regions, create_shops
from Regions import mark_light_world_regions
from Shops import create_shops
from Rules import set_rules
from test.TestBase import TestBase

View File

@ -4,7 +4,8 @@ from EntranceShuffle import link_inverted_entrances
from InvertedRegions import create_inverted_regions
from ItemPool import generate_itempool, difficulties
from Items import ItemFactory
from Regions import mark_light_world_regions, create_shops
from Regions import mark_light_world_regions
from Shops import create_shops
from Rules import set_rules
from test.TestBase import TestBase

View File

@ -4,7 +4,8 @@ from EntranceShuffle import link_inverted_entrances
from InvertedRegions import create_inverted_regions
from ItemPool import generate_itempool, difficulties
from Items import ItemFactory
from Regions import mark_light_world_regions, create_shops
from Regions import mark_light_world_regions
from Shops import create_shops
from Rules import set_rules
from test.TestBase import TestBase

View File

@ -4,7 +4,8 @@ from EntranceShuffle import link_entrances
from InvertedRegions import mark_dark_world_regions
from ItemPool import difficulties, generate_itempool
from Items import ItemFactory
from Regions import create_regions, create_shops
from Regions import create_regions
from Shops import create_shops
from Rules import set_rules
from test.TestBase import TestBase

View File

@ -4,7 +4,8 @@ from EntranceShuffle import link_entrances
from InvertedRegions import mark_dark_world_regions
from ItemPool import difficulties, generate_itempool
from Items import ItemFactory
from Regions import create_regions, create_shops
from Regions import create_regions
from Shops import create_shops
from Rules import set_rules
from test.TestBase import TestBase

View File

@ -4,7 +4,8 @@ from EntranceShuffle import link_entrances
from InvertedRegions import mark_dark_world_regions
from ItemPool import difficulties, generate_itempool
from Items import ItemFactory
from Regions import create_regions, create_shops
from Regions import create_regions
from Shops import create_shops
from Rules import set_rules
from test.TestBase import TestBase