Simplify ShopSlot Shuffling

This commit is contained in:
Fabian Dill 2021-01-10 19:23:57 +01:00
parent 52d5b96435
commit f12259dd7d
5 changed files with 49 additions and 68 deletions

View File

@ -981,7 +981,7 @@ class Dungeon(object):
def __str__(self): def __str__(self):
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})' return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
class Boss(object): class Boss():
def __init__(self, name, enemizer_name, defeat_rule, player: int): def __init__(self, name, enemizer_name, defeat_rule, player: int):
self.name = name self.name = name
self.enemizer_name = enemizer_name self.enemizer_name = enemizer_name
@ -991,7 +991,12 @@ class Boss(object):
def can_defeat(self, state) -> bool: def can_defeat(self, state) -> bool:
return self.defeat_rule(state, self.player) return self.defeat_rule(state, self.player)
class Location(object):
class Location():
shop_slot: bool = False
event: bool = False
locked: bool = False
def __init__(self, player: int, name: str = '', address=None, crystal: bool = False, def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
hint_text: Optional[str] = None, parent=None, hint_text: Optional[str] = None, parent=None,
player_address=None): player_address=None):
@ -1004,8 +1009,6 @@ class Location(object):
self.spot_type = 'Location' self.spot_type = 'Location'
self.hint_text: str = hint_text if hint_text else name self.hint_text: str = hint_text if hint_text else name
self.recursion_count = 0 self.recursion_count = 0
self.event = False
self.locked = False
self.always_allow = lambda item, state: False self.always_allow = lambda item, state: False
self.access_rule = lambda state: True self.access_rule = lambda state: True
self.item_rule = lambda item: True self.item_rule = lambda item: True
@ -1152,7 +1155,8 @@ class Shop():
self.inventory = [None] * self.slots self.inventory = [None] * self.slots
def add_inventory(self, slot: int, item: str, price: int, max: int = 0, def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False): replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False,
player: int = 0):
self.inventory[slot] = { self.inventory[slot] = {
'item': item, 'item': item,
'price': price, 'price': price,
@ -1160,10 +1164,10 @@ class Shop():
'replacement': replacement, 'replacement': replacement,
'replacement_price': replacement_price, 'replacement_price': replacement_price,
'create_location': create_location, 'create_location': create_location,
'player': 0 'player': player
} }
def push_inventory(self, slot: int, item: str, price: int, max: int = 1): def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0):
if not self.inventory[slot]: if not self.inventory[slot]:
raise ValueError("Inventory can't be pushed back if it doesn't exist") raise ValueError("Inventory can't be pushed back if it doesn't exist")
@ -1174,9 +1178,11 @@ class Shop():
'replacement': self.inventory[slot]["item"], 'replacement': self.inventory[slot]["item"],
'replacement_price': self.inventory[slot]["price"], 'replacement_price': self.inventory[slot]["price"],
'create_location': self.inventory[slot]["create_location"], 'create_location': self.inventory[slot]["create_location"],
'player': self.inventory[slot]["player"] 'player': player
} }
def can_push_inventory(self, slot: int):
return self.inventory[slot] and not self.inventory[slot]["replacement"]
class TakeAny(Shop): class TakeAny(Shop):
type = ShopType.TakeAny type = ShopType.TakeAny

View File

@ -590,6 +590,7 @@ def create_dynamic_shop_locations(world, player):
world.clear_location_cache() world.clear_location_cache()
world.push_item(loc, ItemFactory(item['item'], player), False) world.push_item(loc, ItemFactory(item['item'], player), False)
loc.shop_slot = True
loc.event = True loc.event = True
loc.locked = True loc.locked = True

74
Main.py
View File

@ -9,7 +9,7 @@ import time
import zlib import zlib
import concurrent.futures import concurrent.futures
from BaseClasses import World, CollectionState, Item, Region, Location, PlandoItem from BaseClasses import World, CollectionState, Item, Region, Location, Shop
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, SHOP_ID_START from Regions import create_regions, create_shops, 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
@ -215,69 +215,47 @@ def main(args, seed=None):
blacklist_word in item_name for blacklist_word in blacklist_words)} blacklist_word in item_name for blacklist_word in blacklist_words)}
blacklist_words.add("Bee") blacklist_words.add("Bee")
candidates = [location for location in world.get_locations() if candidates = [location for location in world.get_locations() if
not location.locked and not location.item.name in blacklist_words] not location.locked and
not location.shop_slot and
not location.item.name in blacklist_words]
world.random.shuffle(candidates) world.random.shuffle(candidates)
shop_slots = [item for sublist in [shop.region.locations for shop in world.shops] for item in sublist if shop_slots = [item for sublist in (shop.region.locations for shop in world.shops) for item in sublist if
item.name != 'Potion Shop'] # TODO: "w" in shop_shuffle options? item.shop_slot]
shop_slots_adjusted = []
shop_items = []
for location in shop_slots: for location in shop_slots:
slot_num = int(location.name[-1]) - 1 slot_num = int(location.name[-1]) - 1
shop_item = location.parent_region.shop.inventory[slot_num] shop: Shop = location.parent_region.shop
item = location.item if shop.can_push_inventory(slot_num):
# if item is a rupee or single bee, or identical, swap it out
if (shop_item is not None and shop_item['item'] == item.name) or 'Rupee' in item.name or (item.name in ['Bee']):
for c in candidates: # chosen item locations for c in candidates: # chosen item locations
if c.parent_region.shop or c is location: continue
if (shop_item is not None and shop_item['item'] == c.item.name): continue
if c.item_rule(location.item): # if rule is good... if c.item_rule(location.item): # if rule is good...
logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item)) logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
c.item, location.item = location.item, c.item c.item, location.item = location.item, c.item
if not world.can_beat_game(): if not world.can_beat_game():
c.item, location.item = location.item, c.item c.item, location.item = location.item, c.item
else: else:
shop_slots_adjusted.append(location) # we use this candidate
candidates.remove(c)
break break
else:
logging.warning("Ran out of ShopShuffle Item candidate locations.")
break # we ran out of candidates
# update table to location data # update table to location data
item = location.item item_name = location.item.name
if (shop_item and shop_item['item'] == item.name) or 'Rupee' in item.name or item.name == 'Bee': if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
# this will filter items that match the item in the shop or Rupees, or single bees price = world.random.randrange(1, 7)
# really not a way for the player to know a renewable item from a player pool item elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
# bombs can be sitting on top of arrows or a potion refill, but dunno if that's a big deal price = world.random.randrange(4, 24)
# this should rarely happen with the above code in place, and could be an option in config if necessary elif any(x in item_name for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
logging.debug('skipping item shop {}'.format(item.name)) price = world.random.randrange(10, 30)
else: else:
if shop_item is None: price = world.random.randrange(10, 60)
location.parent_region.shop.add_inventory(slot_num, 'None', 0)
shop_item = location.parent_region.shop.inventory[slot_num]
else:
shop_item['replacement'] = shop_item['item']
shop_item['replacement_price'] = shop_item['price']
shop_item['item'] = item.name
if any(x in shop_item['item'] for x in ['Single Bomb', 'Single Arrow']):
shop_item['price'] = world.random.randrange(5, 35)
elif any(x in shop_item['item'] for x in ['Arrows', 'Bombs', 'Clock']):
shop_item['price'] = world.random.randrange(20, 120)
elif any(x in shop_item['item'] for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
shop_item['price'] = world.random.randrange(50, 150)
else:
shop_item['price'] = world.random.randrange(50,300)
shop_item['max'] = 1 price *= 5
shop_item['player'] = item.player if item.player != location.player else 0
shop_items.append(shop_item)
if len(shop_items) > 0: shop.push_inventory(slot_num, item_name, price, 1,
my_prices = [my_item['price'] for my_item in shop_items] location.item.player if location.item.player != location.player else 0)
price_scale = (80*max(8, len(my_prices)+2))/sum(my_prices)
for i in shop_items:
i['price'] *= price_scale
if i['price'] < 5: i['price'] = 5
else: i['price'] = int((i['price']//5)*5)
logging.debug('Adjusting {} of {} shop slots'.format(len(shop_slots_adjusted), len(shop_slots)))
logging.debug('Adjusted {} into shops'.format([x.item.name for x in shop_slots_adjusted]))
logger.info('Patching ROM.') logger.info('Patching ROM.')

View File

@ -415,9 +415,11 @@ def create_shops(world, player: int):
else: else:
if my_shop_slots.pop(): if my_shop_slots.pop():
additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)']) additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
world.itempool.append(ItemFactory(additional_item, player))
slot_name = "{} Slot {}".format(shop.region.name, index + 1) slot_name = "{} Slot {}".format(shop.region.name, index + 1)
loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region) loc = Location(player, slot_name, address=shop_table_by_location[slot_name], parent=shop.region)
loc.shop_slot = True
loc.locked = True
loc.item = ItemFactory(additional_item, player)
shop.region.locations.append(loc) shop.region.locations.append(loc)
world.dynamic_locations.append(loc) world.dynamic_locations.append(loc)

8
Rom.py
View File

@ -683,17 +683,11 @@ def patch_rom(world, rom, player, team, enemized):
# patch items # patch items
for location in world.get_locations(): for location in world.get_locations():
if location.player != player: if location.player != player or location.address is None or location.shop_slot:
continue continue
itemid = location.item.code if location.item is not None else 0x5A itemid = location.item.code if location.item is not None else 0x5A
if location.address is None:
continue
if location.parent_region.shop and 'Slot' in location.name:
continue
if not location.crystal: if not location.crystal:
if location.item is not None: if location.item is not None:
# Keys in their native dungeon should use the orignal item code for keys # Keys in their native dungeon should use the orignal item code for keys