Currently, player entities ignore the custom DATA_CUSTOM_NAME
EntityDataAccessor
, which sadly means that we cannot just easily change the name of the player entity. Hopefully in the future this functionality will be provided so we do not have to apply such hacky methods.
public void show(@NotNull Player entity) {
Location location = entity.getLocation();
Interaction interaction = entity.getWorld().spawn(location, Interaction.class);
entity.addPassenger(interaction);
interaction.customName(this.name);
interaction.setCustomNameVisible(true);
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
double ridingOffset = nmsEntity.getPassengersRidingOffset();
double nametagOffset = nmsEntity.getNameTagOffsetY();
// First, negate the riding offset to get to the bounding of the entity's bounding box
// Negate the natural nametag offset of interaction entities (0.5)
// Add the actual offset of the nametag
double effectiveHeight = -ridingOffset - 0.5 + nametagOffset;
// Calculate height
interaction.setInteractionHeight((float) effectiveHeight);
interaction.setInteractionWidth(0);
((CraftInteraction) interaction).getHandle().setPose(Pose.SNIFFING); // Send the pose to the client, force recalculation of the bounding box.
}
In this case, we use an Interaction entity that rides the entity who's name we want to change. We then utilize our ability to customize the hitbox of this entity by setting its height in order to allow the entity to sit flush on the entity. This means that the heighest point of the entity's bounding box will now match the height of the interaction box.
As we can see in this photo, the height of the interaction entity (circled) matches the hitbox. It should be noted that in some cases, some entities have special exceptions for nametag offsets, which is why it may not always be flush.
This then allows us to add a custom name to the interaction entity, which will cause the location of the nametag to remain in the same place.
When moving away from the interaction entity, the entity will stop rendering. This is because it's very small, and the client thinks it's safe to stop rendering it. Unfortunently, it is not easy to change this.
However, here is an example on how you would bypass it.
ClientboundSetEntityDataPacket initialCreatePacket = new ClientboundSetEntityDataPacket(entityIdName, List.of(
ofData(Reflection.DATA_WIDTH_ID, 0f),
ofData(Reflection.DATA_HEIGHT_ID, (float) effectiveHeight),
ofData(Reflection.DATA_POSE, Pose.CROAKING),
ofData(Reflection.DATA_CUSTOM_NAME_VISIBLE, true)
));
Packet<ClientGamePacketListener> syncData = createSyncData(); // name... sneaking state... etc
ClientboundSetEntityDataPacket afterCreateData = new ClientboundSetEntityDataPacket(entityIdName, List.of(
ofData(Reflection.DATA_HEIGHT_ID, 99999999f)
));
return new ClientboundBundlePacket(List.of(
createPacket(), // Create entity
initialCreatePacket,
syncData,
new ClientboundSetPassengersPacket(buf),
afterCreateData
));
Essentially, you update the height which changes the dimensions but not the stored bounding box. This causes the client to still render the nametag at the previous position, but it will now also use dimensions.
The dimensions are highlighted in white, and as we can see, it goes super high into the skybox. This means that the game will use that box for determining whether or not to stop rendering that entity. So, we essentially have a permenent name tag!
Warning that using the passenger system:
teleport
method call doesn't have theRETAIN_PASSENGERS
flag