Key flood protection in sweep for events
Better loop validation Small key validation tweaks
This commit is contained in:
@@ -401,6 +401,7 @@ class CollectionState(object):
|
|||||||
if locations is None:
|
if locations is None:
|
||||||
locations = self.world.get_filled_locations()
|
locations = self.world.get_filled_locations()
|
||||||
reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and location.can_reach(self)]
|
reachable_events = [location for location in locations if location.event and (not key_only or location.item.key) and location.can_reach(self)]
|
||||||
|
reachable_events = self._do_not_flood_the_keys(reachable_events)
|
||||||
for event in reachable_events:
|
for event in reachable_events:
|
||||||
if (event.name, event.player) not in self.events:
|
if (event.name, event.player) not in self.events:
|
||||||
self.events.append((event.name, event.player))
|
self.events.append((event.name, event.player))
|
||||||
@@ -408,6 +409,17 @@ class CollectionState(object):
|
|||||||
new_locations = len(reachable_events) > checked_locations
|
new_locations = len(reachable_events) > checked_locations
|
||||||
checked_locations = len(reachable_events)
|
checked_locations = len(reachable_events)
|
||||||
|
|
||||||
|
def _do_not_flood_the_keys(self, reachable_events):
|
||||||
|
adjusted_checks = list(reachable_events)
|
||||||
|
for event in reachable_events:
|
||||||
|
if event.name == 'Trench 2 Switch' and self.world.get_location('Swamp Palace - Trench 2 Pot Key', event.player) not in reachable_events:
|
||||||
|
adjusted_checks.remove(event)
|
||||||
|
if event.name == 'Trench 1 Switch' and self.world.get_location('Swamp Palace - Trench 1 Pot Key', event.player) not in reachable_events:
|
||||||
|
adjusted_checks.remove(event)
|
||||||
|
if len(adjusted_checks) < len(reachable_events):
|
||||||
|
return adjusted_checks
|
||||||
|
return reachable_events
|
||||||
|
|
||||||
def has(self, item, player, count=1):
|
def has(self, item, player, count=1):
|
||||||
if count == 1:
|
if count == 1:
|
||||||
return (item, player) in self.prog_items
|
return (item, player) in self.prog_items
|
||||||
|
|||||||
@@ -672,6 +672,7 @@ class ExplorationState(object):
|
|||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
ret = ExplorationState()
|
ret = ExplorationState()
|
||||||
|
ret.unattached_doors = list(self.unattached_doors)
|
||||||
ret.avail_doors = list(self.avail_doors)
|
ret.avail_doors = list(self.avail_doors)
|
||||||
ret.event_doors = list(self.event_doors)
|
ret.event_doors = list(self.event_doors)
|
||||||
ret.visited_orange = list(self.visited_orange)
|
ret.visited_orange = list(self.visited_orange)
|
||||||
@@ -734,7 +735,7 @@ class ExplorationState(object):
|
|||||||
def add_all_doors_check_unattached(self, region, world, player):
|
def add_all_doors_check_unattached(self, region, world, player):
|
||||||
for door in get_doors(world, region, player):
|
for door in get_doors(world, region, player):
|
||||||
if self.can_traverse(door):
|
if self.can_traverse(door):
|
||||||
if door.dest is None and door not in self.unattached_doors:
|
if door.dest is None and not self.in_door_list_ic(door, self.unattached_doors):
|
||||||
self.append_door_to_list(door, self.unattached_doors)
|
self.append_door_to_list(door, self.unattached_doors)
|
||||||
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors):
|
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors):
|
||||||
self.append_door_to_list(door, self.event_doors)
|
self.append_door_to_list(door, self.event_doors)
|
||||||
@@ -751,16 +752,18 @@ class ExplorationState(object):
|
|||||||
if door.name not in self.door_krs.keys():
|
if door.name not in self.door_krs.keys():
|
||||||
self.door_krs[door.name] = key_region
|
self.door_krs[door.name] = key_region
|
||||||
|
|
||||||
|
|
||||||
def add_all_doors_check_keys(self, region, key_door_proposal, world, player):
|
def add_all_doors_check_keys(self, region, key_door_proposal, world, player):
|
||||||
for door in get_doors(world, region, player):
|
for door in get_doors(world, region, player):
|
||||||
if self.can_traverse(door):
|
if self.can_traverse(door):
|
||||||
if door in key_door_proposal and not self.in_door_list(door, self.small_doors) and door not in self.opened_doors:
|
if door in key_door_proposal and door not in self.opened_doors:
|
||||||
self.append_door_to_list(door, self.small_doors)
|
if not self.in_door_list(door, self.small_doors):
|
||||||
elif door.bigKey and not self.big_key_opened and not self.in_door_list(door, self.big_doors):
|
self.append_door_to_list(door, self.small_doors)
|
||||||
self.append_door_to_list(door, self.big_doors)
|
elif door.bigKey and not self.big_key_opened:
|
||||||
elif door.req_event is not None and door.req_event not in self.events and not self.in_door_list(door, self.event_doors):
|
if not self.in_door_list(door, self.big_doors):
|
||||||
self.append_door_to_list(door, self.event_doors)
|
self.append_door_to_list(door, self.big_doors)
|
||||||
|
elif door.req_event is not None and door.req_event not in self.events:
|
||||||
|
if not self.in_door_list(door, self.event_doors):
|
||||||
|
self.append_door_to_list(door, self.event_doors)
|
||||||
elif not self.in_door_list(door, self.avail_doors):
|
elif not self.in_door_list(door, self.avail_doors):
|
||||||
self.append_door_to_list(door, self.avail_doors)
|
self.append_door_to_list(door, self.avail_doors)
|
||||||
|
|
||||||
@@ -824,6 +827,8 @@ def extend_reachable_state(search_regions, state, world, player):
|
|||||||
return local_state
|
return local_state
|
||||||
|
|
||||||
|
|
||||||
|
# todo: this sometimes generates two independent parts - that could be valid if the entrances are accessible
|
||||||
|
# todo: prevent crystal barrier dead ends
|
||||||
def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_region_names):
|
def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_region_names):
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
random.shuffle(available_sectors)
|
random.shuffle(available_sectors)
|
||||||
@@ -836,18 +841,17 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re
|
|||||||
entrance_regions.append(world.get_region(region_name, player))
|
entrance_regions.append(world.get_region(region_name, player))
|
||||||
|
|
||||||
state = extend_reachable_state(entrance_regions, ExplorationState(), world, player)
|
state = extend_reachable_state(entrance_regions, ExplorationState(), world, player)
|
||||||
reachable_doors_new = state.unattached_doors
|
|
||||||
# Loop until all available doors are used
|
# Loop until all available doors are used
|
||||||
while len(reachable_doors_new) > 0:
|
while len(state.unattached_doors) > 0:
|
||||||
# Pick a random available door to connect
|
# Pick a random available door to connect
|
||||||
explorable_door = random.choice(reachable_doors_new)
|
explorable_door = random.choice(state.unattached_doors)
|
||||||
door = explorable_door.door
|
door = explorable_door.door
|
||||||
sector = find_sector_for_door(door, available_sectors)
|
sector = find_sector_for_door(door, available_sectors)
|
||||||
sector.outstanding_doors.remove(door)
|
sector.outstanding_doors.remove(door)
|
||||||
# door_connected = False
|
# door_connected = False
|
||||||
logger.info('Linking %s', door.name)
|
logger.info('Linking %s', door.name)
|
||||||
# Find an available region that has a compatible door
|
# Find an available region that has a compatible door
|
||||||
reachable_doors = [d.door for d in reachable_doors_new]
|
reachable_doors = [d.door for d in state.unattached_doors]
|
||||||
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors)
|
compatibles = find_all_compatible_door_in_sectors_ex(door, available_sectors, reachable_doors)
|
||||||
while len(compatibles) > 0:
|
while len(compatibles) > 0:
|
||||||
connect_sector, connect_door = compatibles.pop()
|
connect_sector, connect_door = compatibles.pop()
|
||||||
@@ -856,7 +860,7 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re
|
|||||||
if is_valid(door, connect_door, sector, connect_sector, available_sectors):
|
if is_valid(door, connect_door, sector, connect_sector, available_sectors):
|
||||||
# Apply connection and add the new region's doors to the available list
|
# Apply connection and add the new region's doors to the available list
|
||||||
maybe_connect_two_way(world, door, connect_door, player)
|
maybe_connect_two_way(world, door, connect_door, player)
|
||||||
reachable_doors_new.remove(explorable_door) # todo: does this remove it from the state list?
|
state.unattached_doors.remove(explorable_door)
|
||||||
connect_sector.outstanding_doors.remove(connect_door)
|
connect_sector.outstanding_doors.remove(connect_door)
|
||||||
if sector != connect_sector: # combine if not the same
|
if sector != connect_sector: # combine if not the same
|
||||||
available_sectors.remove(connect_sector)
|
available_sectors.remove(connect_sector)
|
||||||
@@ -864,12 +868,12 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re
|
|||||||
sector.regions.extend(connect_sector.regions)
|
sector.regions.extend(connect_sector.regions)
|
||||||
if not door.blocked:
|
if not door.blocked:
|
||||||
connect_region = world.get_entrance(door.dest.name, player).parent_region
|
connect_region = world.get_entrance(door.dest.name, player).parent_region
|
||||||
state = extend_reachable([connect_region], state, world, player)
|
state = extend_reachable_state([connect_region], state, world, player)
|
||||||
break # skips else block below
|
break # skips else block below
|
||||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||||
if len(compatibles) == 0: # time to try again
|
if len(compatibles) == 0: # time to try again
|
||||||
sector.outstanding_doors.insert(0, door)
|
sector.outstanding_doors.insert(0, door)
|
||||||
if len(reachable_doors_new) <= 1:
|
if len(state.unattached_doors) <= 1:
|
||||||
raise Exception('Rejected last option due to dead end... infinite loop ensues')
|
raise Exception('Rejected last option due to dead end... infinite loop ensues')
|
||||||
else:
|
else:
|
||||||
# If there's no available region with a door, use an internal connection
|
# If there's no available region with a door, use an internal connection
|
||||||
@@ -877,11 +881,12 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re
|
|||||||
while len(compatibles) > 0:
|
while len(compatibles) > 0:
|
||||||
connect_door = compatibles.pop()
|
connect_door = compatibles.pop()
|
||||||
logger.info(' Adding loop via %s', connect_door.name)
|
logger.info(' Adding loop via %s', connect_door.name)
|
||||||
|
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
||||||
# Check if valid
|
# Check if valid
|
||||||
if is_loop_valid(door, connect_door, sector, available_sectors):
|
if is_loop_valid(door, connect_door, sector, connect_sector, available_sectors):
|
||||||
maybe_connect_two_way(world, door, connect_door, player)
|
maybe_connect_two_way(world, door, connect_door, player)
|
||||||
reachable_doors_new.remove(explorable_door)
|
state.unattached_doors.remove(explorable_door)
|
||||||
reachable_doors_new[:] = [ed for ed in reachable_doors_new if ed.door != connect_door]
|
state.unattached_doors[:] = [ed for ed in state.unattached_doors if ed.door != connect_door]
|
||||||
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
||||||
connect_sector.outstanding_doors.remove(connect_door)
|
connect_sector.outstanding_doors.remove(connect_door)
|
||||||
if sector != connect_sector: # combine if not the same
|
if sector != connect_sector: # combine if not the same
|
||||||
@@ -892,7 +897,7 @@ def shuffle_dungeon_no_repeats_new(world, player, available_sectors, entrance_re
|
|||||||
else:
|
else:
|
||||||
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
logger.info(' Not Linking %s to %s', door.name, connect_door.name)
|
||||||
sector.outstanding_doors.insert(0, door)
|
sector.outstanding_doors.insert(0, door)
|
||||||
if len(reachable_doors_new) <= 2:
|
if len(state.unattached_doors) <= 2:
|
||||||
raise Exception('Rejected last option due to likely improper loops...')
|
raise Exception('Rejected last option due to likely improper loops...')
|
||||||
else:
|
else:
|
||||||
raise Exception('Nothing is apparently compatible with %s', door.name)
|
raise Exception('Nothing is apparently compatible with %s', door.name)
|
||||||
@@ -953,8 +958,9 @@ def shuffle_dungeon_no_repeats(world, player, available_sectors, entrance_region
|
|||||||
while len(compatibles) > 0:
|
while len(compatibles) > 0:
|
||||||
connect_door = compatibles.pop()
|
connect_door = compatibles.pop()
|
||||||
logger.info(' Adding loop via %s', connect_door.name)
|
logger.info(' Adding loop via %s', connect_door.name)
|
||||||
|
connect_sector = find_sector_for_door(connect_door, available_sectors)
|
||||||
# Check if valid
|
# Check if valid
|
||||||
if is_loop_valid(door, connect_door, sector, available_sectors):
|
if is_loop_valid(door, connect_door, sector, connect_sector, available_sectors):
|
||||||
maybe_connect_two_way(world, door, connect_door, player)
|
maybe_connect_two_way(world, door, connect_door, player)
|
||||||
reachable_doors.remove(door)
|
reachable_doors.remove(door)
|
||||||
reachable_doors.remove(connect_door)
|
reachable_doors.remove(connect_door)
|
||||||
@@ -1051,16 +1057,20 @@ def is_valid(door_a, door_b, sector_a, sector_b, available_sectors):
|
|||||||
return False # not sure how we got here, but it's a bad idea
|
return False # not sure how we got here, but it's a bad idea
|
||||||
|
|
||||||
|
|
||||||
def is_loop_valid(door_a, door_b, sector, available_sectors):
|
def is_loop_valid(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||||
if len(available_sectors) == 1:
|
if len(available_sectors) == 1:
|
||||||
return True
|
return True
|
||||||
if len(available_sectors) == 2 and door_b not in sector.outstanding_doors:
|
if len(available_sectors) == 2 and door_b not in sector_a.outstanding_doors:
|
||||||
return True
|
return True
|
||||||
elif not door_a.blocked and not door_b.blocked:
|
elif not door_a.blocked and not door_b.blocked and sector_a != sector_b:
|
||||||
return sector.outflow() - 1 > 0
|
return sector_a.outflow() + sector_b.outflow() - 1 > 0 # door_a has been removed already, so a.outflow is reduced by one
|
||||||
elif door_a.blocked or door_b.blocked:
|
elif door_a.blocked or door_b.blocked and sector_a != sector_b:
|
||||||
return sector.outflow() > 0
|
return sector_a.outflow() + sector_b.outflow() > 0
|
||||||
return True # I think this is always true. Both blocked but we're connecting loops now, so dead end?
|
elif sector_a == sector_b and not door_a.blocked and not door_b.blocked:
|
||||||
|
return sector_a.outflow() - 1 > 0
|
||||||
|
elif sector_a == sector_b and door_a.blocked or door_b.blocked:
|
||||||
|
return sector_a.outflow() > 0
|
||||||
|
return True # I think this is always true. Both blocked but we're connecting loops now, so dead end?
|
||||||
|
|
||||||
|
|
||||||
def are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):
|
def are_there_outstanding_doors_of_type(door_a, door_b, sector_a, sector_b, available_sectors):
|
||||||
|
|||||||
Reference in New Issue
Block a user