def pickups_to_solve_list(pickup_pool: List[PickupEntry], requirement_list: RequirementList, state: State): pickups = [] resources = copy.copy(state.resources) pickups_for_this = list(pickup_pool) for individual in sorted(requirement_list.values()): if individual.satisfied(resources, state.energy): continue # FIXME: this picks Dark Beam to provide Dark Ammo # Create another copy of the list so we can remove elements while iterating for pickup in list(pickups_for_this): new_resources = resource_info.convert_resource_gain_to_current_resources( pickup.resource_gain(resources)) pickup_progression = resource_info.convert_resource_gain_to_current_resources( pickup.progression) if new_resources.get(individual.resource, 0) + pickup_progression.get( individual.resource, 0) > 0: pickups.append(pickup) pickups_for_this.remove(pickup) resource_info.add_resources_into_another( resources, new_resources) if individual.satisfied(resources, state.energy): break if not individual.satisfied(resources, state.energy): return None return pickups
def find_locations_that_gives_items( target_items: List[ItemResourceInfo], all_patches: Dict[int, GamePatches], player: int, ) -> dict[ItemResourceInfo, list[tuple[int, PickupLocation]]]: result: dict[ItemResourceInfo, list[tuple[int, PickupLocation]]] = { item: [] for item in target_items } for other_player, patches in all_patches.items(): for pickup_index, target in patches.pickup_assignment.items(): if target.player != player: continue # TODO: iterate over all tiers of progression resources = resource_info.convert_resource_gain_to_current_resources( target.pickup.resource_gain({})) for resource, quantity in resources.items(): if quantity > 0 and resource in result: result[resource].append( (other_player, PickupLocation(patches.configuration.game, pickup_index))) return result
def test_convert_resource_gain_to_current_resources(resource_gain, expected): # Setup # Run result = convert_resource_gain_to_current_resources(resource_gain) # Assert assert result == expected
def assign_pickup_to_starting_items(self, pickup: PickupEntry) -> "State": pickup_resources = convert_resource_gain_to_current_resources( pickup.resource_gain(self.resources)) # Make sure there's no item percentage on starting items pickup_resources.pop(self.resource_database.item_percentage, None) new_resources = copy.copy(self.resources) add_resources_into_another(new_resources, pickup_resources) new_patches = self.patches.assign_extra_initial_items(pickup_resources) if self.patches.game_specific is not None: energy_per_tank = self.patches.game_specific.energy_per_tank else: energy_per_tank = 100 return State( new_resources, self.collected_resource_nodes, self.energy + _energy_tank_difference(new_resources, self.resources, self.resource_database) * energy_per_tank, self.node, new_patches, self, self.resource_database, self.world_list, )
def find_area_errors(game: GameDescription, area: Area) -> Iterator[str]: nodes_with_paths_in = set() for node in area.nodes: nodes_with_paths_in.update(area.connections[node].keys()) for error in find_node_errors(game, node): yield f"{area.name} - {error}" if node in area.connections.get(node, {}): yield f"{area.name} - Node '{node.name}' has a connection to itself" if area.default_node is not None and area.node_with_name( area.default_node) is None: yield f"{area.name} has default node {area.default_node}, but no node with that name exists" # elif area.default_node is not None: # nodes_with_paths_in.add(area.node_with_name(area.default_node)) for node in area.nodes: if isinstance(node, (TeleporterNode, DockNode)) or area.connections[node]: continue # FIXME: cannot implement this for PickupNodes because their resource gain depends on GamePatches if isinstance(node, EventNode): # if this node would satisfy the victory condition, it does not need outgoing connections current = convert_resource_gain_to_current_resources( node.resource_gain_on_collect(None)) if game.victory_condition.satisfied(current, 0, game.resource_database): continue if node in nodes_with_paths_in: yield f"{area.name} - '{node.name}': Node has paths in, but no connections out."
def create_temple_key_hint( all_patches: Dict[int, GamePatches], player_index: int, temple: HintDarkTemple, world_list: WorldList, ) -> str: """ Creates the text for . :param all_patches: :param player_index: :param temple: :param world_list: :return: """ all_world_names = set() _TEMPLE_NAMES = ["Dark Agon Temple", "Dark Torvus Temple", "Hive Temple"] temple_index = [ HintDarkTemple.AGON_WASTES, HintDarkTemple.TORVUS_BOG, HintDarkTemple.SANCTUARY_FORTRESS ].index(temple) keys = echoes_items.DARK_TEMPLE_KEY_ITEMS[temple_index] index_to_node = { node.pickup_index: node for node in world_list.all_nodes if isinstance(node, PickupNode) } for patches in all_patches.values(): for pickup_index, target in patches.pickup_assignment.items(): if target.player != player_index: continue resources = resource_info.convert_resource_gain_to_current_resources( target.pickup.resource_gain({})) for resource, quantity in resources.items(): if quantity < 1 or resource.index not in keys: continue pickup_node = index_to_node[pickup_index] all_world_names.add( world_list.world_name_from_node(pickup_node, True)) temple_name = hint_lib.color_text(hint_lib.TextColor.ITEM, _TEMPLE_NAMES[temple_index]) names_sorted = [ hint_lib.color_text(hint_lib.TextColor.LOCATION, world) for world in sorted(all_world_names) ] if len(names_sorted) == 0: return f"The keys to {temple_name} are nowhere to be found." elif len(names_sorted) == 1: return f"The keys to {temple_name} can all be found in {names_sorted[0]}." else: last = names_sorted.pop() front = ", ".join(names_sorted) return f"The keys to {temple_name} can be found in {front} and {last}."
def test_logbook_node_resource_gain_on_collect(logbook_node, empty_patches): # Setup node = logbook_node[-1] # Run gain = node.resource_gain_on_collect(empty_patches, {}) # Assert assert convert_resource_gain_to_current_resources(gain) == {node.resource(): 1}
def assign_pickup_to_starting_items(self, pickup: PickupEntry) -> "State": pickup_resources = convert_resource_gain_to_current_resources( pickup.resource_gain(self.resources)) # Make sure there's no item percentage on starting items pickup_resources.pop(self.resource_database.item_percentage, None) new_resources = copy.copy(self.resources) add_resources_into_another(new_resources, pickup_resources) new_patches = self.patches.assign_extra_initial_items(pickup_resources) return State( new_resources, self.collected_resource_nodes, self.energy + _energy_tank_difference(new_resources, self.resources, self.resource_database) * ENERGY_PER_TANK, self.node, new_patches, self, self.resource_database)
def assign_pickup_to_starting_items(self, pickup: PickupEntry) -> "State": pickup_resources = convert_resource_gain_to_current_resources( pickup.resource_gain(self.resources, force_lock=True)) # Make sure there's no item percentage on starting items if self.resource_database.item_percentage is not None: pickup_resources.pop(self.resource_database.item_percentage, None) new_resources = copy.copy(self.resources) add_resources_into_another(new_resources, pickup_resources) new_patches = self.patches.assign_extra_initial_items(pickup_resources) tank_delta = _energy_tank_difference(new_resources, self.resources, self.resource_database) return State( new_resources, self.collected_resource_nodes, self.energy + tank_delta * self.game_data.energy_per_tank, self.node, new_patches, self, self.game_data, )
def create_hints( patches: GamePatches, world_list: WorldList, hide_area: bool, ) -> list: """ Creates the string patches entries that changes the Sky Temple Gateway hint scans with hints for where the STK actually are. :param patches: :param world_list: :param hide_area: Should the hint include only the world? :return: """ location_hint_creator = LocationHintCreator(world_list) sky_temple_key_hints = {} for pickup_index, pickup in patches.pickup_assignment.items(): resources = resource_info.convert_resource_gain_to_current_resources( pickup.resource_gain({})) for resource, quantity in resources.items(): if quantity < 1: continue try: key_number = echoes_items.SKY_TEMPLE_KEY_ITEMS.index( resource.index) + 1 except ValueError: continue assert resource.index not in sky_temple_key_hints sky_temple_key_hints[ resource.index] = "{} is located in {}.".format( _sky_temple_key_name(key_number), color_text( TextColor.LOCATION, location_hint_creator.index_node_name( pickup_index, hide_area), ), ) for starting_resource, quantity in patches.starting_items.items(): if quantity < 1: continue try: key_number = echoes_items.SKY_TEMPLE_KEY_ITEMS.index( starting_resource.index) + 1 except ValueError: continue assert starting_resource.index not in sky_temple_key_hints sky_temple_key_hints[ starting_resource.index] = "{} has no need to be located.".format( _sky_temple_key_name(key_number), ) if len(sky_temple_key_hints) != len(echoes_items.SKY_TEMPLE_KEY_ITEMS): raise ValueError( "Expected to find {} Sky Temple Keys between pickup placement and starting items, found {}" .format(len(echoes_items.SKY_TEMPLE_KEY_ITEMS), len(sky_temple_key_hints))) return [ create_simple_logbook_hint(_SKY_TEMPLE_KEY_SCAN_ASSETS[key_number], sky_temple_key_hints[key_index]) for key_number, key_index in enumerate(echoes_items.SKY_TEMPLE_KEY_ITEMS) ]