> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nameplatebuilder.frotty27.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Resolvers

> Compute segment values per entity using resolver functions.

<Info>This documentation is written for **NameplateBuilder >= v4.260326.7** with **API >= v2.2.0**.</Info>

If you followed the [Quick Start](/modding/quick-start), you already have a working resolver. This page goes deeper into the three things you can configure on a resolver: **archetype filtering**, **caching**, and **format variants**.

## Recap: What a Resolver Does

A resolver is a function you attach to a segment. NameplateBuilder calls it every tick for every visible entity. Your function reads data from the entity and returns a string to display, or `null` to skip that entity.

```java theme={null}
NameplateAPI.define(this, "bounty", "Bounty",
        SegmentTarget.NPCS, "$500")
    .resolver((store, entityRef, variantIndex) -> {
        BountyComponent bounty = store.getComponent(entityRef, bountyType);
        if (bounty == null) return null;
        return "$" + bounty.getAmount();
    });
```

The resolver receives three parameters:

* **`store`** - The entity store. Use it to read components from the entity.
* **`entityRef`** - A reference to the entity being processed. Pass this to `store.getComponent()`.
* **`variantIndex`** - Which display format the player chose (0 = default). Explained in [Format Variants](#format-variants) below.

## Archetype Filtering with `requires()`

Without `requires()`, NameplateBuilder calls your resolver for **every visible entity** in the world - players, NPCs, projectiles, everything. Your resolver then has to check if the entity even has the right component and return `null` if it doesn't. This works, but it's wasteful.

With `requires()`, you tell NameplateBuilder which component your resolver needs. It checks the entity's archetype (the set of components it was created with) **before** calling your resolver. If the entity doesn't have that component, your resolver is never called.

```java theme={null}
NameplateAPI.define(this, "health", "Health",
        SegmentTarget.ALL, "67/69")
    .requires(EntityStatMap.getComponentType()) // only call resolver for entities with stats
    .resolver((store, entityRef, variantIndex) -> {
        EntityStatMap stats = store.getComponent(entityRef, statMapType);
        if (stats == null) return null; // still safe to null-check
        int current = Math.round(stats.get(health).get());
        int max = Math.round(stats.get(health).getMax());
        return current + "/" + max;
    });
```

**When to use it:** Always, when your resolver reads a specific component. There's no downside.

**Keep the null check anyway:** `requires()` filters by archetype (the entity's type), not by whether the component has data. In rare cases a component might exist but return null values. The null check is cheap insurance.

## Caching with `cacheTicks()`

By default, your resolver runs **every tick** (30 times per second). For health or mana that changes constantly, that's exactly what you want. But for data that rarely changes - like a faction name, level, or title - recomputing every tick is wasteful.

`cacheTicks()` tells NameplateBuilder to remember the result and reuse it for a set number of ticks before calling your resolver again:

```java theme={null}
NameplateAPI.define(this, "faction", "Faction",
        SegmentTarget.NPCS, "<Undead>")
    .requires(FactionComponent.getComponentType())
    .cacheTicks(100) // recompute every 100 ticks (~3 seconds at 30 tps)
    .resolver((store, entityRef, variantIndex) -> {
        FactionComponent faction = store.getComponent(entityRef, factionType);
        if (faction == null) return null;
        return "<" + faction.getName() + ">";
    });
```

The cache is **per entity** - each entity gets its own cached result. When an entity dies, its cache is cleared automatically.

**Good candidates for caching:** faction, level, tier, rank, title, class - anything that changes rarely or never.

**Bad candidates:** health, mana, stamina, timers, buffs - anything that changes every few ticks.

## Format Variants

Players can choose between different display formats for a segment. For example, health could be shown as `"42/67"`, `"63%"`, or `"||||||------"`. The `variantIndex` parameter tells your resolver which format the player selected.

First, register the variant names so the UI knows what to show:

```java theme={null}
NameplateAPI.defineVariants(this, "health", List.of(
    "Current/Max",    // variantIndex 0 (the default)
    "Percentage",     // variantIndex 1
    "Bar"             // variantIndex 2
));
```

Then handle the index in your resolver:

```java theme={null}
NameplateAPI.define(this, "health", "Health",
        SegmentTarget.ALL, "67/69")
    .requires(EntityStatMap.getComponentType())
    .resolver((store, entityRef, variantIndex) -> {
        EntityStatMap stats = store.getComponent(entityRef, statMapType);
        if (stats == null) return null;

        int current = Math.round(stats.get(health).get());
        int max = Math.round(stats.get(health).getMax());

        return switch (variantIndex) {
            case 1 -> Math.round(100f * current / max) + "%";
            case 2 -> buildBarString(current, max);
            default -> current + "/" + max; // variant 0, and any unknown index
        };
    });
```

**Always include a `default` case.** If a new variant is added later or the index is somehow unexpected, the default format is shown instead of crashing.

If your segment only has one display format, don't call `defineVariants()` and ignore the `variantIndex` parameter. The format button won't appear in the UI.

See **[Format Variants](/modding/format-variants)** for more details on variant naming, prefix/suffix wrapping, and bar customization.

## Resolver vs Manual Text Priority

If an entity has **both** a resolver and manual text (set via `setText()`) for the same segment, the manual text wins. This is intentional - it lets you override computed values for specific entities when needed.

For example, you might have a resolver that computes health from stats, but use `setText()` to override one specific boss entity with custom text like `"INVULNERABLE"`.

## Complete Example

A full `setup()` method with three segments using different resolver configurations:

```java theme={null}
@Override
protected void setup() {
    ComponentType<EntityStore, EntityStatMap> statMapType =
        EntityStatMap.getComponentType();
    ComponentType<EntityStore, FactionComponent> factionType =
        FactionComponent.getComponentType();
    ComponentType<EntityStore, TitleData> titleType =
        TitleData.getComponentType();

    // Health - runs every tick, has format variants
    NameplateAPI.define(this, "health", "Health",
            SegmentTarget.ALL, "67/69")
        .requires(statMapType)
        .resolver((store, entityRef, variantIndex) -> {
            EntityStatMap stats = store.getComponent(entityRef, statMapType);
            if (stats == null) return null;
            int current = Math.round(stats.get(health).get());
            int max = Math.round(stats.get(health).getMax());
            return switch (variantIndex) {
                case 1 -> Math.round(100f * current / max) + "%";
                default -> current + "/" + max;
            };
        });

    NameplateAPI.defineVariants(this, "health", List.of(
        "Current/Max", "Percentage"
    ));

    // Faction - cached because it rarely changes
    NameplateAPI.define(this, "faction", "Faction",
            SegmentTarget.NPCS, "<Undead>")
        .requires(factionType)
        .cacheTicks(100)
        .resolver((store, entityRef, variantIndex) -> {
            FactionComponent faction = store.getComponent(entityRef, factionType);
            return faction != null ? "<" + faction.getName() + ">" : null;
        });

    // Title - simple, no caching, no variants
    NameplateAPI.define(this, "title", "Title",
            SegmentTarget.PLAYERS, "[Knight]")
        .requires(titleType)
        .resolver((store, entityRef, variantIndex) -> {
            TitleData title = store.getComponent(entityRef, titleType);
            return title != null ? "[" + title.getTitle() + "]" : null;
        });
}
```

## Next Steps

* **[Manual Text](/modding/manual-text)** - For when your data doesn't come from an ECS component
* **[Format Variants](/modding/format-variants)** - Variant naming, prefix/suffix, and bar customization
* **[Advanced](/modding/advanced)** - Hidden metadata keys, cleanup, and edge cases
