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.
This documentation is written for NameplateBuilder >= v4.260326.7 with API >= v2.2.0.
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