diff --git a/BaseClasses.py b/BaseClasses.py index 7a3fe9e8..01179702 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -482,8 +482,6 @@ class CollectionState(object): self.reached_doors = {player: set() for player in range(1, parent.players + 1)} self.opened_doors = {player: set() for player in range(1, parent.players + 1)} self.dungeons_to_check = {player: defaultdict(dict) for player in range(1, parent.players + 1)} - - self.ghost_keys = Counter() self.dungeon_limits = None # self.trace = None @@ -551,9 +549,9 @@ class CollectionState(object): if (new_entrance, new_crystal_state) not in queue: queue.append((new_entrance, new_crystal_state)) # else those connections that are not accessible yet - if self.is_small_door(connection) and not self.world.retro[player]: # todo: retro + if self.is_small_door(connection): door = connection.door - dungeon_name = connection.parent_region.dungeon.name # todo: universal + dungeon_name = connection.parent_region.dungeon.name key_logic = self.world.key_logic[player][dungeon_name] if door.name not in self.reached_doors[player]: self.door_counter[player][0][dungeon_name] += 1 @@ -561,7 +559,8 @@ class CollectionState(object): if key_logic.sm_doors[door]: self.reached_doors[player].add(key_logic.sm_doors[door].name) if not connection.can_reach(self): - checklist = self.dungeons_to_check[player][dungeon_name] + checklist_key = 'Universal' if self.world.retro[player] else dungeon_name + checklist = self.dungeons_to_check[player][checklist_key] checklist[connection.name] = (connection, crystal_state) elif door.name not in self.opened_doors[player]: opened_doors = self.opened_doors[player] @@ -639,7 +638,7 @@ class CollectionState(object): while not done: rrp_ = child_state.reachable_regions[player] bc_ = child_state.blocked_connections[player] - self.dungeon_limits = [dungeon_name] + self.set_dungeon_limits(player, dungeon_name) child_state.traverse_world(child_queue, rrp_, bc_, player) new_events = child_state.sweep_for_events_once() child_state.stale[player] = False @@ -674,13 +673,19 @@ class CollectionState(object): terminal_queue = deque() for door in common_doors: + pair = self.find_door_pair(player, dungeon_name, door) + if door not in self.reached_doors[player]: + self.door_counter[player][0][dungeon_name] += 1 + self.reached_doors[player].add(door) + if pair not in self.reached_doors[player]: + self.reached_doors[player].add(pair) self.opened_doors[player].add(door) if door in checklist: terminal_queue.append(checklist[door]) - if self.find_door_pair(player, dungeon_name, door) not in self.opened_doors[player]: + if pair not in self.opened_doors[player]: self.door_counter[player][1][dungeon_name] += 1 - self.dungeon_limits = [dungeon_name] + self.set_dungeon_limits(player, dungeon_name) rrp_ = self.reachable_regions[player] bc_ = self.blocked_connections[player] for block, crystal in bc_.items(): @@ -722,14 +727,20 @@ class CollectionState(object): return paired_door.name if paired_door else None return None + def set_dungeon_limits(self, player, dungeon_name): + if self.world.retro[player] and self.world.mode[player] == 'standard': + self.dungeon_limits = ['Hyrule Castle', 'Agahnims Tower'] + else: + self.dungeon_limits = [dungeon_name] + @staticmethod def should_explore_child_state(state, dungeon_name, player): - small_key_name = dungeon_keys[dungeon_name] # todo: universal - key_total = state.prog_items[(small_key_name, player)] + state.ghost_keys[(small_key_name, player)] + small_key_name = dungeon_keys[dungeon_name] + key_total = state.prog_items[(small_key_name, player)] remaining_keys = key_total - state.door_counter[player][1][dungeon_name] unopened_doors = state.door_counter[player][0][dungeon_name] - state.door_counter[player][1][dungeon_name] if remaining_keys > 0 and unopened_doors > 0: - key_logic = state.world.key_logic[player][dungeon_name] # todo: universal + key_logic = state.world.key_logic[player][dungeon_name] door_candidates, skip = [], set() for door, paired in key_logic.sm_doors.items(): if door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]: @@ -770,7 +781,6 @@ class CollectionState(object): player: defaultdict(dict, {name: copy.copy(checklist) for name, checklist in self.dungeons_to_check[player].items()}) for player in range(1, self.world.players + 1)} - ret.ghost_keys = self.ghost_keys.copy() return ret def apply_dungeon_exploration(self, rrp, player, dungeon_name, checklist): @@ -783,13 +793,19 @@ class CollectionState(object): common_doors, missing_regions, missing_bc, paths = ec[dungeon_name][exp_key] terminal_queue = deque() for door in common_doors: + pair = self.find_door_pair(player, dungeon_name, door) + if door not in self.reached_doors[player]: + self.door_counter[player][0][dungeon_name] += 1 + self.reached_doors[player].add(door) + if pair not in self.reached_doors[player]: + self.reached_doors[player].add(pair) self.opened_doors[player].add(door) if door in checklist: terminal_queue.append(checklist[door]) - if self.find_door_pair(player, dungeon_name, door) not in self.opened_doors[player]: + if pair not in self.opened_doors[player]: self.door_counter[player][1][dungeon_name] += 1 - self.dungeon_limits = [dungeon_name] + self.set_dungeon_limits(player, dungeon_name) rrp_ = self.reachable_regions[player] bc_ = self.blocked_connections[player] for block, crystal in bc_.items(): @@ -1812,6 +1828,7 @@ class Sector(object): self.entrance_sector = None self.destination_entrance = False self.equations = None + self.item_logic = set() def region_set(self): if self.r_name_set is None: @@ -2552,7 +2569,8 @@ dungeon_keys = { 'Ice Palace': 'Small Key (Ice Palace)', 'Misery Mire': 'Small Key (Misery Mire)', 'Turtle Rock': 'Small Key (Turtle Rock)', - 'Ganons Tower': 'Small Key (Ganons Tower)' + 'Ganons Tower': 'Small Key (Ganons Tower)', + 'Universal': 'Small Key (Universal)' } class PotItem(FastEnum): diff --git a/DoorShuffle.py b/DoorShuffle.py index 3ce078f0..5231fc91 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -1101,9 +1101,9 @@ def assign_cross_keys(dungeon_builders, world, player): logger.debug('Cross Dungeon: Keys unable to assign in pool %s', remaining) # Last Step: Adjust Small Key Dungeon Pool - if not world.retro[player]: - for name, builder in dungeon_builders.items(): - reassign_key_doors(builder, world, player) + for name, builder in dungeon_builders.items(): + reassign_key_doors(builder, world, player) + if not world.retro[player]: log_key_logic(builder.name, world.key_logic[player][builder.name]) actual_chest_keys = max(builder.key_doors_num - builder.key_drop_cnt, 0) dungeon = world.get_dungeon(name, player) diff --git a/DungeonGenerator.py b/DungeonGenerator.py index c8599748..9d76ddb5 100644 --- a/DungeonGenerator.py +++ b/DungeonGenerator.py @@ -1290,13 +1290,16 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, sanc_builder = random.choice(lw_builders) assign_sector(sanc, sanc_builder, candidate_sectors, global_pole) + bow_sectors, retro_std_flag = {}, world.retro[player] and world.mode[player] == 'standard' free_location_sectors = {} crystal_switches = {} crystal_barriers = {} polarized_sectors = {} neutral_sectors = {} for sector in candidate_sectors: - if sector.chest_locations > 0: + if retro_std_flag and 'Bow' in sector.item_logic: # these need to be distributed outside of HC + bow_sectors[sector] = None + elif sector.chest_locations > 0: free_location_sectors[sector] = None elif sector.c_switch: crystal_switches[sector] = None @@ -1306,6 +1309,8 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player, neutral_sectors[sector] = None else: polarized_sectors[sector] = None + if bow_sectors: + assign_bow_sectors(dungeon_map, bow_sectors, global_pole) assign_location_sectors(dungeon_map, free_location_sectors, global_pole) leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole) ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole) @@ -1471,6 +1476,9 @@ def define_sector_features(sectors): sector.blue_barrier = True if door.bigKey: sector.bk_required = True + if region.name in ['PoD Mimics 2', 'PoD Bow Statue Right', 'PoD Mimics 1', 'GT Mimics 1', 'GT Mimics 2', + 'Eastern Single Eyegore', 'Eastern Duo Eyegores']: + sector.item_logic.add('Bow') def assign_sector(sector, dungeon, candidate_sectors, global_pole): @@ -1521,6 +1529,19 @@ def find_sector(r_name, sectors): return None +def assign_bow_sectors(dungeon_map, bow_sectors, global_pole): + sector_list = list(bow_sectors) + random.shuffle(sector_list) + population = [] + for name in dungeon_map: + if name != 'Hyrule Castle': + population.append(name) + choices = random.choices(population, k=len(sector_list)) + for i, choice in enumerate(choices): + builder = dungeon_map[choice] + assign_sector(sector_list[i], builder, bow_sectors, global_pole) + + def assign_location_sectors(dungeon_map, free_location_sectors, global_pole): valid = False choices = None