Move Shop related stuff to a Shops module
This commit is contained in:
parent
f046ca806c
commit
f3e686ba9a
110
BaseClasses.py
110
BaseClasses.py
|
@ -5,12 +5,11 @@ from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict, Counter, deque
|
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 secrets
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from EntranceShuffle import door_addresses, indirect_connections
|
from EntranceShuffle import indirect_connections
|
||||||
from Utils import int16_as_bytes
|
|
||||||
from Items import item_name_groups
|
from Items import item_name_groups
|
||||||
|
|
||||||
|
|
||||||
|
@ -1148,110 +1147,6 @@ class Item(object):
|
||||||
class Crystal(Item):
|
class Crystal(Item):
|
||||||
pass
|
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):
|
class Spoiler(object):
|
||||||
world: World
|
world: World
|
||||||
|
@ -1314,6 +1209,7 @@ class Spoiler(object):
|
||||||
listed_locations.update(other_locations)
|
listed_locations.update(other_locations)
|
||||||
|
|
||||||
self.shops = []
|
self.shops = []
|
||||||
|
from Shops import ShopType
|
||||||
for shop in self.world.shops:
|
for shop in self.world.shops:
|
||||||
if not shop.custom:
|
if not shop.custom:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import logging
|
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 Bosses import place_bosses
|
||||||
from Dungeons import get_dungeon_item_pool
|
from Dungeons import get_dungeon_item_pool
|
||||||
from EntranceShuffle import connect_entrance
|
from EntranceShuffle import connect_entrance
|
||||||
|
|
80
Main.py
80
Main.py
|
@ -8,19 +8,17 @@ import random
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
import concurrent.futures
|
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 Items import ItemFactory, item_table, item_name_groups
|
||||||
from Regions import create_regions, create_shops, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
|
from Regions import create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance
|
||||||
SHOP_ID_START
|
|
||||||
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
from InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||||
from EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
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 Rom import patch_rom, patch_race_rom, patch_enemizer, apply_rom_settings, LocalRom, get_hash_string
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||||
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned, \
|
from Fill import distribute_items_restrictive, flood_items, balance_multiworld_progression, distribute_planned
|
||||||
swap_location_item
|
|
||||||
from ItemPool import generate_itempool, difficulties, fill_prizes
|
from ItemPool import generate_itempool, difficulties, fill_prizes
|
||||||
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
|
from Utils import output_path, parse_player_names, get_options, __version__, _version_tuple
|
||||||
import Patch
|
import Patch
|
||||||
|
@ -213,77 +211,13 @@ def main(args, seed=None):
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
balance_multiworld_progression(world)
|
balance_multiworld_progression(world)
|
||||||
|
|
||||||
shop_slots: typing.List[Location] = [location for shop_locations in (shop.region.locations for shop in world.shops)
|
logger.info("Filling Shop Slots")
|
||||||
for location in shop_locations if location.shop_slot]
|
|
||||||
|
|
||||||
if shop_slots:
|
ShopSlotFill(world)
|
||||||
# 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)
|
|
||||||
|
|
||||||
logger.info('Patching ROM.')
|
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)
|
outfilebase = 'BM_%s' % (args.outputname if args.outputname else world.seed)
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import subprocess
|
||||||
|
|
||||||
from random import randrange
|
from random import randrange
|
||||||
|
|
||||||
|
import Shops
|
||||||
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem
|
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem
|
||||||
|
|
||||||
exit_func = atexit.register(input, "Press enter to close.")
|
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
|
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_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 Regions.shop_table.items()])
|
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
|
||||||
|
|
||||||
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||||
"Blind's Hideout - Left": (0x11d, 0x20),
|
"Blind's Hideout - Left": (0x11d, 0x20),
|
||||||
|
@ -1154,7 +1155,7 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
if roomid in location_shop_ids:
|
if roomid in location_shop_ids:
|
||||||
misc_data = await snes_read(ctx, SHOP_ADDR, (len(location_shop_order)*3)+5)
|
misc_data = await snes_read(ctx, SHOP_ADDR, (len(location_shop_order)*3)+5)
|
||||||
for cnt, b in enumerate(misc_data):
|
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:
|
if int(b) > 0 and my_check not in ctx.locations_checked:
|
||||||
new_check(my_check)
|
new_check(my_check)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1219,7 +1220,7 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
|
|
||||||
for location in ctx.locations_checked:
|
for location in ctx.locations_checked:
|
||||||
try:
|
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)
|
new_locations.append(my_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
102
Regions.py
102
Regions.py
|
@ -1,7 +1,8 @@
|
||||||
import collections
|
import collections
|
||||||
import typing
|
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):
|
def create_regions(world, player):
|
||||||
|
@ -365,104 +366,6 @@ def mark_light_world_regions(world, player: int):
|
||||||
queue.append(exit.connected_region)
|
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 = {
|
old_location_address_to_new_location_address = {
|
||||||
0x2eb18: 0x18001b, # Bottle Merchant
|
0x2eb18: 0x18001b, # Bottle Merchant
|
||||||
|
@ -769,6 +672,7 @@ location_table: typing.Dict[str,
|
||||||
'Turtle Rock - Prize': (
|
'Turtle Rock - Prize': (
|
||||||
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
[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 = {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 = {**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)
|
lookup_id_to_name.update(shop_table_by_location_id)
|
||||||
|
|
3
Rom.py
3
Rom.py
|
@ -17,7 +17,8 @@ import xxtea
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from typing import Optional
|
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 Dungeons import dungeon_music_addresses
|
||||||
from Regions import location_table, old_location_address_to_new_location_address
|
from Regions import location_table, old_location_address_to_new_location_address
|
||||||
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
||||||
|
|
|
@ -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)],
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ from Dungeons import create_dungeons, get_dungeon_item_pool
|
||||||
from EntranceShuffle import mandatory_connections, connect_simple
|
from EntranceShuffle import mandatory_connections, connect_simple
|
||||||
from ItemPool import difficulties, generate_itempool
|
from ItemPool import difficulties, generate_itempool
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from EntranceShuffle import link_inverted_entrances
|
||||||
from InvertedRegions import create_inverted_regions
|
from InvertedRegions import create_inverted_regions
|
||||||
from ItemPool import generate_itempool, difficulties
|
from ItemPool import generate_itempool, difficulties
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from EntranceShuffle import link_inverted_entrances
|
||||||
from InvertedRegions import create_inverted_regions
|
from InvertedRegions import create_inverted_regions
|
||||||
from ItemPool import generate_itempool, difficulties
|
from ItemPool import generate_itempool, difficulties
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from EntranceShuffle import link_inverted_entrances
|
||||||
from InvertedRegions import create_inverted_regions
|
from InvertedRegions import create_inverted_regions
|
||||||
from ItemPool import generate_itempool, difficulties
|
from ItemPool import generate_itempool, difficulties
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from EntranceShuffle import link_entrances
|
||||||
from InvertedRegions import mark_dark_world_regions
|
from InvertedRegions import mark_dark_world_regions
|
||||||
from ItemPool import difficulties, generate_itempool
|
from ItemPool import difficulties, generate_itempool
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from EntranceShuffle import link_entrances
|
||||||
from InvertedRegions import mark_dark_world_regions
|
from InvertedRegions import mark_dark_world_regions
|
||||||
from ItemPool import difficulties, generate_itempool
|
from ItemPool import difficulties, generate_itempool
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from EntranceShuffle import link_entrances
|
||||||
from InvertedRegions import mark_dark_world_regions
|
from InvertedRegions import mark_dark_world_regions
|
||||||
from ItemPool import difficulties, generate_itempool
|
from ItemPool import difficulties, generate_itempool
|
||||||
from Items import ItemFactory
|
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 Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue