From b53a005545d456c719e809a5bd37a8adacab1c09 Mon Sep 17 00:00:00 2001 From: aerinon Date: Mon, 16 Aug 2021 15:28:20 -0600 Subject: [PATCH] Fix for last ditch problems Special bk adjustments Exception for self locking key doors in key lock checker --- BaseClasses.py | 5 ++++- Fill.py | 8 ++++++-- KeyDoorShuffle.py | 26 +++++++++++++++++++++++++- Main.py | 2 +- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/BaseClasses.py b/BaseClasses.py index 91384e46..84ff319d 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -432,7 +432,7 @@ class World(object): else: return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1))) - def can_beat_game(self, starting_state=None): + def can_beat_game(self, starting_state=None, log_error=False): if starting_state: if self.has_beaten_game(starting_state): return True @@ -456,6 +456,9 @@ class World(object): if not sphere: # ran out of places and did not finish yet, quit + if log_error: + missing_locations = ", ".join([x.name for x in prog_locations]) + logging.getLogger('').error(f'Cannot reach the following locations: {missing_locations}') return False for location in sphere: diff --git a/Fill.py b/Fill.py index 9adb0916..c42b6251 100644 --- a/Fill.py +++ b/Fill.py @@ -295,12 +295,14 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite if swap_spot: logging.getLogger('').debug(f'Swapping {old_item} for {item_to_place}') world.push_item(swap_spot, old_item, False) + swap_spot.event = True locations.remove(swap_spot) locations.append(new_spot) return new_spot else: new_spot.item = restore_item - + else: + location.item = old_item return None @@ -315,10 +317,12 @@ def find_spot_for_item(item_to_place, locations, world, base_state, pool, for location in locations: maximum_exploration_state = sweep_from_pool() perform_access_check = True + old_item = None 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 + old_item = location.item location.item = item_to_place test_state = maximum_exploration_state.copy() test_state.stale[item_to_place.player] = True @@ -329,7 +333,7 @@ def find_spot_for_item(item_to_place, locations, world, base_state, pool, 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 + location.item = old_item return None diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 84ff1b38..4319d42b 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -116,6 +116,7 @@ class PlacementRule(object): self.needed_keys_w_bk = None self.needed_keys_wo_bk = None self.check_locations_w_bk = None + self.special_bk_avail = False self.check_locations_wo_bk = None self.bk_relevant = True self.key_reduced = False @@ -164,7 +165,10 @@ class PlacementRule(object): def loc_has_bk(l): return (big_key_loc is not None and big_key_loc == l) or (l.item and l.item.bigkey) - bk_found = any(loc for loc in self.check_locations_w_bk if loc_has_bk(loc)) + # todo: sometimes the bk avail rule doesn't mean the bk must be avail or this rule is invalid + # but sometimes it certainly does + # check threshold vs len(check_loc) maybe to determine bk isn't relevant? + bk_found = self.special_bk_avail or any(loc for loc in self.check_locations_w_bk if loc_has_bk(loc)) if not bk_found: return True check_locations = self.check_locations_wo_bk if bk_blocked else self.check_locations_w_bk @@ -258,6 +262,8 @@ def analyze_dungeon(key_layout, world, player): return original_key_counter = find_counter({}, False, key_layout, False) + if key_layout.big_key_special and forced_big_key_avail(original_key_counter.other_locations) is not None: + original_key_counter = find_counter({}, True, key_layout, False) queue = deque([(None, original_key_counter)]) doors_completed = set() visited_cid = set() @@ -340,6 +346,8 @@ def create_exhaustive_placement_rules(key_layout, world, player): else: placement_self_lock_adjustment(rule, max_ctr, blocked_loc, key_counter, world, player) rule.check_locations_w_bk = accessible_loc + if key_layout.big_key_special: + rule.special_bk_avail = forced_big_key_avail(key_counter.important_locations) is not None # check_sm_restriction_needed(key_layout, max_ctr, rule, blocked_loc) else: if big_key_progress(key_counter) and only_sm_doors(key_counter): @@ -1359,6 +1367,13 @@ def check_bk_special(regions, world, player): return False +def forced_big_key_avail(locations): + for loc in locations: + if loc.forced_big_key(): + return loc + return None + + # Soft lock stuff def validate_key_layout(key_layout, world, player): # retro is all good - except for hyrule castle in standard mode @@ -1962,6 +1977,7 @@ def validate_key_placement(key_layout, world, player): found_prize = False can_progress = (not counter.big_key_opened and big_found and any(d.bigKey for d in counter.child_doors)) or \ found_keys > counter.used_keys and any(not d.bigKey for d in counter.child_doors) or \ + self_locked_child_door(key_layout, counter) or \ (key_layout.prize_relevant and not counter.prize_doors_opened and found_prize) if not can_progress: missing_locations = set(max_counter.free_locations.keys()).difference(found_locations) @@ -1976,3 +1992,11 @@ def validate_key_placement(key_layout, world, player): return True + +def self_locked_child_door(key_layout, counter): + if len(counter.child_doors) == 1: + door = next(iter(counter.child_doors.keys())) + return door.smallKey and key_layout.key_logic.door_rules[door.name].allow_small + return False + + diff --git a/Main.py b/Main.py index d8967f1f..34d35b25 100644 --- a/Main.py +++ b/Main.py @@ -244,7 +244,7 @@ def main(args, seed=None, fish=None): balance_multiworld_progression(world) # if we only check for beatable, we can do this sanity check first before creating the rom - if not world.can_beat_game(): + if not world.can_beat_game(log_error=True): raise RuntimeError(world.fish.translate("cli","cli","cannot.beat.game")) for player in range(1, world.players+1):