def fill_unassigned_hints(self, patches: GamePatches, world_list: WorldList, rng: Random, scan_asset_initial_pickups: dict[NodeIdentifier, frozenset[PickupIndex]], ) -> GamePatches: new_hints = copy.copy(patches.hints) # Get all LogbookAssets from the WorldList potential_hint_locations: set[NodeIdentifier] = { world_list.identifier_for_node(node) for node in world_list.iterate_nodes() if isinstance(node, LogbookNode) } for logbook in potential_hint_locations: if logbook not in scan_asset_initial_pickups: scan_asset_initial_pickups[logbook] = frozenset() # But remove these that already have hints potential_hint_locations -= patches.hints.keys() # We try our best to not hint the same thing twice hinted_indices: set[PickupIndex] = {hint.target for hint in patches.hints.values() if hint.target is not None} # Get interesting items to place hints for possible_indices: set[PickupIndex] = { index for index, target in patches.pickup_assignment.items() if self.interesting_pickup_to_hint(target.pickup) } possible_indices -= hinted_indices debug.debug_print("fill_unassigned_hints had {} decent indices for {} hint locations".format( len(possible_indices), len(potential_hint_locations))) if debug.debug_level() > 1: print(f"> Num pickups per asset:") for asset, pickups in scan_asset_initial_pickups.items(): print(f"* {asset}: {len(pickups)} pickups") print("> Done.") all_pickup_indices = [ node.pickup_index for node in world_list.iterate_nodes() if isinstance(node, PickupNode) ] rng.shuffle(all_pickup_indices) # If there isn't enough indices, use unhinted non-majors placed by generator if (num_indices_needed := len(potential_hint_locations) - len(possible_indices)) > 0: potential_indices = [ index for index in all_pickup_indices if index not in possible_indices and index not in hinted_indices ] debug.debug_print( f"Had only {len(possible_indices)} hintable indices, but needed {len(potential_hint_locations)}." f" Found {len(potential_indices)} less desirable locations.") possible_indices |= set(potential_indices[:num_indices_needed])
def _assign_remaining_items( rng: Random, world_list: WorldList, pickup_assignment: PickupAssignment, remaining_items: List[PickupEntry], randomization_mode: RandomizationMode, ) -> PickupAssignment: """ :param rng: :param world_list: :param pickup_assignment: :param remaining_items: :return: """ unassigned_pickup_nodes = list( filter_unassigned_pickup_nodes(world_list.iterate_nodes(), pickup_assignment)) num_etm = len(unassigned_pickup_nodes) - len(remaining_items) if num_etm < 0: raise InvalidConfiguration( "Received {} remaining items, but there's only {} unassigned pickups" .format(len(remaining_items), len(unassigned_pickup_nodes))) # Shuffle the items to add and the spots to choose from rng.shuffle(remaining_items) rng.shuffle(unassigned_pickup_nodes) assignment = {} if randomization_mode is RandomizationMode.MAJOR_MINOR_SPLIT: remaining_majors = [ item for item in remaining_items if not item.is_expansion ] + ([None] * num_etm) unassigned_major_locations = [ pickup_node for pickup_node in unassigned_pickup_nodes if pickup_node.major_location ] for pickup_node, item in zip(unassigned_major_locations, remaining_majors): if item is not None: assignment[pickup_node.pickup_index] = item remaining_items.remove(item) unassigned_pickup_nodes.remove(pickup_node) assignment.update({ pickup_node.pickup_index: item for pickup_node, item in zip(unassigned_pickup_nodes, remaining_items) }) return assignment
def hide_patches_hints(world_list: WorldList) -> list: """ Creates the string patches entries that changes the Lore scans in the game completely useless text. :return: """ return [ create_simple_logbook_hint(logbook_node.string_asset_id, "Some item was placed somewhere.") for logbook_node in world_list.iterate_nodes() if isinstance(logbook_node, LogbookNode) ]
def export_all_indices( patches: GamePatches, useless_target: PickupTarget, world_list: WorldList, rng: Random, model_style: PickupModelStyle, data_source: PickupModelDataSource, exporter: PickupExporter, visual_etm: PickupEntry, ) -> List[ExportedPickupDetails]: """ Creates the patcher data for all pickups in the game :param patches: :param useless_target: :param world_list: :param rng: :param model_style: :param data_source: :param exporter: :param visual_etm: :return: """ pickup_assignment = patches.pickup_assignment pickup_list = list(pickup_assignment.values()) rng.shuffle(pickup_list) indices = sorted(node.pickup_index for node in world_list.iterate_nodes() if isinstance(node, PickupNode)) pickups = [ exporter.export( index, pickup_assignment.get(index, useless_target), _get_visual_model(i, pickup_list, data_source, visual_etm), model_style, ) for i, index in enumerate(indices) ] return pickups
def create_patches_hints( all_patches: Dict[int, GamePatches], players_config: PlayersConfiguration, world_list: WorldList, namer: HintNamer, rng: Random, ) -> list: exporter = HintExporter(namer, rng, JOKE_HINTS) hints_for_asset: dict[NodeIdentifier, str] = {} for identifier, hint in all_patches[ players_config.player_index].hints.items(): hints_for_asset[identifier] = exporter.create_message_for_hint( hint, all_patches, players_config, True) return [ create_simple_logbook_hint( logbook_node.string_asset_id, hints_for_asset.get(world_list.identifier_for_node(logbook_node), "Someone forgot to leave a message."), ) for logbook_node in world_list.iterate_nodes() if isinstance(logbook_node, LogbookNode) ]
def pickup_index_to_node(world_list: WorldList, index: PickupIndex) -> PickupNode: for node in world_list.iterate_nodes(): if isinstance(node, PickupNode) and node.pickup_index == index: return node raise ValueError(f"PickupNode with {index} not found.")
def _get_nodes_by_teleporter_id( world_list: WorldList) -> Iterator[TeleporterNode]: for node in world_list.iterate_nodes(): if isinstance(node, TeleporterNode) and node.editable: yield node