From 2479b55232fe79c2a7379b992598e17e8bceca3f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 7 Apr 2026 13:16:54 +0000 Subject: [PATCH 1/2] Fix: hide living entity name when it is a vehicle --- .../org/geysermc/geyser/entity/type/Entity.java | 16 +++++++--------- .../geyser/entity/type/LivingEntity.java | 9 ++++++++- .../geyser/entity/type/player/PlayerEntity.java | 2 +- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index de76892edc3..ce5fd44e449 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -567,18 +567,16 @@ public void updateNametag(@Nullable Team team) { } protected void updateNametag(@Nullable Team team, boolean visible) { - if (team != null) { - String newNametag; + if (!visible) { + // The name is not visible to the session player; clear name + setNametag("", false); + return; + } else if (team != null) { // (team) visibility is LivingEntity+, team displayName is Entity+ - if (visible) { - newNametag = team.displayName(getDisplayName(true)); - } else { - // The name is not visible to the session player; clear name - newNametag = ""; - } - setNametag(newNametag, false); + setNametag(team.displayName(getDisplayName(true)), false); return; } + // The name might need to be reset: no more team! setNametag(getDisplayName(customNameVisible), false); } diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index c24b74835fc..a828b82b2c7 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -205,8 +205,15 @@ protected void initializeMetadata() { @Override public void updateNametag(@Nullable Team team) { + // Java hides the name tag when a living entity has any passengers // if name not visible, don't mark it as visible - updateNametag(team, team == null || team.isVisibleFor(session.getPlayerEntity().getUsername())); + updateNametag(team, passengers.isEmpty() && (team == null || team.isVisibleFor(session.getPlayerEntity().getUsername()))); + } + + @Override + public void setPassengers(List passengers) { + super.setPassengers(passengers); + updateNametag(session.getWorldCache().getScoreboard().getTeamFor(teamIdentifier())); } public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java index 68fd2438b58..9764aea4c62 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/player/PlayerEntity.java @@ -94,7 +94,7 @@ protected void initializeMetadata() { // Since 1.20.60, the nametag does not show properly if this is not set :/ // The nametag does disappear properly when the player is invisible though. - dirtyMetadata.put(EntityDataTypes.NAMETAG_ALWAYS_SHOW, (byte) 1); + setNametagAlwaysShow(true); } @Override From 94f876fd1de1fac81fe85873a60d85be1ed945fb Mon Sep 17 00:00:00 2001 From: Eclipse Date: Wed, 8 Apr 2026 08:02:15 +0000 Subject: [PATCH 2/2] Unit tests and some more fixes --- .../geyser/entity/type/LivingEntity.java | 10 ++ .../entity/type/living/ArmorStandEntity.java | 2 +- .../network/NameVisibilityScoreboardTest.java | 101 ++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java index a828b82b2c7..ad8a141bf85 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/LivingEntity.java @@ -28,6 +28,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; +import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.math.GenericMath; import org.cloudburstmc.math.vector.Vector3f; @@ -216,6 +217,15 @@ public void setPassengers(List passengers) { updateNametag(session.getWorldCache().getScoreboard().getTeamFor(teamIdentifier())); } + @Override + public void setCustomName(EntityMetadata, ?> entityMetadata) { + // Update custom name, but reset nametag to be empty when there are passengers + super.setCustomName(entityMetadata); + if (!passengers.isEmpty()) { + setNametag("", false); + } + } + public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) { byte xd = entityMetadata.getPrimitiveValue(); diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java index 92a939cbbf4..84f686f5790 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/living/ArmorStandEntity.java @@ -121,7 +121,7 @@ public void moveAbsoluteRaw(Vector3f position, float yaw, float pitch, float hea @Override public void updateNametag(@Nullable Team team) { // unlike all other LivingEntities, armor stands are not affected by team nametag visibility - super.updateNametag(team, true); + super.updateNametag(team, passengers.isEmpty()); } @Override diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java index 305913607bc..024ac3ff94c 100644 --- a/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/NameVisibilityScoreboardTest.java @@ -29,7 +29,11 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag; import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityLinkPacket; +import org.geysermc.geyser.entity.type.living.ArmorStandEntity; +import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.translator.protocol.java.entity.JavaSetEntityDataTranslator; +import org.geysermc.geyser.translator.protocol.java.entity.JavaSetPassengersTranslator; import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.EntityMetadata; import org.geysermc.mcprotocollib.protocol.data.game.entity.metadata.MetadataTypes; @@ -40,6 +44,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction; import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundSetEntityDataPacket; +import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.entity.ClientboundSetPassengersPacket; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket; import org.junit.jupiter.api.Test; @@ -48,10 +53,13 @@ import java.util.Optional; import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketMatch; +import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacketType; import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnArmorStand; import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.spawnPlayerSilently; +import static org.junit.jupiter.api.Assertions.assertEquals; public class NameVisibilityScoreboardTest { @Test @@ -369,4 +377,97 @@ void teamsDontOverrideCustomName() { assertNoNextPacket(context); }); } + + @Test + void anyPassengersHidePlayerName() { + mockContextScoreboard(context -> { + PlayerEntity player = spawnPlayerSilently(context, "eclipseisoffline", 2L); + spawnArmorStand(context, 3L); + + context.translate(new JavaSetPassengersTranslator(), new ClientboundSetPassengersPacket(2, new int[]{3})); + // Passenger/vehicle link + assertNextPacketType(context, SetEntityLinkPacket.class); + // Passenger metadata + assertNextPacketType(context, SetEntityDataPacket.class); + + // Name metadata doesn't update until called for + player.updateBedrockMetadata(); + assertNextPacket(context, () -> { + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }); + }); + } + + @Test + void anyPassengersHideArmorStandName() { + mockContextScoreboard(context -> { + ArmorStandEntity vehicle = spawnArmorStand(context, 2L); + spawnArmorStand(context, 3L); + + context.translate(new JavaSetEntityDataTranslator(), new ClientboundSetEntityDataPacket(2, new EntityMetadata[]{ + new ObjectEntityMetadata<>(2, MetadataTypes.OPTIONAL_COMPONENT, Optional.of(Component.text("apples are gud"))) + })); + + assertNextPacketMatch(context, SetEntityDataPacket.class, packet -> { + assertEquals("apples are gud", packet.getMetadata().get(EntityDataTypes.NAME)); + }); + + context.translate(new JavaSetPassengersTranslator(), new ClientboundSetPassengersPacket(2, new int[]{3})); + // Passenger/vehicle link + assertNextPacketType(context, SetEntityLinkPacket.class); + // Passenger metadata + assertNextPacketType(context, SetEntityDataPacket.class); + vehicle.updateBedrockMetadata(); + assertNextPacket(context, () -> { + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }); + }); + } + + @Test + void noPassengersShowsArmorStandName() { + mockContextScoreboard(context -> { + ArmorStandEntity vehicle = spawnArmorStand(context, 2L); + spawnArmorStand(context, 3L); + + JavaSetPassengersTranslator setPassengersTranslator = new JavaSetPassengersTranslator(); + + context.translate(setPassengersTranslator, new ClientboundSetPassengersPacket(2, new int[]{3})); + // Passenger/vehicle link + assertNextPacketType(context, SetEntityLinkPacket.class); + // Passenger metadata + assertNextPacketType(context, SetEntityDataPacket.class); + + vehicle.updateBedrockMetadata(); + // Since the vehicle has no custom name, no new name should be sent + assertNoNextPacket(context); + + context.translate(new JavaSetEntityDataTranslator(), new ClientboundSetEntityDataPacket(2, new EntityMetadata[]{ + new ObjectEntityMetadata<>(2, MetadataTypes.OPTIONAL_COMPONENT, Optional.of(Component.text("apples are gud"))) + })); + + // Since it's a vehicle, the custom name should not be sent + assertNextPacketMatch(context, SetEntityDataPacket.class, packet -> { + assertEquals("", packet.getMetadata().get(EntityDataTypes.NAME)); + }); + + context.translate(setPassengersTranslator, new ClientboundSetPassengersPacket(2, new int[0])); + // Passenger/vehicle link + assertNextPacketType(context, SetEntityLinkPacket.class); + // Passenger metadata + assertNextPacketType(context, SetEntityDataPacket.class); + + // No longer a vehicle, now the name is sent + vehicle.updateBedrockMetadata(); + assertNextPacketMatch(context, SetEntityDataPacket.class, packet -> { + assertEquals("apples are gud", packet.getMetadata().get(EntityDataTypes.NAME)); + }); + }); + } }