Change ShopShuffle to operate within current Sphere

This commit is contained in:
Fabian Dill 2021-01-17 22:58:52 +01:00
parent f4281f81f5
commit ba07da6bba
2 changed files with 82 additions and 56 deletions

View File

@ -447,6 +447,29 @@ class World(object):
return False return False
def get_spheres(self):
state = CollectionState(self)
locations = {location for location in self.get_locations()}
while locations:
sphere = set()
for location in locations:
if location.can_reach(state):
sphere.add(location)
yield sphere
if not sphere:
if locations:
yield locations # unreachable locations
break
for location in sphere:
state.collect(location.item, True, location)
locations -= sphere
def fulfills_accessibility(self, state: Optional[CollectionState] = None): def fulfills_accessibility(self, state: Optional[CollectionState] = None):
"""Check if accessibility rules are fulfilled with current or supplied state.""" """Check if accessibility rules are fulfilled with current or supplied state."""
if not state: if not state:
@ -1090,6 +1113,9 @@ class Location():
def __hash__(self): def __hash__(self):
return hash((self.name, self.player)) return hash((self.name, self.player))
def __lt__(self, other):
return (self.player, self.name) < (other.player, other.name)
class Item(object): class Item(object):

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from enum import unique, Enum from enum import unique, Enum
from typing import List, Union, Optional from typing import List, Union, Optional, Set
import logging import logging
from BaseClasses import Location from BaseClasses import Location
@ -117,8 +117,21 @@ class UpgradeShop(Shop):
blacklist = item_name_groups["Potions"] blacklist = item_name_groups["Potions"]
def ShopSlotFill(world): def ShopSlotFill(world):
shop_slots: List[Location] = [location for shop_locations in (shop.region.locations for shop in world.shops) shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
for location in shop_locations if location.shop_slot] for location in shop_locations if location.shop_slot}
removed = set()
for location in shop_slots:
slot_num = int(location.name[-1]) - 1
shop: Shop = location.parent_region.shop
if not shop.can_push_inventory(slot_num):
removed.add(location)
shop.region.locations.remove(location)
if removed:
shop_slots -= removed
# remove locations that may no longer exist from caches, by flushing them entirely
world.clear_location_cache()
world._location_cache = {}
if shop_slots: if shop_slots:
from Fill import swap_location_item from Fill import swap_location_item
@ -127,37 +140,34 @@ def ShopSlotFill(world):
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: List[Location] = [location for location in world.get_locations() if candidates_per_sphere = list(world.get_spheres())
not location.locked and
not location.shop_slot and
not location.item.name in blacklist_words]
world.random.shuffle(candidates) candidate_condition = lambda location: not location.locked and \
not location.shop_slot and \
if not world.fulfills_accessibility(): not location.item.name in blacklist_words
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 # 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
for location in shop_slots: for sphere in candidates_per_sphere:
current_shop_slots = sphere.intersection(shop_slots)
if current_shop_slots:
# randomize order in a deterministic fashion
sphere = sorted(sphere - current_shop_slots)
world.random.shuffle(sphere)
for location in sorted(current_shop_slots):
slot_num = int(location.name[-1]) - 1 slot_num = int(location.name[-1]) - 1
shop: Shop = location.parent_region.shop shop: Shop = location.parent_region.shop
if shop.can_push_inventory(slot_num): never = set() # candidates that will never work
for c in candidates: # chosen item locations for c in sphere: # chosen item locations
if c.item_rule(location.item) and location.item_rule(c.item): # if rule is good... if c in never:
pass
elif not candidate_condition(c): # candidate will never work
never.add(c)
elif c.item_rule(location.item) and location.item_rule(c.item): # if rule is good...
swap_location_item(c, location, check_locked=False) swap_location_item(c, location, check_locked=False)
candidates.remove(c) never.add(c)
if not shuffle_condition(): logger.info(f'Swapping {c} into {location}:: {location.item}')
swap_location_item(c, location, check_locked=False)
continue
logger.debug(f'Swapping {c} into {location}:: {location.item}')
break break
else: else:
@ -165,7 +175,6 @@ def ShopSlotFill(world):
logger.warning("Ran out of ShopShuffle Item candidate locations.") logger.warning("Ran out of ShopShuffle Item candidate locations.")
shop.region.locations.remove(location) shop.region.locations.remove(location)
continue continue
item_name = location.item.name item_name = location.item.name
if any(x in item_name for x in ['Single Bomb', 'Single Arrow']): if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
price = world.random.randrange(1, 7) price = world.random.randrange(1, 7)
@ -177,17 +186,8 @@ def ShopSlotFill(world):
price = world.random.randrange(10, 60) price = world.random.randrange(10, 60)
price *= 5 price *= 5
shop.push_inventory(slot_num, item_name, price, 1, shop.push_inventory(slot_num, item_name, price, 1,
location.item.player if location.item.player != location.player else 0) 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): def create_shops(world, player: int):
cls_mapping = {ShopType.UpgradeShop: UpgradeShop, cls_mapping = {ShopType.UpgradeShop: UpgradeShop,