Beispiel #1
0
def pretty_print_node_type(node: Node, world_list: WorldList):
    if isinstance(node, DockNode):
        try:
            other = world_list.resolve_dock_connection(
                world_list.nodes_to_world(node), node.default_connection)
            other_name = world_list.node_name(other)
        except IndexError as e:
            other_name = (f"(Asset {node.default_connection.area_asset_id:x}, "
                          f"index {node.default_connection.dock_index}) [{e}]")

        return f"{node.default_dock_weakness.name} to {other_name}"

    elif isinstance(node, TeleporterNode):
        other = world_list.area_by_area_location(node.default_connection)
        return f"Teleporter to {world_list.area_name(other)}"

    elif isinstance(node, PickupNode):
        return f"Pickup {node.pickup_index.index}; Major Location? {node.major_location}"

    elif isinstance(node, EventNode):
        return f"Event {node.event.long_name}"

    elif isinstance(node, TranslatorGateNode):
        return f"Translator Gate ({node.gate})"

    elif isinstance(node, LogbookNode):
        message = ""
        if node.lore_type == LoreType.LUMINOTH_LORE:
            message = f" ({node.required_translator.long_name})"
        return f"Logbook {node.lore_type.long_name}{message} for {node.string_asset_id:x}"

    return ""
Beispiel #2
0
def _pretty_name_for_elevator(
    world_list: WorldList,
    original_teleporter_node: TeleporterNode,
    connection: AreaLocation,
) -> str:
    """
    Calculates the name the room that contains this elevator should have
    :param world_list:
    :param original_teleporter_node:
    :param connection:
    :return:
    """
    if original_teleporter_node.keep_name_when_vanilla:
        if original_teleporter_node.default_connection == connection:
            return world_list.nodes_to_area(original_teleporter_node).name

    if connection.area_asset_id in _CUSTOM_NAMES_FOR_ELEVATORS:
        target_area_name = _CUSTOM_NAMES_FOR_ELEVATORS[
            connection.area_asset_id]

    else:
        world = world_list.world_by_area_location(connection)
        area = world.area_by_asset_id(connection.area_asset_id)
        target_area_name = area.name

    return "Transport to {}".format(target_area_name)
Beispiel #3
0
def add_relative_hint(world_list: WorldList,
                      patches: GamePatches,
                      rng: Random,
                      target: PickupIndex,
                      target_precision: HintItemPrecision,
                      relative_type: HintLocationPrecision,
                      precise_distance: bool,
                      precision: Union[HintItemPrecision, HintRelativeAreaName],
                      max_distance: int,
                      ) -> Optional[Hint]:
    """
    Creates a relative hint.
    :return: Might be None, if no hint could be created.
    """
    target_node = node_search.pickup_index_to_node(world_list, target)
    target_area = world_list.nodes_to_area(target_node)
    distances = node_search.distances_to_node(world_list, target_node, patches=patches, cutoff=max_distance)

    def _major_pickups(area: Area) -> Iterator[PickupIndex]:
        for index in area.pickup_indices:
            t = patches.pickup_assignment.get(index)
            # FIXME: None should be ok, but this must be called after junk has been filled
            if t is not None:
                cat = t.pickup.item_category
                if cat.is_major_category or (cat != ItemCategory.EXPANSION
                                             and target_precision == HintItemPrecision.DETAILED):
                    yield index

    area_choices = {
        area: 1 / max(distance, 2)
        for area, distance in distances.items()
        if (distance > 0 and area.in_dark_aether == target_area.in_dark_aether
            and (relative_type == HintLocationPrecision.RELATIVE_TO_AREA or _not_empty(_major_pickups(area))))
    }
    if not area_choices:
        return None
    area = random_lib.select_element_with_weight(dict(sorted(area_choices.items(),
                                                             key=lambda a: a[0].area_asset_id)), rng)

    distance_offset = 0
    if not precise_distance:
        distance_offset = max_distance - distances[area]

    if relative_type == HintLocationPrecision.RELATIVE_TO_AREA:
        relative = RelativeDataArea(distance_offset, world_list.area_to_area_location(area),
                                    precision)
    elif relative_type == HintLocationPrecision.RELATIVE_TO_INDEX:
        relative = RelativeDataItem(distance_offset, rng.choice(list(_major_pickups(area))), precision)
    else:
        raise ValueError(f"Invalid relative_type: {relative_type}")

    precision_pair = PrecisionPair(relative_type, target_precision, include_owner=False, relative=relative)
    return Hint(HintType.LOCATION, precision_pair, target)
Beispiel #4
0
def _elevator_area_name(world_list: WorldList,
                        area_location: AreaLocation,
                        include_world_name: bool,
                        ) -> str:
    if area_location.area_asset_id in _CUSTOM_NAMES_FOR_ELEVATORS:
        return _CUSTOM_NAMES_FOR_ELEVATORS[area_location.area_asset_id]

    else:
        world = world_list.world_by_area_location(area_location)
        area = world.area_by_asset_id(area_location.area_asset_id)
        if include_world_name:
            return world_list.area_name(area, distinguish_dark_aether=True, separator=" - ")
        else:
            return area.name
Beispiel #5
0
def _node_mapping_to_elevator_connection(world_list: WorldList,
                                         elevators: Dict[str, str],
                                         ) -> Dict[int, TeleporterConnection]:
    result = {}
    for source_name, target_node in elevators.items():
        source_node: TeleporterNode = world_list.node_from_name(source_name)
        target_node = world_list.node_from_name(target_node)

        result[source_node.teleporter_instance_id] = TeleporterConnection(
            world_list.nodes_to_world(target_node).world_asset_id,
            world_list.nodes_to_area(target_node).area_asset_id
        )

    return result
Beispiel #6
0
def elevator_area_name(
    world_list: WorldList,
    area_location: AreaLocation,
    include_world_name: bool,
) -> str:
    if area_location.area_asset_id in _CUSTOM_NAMES_FOR_ELEVATORS:
        return _CUSTOM_NAMES_FOR_ELEVATORS[area_location.area_asset_id]

    else:
        world = world_list.world_by_area_location(area_location)
        area = world.area_by_asset_id(area_location.area_asset_id)
        if include_world_name:
            return world_list.area_name(area)
        else:
            return area.name
Beispiel #7
0
def _pickup_assignment_to_item_locations(world_list: WorldList,
                                         pickup_assignment: PickupAssignment,
                                         num_players: int,
                                         ) -> Dict[str, Dict[str, str]]:
    items_locations: DefaultDict[str, Dict[str, str]] = collections.defaultdict(dict)

    for world, area, node in world_list.all_worlds_areas_nodes:
        if not node.is_resource_node or not isinstance(node, PickupNode):
            continue

        if node.pickup_index in pickup_assignment:
            target = pickup_assignment[node.pickup_index]
            if num_players > 1:
                item_name = f"{target.pickup.name} for Player {target.player}"
            else:
                item_name = f"{target.pickup.name}"
        else:
            item_name = _ETM_NAME

        world_name = world.dark_name if area.in_dark_aether else world.name
        items_locations[world_name][world_list.node_name(node)] = item_name

    return {
        world: {
            area: item
            for area, item in sorted(items_locations[world].items())
        }
        for world in sorted(items_locations.keys())
    }
Beispiel #8
0
def _pickup_assignment_to_item_locations(
    world_list: WorldList,
    pickup_assignment: PickupAssignment,
    ordered_pickups: List[PickupEntry],
) -> Dict[str, Dict[str, str]]:
    items_locations = {}

    for world in sorted(world_list.worlds, key=lambda w: w.name):
        items_in_world = {}
        items_locations[world.name] = items_in_world

        for node in sorted(world.all_nodes, key=lambda w: w.name):
            if not node.is_resource_node or not isinstance(node, PickupNode):
                continue

            if node.pickup_index in pickup_assignment:
                pickup = pickup_assignment[node.pickup_index]
                ordered_pickups.append(pickup)
                item_name = pickup.name
            else:
                item_name = "Nothing"

            items_in_world[world_list.node_name(node)] = item_name

    return items_locations
Beispiel #9
0
def _area_name_to_area_location(world_list: WorldList,
                                area_name: str) -> AreaLocation:
    world_name, area_name = re.match("([^/]+)/([^/]+)", area_name).group(1, 2)
    starting_world = world_list.world_with_name(world_name)
    starting_area = starting_world.area_by_name(area_name)
    return AreaLocation(starting_world.world_asset_id,
                        starting_area.area_asset_id)
Beispiel #10
0
def distances_to_node(
    world_list: WorldList,
    starting_node: Node,
    *,
    ignore_elevators: bool = True,
    cutoff: Optional[int] = None,
    patches: Optional[GamePatches] = None,
) -> Dict[Area, int]:
    """
    Compute the shortest distance from a node to all reachable areas.
    :param world_list:
    :param starting_node:
    :param ignore_elevators:
    :param cutoff: Exclude areas with a length longer that cutoff.
    :param patches:
    :return: Dict keyed by area to shortest distance to starting_node.
    """
    g = networkx.DiGraph()

    dock_connections = patches.dock_connection if patches is not None else {}
    elevator_connections = patches.elevator_connection if patches is not None else {}

    for area in world_list.all_areas:
        g.add_node(area)

    for world in world_list.worlds:
        for area in world.areas:
            new_areas = set()
            for node in area.nodes:
                if isinstance(node, DockNode):
                    connection = dock_connections.get(
                        (area.area_asset_id, node.dock_index),
                        node.default_connection)
                    new_areas.add(
                        world.area_by_asset_id(connection.area_asset_id))
                elif isinstance(node, TeleporterNode) and not ignore_elevators:
                    connection = elevator_connections.get(
                        node.teleporter_instance_id, node.default_connection)
                    new_areas.add(world_list.area_by_area_location(connection))

            for next_area in new_areas:
                g.add_edge(area, next_area)

    return networkx.single_source_shortest_path_length(
        g, world_list.nodes_to_area(starting_node), cutoff)
Beispiel #11
0
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}."
Beispiel #12
0
def test_basic_search_with_translator_gate(has_translator: bool,
                                           echoes_resource_database):
    # Setup
    scan_visor = echoes_resource_database.get_item(10)

    node_a = GenericNode("Node A", True, None, 0)
    node_b = GenericNode("Node B", True, None, 1)
    node_c = GenericNode("Node C", True, None, 2)
    translator_node = TranslatorGateNode("Translator Gate", True, None, 3,
                                         TranslatorGate(1), scan_visor)

    world_list = WorldList([
        World("Test World", "Test Dark World", 1, [
            Area(
                "Test Area A", False, 10, 0, True,
                [node_a, node_b, node_c, translator_node], {
                    node_a: {
                        node_b: Requirement.trivial(),
                        translator_node: Requirement.trivial(),
                    },
                    node_b: {
                        node_a: Requirement.trivial(),
                    },
                    node_c: {
                        translator_node: Requirement.trivial(),
                    },
                    translator_node: {
                        node_a: Requirement.trivial(),
                        node_c: Requirement.trivial(),
                    },
                })
        ])
    ])
    game_specific = EchoesGameSpecific(energy_per_tank=100,
                                       safe_zone_heal_per_second=1,
                                       beam_configurations=(),
                                       dangerous_energy_tank=False)
    game = GameDescription(RandovaniaGame.PRIME2,
                           DockWeaknessDatabase([], [], [], []),
                           echoes_resource_database, game_specific,
                           Requirement.impossible(), None, {}, world_list)

    patches = game.create_game_patches()
    patches = patches.assign_gate_assignment({TranslatorGate(1): scan_visor})
    initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99,
                          node_a, patches, None, echoes_resource_database,
                          game.world_list)

    # Run
    reach = reach_with_all_safe_resources(game, initial_state)

    # Assert
    if has_translator:
        assert set(
            reach.safe_nodes) == {node_a, node_b, translator_node, node_c}
    else:
        assert set(reach.safe_nodes) == {node_a, node_b}
Beispiel #13
0
def _create_elevators_field(world_list: WorldList,
                            patches: GamePatches) -> list:
    nodes_by_teleporter_id = {
        node.teleporter_instance_id: node
        for node in world_list.all_nodes if isinstance(node, TeleporterNode)
    }

    elevators = [{
        "origin_location":
        world_list.node_to_area_location(
            nodes_by_teleporter_id[instance_id]).as_json,
        "target_location":
        connection.as_json,
        "room_name":
        "Transport to {}".format(
            world_list.world_by_area_location(connection).name)
    } for instance_id, connection in patches.elevator_connection.items()]

    return elevators
def test_basic_search_with_translator_gate(has_translator: bool,
                                           echoes_resource_database):
    # Setup
    scan_visor = echoes_resource_database.get_by_type_and_index(
        ResourceType.ITEM, 10)

    node_a = GenericNode("Node A", True, 0)
    node_b = GenericNode("Node B", True, 1)
    node_c = GenericNode("Node C", True, 2)
    translator_node = TranslatorGateNode("Translator Gate", True, 3,
                                         TranslatorGate(1), scan_visor)

    world_list = WorldList([
        World("Test World", 1, [
            Area(
                "Test Area A", False, 10, 0,
                [node_a, node_b, node_c, translator_node], {
                    node_a: {
                        node_b: RequirementSet.trivial(),
                        translator_node: RequirementSet.trivial(),
                    },
                    node_b: {
                        node_a: RequirementSet.trivial(),
                    },
                    node_c: {
                        translator_node: RequirementSet.trivial(),
                    },
                    translator_node: {
                        node_a: RequirementSet.trivial(),
                        node_c: RequirementSet.trivial(),
                    },
                })
        ])
    ])
    game = GameDescription(0, "",
                           DockWeaknessDatabase([], [], [],
                                                []), echoes_resource_database,
                           RequirementSet.impossible(), None, {}, world_list)

    patches = GamePatches.with_game(game)
    patches = patches.assign_gate_assignment({TranslatorGate(1): scan_visor})
    initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99,
                          node_a, patches, None, echoes_resource_database)

    # Run
    reach = reach_with_all_safe_resources(game, initial_state)

    # Assert
    if has_translator:
        assert set(
            reach.safe_nodes) == {node_a, node_b, translator_node, node_c}
    else:
        assert set(reach.safe_nodes) == {node_a, node_b}
def _create_world_list(asset_id: int, pickup_index: PickupIndex):
    logbook_node = LogbookNode("Logbook A", True, 0, asset_id, None, None,
                               None, None)
    pickup_node = PickupNode("Pickup Node", True, 1, pickup_index, True)

    world_list = WorldList([
        World("World", 5000, [
            Area("Area", False, 10000, 0, [logbook_node, pickup_node], {}),
        ]),
    ])

    return logbook_node, pickup_node, world_list
Beispiel #16
0
def _pretty_name_for_elevator(world_list: WorldList,
                              original_teleporter_node: TeleporterNode,
                              connection: AreaLocation,
                              ) -> str:
    """
    Calculates the name the room that contains this elevator should have
    :param world_list:
    :param original_teleporter_node:
    :param connection:
    :return:
    """
    if original_teleporter_node.keep_name_when_vanilla:
        if original_teleporter_node.default_connection == connection:
            return world_list.nodes_to_area(original_teleporter_node).name

    return "Transport to {}".format(_elevator_area_name(world_list, connection, False))
Beispiel #17
0
def _create_world_list(asset_id: int, pickup_index: PickupIndex):
    logbook_node = LogbookNode("Logbook A", True, None, 0, asset_id, None,
                               None, None, None)
    pickup_node = PickupNode("Pickup Node", True, None, 1, pickup_index, True)

    world_list = WorldList([
        World("World", "Dark World", 5000, [
            Area("Area", False, 10000, 0, True, [logbook_node, pickup_node],
                 {}),
            Area("Other Area", False, 20000, 0, True, [
                PickupNode(f"Pickup {i}", True, None, 1, PickupIndex(i), True)
                for i in range(pickup_index.index)
            ], {}),
        ]),
    ])

    return logbook_node, pickup_node, world_list
Beispiel #18
0
def _pickup_assignment_to_item_locations(world_list: WorldList,
                                         pickup_assignment: PickupAssignment,
                                         ) -> Dict[str, Dict[str, str]]:
    items_locations = {}

    for world in world_list.worlds:
        items_in_world = {}
        items_locations[world.name] = items_in_world

        for area in world.areas:
            for node in area.nodes:
                if isinstance(node, PickupNode):
                    if node.pickup_index in pickup_assignment:
                        item_name = pickup_assignment[node.pickup_index].name
                    else:
                        item_name = "Nothing"
                    items_in_world[world_list.node_name(node)] = item_name

    return items_locations
Beispiel #19
0
 def read_world_list(self, data: List[Dict]) -> WorldList:
     return WorldList(read_array(data, self.read_world))
def _find_area_with_teleporter(world_list: WorldList,
                               teleporter: Teleporter) -> Area:
    return world_list.area_by_area_location(teleporter.area_location)
Beispiel #21
0
def _name_for_location(world_list: WorldList, location: AreaLocation) -> str:
    if location in prime1_elevators.CUSTOM_NAMES:
        return prime1_elevators.CUSTOM_NAMES[location]
    else:
        return world_list.area_name(world_list.area_by_area_location(location),
                                    separator=":")