Ejemplo n.º 1
0
def test_collected_pickup_indices(state_game_data, empty_patches):
    # Setup
    db = state_game_data.resource_database
    starting = state_game_data.world_list.resolve_teleporter_connection(
        empty_patches.game.starting_location)
    pickup_nodes = [
        node for node in empty_patches.game.world_list.all_nodes
        if isinstance(node, PickupNode)
    ]

    context = NodeContext(
        empty_patches,
        ResourceCollection(),
        empty_patches.game.resource_database,
        empty_patches.game.world_list,
    )
    resources = ResourceCollection.from_dict(
        db, {
            db.item[0]: 5,
            pickup_nodes[0].resource(context): 1,
            pickup_nodes[1].resource(context): 1
        })
    s = state.State(resources, (), 99, starting, empty_patches, None,
                    state_game_data)

    # Run
    indices = list(s.collected_pickup_indices)

    # Assert
    assert indices == [
        pickup_nodes[0].pickup_index, pickup_nodes[1].pickup_index
    ]
Ejemplo n.º 2
0
def test_add_pickup_to_state(state_game_data, empty_patches,
                             generic_item_category):
    # Starting State
    db = state_game_data.resource_database
    starting_node = state_game_data.world_list.resolve_teleporter_connection(
        empty_patches.game.starting_location)
    s = state.State(ResourceCollection(), (), 99, starting_node, empty_patches,
                    None, state_game_data)

    resource_a = db.item[0]
    resource_b = db.item[1]
    p = PickupEntry("B",
                    2,
                    generic_item_category,
                    generic_item_category,
                    progression=(
                        (resource_a, 1),
                        (resource_b, 1),
                    ))

    # Run
    state.add_pickup_to_state(s, p)
    state.add_pickup_to_state(s, p)

    # Assert
    assert s.resources == ResourceCollection.from_dict(db, {
        resource_a: 1,
        resource_b: 1,
    })
Ejemplo n.º 3
0
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
            db = patches.game.resource_database
            resources = ResourceCollection.from_resource_gain(
                db, target.pickup.resource_gain(ResourceCollection()))
            for resource, quantity in resources.as_resource_gain():
                if quantity > 0 and resource in result:
                    result[resource].append(
                        (other_player,
                         PickupLocation(patches.configuration.game,
                                        pickup_index)))

    return result
Ejemplo n.º 4
0
def test_add_resources_into_another(blank_resource_db, a, b, result):
    a = wrap(blank_resource_db, a)
    b = wrap(blank_resource_db, b)
    result = wrap(blank_resource_db, result)

    ac = ResourceCollection.from_dict(blank_resource_db, a)
    bc = ResourceCollection.from_dict(blank_resource_db, b)

    ac.add_resource_gain(bc.as_resource_gain())

    assert dict(ac.as_resource_gain()) == result
Ejemplo n.º 5
0
def calculate_pool_results(layout_configuration: BaseConfiguration,
                           resource_database: ResourceDatabase,
                           base_patches: GamePatches = None,
                           rng: Random = None,
                           rng_required: bool = False
                           ) -> PoolResults:
    """
    Creates a PoolResults with all starting items and pickups in fixed locations, as well as a list of
    pickups we should shuffle.
    :param layout_configuration:
    :param resource_database:
    :return:
    """
    base_results = PoolResults([], {}, ResourceCollection.with_database(resource_database))

    # Adding major items to the pool
    _extend_pool_results(base_results, add_major_items(resource_database,
                                                       layout_configuration.major_items_configuration,
                                                       layout_configuration.ammo_configuration))

    # Adding ammo to the pool
    base_results.pickups.extend(add_ammo(resource_database,
                                         layout_configuration.ammo_configuration))
    try:
        layout_configuration.game.generator.item_pool_creator(
            base_results, layout_configuration, resource_database, base_patches, rng,
        )
    except MissingRng as e:
        if rng_required:
            raise e

    return base_results
Ejemplo n.º 6
0
def add_artifacts(
    resource_database: ResourceDatabase,
    mode: LayoutArtifactMode,
    artifact_minimum_progression: int,
) -> PoolResults:
    """
    :param resource_database:
    :param mode
    :param artifact_minimum_progression
    :return:
    """
    item_pool: List[PickupEntry] = []
    initial_resources = ResourceCollection.with_database(resource_database)

    artifacts_to_place = mode.value

    for i in range(artifacts_to_place):
        item_pool.append(
            pickup_creator.create_artifact(i, artifact_minimum_progression,
                                           resource_database))

    first_automatic_artifact = artifacts_to_place

    for automatic_artifact in range(first_automatic_artifact, 12):
        initial_resources.add_resource_gain(
            pickup_creator.create_artifact(automatic_artifact,
                                           artifact_minimum_progression,
                                           resource_database).all_resources, )

    return PoolResults(item_pool, {}, initial_resources)
Ejemplo n.º 7
0
    def assign_pickup_to_starting_items(self, pickup: PickupEntry) -> "State":
        pickup_resources = ResourceCollection.from_resource_gain(
            self.resource_database,
            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.remove_resource(
                self.resource_database.item_percentage)

        new_resources = self.resources.duplicate()
        new_resources.add_resource_gain(pickup_resources.as_resource_gain())
        new_patches = self.patches.assign_extra_initial_items(
            pickup_resources.as_resource_gain())

        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,
        )
Ejemplo n.º 8
0
def test_assign_extra_initial_items_merge(empty_patches, initial, new_items, expected):
    db = empty_patches.game.resource_database
    initial = wrap(db, initial)
    new_items = wrap(db, new_items)
    expected = wrap(db, expected)

    # Setup
    initial_patches = dataclasses.replace(empty_patches, starting_items=ResourceCollection.from_dict(db, initial))

    # Run
    new_patches = initial_patches.assign_extra_initial_items(
        ResourceCollection.from_dict(db, new_items).as_resource_gain(),
    )

    # Assert
    assert new_patches.starting_items == ResourceCollection.from_dict(db, expected)
Ejemplo n.º 9
0
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 = ResourceCollection.from_resource_gain(game.resource_database, 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."
Ejemplo n.º 10
0
    def get_damage_reduction(self, resource: SimpleResourceInfo,
                             current_resources: ResourceCollection):
        cached_result = current_resources.get_damage_reduction_cache(resource)
        if cached_result is not None:
            return cached_result

        multiplier = self.base_damage_reduction(self, current_resources)

        for reduction in self.damage_reductions.get(resource, []):
            if reduction.inventory_item is None or current_resources[
                    reduction.inventory_item] > 0:
                multiplier *= reduction.damage_multiplier

        current_resources.add_damage_reduction_cache(resource, multiplier)

        return multiplier
Ejemplo n.º 11
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),
    ]
Ejemplo n.º 12
0
def test_logbook_node_requirements_to_leave(logbook_node,
                                            empty_patches):
    # Setup
    has_translator, translator, node = logbook_node
    db = empty_patches.game.resource_database

    def ctx(resources):
        return NodeContext(empty_patches, resources, db, empty_patches.game.world_list)

    # Run
    to_leave = node.requirement_to_leave(ctx({}))

    # Assert
    rc2 = ResourceCollection.from_resource_gain(db, [])
    rc3 = ResourceCollection.from_resource_gain(db, [(translator, 1)])

    assert to_leave.satisfied(rc2, 99, None) != has_translator
    assert to_leave.satisfied(rc3, 99, None)
Ejemplo n.º 13
0
def test_remove_resource_missing(echoes_resource_database):
    m = echoes_resource_database.get_item("Missile")
    beam = echoes_resource_database.get_item("Light")
    col = ResourceCollection.from_dict(echoes_resource_database, {
        beam: 1,
    })
    col.remove_resource(m)

    assert dict(col.as_resource_gain()) == {beam: 1}
Ejemplo n.º 14
0
def test_assign_extra_initial_items_to_empty(empty_patches, items):
    db = empty_patches.game.resource_database
    items = wrap(db, items)

    # Run
    new_patches = empty_patches.assign_extra_initial_items(items)

    # Assert
    assert new_patches.starting_items == ResourceCollection.from_resource_gain(db, items)
Ejemplo n.º 15
0
def find_invalid_strongly_connected_components(game: GameDescription) -> Iterator[str]:
    import networkx
    graph = networkx.DiGraph()

    for node in game.world_list.iterate_nodes():
        if isinstance(node, DockLockNode):
            continue
        graph.add_node(node)

    context = NodeContext(
        patches=GamePatches.create_from_game(game, 0, None),
        current_resources=ResourceCollection.with_database(game.resource_database),
        database=game.resource_database,
        node_provider=game.world_list,
    )

    for node in game.world_list.iterate_nodes():
        if node not in graph:
            continue

        for other, req in game.world_list.potential_nodes_from(node, context):
            if other not in graph:
                continue

            if req != Requirement.impossible():
                graph.add_edge(node, other)

    starting_node = game.world_list.resolve_teleporter_connection(game.starting_location)

    for strong_comp in networkx.strongly_connected_components(graph):
        components: set[Node] = strong_comp

        # The starting location determines the default component
        if starting_node in components:
            continue

        if any(node.extra.get("different_strongly_connected_component", False) for node in components):
            continue

        if len(components) == 1:
            node = next(iter(components))

            # If the component is a single node which is the default node of it's area, allow it
            area = game.world_list.nodes_to_area(node)
            if area.default_node == node.name:
                continue

            # We accept nodes that have no paths out or in.
            if not graph.in_edges(node) and not graph.edges(node):
                continue

        names = sorted(
            game.world_list.node_name(node, with_world=True)
            for node in strong_comp
        )
        yield "Unknown strongly connected component detected containing {} nodes:\n{}".format(len(names), names)
Ejemplo n.º 16
0
 def _calculate_starting_inventory(self, resources: ResourceCollection):
     result = {}
     for resource, quantity in resources.as_resource_gain():
         try:
             result[_get_item_id_for_item(resource)] = quantity
         except KeyError:
             print(
                 f"Skipping {resource} for starting inventory: no item id")
             continue
     return result
Ejemplo n.º 17
0
def test_requirement_damage(damage, items, requirement,
                            echoes_resource_database):
    req = data_reader.read_requirement(requirement, echoes_resource_database)

    collection = ResourceCollection.from_dict(
        echoes_resource_database,
        {echoes_resource_database.get_item(item): 1
         for item in items})

    assert req.damage(collection, echoes_resource_database) == damage
def test_create_starting_popup_empty(default_echoes_configuration,
                                     echoes_resource_database):
    starting_items = ResourceCollection.with_database(echoes_resource_database)

    # Run
    result = patch_data_factory._create_starting_popup(
        default_echoes_configuration, echoes_resource_database, starting_items)

    # Assert
    assert result == []
Ejemplo n.º 19
0
 def patch_requirements(self, static_resources: ResourceCollection,
                        damage_multiplier: float,
                        database: ResourceDatabase) -> Requirement:
     if static_resources.is_resource_set(self.resource):
         if self.satisfied(static_resources, 0, database):
             return Requirement.trivial()
         else:
             return Requirement.impossible()
     else:
         return self.multiply_amount(damage_multiplier)
Ejemplo n.º 20
0
def test_convert_resource_gain_to_current_resources(blank_resource_db, resource_gain, expected):
    # Setup
    db = blank_resource_db
    resource_gain = wrap(db, resource_gain)
    expected = wrap(db, expected)

    # Run
    result = ResourceCollection.from_resource_gain(db, resource_gain)

    # Assert
    assert dict(result.as_resource_gain()) == expected
Ejemplo n.º 21
0
def test_state_with_pickup(state_game_data, empty_patches,
                           generic_item_category):
    # Setup
    db = state_game_data.resource_database
    starting = state.State(ResourceCollection(), (), 99, None, empty_patches,
                           None, state_game_data)

    resource_a = db.item[0]
    p = PickupEntry("A",
                    2,
                    generic_item_category,
                    generic_item_category,
                    progression=((resource_a, 1), ))

    # Run
    final = state.state_with_pickup(starting, p)

    # Assert
    assert final.previous_state is starting
    assert final.resources == ResourceCollection.from_dict(db, {resource_a: 1})
Ejemplo n.º 22
0
def test_logbook_node_resource_gain_on_collect(logbook_node,
                                               empty_patches):
    # Setup
    db = empty_patches.game.resource_database
    node = logbook_node[-1]
    context = NodeContext(empty_patches, ResourceCollection(), db, MagicMock())

    # Run
    gain = node.resource_gain_on_collect(context)

    # Assert
    assert dict(gain) == {node.resource(context): 1}
Ejemplo n.º 23
0
def pickups_to_solve_list(pickup_pool: list[PickupEntry],
                          requirement_list: RequirementList, state: State):
    pickups = []

    db = state.resource_database
    resources = state.resources.duplicate()
    pickups_for_this = list(pickup_pool)

    # Check pickups that give less items in total first
    # This means we test for expansions before the major items, in case both give the same resource
    # Useful to get Dark Beam Ammo Expansion instead of Dark Beam.
    pickups_for_this.sort(key=lambda p: sum(
        1 for _ in p.resource_gain(resources, force_lock=True)))

    for individual in sorted(requirement_list.values()):
        if individual.satisfied(resources, state.energy,
                                state.resource_database):
            continue

        # Create another copy of the list so we can remove elements while iterating
        for pickup in list(pickups_for_this):
            new_resources = ResourceCollection.from_resource_gain(
                db, pickup.resource_gain(resources, force_lock=True))
            pickup_progression = ResourceCollection.from_resource_gain(
                db, pickup.progression)
            if new_resources[individual.resource] + pickup_progression[
                    individual.resource] > 0:
                pickups.append(pickup)
                pickups_for_this.remove(pickup)
                resources.add_resource_gain(new_resources.as_resource_gain())

            if individual.satisfied(resources, state.energy,
                                    state.resource_database):
                break

        if not individual.satisfied(resources, state.energy,
                                    state.resource_database):
            return None

    return pickups
Ejemplo n.º 24
0
    def _resources_to_give_for_pickup(
        self,
        pickup: PickupEntry,
        inventory: Inventory,
    ) -> tuple[str, ResourceCollection]:
        inventory_resources = ResourceCollection.with_database(
            self.game.resource_database)
        inventory_resources.add_resource_gain([
            (item, inv_item.capacity) for item, inv_item in inventory.items()
        ])
        conditional = pickup.conditional_for_resources(inventory_resources)
        if conditional.name is not None:
            item_name = conditional.name
        else:
            item_name = pickup.name

        resources_to_give = ResourceCollection.with_database(
            self.game.resource_database)

        if pickup.respects_lock and not pickup.unlocks_resource and (
                pickup.resource_lock is not None
                and inventory_resources[pickup.resource_lock.locked_by] == 0):
            pickup_resources = list(
                pickup.resource_lock.convert_gain(conditional.resources))
            item_name = f"Locked {item_name}"
        else:
            pickup_resources = conditional.resources

        inventory_resources.add_resource_gain(pickup_resources)
        resources_to_give.add_resource_gain(pickup_resources)
        resources_to_give.add_resource_gain(
            pickup.conversion_resource_gain(inventory_resources))

        # Ignore item% for received items
        if self.game.resource_database.item_percentage is not None:
            resources_to_give.remove_resource(
                self.game.resource_database.item_percentage)

        return item_name, resources_to_give
Ejemplo n.º 25
0
def test_sky_temple_key_distribution_logic_all_bosses_valid(
        echoes_resource_database):
    # Run
    results = sky_temple_keys.add_sky_temple_key_distribution_logic(
        echoes_resource_database, LayoutSkyTempleKeyMode.ALL_BOSSES)

    # Assert
    assert results.pickups == []
    assert results.initial_resources == ResourceCollection.from_dict(
        echoes_resource_database, {})
    assert list(
        results.assignment.keys()
    ) == sky_temple_keys._GUARDIAN_INDICES + sky_temple_keys._SUB_GUARDIAN_INDICES
Ejemplo n.º 26
0
def add_energy_cells(resource_database: ResourceDatabase, ) -> PoolResults:
    """
    :param resource_database:
    :return:
    """
    item_pool: List[PickupEntry] = []

    for i in range(9):
        item_pool.append(
            pickup_creator.create_energy_cell(i, resource_database))

    return PoolResults(item_pool, {},
                       ResourceCollection.with_database(resource_database))
Ejemplo n.º 27
0
def additional_starting_items(layout_configuration: BaseConfiguration,
                              resource_database: ResourceDatabase,
                              starting_items: ResourceCollection) -> List[str]:
    initial_items = calculate_pool_results(layout_configuration,
                                           resource_database).initial_resources

    return [
        add_quantity_to_resource(resource_user_friendly_name(item), quantity)
        for item, quantity in sorted(
            starting_items.as_resource_gain(),
            key=lambda a: resource_user_friendly_name(a[0]))
        if 0 < quantity != initial_items[item]
    ]
Ejemplo n.º 28
0
def test_sky_temple_key_distribution_logic_all_guardians_valid(
        echoes_resource_database):
    # Run
    results = sky_temple_keys.add_sky_temple_key_distribution_logic(
        echoes_resource_database, LayoutSkyTempleKeyMode.ALL_GUARDIANS)

    # Assert
    assert results.pickups == []
    assert results.initial_resources == ResourceCollection.from_dict(
        echoes_resource_database, {
            echoes_resource_database.get_item(f'TempleKey{i}'): 1
            for i in range(4, 10)
        })
    assert list(results.assignment.keys()) == sky_temple_keys._GUARDIAN_INDICES
Ejemplo n.º 29
0
    def _add_minimal_logic_initial_resources(
        self,
        resources: ResourceCollection,
        game: GameDescription,
        major_items: MajorItemsConfiguration,
    ) -> None:
        resource_database = game.resource_database

        if game.minimal_logic is None:
            raise ValueError(
                f"Minimal logic enabled, but {game.game} doesn't have support for it."
            )

        item_db = default_database.item_database_for_game(game.game)

        items_to_skip = set()
        for it in game.minimal_logic.items_to_exclude:
            if it.reason is None or major_items.items_state[
                    item_db.major_items[it.reason]].num_shuffled_pickups != 0:
                items_to_skip.add(it.name)

        custom_item_count = game.minimal_logic.custom_item_amount
        events_to_skip = {
            it.name
            for it in game.minimal_logic.events_to_exclude
        }

        resources.add_resource_gain([(event, 1)
                                     for event in resource_database.event
                                     if event.short_name not in events_to_skip
                                     ])

        resources.add_resource_gain([
            (item, custom_item_count.get(item.short_name, 1))
            for item in resource_database.item
            if item.short_name not in items_to_skip
        ])
Ejemplo n.º 30
0
def add_dark_temple_keys(resource_database: ResourceDatabase, ) -> PoolResults:
    """
    :param resource_database:
    :return:
    """
    item_pool: List[PickupEntry] = []

    for temple_index in range(3):
        for i in range(3):
            item_pool.append(
                pickup_creator.create_dark_temple_key(i, temple_index,
                                                      resource_database))

    return PoolResults(item_pool, {},
                       ResourceCollection.with_database(resource_database))