diff --git a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java index d5170d2fc..b43e020bc 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/player/PlayerEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity.player; import com.github.steveice10.mc.auth.data.GameProfile; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -37,10 +38,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; -import com.nukkitx.protocol.bedrock.packet.AddPlayerPacket; -import com.nukkitx.protocol.bedrock.packet.MovePlayerPacket; -import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; -import com.nukkitx.protocol.bedrock.packet.UpdateAttributesPacket; +import com.nukkitx.protocol.bedrock.packet.*; import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; @@ -50,7 +48,10 @@ import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.connector.scoreboard.Objective; +import org.geysermc.connector.scoreboard.Score; import org.geysermc.connector.scoreboard.Team; +import org.geysermc.connector.scoreboard.UpdateType; import java.util.Collections; import java.util.UUID; @@ -85,6 +86,12 @@ public class PlayerEntity extends LivingEntity { @Override public void spawnEntity(GeyserSession session) { + // Check to see if the player should have a belowname counterpart added + Objective objective = session.getWorldCache().getScoreboard().getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME); + if (objective != null) { + setBelowNameText(session, objective); + } + AddPlayerPacket addPlayerPacket = new AddPlayerPacket(); addPlayerPacket.setUuid(uuid); addPlayerPacket.setUsername(username); @@ -326,4 +333,34 @@ public class PlayerEntity extends LivingEntity { metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, height); } + + public void setBelowNameText(GeyserSession session, Objective objective) { + if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) { + int amount; + Score score = objective.getScores().get(username); + if (score != null) { + amount = score.getCurrentData().getScore(); + } else { + amount = 0; + } + String displayString = amount + " " + objective.getDisplayName(); + + metadata.put(EntityData.SCORE_TAG, displayString); + if (valid) { + // Already spawned - we still need to run the rest of this code because the spawn packet will be + // providing the information + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityData.SCORE_TAG, displayString); + session.sendUpstreamPacket(packet); + } + } else { + if (valid && metadata.remove(EntityData.SCORE_TAG) != null) { + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(geyserId); + packet.getMetadata().put(EntityData.SCORE_TAG, ""); + session.sendUpstreamPacket(packet); + } + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java index a9d856b9e..afc90fbe0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityCache.java @@ -138,6 +138,10 @@ public class EntityCache { return playerEntities.remove(uuid); } + public Collection getAllPlayerEntities() { + return playerEntities.values(); + } + public void addBossBar(UUID uuid, BossBar bossBar) { bossBars.put(uuid, bossBar); bossBar.addBossBar(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java index ab5f7b350..fec000420 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaScoreboardObjectiveTranslator.java @@ -26,9 +26,11 @@ package org.geysermc.connector.network.translators.java.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ObjectiveAction; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerScoreboardObjectivePacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.network.translators.PacketTranslator; @@ -63,7 +65,20 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName())) .setType(packet.getType().ordinal()); - case REMOVE -> scoreboard.unregisterObjective(packet.getName()); + case REMOVE -> { + scoreboard.unregisterObjective(packet.getName()); + if (objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) { + // Clear the score tag from all players + for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { + if (!entity.isValid()) { + // Player hasn't spawned yet - don't bother + continue; + } + + entity.setBelowNameText(session, null); + } + } + } } if (objective == null || !objective.isActive()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java index 6a210ce51..870f89a43 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/scoreboard/JavaUpdateScoreTranslator.java @@ -26,9 +26,13 @@ package org.geysermc.connector.network.translators.java.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardAction; +import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.packet.ingame.server.scoreboard.ServerUpdateScorePacket; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.WorldCache; import org.geysermc.connector.network.translators.PacketTranslator; @@ -58,19 +62,40 @@ public class JavaUpdateScoreTranslator extends PacketTranslator { objective.setScore(packet.getEntry(), packet.getValue()); - break; - case REMOVE: + if (isBelowName) { + // Update the below name score on this player + setBelowName(session, objective, packet.getEntry(), packet.getValue()); + } + } + case REMOVE -> { + if (packet.getObjective().isEmpty()) { + // An empty objective name means all scores are reset for that player (/scoreboard players reset PLAYERNAME) + Objective belowName = scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME); + if (belowName != null) { + setBelowName(session, belowName, packet.getEntry(), 0); + } + } + if (objective != null) { objective.removeScore(packet.getEntry()); + + if (isBelowName) { + // Update the score on this player to now reflect 0 + setBelowName(session, objective, packet.getEntry(), 0); + } } else { for (Objective objective1 : scoreboard.getObjectives()) { objective1.removeScore(packet.getEntry()); } } - break; + } } // ScoreboardUpdater will handle it for us if the packets per second @@ -79,4 +104,43 @@ public class JavaUpdateScoreTranslator extends PacketTranslator objectives = new ConcurrentHashMap<>(); - private final Map objectiveSlots = new HashMap<>(); + @Getter + private final Map objectiveSlots = new EnumMap<>(ScoreboardPosition.class); private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads private int lastAddScoreCount = 0; @@ -103,6 +105,19 @@ public final class Scoreboard { objective.pendingRemove(); } objectiveSlots.put(displaySlot, objective); + + if (displaySlot == ScoreboardPosition.BELOW_NAME) { + // Display the below name score option to all players + // Of note: unlike Bedrock, if there is an objective in the below name slot, everyone has a display + for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) { + if (!entity.isValid()) { + // Player hasn't spawned yet - don't bother, it'll be done then + continue; + } + + entity.setBelowNameText(session, objective); + } + } } public Team registerNewTeam(String teamName, String[] players) {