Пример #1
0
def _area_name(world_list: WorldList, pickup_node: PickupNode,
               hide_world: bool) -> str:
    area = world_list.nodes_to_area(pickup_node)
    if hide_world:
        return area.name
    else:
        return world_list.area_name(area)
Пример #2
0
def _name_for_location(world_list: WorldList, location: AreaIdentifier) -> str:
    loc = location.as_tuple
    if loc in prime1_elevators.RANDOM_PRIME_CUSTOM_NAMES and loc != (
            "Frigate Orpheon", "Exterior Docking Hangar"):
        return prime1_elevators.RANDOM_PRIME_CUSTOM_NAMES[loc]
    else:
        return world_list.area_name(world_list.area_by_area_location(location),
                                    separator=":")
Пример #3
0
def test_connections_from_dock_blast_shield(empty_patches):
    # Setup
    trivial = Requirement.trivial()
    req_1 = ResourceRequirement.simple(
        SimpleResourceInfo(0, "Ev1", "Ev1", ResourceType.EVENT))
    req_2 = ResourceRequirement.simple(
        SimpleResourceInfo(1, "Ev2", "Ev2", ResourceType.EVENT))
    dock_type = DockType("Type", "Type", frozendict())
    weak_1 = DockWeakness(0, "Weak 1", frozendict(), req_1, None)
    weak_2 = DockWeakness(1, "Weak 2", frozendict(), trivial,
                          DockLock(DockLockType.FRONT_BLAST_BACK_BLAST, req_2))

    node_1_identifier = NodeIdentifier.create("W", "Area 1", "Node 1")
    node_2_identifier = NodeIdentifier.create("W", "Area 2", "Node 2")

    node_1 = DockNode(node_1_identifier, 0, False, None, "", ("default", ), {},
                      dock_type, node_2_identifier, weak_1, None, None)
    node_1_lock = DockLockNode.create_from_dock(node_1, 1)
    node_2 = DockNode(node_2_identifier, 2, False, None, "", ("default", ), {},
                      dock_type, node_1_identifier, weak_2, None, None)
    node_2_lock = DockLockNode.create_from_dock(node_2, 3)

    area_1 = Area("Area 1", None, True, [node_1, node_1_lock], {}, {})
    area_2 = Area("Area 2", None, True, [node_2, node_2_lock], {}, {})

    world = World("W", [area_1, area_2], {})
    world_list = WorldList([world])
    world_list.ensure_has_node_cache()

    game_mock = MagicMock()
    game_mock.world_list = world_list
    patches = dataclasses.replace(empty_patches, game=game_mock)

    context = NodeContext(
        patches=patches,
        current_resources=ResourceCollection(),
        database=patches.game.resource_database,
        node_provider=world_list,
    )

    # Run
    result_1 = list(node_1.connections_from(context))
    result_2 = list(node_2.connections_from(context))

    # Assert
    simple = ResourceRequirement.simple

    assert result_1 == [
        (node_2,
         RequirementAnd(
             [req_1,
              simple(NodeResourceInfo.from_node(node_2, context))])),
        (node_1_lock, RequirementAnd([trivial, req_2])),
    ]
    assert result_2 == [
        (node_1, simple(NodeResourceInfo.from_node(node_2, context))),
        (node_2_lock, req_2),
    ]
Пример #4
0
    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])
Пример #5
0
    def add_relative_hint(self, 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 or (not cat.is_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].name)), rng)

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

        if relative_type == HintLocationPrecision.RELATIVE_TO_AREA:
            relative = RelativeDataArea(distance_offset, world_list.identifier_for_area(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)
Пример #6
0
def _calculate_dangerous_resources_in_db(
    wl: WorldList,
    db: DockWeaknessDatabase,
    database: ResourceDatabase,
) -> Iterator[ResourceInfo]:
    for dock_type in db.dock_types:
        for dock_weakness in db.weaknesses[dock_type].values():
            yield from wl.open_requirement_for(dock_weakness).as_set(
                database).dangerous_resources
            if dock_weakness.lock is not None:
                yield from wl.lock_requirement_for(dock_weakness).as_set(
                    database).dangerous_resources
Пример #7
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.
    """
    import networkx
    g = networkx.DiGraph()

    if patches is None:
        def get_elevator_connection_for(n: TeleporterNode):
            return n.default_connection

        def get_dock_connection_for(n: DockNode):
            return n.default_connection
    else:
        get_elevator_connection_for = patches.get_elevator_connection_for

        def get_dock_connection_for(n: DockNode):
            return patches.get_dock_connection_for(n).identifier

    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:
                connection = None
                if isinstance(node, DockNode):
                    connection = get_dock_connection_for(node).area_identifier

                elif isinstance(node, TeleporterNode) and not ignore_elevators:
                    connection = get_elevator_connection_for(node)

                if connection is not None:
                    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)
Пример #8
0
def write_world_list(world_list: WorldList) -> list:
    errors = []
    known_indices = {}

    worlds = []
    for world in world_list.worlds:
        try:
            worlds.append(write_world(world))

            for node in world.all_nodes:
                if isinstance(node, PickupNode):
                    name = world_list.node_name(node, with_world=True, distinguish_dark_aether=True)
                    if node.pickup_index in known_indices:
                        errors.append(f"{name} has {node.pickup_index}, "
                                      f"but it was already used in {known_indices[node.pickup_index]}")
                    else:
                        known_indices[node.pickup_index] = name

        except ValueError as e:
            errors.append(str(e))

    if errors:
        raise ValueError("\n\n".join(errors))

    return worlds
Пример #9
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 + 1}"
            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())
    }
Пример #10
0
def test_calculate_dangerous_resources(danger_a, danger_b, expected_result):
    set_a: Requirement = MagicMock()
    set_b: Requirement = MagicMock()

    set_a.as_set.return_value.dangerous_resources = danger_a
    set_b.as_set.return_value.dangerous_resources = danger_b

    n1 = MagicMock()
    n1.node_index = 0
    n2 = MagicMock()
    n2.node_index = 1
    n3 = MagicMock()
    n3.node_index = 2
    n4 = MagicMock()
    n4.node_index = 3

    area_a = Area("area_a", 0, True, [n1, n2], {n1: {n2: set_a}, n2: {}}, {})
    area_b = Area("area_b", 0, True, [n3, n4], {n3: {}, n4: {n3: set_b}}, {})
    world = World("W", [area_a, area_b], {})
    wl = WorldList([world])

    # Run
    result = game_description._calculate_dangerous_resources_in_areas(wl, None)

    # Assert
    assert set(result) == set(expected_result)
Пример #11
0
def _get_nodes_by_teleporter_id(
        world_list: WorldList) -> Dict[NodeIdentifier, TeleporterNode]:
    return {
        world_list.identifier_for_node(node): node
        for node in world_list.all_nodes
        if isinstance(node, TeleporterNode) and node.editable
    }
Пример #12
0
def _area_name_to_area_location(world_list: WorldList,
                                area_name: str) -> AreaIdentifier:
    world_name, area_name = re.match("([^/]+)/([^/]+)", area_name).group(1, 2)

    # Filter out dark world names
    world_name = world_list.world_with_name(world_name).name

    return AreaIdentifier(world_name, area_name)
Пример #13
0
def _calculate_dangerous_resources_in_areas(
    wl: WorldList,
    database: ResourceDatabase,
) -> Iterator[ResourceInfo]:
    for area in wl.all_areas:
        for node in area.nodes:
            for _, requirement in wl.area_connections_from(node):
                yield from requirement.as_set(database).dangerous_resources
Пример #14
0
def _area_identifier_to_json(world_list: WorldList,
                             identifier: AreaIdentifier) -> dict:
    world = world_list.world_by_area_location(identifier)
    area = world.area_by_identifier(identifier)

    return {
        "world_asset_id": world.extra['asset_id'],
        "area_asset_id": area.extra['asset_id'],
    }
Пример #15
0
def _get_elevator_or_area_name(custom_names_to_use: dict[RandovaniaGame,
                                                         dict[tuple[str, str],
                                                              str]],
                               game: RandovaniaGame, world_list: WorldList,
                               area_location: AreaIdentifier,
                               include_world_name: bool) -> str:
    custom_names_by_game = custom_names_to_use.get(game, {})

    area_loc = area_location.as_tuple
    if area_loc in custom_names_by_game:
        return custom_names_by_game[area_loc]

    else:
        area = world_list.area_by_area_location(area_location)

        if include_world_name:
            return world_list.area_name(area)
        else:
            return area.name
Пример #16
0
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
Пример #17
0
def test_basic_search_with_translator_gate(has_translator: bool, echoes_resource_database, echoes_game_patches):
    # Setup
    scan_visor = echoes_resource_database.get_item("DarkVisor")
    nc = functools.partial(NodeIdentifier.create, "Test World", "Test Area A")

    node_a = GenericNode(nc("Node A"), True, None, "", ("default",), {})
    node_b = GenericNode(nc("Node B"), True, None, "", ("default",), {})
    node_c = GenericNode(nc("Node C"), True, None, "", ("default",), {})
    translator_node = ConfigurableNode(translator_identif := nc("Translator Gate"),
                                       True, None, "", ("default",), {})

    world_list = WorldList([
        World("Test World", [
            Area("Test Area A", None, 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 = GameDescription(RandovaniaGame.METROID_PRIME_ECHOES, DockWeaknessDatabase([], {}, (None, None)),
                           echoes_resource_database, ("default",), Requirement.impossible(),
                           None, {}, None, world_list)

    patches = echoes_game_patches.assign_node_configuration({
        translator_identif: ResourceRequirement(scan_visor, 1, False)
    })
    initial_state = State({scan_visor: 1 if has_translator else 0}, (), 99,
                          node_a, patches, None, StateGameData(echoes_resource_database, game.world_list, 100, 99))

    # Run
    reach = reach_lib.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}
Пример #18
0
def pretty_print_node_type(node: Node, world_list: WorldList):
    if isinstance(node, DockNode):
        try:
            other = world_list.node_by_identifier(node.default_connection)
            other_name = world_list.node_name(other)
        except IndexError as e:
            other_name = (f"(Area {node.default_connection.area_name}, "
                          f"index {node.default_connection.node_name}) [{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, ConfigurableNode):
        return f"Configurable Node"

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

    elif isinstance(node, PlayerShipNode):
        unlocked_pretty = list(pretty_print_requirement(node.is_unlocked))
        if len(unlocked_pretty) > 1:
            unlocked_by = "Complex requirement"
        else:
            unlocked_by = unlocked_pretty[0][1]
        return f"Player Ship (Unlocked by {unlocked_by})"

    return ""
Пример #19
0
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)
    ]
Пример #20
0
def test_connections_from_dock_blast_shield(empty_patches):
    # Setup
    trivial = Requirement.trivial()
    req_1 = ResourceRequirement(
        SimpleResourceInfo("Ev1", "Ev1", ResourceType.EVENT), 1, False)
    req_2 = ResourceRequirement(
        SimpleResourceInfo("Ev2", "Ev2", ResourceType.EVENT), 1, False)
    dock_type = DockType("Type", "Type", frozendict())
    weak_1 = DockWeakness("Weak 1", frozendict(), req_1, None)
    weak_2 = DockWeakness("Weak 2", frozendict(), trivial,
                          DockLock(DockLockType.FRONT_BLAST_BACK_BLAST, req_2))

    node_1_identifier = NodeIdentifier.create("W", "Area 1", "Node 1")
    node_2_identifier = NodeIdentifier.create("W", "Area 2", "Node 2")

    node_1 = DockNode(node_1_identifier, False, None, "", ("default", ), {},
                      dock_type, node_2_identifier, weak_1, None, None)
    node_1_lock = DockLockNode.create_from_dock(node_1)
    node_2 = DockNode(node_2_identifier, False, None, "", ("default", ), {},
                      dock_type, node_1_identifier, weak_2, None, None)
    node_2_lock = DockLockNode.create_from_dock(node_2)

    area_1 = Area("Area 1", None, True, [node_1, node_1_lock], {}, {})
    area_2 = Area("Area 2", None, True, [node_2, node_2_lock], {}, {})

    world = World("W", [area_1, area_2], {})
    world_list = WorldList([world])

    context = NodeContext(
        patches=empty_patches,
        current_resources={},
        database=None,
        node_provider=world_list,
    )

    # Run
    result_1 = list(node_1.connections_from(context))
    result_2 = list(node_2.connections_from(context))

    # Assert
    assert result_1 == [
        (node_2,
         RequirementAnd([req_1,
                         ResourceRequirement.simple(node_2_identifier)])),
        (node_1_lock, RequirementAnd([trivial, req_2])),
    ]
    assert result_2 == [
        (node_1, ResourceRequirement.simple(node_2_identifier)),
        (node_2_lock, req_2),
    ]
Пример #21
0
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)
    ]
Пример #22
0
 def get_mutable(self) -> "GameDescription":
     if self.mutable:
         return self
     else:
         result = GameDescription(
             game=self.game,
             resource_database=self.resource_database,
             layers=self.layers,
             dock_weakness_database=self.dock_weakness_database,
             world_list=WorldList(
                 [world.duplicate() for world in self.world_list.worlds]),
             victory_condition=self.victory_condition,
             starting_location=self.starting_location,
             initial_states=copy.copy(self.initial_states),
             minimal_logic=self.minimal_logic,
         )
         result.mutable = True
         return result
Пример #23
0
def create_elevator_database(world_list: WorldList,
                             all_teleporters: List[NodeIdentifier],
                             ) -> Tuple[ElevatorHelper, ...]:
    """
    Creates a tuple of Elevator objects, exclude those that belongs to one of the areas provided.
    :param world_list:
    :param all_teleporters: Set of teleporters to use
    :return:
    """
    all_helpers = [
        ElevatorHelper(world_list.identifier_for_node(node), node.default_connection)

        for world, area, node in world_list.all_worlds_areas_nodes
        if isinstance(node, TeleporterNode)
    ]
    return tuple(
        helper
        for helper in all_helpers
        if helper.teleporter in all_teleporters
    )
Пример #24
0
def _pretty_name_for_elevator(
    game: RandovaniaGame,
    world_list: WorldList,
    original_teleporter_node: TeleporterNode,
    connection: AreaIdentifier,
) -> 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(
        elevators.get_elevator_or_area_name(game, world_list, connection,
                                            False))
Пример #25
0
def _create_world_list(asset_id: int, pickup_index: PickupIndex):
    nc = NodeIdentifier.create

    logbook_node = LogbookNode(nc("World", "Area",
                                  "Logbook A"), True, None, "", ("default", ),
                               {}, asset_id, None, None, None, None)
    pickup_node = PickupNode(nc("World", "Area", "Pickup Node"), True, None,
                             "", ("default", ), {}, pickup_index, True)

    world_list = WorldList([
        World("World", [
            Area("Area", 0, True, [logbook_node, pickup_node], {}, {}),
            Area("Other Area", 0, True, [
                PickupNode(nc("World", "Other Area", f"Pickup {i}"), True,
                           None, "", ("default", ), {}, PickupIndex(i), True)
                for i in range(pickup_index.index)
            ], {}, {}),
        ], {}),
    ])

    return logbook_node, pickup_node, world_list
Пример #26
0
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
Пример #27
0
 def read_world_list(self, data: List[Dict]) -> WorldList:
     return WorldList(read_array(data, self.read_world))
Пример #28
0
    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.all_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()

        # Get interesting items to place hints for
        possible_indices = set(patches.pickup_assignment.keys())
        possible_indices -= {
            hint.target
            for hint in patches.hints.values() if hint.target is not None
        }
        possible_indices -= {
            index
            for index in possible_indices
            if not self.interesting_pickup_to_hint(
                patches.pickup_assignment[index].pickup)
        }

        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.")

        # But if we don't have enough hints, just pick randomly from everything
        if len(possible_indices) < len(potential_hint_locations):
            possible_indices = {
                node.pickup_index
                for node in world_list.all_nodes
                if isinstance(node, PickupNode)
            }

        # Get an stable order
        ordered_possible_indices = list(sorted(possible_indices))
        ordered_potential_hint_locations = list(
            sorted(potential_hint_locations))

        num_logbooks: dict[PickupIndex, int] = {
            index: sum(1 for indices in scan_asset_initial_pickups.values()
                       if index in indices)
            for index in ordered_possible_indices
        }
        max_seen = max(num_logbooks.values()) if num_logbooks else 0
        pickup_indices_weight: dict[PickupIndex, int] = {
            index: max_seen - num_logbook
            for index, num_logbook in num_logbooks.items()
        }
        # Ensure all indices are present with at least weight 0
        for index in ordered_possible_indices:
            if index not in pickup_indices_weight:
                pickup_indices_weight[index] = 0

        for logbook in sorted(ordered_potential_hint_locations,
                              key=lambda r: len(scan_asset_initial_pickups[r]),
                              reverse=True):
            try:
                new_index = random_lib.select_element_with_weight(
                    pickup_indices_weight, rng)
            except StopIteration:
                # If everything has weight 0, then just choose randomly.
                new_index = random_lib.random_key(pickup_indices_weight, rng)

            del pickup_indices_weight[new_index]

            new_hints[logbook] = Hint(HintType.LOCATION, None, new_index)
            debug.debug_print(
                f"Added hint at {logbook} for item at {new_index}")

        return dataclasses.replace(patches, hints=new_hints)
Пример #29
0
def _find_area_with_teleporter(world_list: WorldList,
                               teleporter: NodeIdentifier) -> Area:
    return world_list.area_by_area_location(teleporter.area_location)
Пример #30
0
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.")