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):

112
Shops.py
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,67 +140,54 @@ 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:
slot_num = int(location.name[-1]) - 1 current_shop_slots = sphere.intersection(shop_slots)
shop: Shop = location.parent_region.shop if current_shop_slots:
if shop.can_push_inventory(slot_num): # randomize order in a deterministic fashion
for c in candidates: # chosen item locations sphere = sorted(sphere - current_shop_slots)
if c.item_rule(location.item) and location.item_rule(c.item): # if rule is good... world.random.shuffle(sphere)
for location in sorted(current_shop_slots):
swap_location_item(c, location, check_locked=False) slot_num = int(location.name[-1]) - 1
candidates.remove(c) shop: Shop = location.parent_region.shop
if not shuffle_condition(): never = set() # candidates that will never work
for c in sphere: # chosen item locations
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)
continue never.add(c)
logger.info(f'Swapping {c} into {location}:: {location.item}')
break
logger.debug(f'Swapping {c} into {location}:: {location.item}') else:
break # This *should* never happen. But let's fail safely just in case.
logger.warning("Ran out of ShopShuffle Item candidate locations.")
else: shop.region.locations.remove(location)
# This *should* never happen. But let's fail safely just in case. continue
logger.warning("Ran out of ShopShuffle Item candidate locations.") item_name = location.item.name
shop.region.locations.remove(location) if any(x in item_name for x in ['Single Bomb', 'Single Arrow']):
continue price = world.random.randrange(1, 7)
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']):
item_name = location.item.name price = world.random.randrange(4, 24)
if any(x in item_name for x in ['Single Bomb', 'Single Arrow']): elif any(x in item_name for x in ['Compass', 'Map', 'Small Key', 'Piece of Heart']):
price = world.random.randrange(1, 7) price = world.random.randrange(10, 30)
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock']): else:
price = world.random.randrange(4, 24) price = world.random.randrange(10, 60)
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 = {}
price *= 5
shop.push_inventory(slot_num, item_name, price, 1,
location.item.player if location.item.player != location.player else 0)
def create_shops(world, player: int): def create_shops(world, player: int):
cls_mapping = {ShopType.UpgradeShop: UpgradeShop, cls_mapping = {ShopType.UpgradeShop: UpgradeShop,