> ## 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.

# Advanced

> Hidden keys, cleanup, undefine, and common mistakes.

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

This page covers features you'll rarely need, but are good to know about.

## Replacing Built-in Segments

When your mod provides a better name for an entity than the default role name, use `replaces()` to automatically hide the built-in segment on entities where your segment has a value. On all other entities, the built-in segment shows normally.

**Why this exists:** NameplateBuilder uses one chain per entity type (NPC/Player). All NPCs share the same chain. Without `replaces`, a player who has both "Entity Name" and "Pet Name" in their chain would see `Bear - Fluffy` on a named pet. With `replaces`, they see just `Fluffy` on the pet and `Bear` on wild NPCs - without changing anything in their chain.

```java theme={null}
NameplateAPI.define(this, "pet-name", "Pet Name",
        SegmentTarget.NPCS, "Fluffy")
    .replaces("entity-name")
    .enabledByDefault(SegmentTarget.NPCS)
    .resolver((store, entityRef, variantIndex) -> {
        var name = store.getComponent(entityRef, petNameType);
        return name != null ? name.getName() : null;
    });
```

**How it works:**

* When your resolver returns a value (e.g. `"Fluffy"`) for an entity, the target segment (`entity-name`) is hidden for that entity
* When your resolver returns `null` (entity has no pet name), the target segment shows normally
* The replacement is per-entity - it only affects entities where your segment has a value
* Players do not need to change their chain. Both segments can be in the chain, and the right one shows automatically
* Multiple segments can replace the same target. They all show - only the target is hidden

**Restrictions:**

* `replaces()` can only target NameplateBuilder's built-in segments: `entity-name`, `player-name`, `health`, `stamina`, `mana`
* A segment cannot replace itself
* Circular replacements (A replaces B, B replaces A) are safe - neither gets suppressed

**When to use it:** When your segment provides a context-specific version of information that a built-in segment already shows. Common examples:

* A pet naming mod replacing `entity-name` with the pet's custom name
* A disguise mod replacing `player-name` with a fake name
* A shield mod replacing `health` with a combined health+shield display

**When NOT to use it:** When your segment shows additional information alongside the built-in (like elite tier, guild tag, or buff icons). Those should be separate segments in the chain, not replacements.

## Default Segments

By default, new players start with empty chains and have to manually add segments via `/npb`. If you want your segment to appear in a player's chain automatically (so it works out of the box), use `enabledByDefault()`:

```java theme={null}
NameplateAPI.define(this, "health", "Health",
        SegmentTarget.ALL, "67/69")
    .enabledByDefault()
    .resolver((store, entityRef, variantIndex) -> {
        // ...
    });
```

This adds the segment to both the NPC and Player chains for any player who hasn't customized their chains yet. Once a player edits their chain in `/npb`, their preferences take over and the default no longer applies.

You can also target a specific chain:

```java theme={null}
// Only default-enable on the NPC chain
NameplateAPI.define(this, "faction", "Faction",
        SegmentTarget.NPCS, "<Undead>")
    .enabledByDefault(SegmentTarget.NPCS)
    .resolver((store, entityRef, variantIndex) -> {
        // ...
    });
```

**Limit:** Each mod can mark at most **3 segments** as `enabledByDefault`. This prevents mods from flooding new players' chains. If you exceed the limit, NameplateBuilder logs a warning and ignores the extra defaults.

## Hidden Metadata Keys

Sometimes you need to store per-entity data alongside your visible segments - things like spawn timestamps, internal state flags, or tracking counters. You don't want players to see this data in their nameplate.

Any key that starts with `_` (underscore) is **hidden**. It's stored in the `NameplateData` component like any other key, but NameplateBuilder's aggregator skips it when building the nameplate text. It never appears in the player UI either.

```java theme={null}
// Store a spawn tick so you can compute a lifetime later
data.setText("_spawn_tick", String.valueOf(currentTick));

// Store internal state - invisible to players
data.setText("_phase", "enraged");
data.setText("_last_hit_by", attackerName);

// Read it back in a later tick
String spawnTick = data.getText("_spawn_tick");
long lifetime = currentTick - Long.parseLong(spawnTick);
```

**Why use this instead of a separate ECS component?** Convenience. If you only need a couple of small values per entity and they're closely tied to your nameplate logic, hidden keys save you from registering and managing a separate component. For larger or more structured data, a dedicated component is still the better choice.

## Automatic Cleanup

NameplateBuilder handles two cleanup scenarios automatically. You don't need to write any code for either.

### Entity Death

When an entity receives a `DeathComponent`, NameplateBuilder immediately:

1. Sends an empty nameplate to all viewers (clears the text above the entity)
2. Removes the `NameplateData` component from the entity

This prevents stale nameplate text from lingering during death animations.

### Plugin Unload

When your mod is unloaded (server shutdown, hot reload, etc.), NameplateBuilder automatically removes all segment definitions that your plugin registered. You don't need a shutdown hook or cleanup code.

## Removing a Segment at Runtime with `undefine()`

In rare cases, you might want to remove a segment from the UI **while the server is running**. For example, a minigame mod that adds segments when a game starts and removes them when it ends:

```java theme={null}
// Game starts - add the segment
NameplateAPI.define(this, "score", "Score",
        SegmentTarget.PLAYERS, "0 pts");

// Game ends - remove it from the UI
NameplateAPI.undefine(this, "score");
```

After calling `undefine()`:

* The segment disappears from the `/npb` UI
* Players who had it in their chain no longer see it rendered
* Any `NameplateData` text on entities is **not** removed - it stays until the entity dies or you explicitly clear it

**Most mods will never need this.** If your segment exists for the lifetime of your plugin, the automatic plugin-unload cleanup is sufficient.

## Common Mistakes

### Using `store.addComponent()` inside a tick system

**Problem:** The entity store is locked during tick processing. Adding a component directly throws `IllegalStateException: Store is currently processing`.

**Fix:** Use `commandBuffer.putComponent()` instead. The `CommandBuffer` queues the change and applies it after the tick finishes. See [Manual Text - Tick System Pattern](/modding/manual-text#tick-system-pattern-npc-spawn-initialization).

### Using `NameplateAPI.setText()` inside a tick system on a new entity

**Problem:** `setText()` tries to add a `NameplateData` component if the entity doesn't have one, which hits the same store-lock issue.

**Fix:** Build a `NameplateData` object manually and use `commandBuffer.putComponent()`:

```java theme={null}
NameplateData data = new NameplateData();
data.setText("bounty", "$500");
commandBuffer.putComponent(entityRef, nameplateDataType, data);
```

### Forgetting the manifest dependency

**Problem:** Calling any `NameplateAPI` method throws `NameplateNotInitializedException`.

**Fix:** Add the dependency to your `manifest.json`:

```json theme={null}
{
  "Dependencies": {
    "Frotty27:NameplateBuilder": ">=4.260326.7"
  }
}
```

### Calling `undefine()` in a shutdown hook

**Problem:** Unnecessary - NameplateBuilder already cleans up all your segments when your plugin unloads.

**Fix:** Remove the `undefine()` call from your shutdown code.

### Removing nameplate data on entity death

**Problem:** Unnecessary - NameplateBuilder already handles death cleanup automatically.

**Fix:** Remove your death-cleanup code. Let NameplateBuilder handle it.

### Not handling the `default` case in variant switches

**Problem:** If a player selects a variant index your resolver doesn't handle, it returns `null` and the segment disappears.

**Fix:** Always include a `default` case that returns the base format:

```java theme={null}
return switch (variantIndex) {
    case 1 -> percent + "%";
    case 2 -> barString;
    default -> current + "/" + max; // catches index 0 and any unexpected index
};
```

## Complete Plugin Example

A full plugin using both resolvers and manual text, with format variants:

```java theme={null}
package com.example.mymod;

import com.frotty27.nameplatebuilder.api.NameplateAPI;
import com.frotty27.nameplatebuilder.api.SegmentTarget;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;

import java.util.List;

public final class MyModPlugin extends JavaPlugin {

    public MyModPlugin(JavaPluginInit init) {
        super(init);
    }

    @Override
    protected void setup() {
        ComponentType<EntityStore, EntityStatMap> statMapType =
            EntityStatMap.getComponentType();
        ComponentType<EntityStore, FactionComponent> factionType =
            FactionComponent.getComponentType();

        // Health - resolver with format variants, recomputed every tick
        NameplateAPI.define(this, "health", "Health",
                SegmentTarget.ALL, "100/100")
            .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 - resolver with caching, 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;
            });

        // Guild - manual text, set from event handlers or commands
        // No resolver needed - text is pushed via setText() at runtime
        NameplateAPI.define(this, "guild", "Guild Tag",
                SegmentTarget.PLAYERS, "[Warriors]");
    }
}
```

## Further Reading

* **[API Reference](/reference/api)** - Full method tables and exception docs
* **[Architecture](/reference/architecture)** - Internal systems and data flow
* **[Example Mod](https://github.com/TimShol/hytale-nameplate-builder)** - Full working source code
