Skip to content

Instantly share code, notes, and snippets.

@Owen1212055
Last active October 26, 2024 14:28
Show Gist options
  • Save Owen1212055/f5d59169d3a6a5c32f0c173d57eb199d to your computer and use it in GitHub Desktop.
Save Owen1212055/f5d59169d3a6a5c32f0c173d57eb199d to your computer and use it in GitHub Desktop.
Custom Player Entity Names

Custom Player Names

alt text

This is meant as a resource to mimic setting player names whilst ensuring that it is positioned in the same way as an actual nametag.

The current issue

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.

My implementation (simplified)

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.

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

Why is the entity disappearing?

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

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!

@Swedz
Copy link

Swedz commented Jul 31, 2024

Warning that using the passenger system:

  • Prevents plugins from teleporting the entity in the same dimension when the teleport method call doesn't have the RETAIN_PASSENGERS flag
  • Prevents commands and plugins from teleporting the entity across dimensions (specifically NOT referring to portals)

@Swedz
Copy link

Swedz commented Jul 31, 2024

Also, you can create the interaction entity using the below code. The height of the hitbox doesn't need to be anything beyond about 3 to get maximum visibility. You do not need to use packets to update the height of the interaction entity without moving the text. This includes some code to prevent the player from seeing their own nametag, as well as preventing players who shouldn't be able to see the player (due to plugins like /vanish) from seeing the nametag entity.

Interaction displayEntity = player.getWorld().spawn(player.getLocation(), Interaction.class, (entity) ->
{
	entity.setPersistent(false);
	entity.setInteractionHeight(0.5f);
	entity.setInteractionWidth(0);
	for(Player viewer : player.getWorld().getPlayers())
	{
		boolean hide = viewer.equals(player) || !viewer.canSee(player);
		if(hide)
		{
			viewer.hideEntity(registry.getPlugin(), entity);
		}
	}
	player.addPassenger(entity);
});
displayEntity.setInteractionHeight(3f);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment