diff --git a/BaseClasses.py b/BaseClasses.py index 358227f5..fbaea7b2 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -287,6 +287,9 @@ class World(object): def find_items(self, item, player): return [location for location in self.get_locations() if location.item is not None and location.item.name == item and location.item.player == player] + def find_items_not_key_only(self, item, player): + return [location for location in self.get_locations() if location.item is not None and location.item.name == item and location.item.player == player and location.forced_item is None] + def push_precollected(self, item): item.world = self if (item.smallkey and self.keyshuffle[item.player]) or (item.bigkey and self.bigkeyshuffle[item.player]): diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index 794bbf57..337204c5 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -815,10 +815,10 @@ def validate_key_layout(key_layout, world, player): for region in key_layout.start_regions: state.visit_region(region, key_checks=True) state.add_all_doors_check_keys(region, flat_proposal, world, player) - return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, world, player) + return validate_key_layout_sub_loop(key_layout, state, {}, flat_proposal, None, None, world, player) -def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposal, world, player): +def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposal, prev_state, prev_avail, world, player): expand_key_state(state, flat_proposal, world, player) smalls_avail = len(state.small_doors) > 0 # de-dup crystal repeats num_bigs = 1 if len(state.big_doors) > 0 else 0 # all or nothing @@ -829,7 +829,9 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa ttl_key_only = count_key_only_locations(state) available_small_locations = cnt_avail_small_locations(ttl_locations, ttl_key_only, state, world, player) available_big_locations = cnt_avail_big_locations(ttl_locations, state, world, player) - if (not smalls_avail or available_small_locations == 0) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0): + if invalid_self_locking_key(state, prev_state, prev_avail, world, player): + return False + if (not smalls_avail or not enough_small_locations(state, available_small_locations)) and (state.big_key_opened or num_bigs == 0 or available_big_locations == 0): return False else: if smalls_avail and available_small_locations > 0: @@ -841,7 +843,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa state_copy.used_locations += 1 code = state_id(state_copy, flat_proposal) if code not in checked_states.keys(): - valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, world, player) + valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, + state, available_small_locations, world, player) checked_states[code] = valid else: valid = checked_states[code] @@ -853,7 +856,8 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa state_copy.used_locations += 1 code = state_id(state_copy, flat_proposal) if code not in checked_states.keys(): - valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, world, player) + valid = validate_key_layout_sub_loop(key_layout, state_copy, checked_states, flat_proposal, + state, available_small_locations, world, player) checked_states[code] = valid else: valid = checked_states[code] @@ -862,6 +866,40 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa return True +def invalid_self_locking_key(state, prev_state, prev_avail, world, player): + if prev_state is None or state.used_smalls == prev_state.used_smalls: + return False + new_locations = set(state.found_locations).difference(set(prev_state.found_locations)) + important_found = False + for loc in new_locations: + important_found |= important_location(loc, world, player) + if not important_found: + return False + new_small_doors = set(state.small_doors).difference(set(prev_state.small_doors)) + new_bk_doors = set(state.big_doors).difference(set(prev_state.big_doors)) + if len(new_small_doors) > 0 or len(new_bk_doors) > 0: + return False + return prev_avail - 1 == 0 + + +# does not allow dest doors +def count_unique_sm_doors(doors): + unique_d_set = set() + for d in doors: + if d not in unique_d_set and d.dest not in unique_d_set and not d.bigKey: + unique_d_set.add(d) + return len(unique_d_set) + + +def enough_small_locations(state, avail_small_loc): + unique_d_set = set() + for exp_door in state.small_doors: + door = exp_door.door + if door not in unique_d_set and door.dest not in unique_d_set: + unique_d_set.add(door) + return avail_small_loc >= len(unique_d_set) + + def cnt_avail_small_locations(free_locations, key_only, state, world, player): if not world.keyshuffle[player] and not world.retro[player]: bk_adj = 1 if state.big_key_opened and not state.big_key_special else 0 diff --git a/Main.py b/Main.py index 5c5da4e0..4a716939 100644 --- a/Main.py +++ b/Main.py @@ -23,7 +23,7 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute from ItemList import generate_itempool, difficulties, fill_prizes from Utils import output_path, parse_player_names -__version__ = '0.0.10pre' +__version__ = '0.0.11pre' def main(args, seed=None): diff --git a/Regions.py b/Regions.py index e92347c1..23fba53c 100644 --- a/Regions.py +++ b/Regions.py @@ -740,10 +740,11 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None ret.exits.append(Entrance(player, exit, ret)) for location in locations: if location in key_only_locations: - ret.locations.append(Location(player, location, None, False, None, ret, key_only_locations[location])) + ko_hint = 'in a pot' if 'Pot' in location else 'with an enemy' + ret.locations.append(Location(player, location, None, False, ko_hint, ret, key_only_locations[location])) else: - address, player_address, crystal, hint_text = location_table[location] - ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address)) + address, player_address, crystal, hint_text = location_table[location] + ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address)) return ret def mark_light_world_regions(world, player): diff --git a/Rom.py b/Rom.py index 38b5448f..a0b8c8b0 100644 --- a/Rom.py +++ b/Rom.py @@ -1725,7 +1725,7 @@ def write_strings(rom, world, player, team): hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull'] else 8 while hint_count > 0: this_item = items_to_hint.pop(0) - this_location = world.find_items(this_item, player) + this_location = world.find_items_not_key_only(this_item, player) random.shuffle(this_location) #This looks dumb but prevents hints for Skull Woods Pinball Room's key safely with any item pool. if this_location: diff --git a/RoomData.py b/RoomData.py index e80fb087..337b1d73 100644 --- a/RoomData.py +++ b/RoomData.py @@ -279,6 +279,7 @@ class Room(object): def delete(self, list_idx): self.doorList[list_idx] = (Position.FF, DoorKind.FF) + self.modified = True def address(self): return self.doorListAddress