diff --git a/BaseClasses.py b/BaseClasses.py index c4257fa1..7a3fe9e8 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -505,6 +505,7 @@ class CollectionState(object): unresolved_events = [x for y in self.reachable_regions[player] for x in y.locations if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement) and x not in self.locations_checked and x.can_reach(self)] + unresolved_events = self._do_not_flood_the_keys(unresolved_events) if len(unresolved_events) == 0: self.check_key_doors_in_dungeons(rrp, player) diff --git a/Fill.py b/Fill.py index d2480df8..2a578ff7 100644 --- a/Fill.py +++ b/Fill.py @@ -221,7 +221,10 @@ def fill_restrictive(world, base_state, locations, itempool, keys_in_itempool = if world.accessibility[item_to_place.player] != 'none': logging.getLogger('').warning('Not all items placed. Game beatable anyway. (Could not place %s)' % item_to_place) continue - raise FillError('No more spots to place %s' % item_to_place) + spot_to_fill = last_ditch_placement(item_to_place, locations, world, maximum_exploration_state, + base_state, itempool, keys_in_itempool, single_player_placement) + if spot_to_fill is None: + raise FillError('No more spots to place %s' % item_to_place) world.push_item(spot_to_fill, item_to_place, False) track_outside_keys(item_to_place, spot_to_fill, world) @@ -258,6 +261,69 @@ def track_outside_keys(item, location, world): world.key_logic[item.player][item_dungeon].outside_keys += 1 +def last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, + keys_in_itempool=None, single_player_placement=False): + def location_preference(loc): + if not loc.item.advancement: + return 1 + if loc.item.type and loc.item.type != 'Sword': + if loc.item.type in ['Map', 'Compass']: + return 2 + else: + return 3 + return 4 + + possible_swaps = [x for x in state.locations_checked + if x.item.type not in ['Event', 'Crystal']] + swap_locations = sorted(possible_swaps, key=location_preference) + + for location in swap_locations: + old_item = location.item + new_pool = list(itempool) + [old_item] + new_spot = find_spot_for_item(item_to_place, [location], world, base_state, new_pool, + keys_in_itempool, single_player_placement) + if new_spot: + new_spot.item = item_to_place + swap_spot = find_spot_for_item(old_item, locations, world, base_state, itempool, + keys_in_itempool, single_player_placement) + if swap_spot: + logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}') + world.push_item(swap_spot, old_item, False) + locations.remove(swap_spot) + locations.append(new_spot) + return new_spot + return None + + +def find_spot_for_item(item_to_place, locations, world, base_state, pool, + keys_in_itempool=None, single_player_placement=False): + def sweep_from_pool(): + new_state = base_state.copy() + for item in pool: + new_state.collect(item, True) + new_state.sweep_for_events() + return new_state + for location in locations: + maximum_exploration_state = sweep_from_pool() + perform_access_check = True + if world.accessibility[item_to_place.player] == 'none': + perform_access_check = not world.has_beaten_game(maximum_exploration_state, item_to_place.player) if single_player_placement else not world.has_beaten_game(maximum_exploration_state) + + if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there + location.item = item_to_place + test_state = maximum_exploration_state.copy() + test_state.stale[item_to_place.player] = True + else: + test_state = maximum_exploration_state + if (not single_player_placement or location.player == item_to_place.player) \ + and location.can_fill(test_state, item_to_place, perform_access_check) \ + and valid_key_placement(item_to_place, location, pool if (keys_in_itempool and keys_in_itempool[item_to_place.player]) else world.itempool, world): + return location + if item_to_place.smallkey or item_to_place.bigkey: + location.item = None + return None + + def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None): # If not passed in, then get a shuffled list of locations to fill in if not fill_locations: