Some Shop fixes:
Make sure that dark lake hylia shop in inverted retains the blue potion, while allowing shop slots on top (potion will always be the leftmost slot, ignoring "i"/"f"/"g") Cull Shop swap Candidates before generating weights, then keep track of updated sphere sizes during swaps. This significantly reduces the chance to run out of candidates, as large clumps of false candidates do not get included in the weight Shop fill is slower with this, as all candidates need to be swept once, instead of on-demand; but this seemed the best way to address the remaining issues.
This commit is contained in:
parent
5503547663
commit
e4d4ff667c
|
@ -450,7 +450,7 @@ class World(object):
|
||||||
def get_spheres(self):
|
def get_spheres(self):
|
||||||
state = CollectionState(self)
|
state = CollectionState(self)
|
||||||
|
|
||||||
locations = {location for location in self.get_locations()}
|
locations = set(self.get_locations())
|
||||||
|
|
||||||
while locations:
|
while locations:
|
||||||
sphere = set()
|
sphere = set()
|
||||||
|
@ -1138,6 +1138,11 @@ class Item(object):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.name == other.name and self.player == other.player
|
return self.name == other.name and self.player == other.player
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if other.player != self.player:
|
||||||
|
return other.player < self.player
|
||||||
|
return self.name < other.name
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self.name, self.player))
|
return hash((self.name, self.player))
|
||||||
|
|
||||||
|
|
67
Shops.py
67
Shops.py
|
@ -155,41 +155,59 @@ def ShopSlotFill(world):
|
||||||
shop_slots -= removed
|
shop_slots -= removed
|
||||||
|
|
||||||
if shop_slots:
|
if shop_slots:
|
||||||
|
del shop_slots
|
||||||
|
|
||||||
from Fill import swap_location_item
|
from Fill import swap_location_item
|
||||||
# TODO: allow each game to register a blacklist to be used here?
|
# TODO: allow each game to register a blacklist to be used here?
|
||||||
blacklist_words = {"Rupee"}
|
blacklist_words = {"Rupee"}
|
||||||
blacklist_words = {item_name for item_name in item_table if any(
|
blacklist_words = {item_name for item_name in item_table if any(
|
||||||
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_per_sphere = list(list(sphere) for sphere in world.get_spheres())
|
|
||||||
|
|
||||||
candidate_condition = lambda location: not location.locked and \
|
locations_per_sphere = list(list(sphere) for sphere in world.get_spheres())
|
||||||
not location.shop_slot and \
|
|
||||||
not location.item.name in blacklist_words
|
|
||||||
|
|
||||||
# currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory
|
# 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
|
# Potentially create Locations as needed and make inventory the only source, to prevent divergence
|
||||||
cumu_weights = []
|
cumu_weights = []
|
||||||
|
shops_per_sphere = []
|
||||||
|
candidates_per_sphere = []
|
||||||
|
|
||||||
for sphere in candidates_per_sphere:
|
# sort spheres into piles of valid candidates and shops
|
||||||
|
for sphere in locations_per_sphere:
|
||||||
|
current_shops_slots = []
|
||||||
|
current_candidates = []
|
||||||
|
shops_per_sphere.append(current_shops_slots)
|
||||||
|
candidates_per_sphere.append(current_candidates)
|
||||||
|
for location in sphere:
|
||||||
|
if location.shop_slot:
|
||||||
|
if not location.shop_slot_disabled:
|
||||||
|
current_shops_slots.append(location)
|
||||||
|
elif not location.locked and not location.item.name in blacklist_words:
|
||||||
|
current_candidates.append(location)
|
||||||
if cumu_weights:
|
if cumu_weights:
|
||||||
x = cumu_weights[-1]
|
x = cumu_weights[-1]
|
||||||
else:
|
else:
|
||||||
x = 0
|
x = 0
|
||||||
cumu_weights.append(len(sphere) + x)
|
cumu_weights.append(len(current_candidates) + x)
|
||||||
world.random.shuffle(sphere)
|
|
||||||
|
|
||||||
for i, sphere in enumerate(candidates_per_sphere):
|
world.random.shuffle(current_candidates)
|
||||||
current_shop_slots = [location for location in sphere if location.shop_slot and not location.shop_slot_disabled]
|
|
||||||
|
del(locations_per_sphere)
|
||||||
|
|
||||||
|
total_spheres = len(candidates_per_sphere)
|
||||||
|
|
||||||
|
for i, current_shop_slots in enumerate(shops_per_sphere):
|
||||||
if current_shop_slots:
|
if current_shop_slots:
|
||||||
|
candidate_sphere_ids = list(range(i, total_spheres))
|
||||||
for location in current_shop_slots:
|
for location in current_shop_slots:
|
||||||
shop: Shop = location.parent_region.shop
|
shop: Shop = location.parent_region.shop
|
||||||
# TODO: might need to implement trying randomly across spheres until canditates are exhausted.
|
swapping_sphere_id = world.random.choices(candidate_sphere_ids,
|
||||||
# As spheres may be as small as one item.
|
cum_weights=cumu_weights[i:])[0]
|
||||||
swapping_sphere = world.random.choices(candidates_per_sphere[i:], cum_weights=cumu_weights[i:])[0]
|
swapping_sphere: list = candidates_per_sphere[swapping_sphere_id]
|
||||||
for c in swapping_sphere: # chosen item locations
|
for c in swapping_sphere: # chosen item locations
|
||||||
if candidate_condition(c) and c.item_rule(location.item) and location.item_rule(c.item):
|
if c.item_rule(location.item) and location.item_rule(c.item):
|
||||||
swap_location_item(c, location, check_locked=False)
|
swap_location_item(c, location, check_locked=False)
|
||||||
logger.debug(f'Swapping {c} into {location}:: {location.item}')
|
logger.debug(f'Swapping {c} into {location}:: {location.item}')
|
||||||
break
|
break
|
||||||
|
@ -199,6 +217,11 @@ def ShopSlotFill(world):
|
||||||
logger.warning("Ran out of ShopShuffle Item candidate locations.")
|
logger.warning("Ran out of ShopShuffle Item candidate locations.")
|
||||||
location.shop_slot_disabled = True
|
location.shop_slot_disabled = True
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# remove candidate
|
||||||
|
swapping_sphere.remove(c)
|
||||||
|
cumu_weights[swapping_sphere_id] -= 1
|
||||||
|
|
||||||
item_name = location.item.name
|
item_name = location.item.name
|
||||||
if any(x in item_name for x in ['Single Bomb', 'Single Arrow', 'Piece of Heart']):
|
if any(x in item_name for x in ['Single Bomb', 'Single Arrow', 'Piece of Heart']):
|
||||||
price = world.random.randrange(1, 7)
|
price = world.random.randrange(1, 7)
|
||||||
|
@ -244,11 +267,15 @@ def create_shops(world, player: int):
|
||||||
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
|
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
|
||||||
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
|
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
|
||||||
if world.mode[player] == "inverted":
|
if world.mode[player] == "inverted":
|
||||||
|
# make sure that blue potion is available in inverted, special case locked = None; lock when done.
|
||||||
player_shop_table["Dark Lake Hylia Shop"] = \
|
player_shop_table["Dark Lake Hylia Shop"] = \
|
||||||
player_shop_table["Dark Lake Hylia Shop"]._replace(locked=True, items=_inverted_hylia_shop_defaults)
|
player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
|
||||||
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items():
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items():
|
||||||
region = world.get_region(region_name, player)
|
region = world.get_region(region_name, player)
|
||||||
shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset)
|
shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset)
|
||||||
|
# special case: allow shop slots, but do not allow overwriting of base inventory behind them
|
||||||
|
if locked is None:
|
||||||
|
shop.locked = True
|
||||||
region.shop = shop
|
region.shop = shop
|
||||||
world.shops.append(shop)
|
world.shops.append(shop)
|
||||||
for index, item in enumerate(inventory):
|
for index, item in enumerate(inventory):
|
||||||
|
@ -261,7 +288,7 @@ def create_shops(world, player: int):
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
if single_purchase_slots.pop():
|
if single_purchase_slots.pop():
|
||||||
if world.goal[player] != 'icerodhunt':
|
if world.goal[player] != 'icerodhunt':
|
||||||
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
|
additional_item = 'Rupees (50)'
|
||||||
else:
|
else:
|
||||||
additional_item = 'Nothing'
|
additional_item = 'Nothing'
|
||||||
loc.item = ItemFactory(additional_item, player)
|
loc.item = ItemFactory(additional_item, player)
|
||||||
|
@ -278,7 +305,7 @@ class ShopData(NamedTuple):
|
||||||
type: ShopType
|
type: ShopType
|
||||||
shopkeeper: int
|
shopkeeper: int
|
||||||
custom: bool
|
custom: bool
|
||||||
locked: bool
|
locked: Optional[bool]
|
||||||
items: List
|
items: List
|
||||||
sram_offset: int
|
sram_offset: int
|
||||||
|
|
||||||
|
@ -405,11 +432,7 @@ def shuffle_shops(world, items, player: int):
|
||||||
if shop.region.player == player:
|
if shop.region.player == player:
|
||||||
if shop.type == ShopType.UpgradeShop:
|
if shop.type == ShopType.UpgradeShop:
|
||||||
upgrade_shops.append(shop)
|
upgrade_shops.append(shop)
|
||||||
elif shop.type == ShopType.Shop:
|
elif shop.type == ShopType.Shop and not shop.locked:
|
||||||
if shop.region.name == 'Potion Shop' and not 'w' in option:
|
|
||||||
# don't modify potion shop
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
shops.append(shop)
|
shops.append(shop)
|
||||||
total_inventory.extend(shop.inventory)
|
total_inventory.extend(shop.inventory)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue