Make sure shop slots obey accessibility rules.
The way this is checked is quite computationally expensive, should revisit later.
This commit is contained in:
parent
058436e47f
commit
0978daba69
|
@ -448,6 +448,65 @@ class World(object):
|
|||
|
||||
return False
|
||||
|
||||
def fulfills_accessibility(self):
|
||||
state = CollectionState(self)
|
||||
locations = {location for location in self.get_locations() if location.item}
|
||||
beatable_players = set()
|
||||
items_players = set()
|
||||
locations_players = set()
|
||||
for player in self.player_ids:
|
||||
access = self.accessibility[player]
|
||||
if access == "none":
|
||||
beatable_players.add(player)
|
||||
elif access == "items":
|
||||
items_players.add(player)
|
||||
elif access == "locations":
|
||||
locations_players.add(player)
|
||||
else:
|
||||
raise Exception(f"unknown access rule {access} for player {player}")
|
||||
|
||||
beatable_fulfilled = False
|
||||
|
||||
def location_conditition(location : Location):
|
||||
"""Determine if this location has to be accessible"""
|
||||
if location.player in locations_players:
|
||||
return True
|
||||
elif location.player in items_players:
|
||||
return location.item.advancement or location.event
|
||||
|
||||
return False
|
||||
|
||||
def all_done():
|
||||
"""Check if all access rules are fulfilled"""
|
||||
if beatable_fulfilled:
|
||||
for location in locations:
|
||||
if location_conditition(location):
|
||||
return False
|
||||
return True
|
||||
|
||||
while locations:
|
||||
sphere = set()
|
||||
for location in locations:
|
||||
if location.can_reach(state):
|
||||
sphere.add(location)
|
||||
|
||||
if not sphere:
|
||||
# ran out of places and did not finish yet, quit
|
||||
logging.debug(f"Could not access required locations.")
|
||||
return False
|
||||
|
||||
for location in sphere:
|
||||
locations.remove(location)
|
||||
state.collect(location.item, True, location)
|
||||
|
||||
if self.has_beaten_game(state):
|
||||
beatable_fulfilled = True
|
||||
|
||||
if all_done():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
class CollectionState(object):
|
||||
|
||||
def __init__(self, parent: World):
|
||||
|
|
28
Main.py
28
Main.py
|
@ -238,22 +238,17 @@ def main(args, seed=None):
|
|||
if shop.can_push_inventory(slot_num):
|
||||
for c in candidates: # chosen item locations
|
||||
if c.item_rule(location.item): # if rule is good...
|
||||
logging.debug('Swapping {} with {}:: {} ||| {}'.format(c, location, c.item, location.item))
|
||||
logger.debug(f'Swapping {c} into {location}:: {c.item}')
|
||||
swap_location_item(c, location, check_locked=False)
|
||||
# TODO: should likely be (all_state-c.item).can_reach(shop.region)
|
||||
# can still be can_beat_game when beatable-only for the item-owning player
|
||||
# appears to be the main source of "progression items unreachable Exception"
|
||||
# in door-rando + multishop
|
||||
if not world.can_beat_game():
|
||||
# swap back
|
||||
candidates.remove(c)
|
||||
if not world.fulfills_accessibility():
|
||||
swap_location_item(c, location, check_locked=False)
|
||||
else:
|
||||
# we use this candidate
|
||||
candidates.remove(c)
|
||||
break
|
||||
continue
|
||||
break
|
||||
|
||||
else:
|
||||
# This *should* never happen. But let's fail safely just in case.
|
||||
logging.warning("Ran out of ShopShuffle Item candidate locations.")
|
||||
logger.warning("Ran out of ShopShuffle Item candidate locations.")
|
||||
shop.region.locations.remove(location)
|
||||
continue
|
||||
|
||||
|
@ -381,7 +376,7 @@ def main(args, seed=None):
|
|||
|
||||
pool = concurrent.futures.ThreadPoolExecutor()
|
||||
multidata_task = None
|
||||
check_beatability_task = pool.submit(world.can_beat_game)
|
||||
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
||||
if not args.suppress_rom:
|
||||
|
||||
rom_futures = []
|
||||
|
@ -477,8 +472,11 @@ def main(args, seed=None):
|
|||
f.write(multidata)
|
||||
|
||||
multidata_task = pool.submit(write_multidata, rom_futures)
|
||||
if not check_beatability_task.result():
|
||||
raise Exception("Game appears unbeatable. Aborting.")
|
||||
if not check_accessibility_task.result():
|
||||
if not world.can_beat_game():
|
||||
raise Exception("Game appears is unbeatable. Aborting.")
|
||||
else:
|
||||
logger.warning("Location Accessibility requirements not fulfilled.")
|
||||
if not args.skip_playthrough:
|
||||
logger.info('Calculating playthrough.')
|
||||
create_playthrough(world)
|
||||
|
|
|
@ -414,7 +414,7 @@ def create_shops(world, player: int):
|
|||
pass
|
||||
else:
|
||||
if my_shop_slots.pop():
|
||||
additional_item = world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
|
||||
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)
|
||||
loc.shop_slot = True
|
||||
|
|
Loading…
Reference in New Issue