Skip to main content
Most segments should use resolvers. This page covers the alternative: pushing text to entities yourself using setText() and clearText().

When Do You Need This?

You need manual text when your segment’s value doesn’t come from an ECS component on the entity. Some examples:
  • An admin runs /setbounty goblin 500 - the bounty is stored in your mod’s config, not as a component on the entity
  • A player earns a title from an achievement system - the title lives in a database, not on the entity
  • A buff is applied through a chat command - there’s no BuffComponent to read from
  • You need to seed initial nameplate data on NPCs when they first spawn
If the data does live on the entity as a component (health, stats, faction, etc.), use a resolver instead - it’s simpler and you don’t need to write any tick systems.

The Two Methods

setText() - Set or Update

NameplateAPI.setText(store, entityRef, "bounty", "$500");
This sets the text for your segment on a specific entity. If the entity doesn’t have a NameplateData component yet, one is created automatically. If the segment already has text on this entity, it’s overwritten. You can call this from anywhere you have access to the Store and a Ref - event handlers, command handlers, systems, etc.

clearText() - Remove

NameplateAPI.clearText(store, entityRef, "bounty");
This removes your segment’s text from the entity. If the entity has no other segments left, the entire NameplateData component is removed automatically.

Example: Admin Command Sets a Bounty

Here’s a concrete scenario. Your mod has a /setbounty command. When an admin uses it, you want to show the bounty above the targeted entity:
// In your command handler:
public void onSetBounty(Store<EntityStore> store, Ref<EntityStore> targetEntity,
                        int amount) {
    // Tell NameplateBuilder to show this text on the entity
    NameplateAPI.setText(store, targetEntity, "bounty", "$" + amount);
}

public void onClearBounty(Store<EntityStore> store, Ref<EntityStore> targetEntity) {
    // Remove the bounty text from the entity
    NameplateAPI.clearText(store, targetEntity, "bounty");
}
You still need to call define() in setup() so the segment appears in the player UI:
@Override
protected void setup() {
    // Register the segment for the UI - no resolver needed
    NameplateAPI.define(this, "bounty", "Bounty",
            SegmentTarget.NPCS, "$500");
}

The Tick System Restriction

There’s one important restriction: setText() cannot be called inside an EntityTickingSystem if the entity doesn’t already have a NameplateData component. This is because setText() needs to add the component to the entity, and Hytale locks the store for structural changes during tick processing. If you try, you’ll get:
IllegalStateException: Store is currently processing
This only matters when you need to initialize nameplate data on entities that don’t have it yet (like newly spawned NPCs). Once an entity has the component, updating it from a tick system is fine.

Tick System Pattern: NPC Spawn Initialization

If you need to give NPCs nameplate data when they first appear in the world, you need a tick system that uses CommandBuffer instead of setText():
final class MyNpcNameplateSystem extends EntityTickingSystem<EntityStore> {

    private final ComponentType<EntityStore, NPCEntity> npcType;
    private final ComponentType<EntityStore, NameplateData> nameplateDataType;

    MyNpcNameplateSystem(ComponentType<EntityStore, NameplateData> nameplateDataType) {
        this.npcType = NPCEntity.getComponentType();
        this.nameplateDataType = nameplateDataType;
    }

    @Override
    public Archetype<EntityStore> getQuery() {
        // Only tick on entities that have an NPCEntity component
        return Archetype.of(npcType);
    }

    @Override
    public SystemGroup<EntityStore> getGroup() {
        return EntityTrackerSystems.QUEUE_UPDATE_GROUP;
    }

    @Override
    public void tick(float deltaTime, int index, ArchetypeChunk<EntityStore> chunk,
                     Store<EntityStore> store, CommandBuffer<EntityStore> commandBuffer) {

        // Check if this is the right type of NPC
        NPCEntity npc = chunk.getComponent(index, npcType);
        if (npc == null || !"Kweebec".equals(npc.getRoleName())) {
            return;
        }

        Ref<EntityStore> entityRef = chunk.getReferenceTo(index);

        // Skip if this entity already has nameplate data (already initialized)
        if (store.getComponent(entityRef, nameplateDataType) != null) {
            return;
        }

        // First time seeing this entity - create the component manually
        NameplateData data = new NameplateData();
        data.setText("faction", "<Forest>");
        data.setText("level", "Lv. 10");

        // Use commandBuffer.putComponent() instead of store.addComponent()
        // The CommandBuffer queues the change and applies it after the tick finishes
        commandBuffer.putComponent(entityRef, nameplateDataType, data);
    }
}
Why CommandBuffer? Inside a tick system, the entity store is locked. You can’t add or remove components directly. CommandBuffer queues the change and applies it safely after the system finishes processing. Why putComponent() instead of addComponent()? addComponent() throws an error if the component already exists. putComponent() adds it if missing or replaces it if it exists - safer when multiple systems or mods might initialize the same entity. Register the system in your plugin’s setup():
@Override
protected void setup() {
    NameplateAPI.define(this, "faction", "Faction",
            SegmentTarget.NPCS, "<Forest>");
    NameplateAPI.define(this, "level", "Level",
            SegmentTarget.ALL, "Lv. 10");

    ComponentType<EntityStore, NameplateData> nameplateDataType =
        NameplateAPI.getComponentType();

    getEntityStoreRegistry().registerSystem(new MyNpcNameplateSystem(nameplateDataType));
}

Updating Text Every Tick

Once an entity already has a NameplateData component (from initialization or any other source), you can safely update it from a tick system. No CommandBuffer needed - you’re modifying existing data, not adding a new component:
@Override
public void tick(float deltaTime, int index, ArchetypeChunk<EntityStore> chunk,
                 Store<EntityStore> store, CommandBuffer<EntityStore> commandBuffer) {

    NameplateData data = chunk.getComponent(index, nameplateDataType);
    if (data == null) return; // entity doesn't have nameplate data yet

    // This is safe - it's just a HashMap.put() internally
    data.setText("timer", formatTimer(computeTimeLeft()));
}

Working with NameplateData Directly

If you need lower-level access to the nameplate component, you can read and write it directly:
ComponentType<EntityStore, NameplateData> type = NameplateAPI.getComponentType();
NameplateData data = store.getComponent(entityRef, type);

if (data != null) {
    data.setText("health", "42/67");        // set or update a value
    String current = data.getText("health"); // read the current value
    data.removeText("health");              // remove a single key
    boolean empty = data.isEmpty();         // check if any segments remain

    for (var entry : data.getEntries()) {
        String key = entry.getKey();        // segment ID
        String value = entry.getValue();    // current text
    }
}

Manual Text + Resolver Priority

If an entity has both manual text and a resolver for the same segment, the manual text wins. This is useful when you want a resolver to handle the normal case but override specific entities:
// In setup() - resolver computes health normally
NameplateAPI.define(this, "health", "Health", SegmentTarget.ALL, "67/69")
    .requires(statMapType)
    .resolver((store, entityRef, variantIndex) -> {
        // ... compute health from stats ...
    });

// At runtime - override one specific boss entity
NameplateAPI.setText(store, bossEntityRef, "health", "INVULNERABLE");

Next Steps

  • Format Variants - Let players pick between display formats
  • Advanced - Hidden metadata keys, cleanup, and edge cases