diff --git a/Dungeon.py b/Dungeon.py index e7ba3123f..9bb0447a7 100644 --- a/Dungeon.py +++ b/Dungeon.py @@ -67,8 +67,8 @@ def shuffle_dungeon_rewards(self) -> str: return self.world.settings.shuffle_dungeon_rewards @property - def empty(self) -> bool: - return self.world.empty_dungeons[self.name].empty + def precompleted(self) -> bool: + return self.world.precompleted_dungeons.get(self.name, False) @property def keys(self) -> list[Item]: @@ -112,28 +112,28 @@ def is_dungeon_item(self, item: Item) -> bool: return item.name in [dungeon_item.name for dungeon_item in self.all_items] def get_restricted_dungeon_items(self) -> Iterator[Item]: - if self.shuffle_mapcompass == 'dungeon' or (self.empty and self.shuffle_mapcompass in ['any_dungeon', 'overworld', 'keysanity', 'regional']): + if self.shuffle_mapcompass == 'dungeon' or (self.precompleted and self.shuffle_mapcompass in ('any_dungeon', 'overworld', 'keysanity', 'regional')): yield from self.dungeon_items - if self.shuffle_smallkeys == 'dungeon' or (self.empty and self.shuffle_smallkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']): + if self.shuffle_smallkeys == 'dungeon' or (self.precompleted and self.shuffle_smallkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional')): yield from self.small_keys - if self.shuffle_bosskeys == 'dungeon' or (self.empty and self.shuffle_bosskeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']): + if self.shuffle_bosskeys == 'dungeon' or (self.precompleted and self.shuffle_bosskeys in ('any_dungeon', 'overworld', 'keysanity', 'regional')): yield from self.boss_key - if self.shuffle_silver_rupees == 'dungeon' or (self.empty and self.shuffle_silver_rupees in ['any_dungeon', 'overworld', 'anywhere', 'regional']): + if self.shuffle_silver_rupees == 'dungeon' or (self.precompleted and self.shuffle_silver_rupees in ('any_dungeon', 'overworld', 'anywhere', 'regional')): yield from self.silver_rupees if self.shuffle_dungeon_rewards in ('vanilla', 'dungeon'): # we don't lock rewards inside pre-completed dungeons since they're still useful outside yield from self.reward # get a list of items that don't have to be in their proper dungeon def get_unrestricted_dungeon_items(self) -> Iterator[Item]: - if self.empty: + if self.precompleted: return - if self.shuffle_mapcompass in ['any_dungeon', 'overworld', 'keysanity', 'regional']: + if self.shuffle_mapcompass in ('any_dungeon', 'overworld', 'keysanity', 'regional'): yield from self.dungeon_items - if self.shuffle_smallkeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']: + if self.shuffle_smallkeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'): yield from self.small_keys - if self.shuffle_bosskeys in ['any_dungeon', 'overworld', 'keysanity', 'regional']: + if self.shuffle_bosskeys in ('any_dungeon', 'overworld', 'keysanity', 'regional'): yield from self.boss_key - if self.shuffle_silver_rupees in ['any_dungeon', 'overworld', 'anywhere', 'regional']: + if self.shuffle_silver_rupees in ('any_dungeon', 'overworld', 'anywhere', 'regional'): yield from self.silver_rupees if self.shuffle_dungeon_rewards in ('any_dungeon', 'overworld', 'anywhere', 'regional'): yield from self.reward diff --git a/Fill.py b/Fill.py index b2ef1d081..0bebe4a5a 100644 --- a/Fill.py +++ b/Fill.py @@ -146,8 +146,11 @@ def distribute_items_restrictive(worlds: list[World], fill_locations: Optional[l # If some dungeons are supposed to be empty, fill them with useless items. if worlds[0].settings.empty_dungeons_mode != 'none': - empty_locations = [location for location in fill_locations - if location.world.empty_dungeons[HintArea.at(location).dungeon_name].empty] + empty_locations = [ + location + for location in fill_locations + if location.world.precompleted_dungeons.get(HintArea.at(location).dungeon_name, False) + ] for location in empty_locations: fill_locations.remove(location) @@ -268,7 +271,7 @@ def fill_dungeon_unique_item(worlds: list[World], search: Search, fill_locations minor_items = [item for item in itempool if not item.majoritem] if worlds[0].settings.empty_dungeons_mode != 'none': - dungeons = [dungeon for world in worlds for dungeon in world.dungeons if not world.empty_dungeons[dungeon.name].empty] + dungeons = [dungeon for world in worlds for dungeon in world.dungeons if not world.precompleted_dungeons.get(dungeon.name, False)] else: dungeons = [dungeon for world in worlds for dungeon in world.dungeons] diff --git a/Hints.py b/Hints.py index 1ea2bf9d9..e00de0928 100644 --- a/Hints.py +++ b/Hints.py @@ -715,6 +715,7 @@ def get_barren_hint(spoiler: Spoiler, world: World, checked: set[str], all_check areas = list(filter(lambda area: area not in checked_areas and str(area) not in world.hint_type_overrides['barren'] + and not world.precompleted_dungeons.get(area.dungeon_name, False) and not (world.barren_dungeon >= world.hint_dist_user['dungeons_barren_limit'] and world.empty_areas[area]['dungeon']) and any( location.name not in all_checked @@ -766,13 +767,22 @@ def get_barren_hint(spoiler: Spoiler, world: World, checked: set[str], all_check return GossipText("plundering %s is a foolish choice." % area.text(world.settings.clearer_hints), ['Pink']), None -def is_not_checked(locations: Iterable[Location], checked: set[HintArea | str]) -> bool: - return not any(location.name in checked or HintArea.at(location) in checked for location in locations) +def is_checked(locations: Iterable[Location], checked: set[HintArea | str]) -> bool: + for location in locations: + if location.name in checked: + return True + hint_area = HintArea.at(location) + if hint_area in checked: + return True + if location.world.precompleted_dungeons.get(hint_area.dungeon_name, False): + # don't hint locations in precompleted dungeons + return True + return False def get_good_item_hint(spoiler: Spoiler, world: World, checked: set[str]) -> HintReturn: locations = list(filter(lambda location: - is_not_checked([location], checked) + not is_checked([location], checked) and ((location.item.majoritem and location.item.name not in unHintableWothItems) or location.name in world.added_hint_types['item'] @@ -809,22 +819,20 @@ def get_specific_item_hint(spoiler: Spoiler, world: World, checked: set[str]) -> if itemname == "Bottle" and world.settings.hint_dist == "bingo": locations = [ location for location in world.get_filled_locations() - if (is_not_checked([location], checked) - and location.name not in world.hint_exclusions - and location.item.name in bingoBottlesForHints - and not location.locked - and location.name not in world.hint_type_overrides['named-item'] - ) + if not is_checked([location], checked) + and location.name not in world.hint_exclusions + and location.item.name in bingoBottlesForHints + and not location.locked + and location.name not in world.hint_type_overrides['named-item'] ] else: locations = [ location for location in world.get_filled_locations() - if (is_not_checked([location], checked) - and location.name not in world.hint_exclusions - and location.item.name == itemname - and not location.locked - and location.name not in world.hint_type_overrides['named-item'] - ) + if not is_checked([location], checked) + and location.name not in world.hint_exclusions + and location.item.name == itemname + and not location.locked + and location.name not in world.hint_type_overrides['named-item'] ] if len(locations) > 0: @@ -885,24 +893,24 @@ def get_specific_item_hint(spoiler: Spoiler, world: World, checked: set[str]) -> if itemname == "Bottle" and world.settings.hint_dist == "bingo": locations = [ location for location in named_item_locations - if (is_not_checked([location], checked) - and location.item.world.id == world.id - and location.name not in world.hint_exclusions - and location.item.name in bingoBottlesForHints - and not location.locked - and (itemname, world.id) not in always_locations - and location.name not in world.hint_type_overrides['named-item']) + if not is_checked([location], checked) + and location.item.world.id == world.id + and location.name not in world.hint_exclusions + and location.item.name in bingoBottlesForHints + and not location.locked + and (itemname, world.id) not in always_locations + and location.name not in world.hint_type_overrides['named-item'] ] else: locations = [ location for location in named_item_locations - if (is_not_checked([location], checked) - and location.item.world.id == world.id - and location.name not in world.hint_exclusions - and location.item.name == itemname - and not location.locked - and (itemname, world.id) not in always_locations - and location.name not in world.hint_type_overrides['named-item']) + if not is_checked([location], checked) + and location.item.world.id == world.id + and location.name not in world.hint_exclusions + and location.item.name == itemname + and not location.locked + and (itemname, world.id) not in always_locations + and location.name not in world.hint_type_overrides['named-item'] ] if len(locations) > 0: @@ -939,14 +947,13 @@ def get_specific_item_hint(spoiler: Spoiler, world: World, checked: set[str]) -> def get_random_location_hint(spoiler: Spoiler, world: World, checked: set[str]) -> HintReturn: locations = list(filter(lambda location: - is_not_checked([location], checked) + not is_checked([location], checked) and location.item.type not in ('Drop', 'Event', 'Shop') and not is_restricted_dungeon_item(location.item) and not location.locked and location.name not in world.hint_exclusions and location.name not in world.hint_type_overrides['item'] - and location.item.name not in world.item_hint_type_overrides['item'] - and (location.world.settings.empty_dungeons_mode == 'none' or not location.world.empty_dungeons[HintArea.at(location).dungeon_name].empty), + and location.item.name not in world.item_hint_type_overrides['item'], world.get_filled_locations())) if not locations: return None @@ -965,25 +972,19 @@ def get_random_location_hint(spoiler: Spoiler, world: World, checked: set[str]) def get_specific_hint(spoiler: Spoiler, world: World, checked: set[str], hint_type: str) -> HintReturn: - def is_valid_hint(hint: Hint) -> bool: - location = world.get_location(hint.name) - if not is_not_checked([world.get_location(hint.name)], checked): - return False - if location.world.settings.empty_dungeons_mode != 'none' and location.world.empty_dungeons[HintArea.at(location).dungeon_name].empty: - return False - return True - hint_group = get_hint_group(hint_type, world) - hint_group = list(filter(is_valid_hint, hint_group)) + hint_group = list(filter(lambda hint: not is_checked([world.get_location(hint.name)], checked), hint_group)) if not hint_group: return None hint = random.choice(hint_group) - if world.hint_dist_user['upgrade_hints'] in ['on', 'limited']: + if world.hint_dist_user['upgrade_hints'] in ('on', 'limited'): upgrade_list = get_upgrade_hint_list(world, [hint.name]) - upgrade_list = list(filter(lambda upgrade: is_not_checked([world.get_location(location) for location in get_multi( - upgrade.name).locations], checked), upgrade_list)) + upgrade_list = list(filter( + lambda upgrade: not is_checked([world.get_location(location) for location in get_multi(upgrade.name).locations], checked), + upgrade_list, + )) if upgrade_list is not None: multi = None @@ -1030,8 +1031,10 @@ def get_dungeon_hint(spoiler: Spoiler, world: World, checked: set[str]) -> HintR def get_random_multi_hint(spoiler: Spoiler, world: World, checked: set[str], hint_type: str) -> HintReturn: hint_group = get_hint_group(hint_type, world) - multi_hints = list(filter(lambda hint: is_not_checked([world.get_location(location) for location in get_multi( - hint.name).locations], checked), hint_group)) + multi_hints = list(filter( + lambda hint: not is_checked([world.get_location(location) for location in get_multi(hint.name).locations], checked), + hint_group, + )) if not multi_hints: return None @@ -1042,8 +1045,10 @@ def get_random_multi_hint(spoiler: Spoiler, world: World, checked: set[str], hin multi = get_multi(hint.name) upgrade_list = get_upgrade_hint_list(world, multi.locations) - upgrade_list = list(filter(lambda upgrade: is_not_checked([world.get_location(location) for location in get_multi( - upgrade.name).locations], checked), upgrade_list)) + upgrade_list = list(filter( + lambda upgrade: not is_checked([world.get_location(location) for location in get_multi(upgrade.name).locations], checked), + upgrade_list, + )) if upgrade_list: for upgrade in upgrade_list: @@ -1322,12 +1327,6 @@ def build_gossip_hints(spoiler: Spoiler, worlds: list[World]) -> None: if item_world.id not in checked_locations: checked_locations[item_world.id] = set() checked_locations[item_world.id].add(location.name) - for dungeon_name, info in world.empty_dungeons.items(): - if info.empty: - for region in world.regions: - if region.dungeon != None and region.dungeon.name == dungeon_name: - precompleted_locations = list(map(lambda location: location.name, region.locations)) - checked_locations[world.id].update(precompleted_locations) # Build all the hints. for world in worlds: @@ -1485,8 +1484,10 @@ def build_world_gossip_hints(spoiler: Spoiler, world: World, checked_locations: # Add required location hints, only if hint copies > 0 if hint_dist['always'][1] > 0: - always_locations = list(filter(lambda hint: is_not_checked([world.get_location(hint.name)], checked_always_locations), - get_hint_group('always', world))) + always_locations = list(filter( + lambda hint: not is_checked([world.get_location(hint.name)], checked_always_locations), + get_hint_group('always', world), + )) for hint in always_locations: location = world.get_location(hint.name) checked_always_locations.add(hint.name) diff --git a/ItemPool.py b/ItemPool.py index eaa466066..7630d71a9 100644 --- a/ItemPool.py +++ b/ItemPool.py @@ -867,7 +867,7 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]: world.state.collect(ItemFactory(item, world)) item = get_junk_item()[0] shuffle_item = True - elif shuffle_setting in ('any_dungeon', 'overworld', 'keysanity', 'regional', 'anywhere') and not world.empty_dungeons[dungeon.name].empty: + elif shuffle_setting in ('any_dungeon', 'overworld', 'keysanity', 'regional', 'anywhere') and not world.precompleted_dungeons.get(dungeon.name, False): shuffle_item = True elif shuffle_item is None: dungeon_collection.append(ItemFactory(item, world)) @@ -926,8 +926,10 @@ def get_pool_core(world: World) -> tuple[list[str], dict[str, Item]]: world.state.collect(ItemFactory('Small Key (Shadow Temple)', world)) world.state.collect(ItemFactory('Small Key (Shadow Temple)', world)) - if (not world.keysanity or (world.empty_dungeons['Fire Temple'].empty and world.settings.shuffle_smallkeys != 'remove'))\ - and not world.dungeon_mq['Fire Temple']: + if ( + (not world.keysanity or (world.precompleted_dungeons['Fire Temple'] and world.settings.shuffle_smallkeys != 'remove')) + and not world.dungeon_mq['Fire Temple'] + ): world.state.collect(ItemFactory('Small Key (Fire Temple)', world)) if world.settings.shuffle_ganon_bosskey == 'on_lacs': diff --git a/Main.py b/Main.py index a540a8671..6c95ffc78 100644 --- a/Main.py +++ b/Main.py @@ -159,14 +159,16 @@ def build_world_graphs(settings: Settings) -> list[World]: if world.settings.shuffle_dungeon_rewards in ('vanilla', 'reward'): world.fill_bosses() - if settings.empty_dungeons_mode == 'rewards': - world.set_empty_dungeon_rewards(settings.empty_dungeons_rewards) - if settings.triforce_hunt: settings.distribution.configure_triforce_hunt(worlds) logger.info('Setting Entrances.') set_entrances(worlds, savewarps_to_connect) + + for world in worlds: + if world.settings.empty_dungeons_mode == 'rewards': + world.set_empty_dungeon_rewards(world.settings.empty_dungeons_rewards) + return worlds diff --git a/Patches.py b/Patches.py index ce8bfec32..00e6e4bfa 100644 --- a/Patches.py +++ b/Patches.py @@ -2793,8 +2793,8 @@ def configure_dungeon_info(rom: Rom, world: World) -> None: if location.world.id == world.id and area.is_dungeon: dungeon_rewards[codes.index(area.dungeon_name)] = boss_reward_index(location.item) - dungeon_is_mq = [1 if world.dungeon_mq.get(c) else 0 for c in codes] - dungeon_precompleted = [1 if world.empty_dungeons[c].empty else 0 for c in codes] + dungeon_is_mq = [int(world.dungeon_mq.get(c, False)) for c in codes] + dungeon_precompleted = [int(world.precompleted_dungeons.get(c, False)) for c in codes] rom.write_int32(rom.sym('CFG_DUNGEON_INFO_ENABLE'), 2) rom.write_int32(rom.sym('CFG_DUNGEON_INFO_MQ_ENABLE'), int(mq_enable)) diff --git a/Plandomizer.py b/Plandomizer.py index a01605610..24b3fb3fc 100644 --- a/Plandomizer.py +++ b/Plandomizer.py @@ -420,7 +420,7 @@ def add_location(self, new_location: str, new_item: str) -> None: raise KeyError('Cannot add location that already exists') self.locations[new_location] = LocationRecord(new_item) - def configure_dungeons(self, world: World, mq_dungeon_pool: list[str], empty_dungeon_pool: list[str]) -> tuple[int, int]: + def configure_dungeons(self, world: World, mq_dungeon_pool: list[str], precompleted_dungeon_pool: list[str]) -> tuple[int, int]: dist_num_mq, dist_num_empty = 0, 0 for (name, record) in self.dungeons.items(): if record.mq is not None: @@ -430,10 +430,10 @@ def configure_dungeons(self, world: World, mq_dungeon_pool: list[str], empty_dun world.dungeon_mq[name] = True for (name, record) in self.empty_dungeons.items(): if record.empty is not None: - empty_dungeon_pool.remove(name) + precompleted_dungeon_pool.remove(name) if record.empty: dist_num_empty += 1 - world.empty_dungeons[name].empty = True + world.precompleted_dungeons[name] = True return dist_num_mq, dist_num_empty def configure_trials(self, trial_pool: list[str]) -> list[str]: @@ -1098,7 +1098,7 @@ def configure_effective_starting_items(self, worlds: list[World], world: World) skipped_locations_from_dungeons += [world.get_location(loc_name) for loc_name in location_groups['BossHeart']] for location in skipped_locations_from_dungeons: hint_area = HintArea.at(location) - if hint_area.is_dungeon and iter_world.empty_dungeons[hint_area.dungeon_name].empty: + if hint_area.is_dungeon and iter_world.precompleted_dungeons.get(hint_area.dungeon_name, False): skipped_locations.append(location) world.item_added_hint_types['barren'].append(location.item.name) for location in skipped_locations: @@ -1362,8 +1362,8 @@ def update_spoiler(self, spoiler: Spoiler, output_spoiler: bool) -> None: for world in spoiler.worlds: world_dist = self.world_dists[world.id] world_dist.randomized_settings = {randomized_item: getattr(world.settings, randomized_item) for randomized_item in world.randomized_list} - world_dist.dungeons = {dung: DungeonRecord({ 'mq': world.dungeon_mq[dung] }) for dung in world.dungeon_mq} - world_dist.empty_dungeons = {dung: EmptyDungeonRecord({ 'empty': world.empty_dungeons[dung].empty }) for dung in world.empty_dungeons} + world_dist.dungeons = {name: DungeonRecord({ 'mq': is_mq }) for name, is_mq in world.dungeon_mq.items()} + world_dist.empty_dungeons = {name: EmptyDungeonRecord({ 'empty': is_precompleted }) for name, is_precompleted in world.precompleted_dungeons.items()} world_dist.trials = {trial: TrialRecord({ 'active': not world.skipped_trials[trial] }) for trial in world.skipped_trials} if hasattr(world, 'song_notes'): world_dist.songs = {song: SongRecord({ 'notes': str(world.song_notes[song]) }) for song in world.song_notes} diff --git a/Region.py b/Region.py index 799b76928..7576f4324 100644 --- a/Region.py +++ b/Region.py @@ -96,11 +96,11 @@ def can_fill(self, item: Item, manual: bool = False) -> bool: if not manual and self.world.settings.empty_dungeons_mode != 'none' and item.dungeonitem: # An empty dungeon can only store its own dungeon items - if self.dungeon and self.dungeon.world.empty_dungeons[self.dungeon.name].empty: + if self.dungeon and self.dungeon.world.precompleted_dungeons.get(self.dungeon.name, False): return self.dungeon.is_dungeon_item(item) and item.world.id == self.world.id # Items from empty dungeons can only be in their own dungeons for dungeon in item.world.dungeons: - if item.world.empty_dungeons[dungeon.name].empty and dungeon.is_dungeon_item(item): + if item.world.precompleted_dungeons.get(dungeon.name, False) and dungeon.is_dungeon_item(item): return False is_self_dungeon_restricted = False diff --git a/SettingsList.py b/SettingsList.py index 17eb68462..c3b4f8025 100644 --- a/SettingsList.py +++ b/SettingsList.py @@ -1550,7 +1550,9 @@ class SettingInfos: randomly rolled with no major items, but their dungeon rewards won't be given for free. - 'Specific Dungeons': Choose which specific dungeons will be pre-completed. - - 'Specific Rewards': Choose which specific dungeon rewards will be in pre-completed dungeons. Not compatible with shuffled dungeon rewards. + - 'Specific Rewards': Choose which specific dungeon rewards will be in + pre-completed dungeons. If dungeon rewards are shuffled, rewards in side + dungeons or the overworld will have no effect on pre-completion. - 'Count': Choose how many pre-completed dungeons will be randomly chosen. A same dungeon won't be both MQ and pre-completed unless it has been @@ -1574,7 +1576,6 @@ class SettingInfos: '!specific': {'settings': ['empty_dungeons_specific']}, '!rewards': {'settings': ['empty_dungeons_rewards']}, '!count': {'settings': ['empty_dungeons_count']}, - 'rewards': {'settings': ['shuffle_dungeon_rewards']}, }, gui_params = { 'distribution': [ diff --git a/World.py b/World.py index 1463f13e8..32342dfa2 100644 --- a/World.py +++ b/World.py @@ -115,33 +115,17 @@ def __init__(self, world_id: int, settings: Settings, resolve_randomized_setting 'Forest': False, } - # empty dungeons will be decided later - class EmptyDungeons(dict): - class EmptyDungeonInfo: - def __init__(self, boss_name: Optional[str]) -> None: - self.empty: bool = False - self.boss_name: Optional[str] = boss_name - self.hint_name: Optional[HintArea] = None - - def __init__(self): - super().__init__() - self['Deku Tree'] = self.EmptyDungeonInfo('Queen Gohma') - self['Dodongos Cavern'] = self.EmptyDungeonInfo('King Dodongo') - self['Jabu Jabus Belly'] = self.EmptyDungeonInfo('Barinade') - self['Forest Temple'] = self.EmptyDungeonInfo('Phantom Ganon') - self['Fire Temple'] = self.EmptyDungeonInfo('Volvagia') - self['Water Temple'] = self.EmptyDungeonInfo('Morpha') - self['Spirit Temple'] = self.EmptyDungeonInfo('Twinrova') - self['Shadow Temple'] = self.EmptyDungeonInfo('Bongo Bongo') - - for area in HintArea: - if area.is_dungeon and area.dungeon_name in self: - self[area.dungeon_name].hint_name = area - - def __missing__(self, dungeon_name: str) -> EmptyDungeonInfo: - return self.EmptyDungeonInfo(None) - - self.empty_dungeons: dict[str, EmptyDungeons.EmptyDungeonInfo] = EmptyDungeons() + # precompleted dungeons will be decided later + self.precompleted_dungeons: dict[str, bool] = { + 'Deku Tree': False, + 'Dodongos Cavern': False, + 'Jabu Jabus Belly': False, + 'Forest Temple': False, + 'Fire Temple': False, + 'Water Temple': False, + 'Spirit Temple': False, + 'Shadow Temple': False, + } # dungeon forms will be decided later self.dungeon_mq: dict[str, bool] = { @@ -233,12 +217,6 @@ def __missing__(self, dungeon_name: str) -> EmptyDungeonInfo: if dist in i['types']: self.item_hint_type_overrides[dist].append(i['item']) - # Make empty dungeons non-hintable as barren dungeons - if settings.empty_dungeons_mode != 'none': - for info in self.empty_dungeons.values(): - if info.empty: - self.hint_type_overrides['barren'].append(str(info.hint_name)) - self.hint_text_overrides: dict[str, str] = {} for loc in self.hint_dist_user['add_locations']: if 'text' in loc: @@ -365,7 +343,7 @@ def copy(self) -> World: new_world.skipped_trials = copy.copy(self.skipped_trials) new_world.dungeon_mq = copy.copy(self.dungeon_mq) - new_world.empty_dungeons = copy.copy(self.empty_dungeons) + new_world.precompleted_dungeons = copy.copy(self.precompleted_dungeons) new_world.shop_prices = copy.copy(self.shop_prices) new_world.triforce_goal = self.triforce_goal new_world.triforce_count = self.triforce_count @@ -500,14 +478,14 @@ def resolve_random_settings(self) -> None: if trial not in chosen_trials and trial not in dist_chosen: self.skipped_trials[trial] = True - # Determine empty and MQ Dungeons (avoid having both empty & MQ dungeons unless necessary) + # Determine precompleted and MQ Dungeons (avoid having an MQ dungeon be precompleted unless necessary) mq_dungeon_pool = list(self.dungeon_mq) - empty_dungeon_pool = list(self.empty_dungeons) - dist_num_mq, dist_num_empty = self.distribution.configure_dungeons(self, mq_dungeon_pool, empty_dungeon_pool) + precompleted_dungeon_pool = list(self.precompleted_dungeons) + dist_num_mq, dist_num_empty = self.distribution.configure_dungeons(self, mq_dungeon_pool, precompleted_dungeon_pool) if self.settings.empty_dungeons_mode == 'specific': for dung in self.settings.empty_dungeons_specific: - self.empty_dungeons[dung].empty = True + self.precompleted_dungeons[dung] = True if self.settings.mq_dungeons_mode == 'specific': for dung in self.settings.mq_dungeons_specific: @@ -517,20 +495,20 @@ def resolve_random_settings(self) -> None: nb_to_pick = self.settings.empty_dungeons_count - dist_num_empty if nb_to_pick < 0: raise RuntimeError(f"{dist_num_empty} dungeons are set to empty on world {self.id+1}, but only {self.settings.empty_dungeons_count} empty dungeons allowed") - if len(empty_dungeon_pool) < nb_to_pick: - non_empty = 8 - dist_num_empty - len(empty_dungeon_pool) + if len(precompleted_dungeon_pool) < nb_to_pick: + non_empty = 8 - dist_num_empty - len(precompleted_dungeon_pool) raise RuntimeError(f"On world {self.id+1}, {dist_num_empty} dungeons are set to empty and {non_empty} to non-empty. Can't reach {self.settings.empty_dungeons_count} empty dungeons.") # Prioritize non-MQ dungeons non_mq, mq = [], [] - for dung in empty_dungeon_pool: + for dung in precompleted_dungeon_pool: (mq if self.dungeon_mq[dung] else non_mq).append(dung) for dung in random.sample(non_mq, min(nb_to_pick, len(non_mq))): - self.empty_dungeons[dung].empty = True + self.precompleted_dungeons[dung] = True nb_to_pick -= 1 if nb_to_pick > 0: for dung in random.sample(mq, nb_to_pick): - self.empty_dungeons[dung].empty = True + self.precompleted_dungeons[dung] = True if self.settings.mq_dungeons_mode == 'random' and 'mq_dungeons_count' not in dist_keys: for dungeon in mq_dungeon_pool: @@ -550,7 +528,7 @@ def resolve_random_settings(self) -> None: # Prioritize non-empty dungeons non_empty, empty = [], [] for dung in mq_dungeon_pool: - (empty if self.empty_dungeons[dung].empty else non_empty).append(dung) + (empty if self.precompleted_dungeons.get(dung, False) else non_empty).append(dung) for dung in random.sample(non_empty, min(nb_to_pick, len(non_empty))): self.dungeon_mq[dung] = True nb_to_pick -= 1 @@ -772,12 +750,11 @@ def fill_bosses(self, boss_count: int = 9) -> None: self.push_item(loc, item) def set_empty_dungeon_rewards(self, empty_rewards: list[str] = []) -> None: - empty_dungeon_bosses = list(map(lambda reward: self.find_items(reward)[0].name, empty_rewards)) + empty_dungeon_bosses = list(map(lambda reward: self.find_items(reward)[0], empty_rewards)) for boss in empty_dungeon_bosses: - for dungeon_item in self.empty_dungeons.items(): - if dungeon_item[1].boss_name == boss: - dungeon_item[1].empty = True - self.hint_type_overrides['barren'].append(dungeon_item[1].hint_name) + hint_area = HintArea.at(boss) + if hint_area.dungeon_name in self.precompleted_dungeons: # filter out side dungeons and overworld + self.precompleted_dungeons[hint_area.dungeon_name] = True def set_goals(self) -> None: # Default goals are divided into 3 primary categories: