Beispiel #1
0
def test_gate_assignment_for_configuration_all_emerald(echoes_game_description, default_echoes_configuration):
    # Setup
    patches_factory = echoes_game_description.game.generator.base_patches_factory
    scan_visor = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Scan Visor")
    emerald = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Emerald Translator")

    translator_configuration = default_echoes_configuration.translator_configuration
    configuration = dataclasses.replace(
        default_echoes_configuration,
        translator_configuration=dataclasses.replace(
            translator_configuration,
            translator_requirement={
                key: LayoutTranslatorRequirement.EMERALD
                for key in translator_configuration.translator_requirement.keys()
            }
        )
    )

    rng = MagicMock()

    # Run
    results = patches_factory.configurable_node_assignment(
        configuration, echoes_game_description, rng)

    # Assert
    assert list(results.values()) == [
        RequirementAnd([
            ResourceRequirement(scan_visor, 1, False),
            ResourceRequirement(emerald, 1, False),
        ])
    ] * len(translator_configuration.translator_requirement)
def test_gate_assignment_for_configuration_all_random(
        echoes_resource_database):
    # Setup
    violet = find_resource_info_with_long_name(echoes_resource_database.item,
                                               "Violet Translator")
    emerald = find_resource_info_with_long_name(echoes_resource_database.item,
                                                "Emerald Translator")

    configuration = MagicMock()
    configuration.translator_configuration.translator_requirement = {
        TranslatorGate(index): LayoutTranslatorRequirement.RANDOM
        for index in [1, 15, 23]
    }

    rng = MagicMock()
    rng.choice.side_effect = [
        LayoutTranslatorRequirement.EMERALD,
        LayoutTranslatorRequirement.VIOLET,
        LayoutTranslatorRequirement.EMERALD,
    ]

    # Run
    results = base_patches_factory.gate_assignment_for_configuration(
        configuration, echoes_resource_database, rng)

    # Assert
    assert results == {
        TranslatorGate(1): emerald,
        TranslatorGate(15): violet,
        TranslatorGate(23): emerald,
    }
Beispiel #3
0
def test_gate_assignment_for_configuration_all_random(echoes_game_description, default_echoes_configuration):
    # Setup
    patches_factory = echoes_game_description.game.generator.base_patches_factory
    scan_visor = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Scan Visor")
    violet = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Violet Translator")
    emerald = find_resource_info_with_long_name(echoes_game_description.resource_database.item, "Emerald Translator")

    translator_configuration = default_echoes_configuration.translator_configuration
    configuration = dataclasses.replace(
        default_echoes_configuration,
        translator_configuration=translator_configuration.with_full_random(),
    )

    requirements = [
        RequirementAnd([
            ResourceRequirement(scan_visor, 1, False),
            ResourceRequirement(emerald, 1, False),
        ]),
        RequirementAnd([
            ResourceRequirement(scan_visor, 1, False),
            ResourceRequirement(violet, 1, False),
        ])
    ]
    requirements = requirements * len(translator_configuration.translator_requirement)

    choices = [LayoutTranslatorRequirement.EMERALD, LayoutTranslatorRequirement.VIOLET]
    rng = MagicMock()
    rng.choice.side_effect = choices * len(translator_configuration.translator_requirement)

    # Run
    results = patches_factory.configurable_node_assignment(
        configuration, echoes_game_description, rng)

    # Assert
    assert list(results.values()) == requirements[:len(translator_configuration.translator_requirement)]
Beispiel #4
0
 def _get_command_visor(self):
     try:
         return find_resource_info_with_long_name(
             self.game.resource_database.item,
             "Command Visor"
         )
     except MissingResource:
         return None
    def create_tracker(self, game_enum: RandovaniaGame):
        effective_theme = self.theme
        if not path_for(game_enum, effective_theme).is_file():
            effective_theme = TrackerTheme.CLASSIC

        if (game_enum, effective_theme) == self._last_tracker_config:
            return
        self.delete_tracker()

        resource_database = default_database.resource_database_for(game_enum)

        with path_for(game_enum,
                      effective_theme).open("r") as tracker_details_file:
            tracker_details = json.load(tracker_details_file)

        for element in tracker_details["elements"]:
            text_template = ""

            if "image_path" in element:
                image_path = get_data_path().joinpath(element["image_path"])
                pixmap = QPixmap(str(image_path))

                label = ClickableLabel(self.inventory_group,
                                       paint_with_opacity(pixmap, 0.3),
                                       paint_with_opacity(pixmap, 1.0))
                label.set_checked(False)
                label.set_ignore_mouse_events(True)

            elif "label" in element:
                label = QLabel(self.inventory_group)
                label.setAlignment(Qt.AlignCenter)
                text_template = element["label"]

            else:
                raise ValueError(f"Invalid element: {element}")

            resources = [
                find_resource_info_with_long_name(resource_database.item,
                                                  resource_name)
                for resource_name in element["resources"]
            ]
            self._tracker_elements.append(
                Element(label, resources, text_template))
            self.inventory_layout.addWidget(label, element["row"],
                                            element["column"])

        self.inventory_spacer = QSpacerItem(5, 5, QSizePolicy.Expanding,
                                            QSizePolicy.Expanding)
        self.inventory_layout.addItem(self.inventory_spacer,
                                      self.inventory_layout.rowCount(),
                                      self.inventory_layout.columnCount())
        self._update_tracker_from_hook({})

        self._last_tracker_config = (game_enum, effective_theme)
    def configurable_node_assignment(
            self, configuration: EchoesConfiguration, game: GameDescription,
            rng: Random) -> Iterator[NodeConfigurationAssociation]:
        """
        :param configuration:
        :param game:
        :param rng:
        :return:
        """

        all_choices = list(LayoutTranslatorRequirement)
        all_choices.remove(LayoutTranslatorRequirement.RANDOM)
        all_choices.remove(LayoutTranslatorRequirement.RANDOM_WITH_REMOVED)
        without_removed = copy.copy(all_choices)
        without_removed.remove(LayoutTranslatorRequirement.REMOVED)
        random_requirements = {
            LayoutTranslatorRequirement.RANDOM,
            LayoutTranslatorRequirement.RANDOM_WITH_REMOVED
        }

        result = []

        scan_visor = search.find_resource_info_with_long_name(
            game.resource_database.item, "Scan Visor")
        scan_visor_req = ResourceRequirement.simple(scan_visor)

        for node in game.world_list.iterate_nodes():
            if not isinstance(node, ConfigurableNode):
                continue

            identifier = game.world_list.identifier_for_node(node)
            requirement = configuration.translator_configuration.translator_requirement[
                identifier]
            if requirement in random_requirements:
                if rng is None:
                    raise MissingRng("Translator")
                requirement = rng.choice(
                    all_choices if requirement == LayoutTranslatorRequirement.
                    RANDOM_WITH_REMOVED else without_removed)

            translator = game.resource_database.get_by_type_and_index(
                ResourceType.ITEM, requirement.item_name)
            result.append((identifier,
                           RequirementAnd([
                               scan_visor_req,
                               ResourceRequirement.simple(translator),
                           ])))

        return result
Beispiel #7
0
def list_paths_with_resource_logic(args):
    gd = load_game_description(args)

    resource = None
    for resource_type in gd.resource_database:
        try:
            resource = find_resource_info_with_long_name(
                resource_type, args.resource)
            break
        except MissingResource:
            continue

    if resource is None:
        print(f"A resource named {args.resource} was not found.")
        raise SystemExit(1)

    _list_paths_with_resource(gd, args.print_only_area, resource, None)
def test_gate_assignment_for_configuration_all_emerald(
        echoes_resource_database):
    # Setup
    emerald = find_resource_info_with_long_name(echoes_resource_database.item,
                                                "Emerald Translator")
    indices = [1, 15, 23]

    configuration = MagicMock()
    configuration.translator_configuration.translator_requirement = {
        TranslatorGate(index): LayoutTranslatorRequirement.EMERALD
        for index in indices
    }

    rng = MagicMock()

    # Run
    results = base_patches_factory.gate_assignment_for_configuration(
        configuration, echoes_resource_database, rng)

    # Assert
    assert results == {TranslatorGate(index): emerald for index in indices}
def test_bit_pack_pickup_entry(has_convert: bool, echoes_resource_database):
    # Setup
    name = "Some Random Name"
    if has_convert:
        convert_resources = (ResourceConversion(
            find_resource_info_with_long_name(echoes_resource_database.item,
                                              "Morph Ball"),
            find_resource_info_with_long_name(echoes_resource_database.item,
                                              "Item Percentage")), )
    else:
        convert_resources = ()

    pickup = PickupEntry(
        name=name,
        model_index=26,
        item_category=ItemCategory.TEMPLE_KEY,
        broad_category=ItemCategory.KEY,
        resources=(ConditionalResources(
            "Morph Ball",
            None,
            (
                (find_resource_info_with_long_name(
                    echoes_resource_database.item, "Morph Ball"), 2),
                (find_resource_info_with_long_name(
                    echoes_resource_database.item, "Item Percentage"), 5),
            ),
        ),
                   ConditionalResources(
                       "Grapple Beam",
                       find_resource_info_with_long_name(
                           echoes_resource_database.item, "Morph Ball"),
                       ((find_resource_info_with_long_name(
                           echoes_resource_database.item,
                           "Grapple Beam"), 3), ),
                   )),
        convert_resources=convert_resources)

    # Run
    encoded = bitpacking.pack_value(
        BitPackPickupEntry(pickup, echoes_resource_database))
    decoder = BitPackDecoder(encoded)
    decoded = BitPackPickupEntry.bit_pack_unpack(decoder,
                                                 echoes_resource_database)

    # Assert
    assert pickup == decoded
Beispiel #10
0
def test_bit_pack_pickup_entry(has_convert: bool, echoes_resource_database,
                               generic_item_category):
    # Setup
    name = "Some Random Name"
    if has_convert:
        resource_lock = ResourceLock(
            find_resource_info_with_long_name(echoes_resource_database.item,
                                              "Morph Ball"),
            find_resource_info_with_long_name(echoes_resource_database.item,
                                              "Item Percentage"),
            find_resource_info_with_long_name(echoes_resource_database.item,
                                              "Space Jump Boots"),
        )
    else:
        resource_lock = None

    pickup = PickupEntry(
        name=name,
        model=PickupModel(
            game=RandovaniaGame.METROID_PRIME_CORRUPTION,
            name="HyperMissile",
        ),
        item_category=generic_item_category,
        broad_category=generic_item_category,
        progression=(
            (find_resource_info_with_long_name(echoes_resource_database.item,
                                               "Morph Ball"), 1),
            (find_resource_info_with_long_name(echoes_resource_database.item,
                                               "Grapple Beam"), 1),
        ),
        extra_resources=((find_resource_info_with_long_name(
            echoes_resource_database.item, "Item Percentage"), 5), ),
        resource_lock=resource_lock)

    # Run
    encoded = bitpacking.pack_value(
        BitPackPickupEntry(pickup, echoes_resource_database))
    decoder = BitPackDecoder(encoded)
    decoded = BitPackPickupEntry.bit_pack_unpack(decoder,
                                                 echoes_resource_database)

    # Assert
    assert pickup == decoded
Beispiel #11
0
 def item(name: str):
     return find_resource_info_with_long_name(game.resource_database.item,
                                              name)
Beispiel #12
0
 def _get_command_visor(self):
     return find_resource_info_with_long_name(
         self.game.resource_database.item, "Command Visor")
Beispiel #13
0
 def _get_scan_visor(self):
     return find_resource_info_with_long_name(
         self.game.resource_database.item, "Scan Visor")
Beispiel #14
0
def decode_single(player_index: int, all_pools: Dict[int, PoolResults],
                  game: GameDescription, game_modifications: dict,
                  configuration: BaseConfiguration) -> GamePatches:
    """
    Decodes a dict created by `serialize` back into a GamePatches.
    :param player_index:
    :param all_pools:
    :param game:
    :param game_modifications:
    :param configuration:
    :return:
    """
    world_list = game.world_list
    weakness_db = game.dock_weakness_database

    if game_modifications["game"] != game.game.value:
        raise ValueError(
            f"Expected '{game.game.value}', got '{game_modifications['game']}'"
        )

    initial_pickup_assignment = all_pools[player_index].assignment

    # Starting Location
    starting_location = AreaIdentifier.from_string(
        game_modifications["starting_location"])

    # Initial items
    starting_items = {
        find_resource_info_with_long_name(game.resource_database.item,
                                          resource_name): quantity
        for resource_name, quantity in
        game_modifications["starting_items"].items()
    }

    # Elevators
    elevator_connection: ElevatorConnection = {
        NodeIdentifier.from_string(source_name):
        AreaIdentifier.from_string(target_name)
        for source_name, target_name in
        game_modifications["teleporters"].items()
    }

    # Dock Weakness
    dock_weakness: dict[NodeIdentifier, DockWeakness] = {
        NodeIdentifier.from_string(source_name): weakness_db.get_by_weakness(
            weakness_data["type"],
            weakness_data["name"],
        )
        for source_name, weakness_data in
        game_modifications["dock_weakness"].items()
    }

    # Configurable Nodes
    configurable_nodes = {
        NodeIdentifier.from_string(identifier):
        data_reader.read_requirement(requirement, game.resource_database)
        for identifier, requirement in
        game_modifications["configurable_nodes"].items()
    }

    # Pickups
    target_name_re = re.compile(r"(.*) for Player (\d+)")

    pickup_assignment: PickupAssignment = {}
    for world_name, world_data in game_modifications["locations"].items():
        for area_node_name, target_name in typing.cast(dict[str, str],
                                                       world_data).items():
            if target_name == _ETM_NAME:
                continue

            pickup_name_match = target_name_re.match(target_name)
            if pickup_name_match is not None:
                pickup_name = pickup_name_match.group(1)
                target_player = int(pickup_name_match.group(2)) - 1
            else:
                pickup_name = target_name
                target_player = 0

            node_identifier = NodeIdentifier.create(
                world_name, *area_node_name.split("/", 1))
            node = world_list.node_by_identifier(node_identifier)
            assert isinstance(node, PickupNode)
            if node.pickup_index in initial_pickup_assignment:
                pickup = initial_pickup_assignment[node.pickup_index]
                if (pickup_name, target_player) != (pickup.name, player_index):
                    raise ValueError(
                        f"{area_node_name} should be vanilla based on configuration"
                    )

            elif pickup_name != _ETM_NAME:
                configuration_item_pool = all_pools[target_player].pickups
                pickup = _find_pickup_with_name(configuration_item_pool,
                                                pickup_name)
                configuration_item_pool.remove(pickup)
            else:
                pickup = None

            if pickup is not None:
                pickup_assignment[node.pickup_index] = PickupTarget(
                    pickup, target_player)

    # Hints
    hints = {}
    for identifier_str, hint in game_modifications["hints"].items():
        hints[NodeIdentifier.from_string(identifier_str)] = Hint.from_json(
            hint)

    return GamePatches(
        player_index=player_index,
        configuration=configuration,
        pickup_assignment=pickup_assignment,  # PickupAssignment
        elevator_connection=elevator_connection,  # ElevatorConnection
        dock_connection={},  # Dict[Tuple[int, int], DockConnection]
        dock_weakness=dock_weakness,
        configurable_nodes=configurable_nodes,
        starting_items=starting_items,  # ResourceGainTuple
        starting_location=starting_location,  # AreaIdentifier
        hints=hints,
    )
Beispiel #15
0
 def item(name):
     return search.find_resource_info_with_long_name(
         echoes_game_description.resource_database.item, name)
def decode_single(player_index: int, all_pools: Dict[int, PoolResults],
                  game: GameDescription, game_modifications: dict,
                  configuration: EchoesConfiguration) -> GamePatches:
    """
    Decodes a dict created by `serialize` back into a GamePatches.
    :param player_index:
    :param all_pools:
    :param game:
    :param game_modifications:
    :param configuration:
    :return:
    """
    world_list = game.world_list

    initial_pickup_assignment = all_pools[player_index].assignment

    # Starting Location
    starting_location = _area_name_to_area_location(
        world_list, game_modifications["starting_location"])

    # Initial items
    starting_items = {
        find_resource_info_with_long_name(game.resource_database.item,
                                          resource_name): quantity
        for resource_name, quantity in
        game_modifications["starting_items"].items()
    }

    # Elevators
    elevator_connection: ElevatorConnection = {}
    for source_name, target_name in game_modifications["elevators"].items():
        source_area = _area_name_to_area_location(world_list, source_name)
        target_area = _area_name_to_area_location(world_list, target_name)

        potential_source_nodes = [
            node
            for node in world_list.area_by_area_location(source_area).nodes
            if isinstance(node, TeleporterNode)
        ]
        assert len(potential_source_nodes) == 1
        source_node = potential_source_nodes[0]
        elevator_connection[source_node.teleporter] = target_area

    # Translator Gates
    translator_gates = {
        _find_gate_with_name(gate_name):
        find_resource_info_with_long_name(game.resource_database.item,
                                          resource_name)
        for gate_name, resource_name in
        game_modifications["translators"].items()
    }

    # Pickups
    target_name_re = re.compile(r"(.*) for Player (\d+)")

    pickup_assignment: PickupAssignment = {}
    for world_name, world_data in game_modifications["locations"].items():
        for area_node_name, target_name in world_data.items():
            if target_name == _ETM_NAME:
                continue

            pickup_name_match = target_name_re.match(target_name)
            if pickup_name_match is not None:
                pickup_name = pickup_name_match.group(1)
                target_player = int(pickup_name_match.group(2)) - 1
            else:
                pickup_name = target_name
                target_player = 0

            node = world_list.node_from_name(f"{world_name}/{area_node_name}")
            assert isinstance(node, PickupNode)
            if node.pickup_index in initial_pickup_assignment:
                pickup = initial_pickup_assignment[node.pickup_index]
                if (pickup_name, target_player) != (pickup.name, player_index):
                    raise ValueError(
                        f"{area_node_name} should be vanilla based on configuration"
                    )

            elif pickup_name != _ETM_NAME:
                configuration_item_pool = all_pools[target_player].pickups
                pickup = _find_pickup_with_name(configuration_item_pool,
                                                pickup_name)
                configuration_item_pool.remove(pickup)
            else:
                pickup = None

            if pickup is not None:
                pickup_assignment[node.pickup_index] = PickupTarget(
                    pickup, target_player)

    # Hints
    hints = {}
    for asset_id, hint in game_modifications["hints"].items():
        hints[LogbookAsset(int(asset_id))] = Hint.from_json(hint)

    return GamePatches(
        player_index=player_index,
        pickup_assignment=pickup_assignment,  # PickupAssignment
        elevator_connection=elevator_connection,  # ElevatorConnection
        dock_connection={},  # Dict[Tuple[int, int], DockConnection]
        dock_weakness={},  # Dict[Tuple[int, int], DockWeakness]
        translator_gates=translator_gates,
        starting_items=starting_items,  # ResourceGainTuple
        starting_location=starting_location,  # AreaLocation
        hints=hints,
    )
 def get_item_by_name(self, name: str) -> ItemResourceInfo:
     return search.find_resource_info_with_long_name(self.item, name)
def _patches_with_data(request, echoes_game_description, echoes_item_database):
    game = echoes_game_description

    data = {
        "starting_location": "Temple Grounds/Landing Site",
        "starting_items": {},
        "elevators": {
            "Temple Grounds/Temple Transport C":
            "Great Temple/Temple Transport C",
            "Temple Grounds/Transport to Agon Wastes":
            "Agon Wastes/Transport to Temple Grounds",
            "Temple Grounds/Transport to Torvus Bog":
            "Torvus Bog/Transport to Temple Grounds",
            "Temple Grounds/Temple Transport B":
            "Great Temple/Temple Transport B",
            "Temple Grounds/Transport to Sanctuary Fortress":
            "Sanctuary Fortress/Transport to Temple Grounds",
            "Temple Grounds/Temple Transport A":
            "Great Temple/Temple Transport A",
            "Great Temple/Temple Transport A":
            "Temple Grounds/Temple Transport A",
            "Great Temple/Temple Transport C":
            "Temple Grounds/Temple Transport C",
            "Great Temple/Temple Transport B":
            "Temple Grounds/Temple Transport B",
            "Sky Temple Grounds/Sky Temple Gateway":
            "Sky Temple/Sky Temple Energy Controller",
            "Sky Temple/Sky Temple Energy Controller":
            "Sky Temple Grounds/Sky Temple Gateway",
            "Agon Wastes/Transport to Temple Grounds":
            "Temple Grounds/Transport to Agon Wastes",
            "Agon Wastes/Transport to Torvus Bog":
            "Torvus Bog/Transport to Agon Wastes",
            "Agon Wastes/Transport to Sanctuary Fortress":
            "Sanctuary Fortress/Transport to Agon Wastes",
            "Torvus Bog/Transport to Temple Grounds":
            "Temple Grounds/Transport to Torvus Bog",
            "Torvus Bog/Transport to Agon Wastes":
            "Agon Wastes/Transport to Torvus Bog",
            "Torvus Bog/Transport to Sanctuary Fortress":
            "Sanctuary Fortress/Transport to Torvus Bog",
            "Sanctuary Fortress/Transport to Temple Grounds":
            "Temple Grounds/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Transport to Agon Wastes":
            "Agon Wastes/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Transport to Torvus Bog":
            "Torvus Bog/Transport to Sanctuary Fortress",
            "Sanctuary Fortress/Aerie":
            "Sanctuary Fortress/Aerie Transport Station",
            "Sanctuary Fortress/Aerie Transport Station":
            "Sanctuary Fortress/Aerie",
        },
        "translators": {},
        "locations": {},
        "hints": {}
    }
    patches = dataclasses.replace(game.create_game_patches(), player_index=0)

    locations = collections.defaultdict(dict)
    for world, area, node in game.world_list.all_worlds_areas_nodes:
        if node.is_resource_node and isinstance(node, PickupNode):
            world_name = world.dark_name if area.in_dark_aether else world.name
            locations[world_name][game.world_list.node_name(
                node)] = game_patches_serializer._ETM_NAME

    data["locations"] = {
        world: {area: item
                for area, item in sorted(locations[world].items())}
        for world in sorted(locations.keys())
    }

    if request.param.get("starting_item"):
        item_name = request.param.get("starting_item")
        patches = patches.assign_extra_initial_items({
            find_resource_info_with_long_name(game.resource_database.item, item_name):
            1,
        })
        data["starting_items"][item_name] = 1

    if request.param.get("elevator"):
        teleporter, elevator_source = request.param.get("elevator")
        elevator_connection = copy.copy(patches.elevator_connection)
        elevator_connection[teleporter] = game.starting_location

        patches = dataclasses.replace(patches,
                                      elevator_connection=elevator_connection)
        data["elevators"][elevator_source] = "Temple Grounds/Landing Site"

    if request.param.get("translator"):
        gates = {}
        for index, gate_name, translator in request.param.get("translator"):
            gates[TranslatorGate(index)] = find_resource_info_with_long_name(
                game.resource_database.item, translator)
            data["translators"][gate_name] = translator

        patches = patches.assign_gate_assignment(gates)

    if request.param.get("pickup"):
        pickup_name = request.param.get("pickup")
        pickup = pickup_creator.create_major_item(
            echoes_item_database.major_items[pickup_name], MajorItemState(),
            True, game.resource_database, None, False)

        patches = patches.assign_new_pickups([(PickupIndex(5),
                                               PickupTarget(pickup, 0))])
        data["locations"]["Temple Grounds"][
            'Transport to Agon Wastes/Pickup (Missile)'] = pickup_name

    if request.param.get("hint"):
        asset, hint = request.param.get("hint")
        patches = patches.assign_hint(LogbookAsset(asset),
                                      Hint.from_json(hint))
        data["hints"][str(asset)] = hint

    return data, patches
Beispiel #19
0
 def _get_scan_visor(self) -> ItemResourceInfo:
     try:
         return find_resource_info_with_long_name(
             self.resource_database.item, "Scan Visor")
     except MissingResource:
         return None
    def create_tracker(self):
        tracker_name = self.selected_tracker
        if tracker_name == self._current_tracker_name or tracker_name is None:
            return

        self.delete_tracker()

        with path_for(
                self.trackers[tracker_name]).open("r") as tracker_details_file:
            tracker_details = json.load(tracker_details_file)

        game_enum = RandovaniaGame(tracker_details["game"])
        resource_database = default_database.resource_database_for(game_enum)

        for element in tracker_details["elements"]:
            text_template = ""
            minimum_to_check = element.get("minimum_to_check", 1)
            field_to_check = FieldToCheck(
                element.get("field_to_check", FieldToCheck.CAPACITY.value))

            labels = []
            if "image_path" in element:
                paths = element["image_path"]
                if not isinstance(paths, list):
                    paths = [paths]

                visible = True
                for path in paths:
                    image_path = get_data_path().joinpath(path)
                    if not image_path.exists():
                        logging.error("Tracker asset not found: %s",
                                      image_path)
                    pixmap = QtGui.QPixmap(str(image_path))

                    label = ClickableLabel(self.inventory_group,
                                           paint_with_opacity(pixmap, 0.3),
                                           paint_with_opacity(pixmap, 1.0))
                    label.set_checked(False)
                    label.set_ignore_mouse_events(True)
                    label.setVisible(visible)
                    visible = False
                    labels.append(label)

            elif "label" in element:
                label = QtWidgets.QLabel(self.inventory_group)
                label.setAlignment(QtCore.Qt.AlignCenter)
                text_template = element["label"]
                labels.append(label)

            else:
                raise ValueError(f"Invalid element: {element}")

            resources = [
                find_resource_info_with_long_name(resource_database.item,
                                                  resource_name)
                for resource_name in element["resources"]
            ]
            for resource, label in zip(resources, labels):
                label.setToolTip(resource.long_name)

            self._tracker_elements.append(
                Element(labels, resources, text_template, minimum_to_check,
                        field_to_check))
            for label in labels:
                self.inventory_layout.addWidget(label, element["row"],
                                                element["column"])

        self.inventory_spacer = QtWidgets.QSpacerItem(
            5, 5, QtWidgets.QSizePolicy.Expanding,
            QtWidgets.QSizePolicy.Expanding)
        self.inventory_layout.addItem(self.inventory_spacer,
                                      self.inventory_layout.rowCount(),
                                      self.inventory_layout.columnCount())

        self._current_tracker_game = game_enum
        self._update_tracker_from_hook({})

        self._current_tracker_name = tracker_name