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 fc7b867bb..ce9470ccd 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 @@ -27,6 +27,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.scoreboard.ScoreboardPosition; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -50,9 +51,11 @@ import org.geysermc.connector.entity.attribute.AttributeType; 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.utils.AttributeUtils; -import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -111,6 +114,22 @@ public class PlayerEntity extends LivingEntity { updateEquipment(session); updateBedrockAttributes(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) { + boolean hasScore = false; + for (Score score : objective.getScores().values()) { + if (score.getName().equals(this.username)) { + hasScore = true; + session.getWorldCache().getScoreboard().sendBelowNameUpdate(objective, score, this); + break; + } + } + if (!hasScore) { + //TODO session.getWorldCache().getScoreboard().sendBelowNameUpdate(objective, null, this); + } + } } public void sendPlayer(GeyserSession session) { @@ -257,12 +276,7 @@ public class PlayerEntity extends LivingEntity { } Team team = session.getWorldCache().getScoreboard().getTeamFor(username); if (team != null) { - String displayName = ""; - if (team.isVisibleFor(session.getPlayerEntity().getUsername())) { - displayName = MessageTranslator.toChatColor(team.getColor()) + username; - displayName = team.getCurrentData().getDisplayName(displayName); - } - metadata.put(EntityData.NAMETAG, displayName); + metadata.put(EntityData.NAMETAG, team.getDisplayNameFor(username, session.getPlayerEntity().getUsername())); } } 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 40000551c..c5e889f4d 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 @@ -124,10 +124,26 @@ public class EntityCache { playerEntities.put(entity.getUuid(), entity); } + public Collection getPlayerEntities() { + return playerEntities.values(); + } + public PlayerEntity getPlayerEntity(UUID uuid) { return playerEntities.get(uuid); } + /** + * @return a {@link PlayerEntity} with the given username + */ + public PlayerEntity getPlayerEntity(String username) { + for (PlayerEntity entity : playerEntities.values()) { + if (username.equals(entity.getUsername())) { + return entity; + } + } + return null; + } + public void removePlayerEntity(UUID uuid) { playerEntities.remove(uuid); } 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 3ebcba44d..bf3d67d59 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 @@ -70,7 +70,7 @@ public class JavaScoreboardObjectiveTranslator extends PacketTranslator { int pps = session.getWorldCache().increaseAndGetScoreboardPacketsPerSecond(); Scoreboard scoreboard = session.getWorldCache().getScoreboard(); - Team team = scoreboard.getTeam(packet.getTeamName()); + Team team = null; + + if (packet.getAction() != TeamAction.CREATE) { + team = scoreboard.getTeam(packet.getTeamName()); + } switch (packet.getAction()) { case CREATE: scoreboard.registerNewTeam(packet.getTeamName(), toPlayerSet(packet.getPlayers())) @@ -64,6 +73,7 @@ public class JavaTeamTranslator extends PacketTranslator { .setNameTagVisibility(packet.getNameTagVisibility()) .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale())) .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale())); + updatePlayerNameTags(session, team, packet.getPlayers(), false); break; case UPDATE: if (team == null) { @@ -73,6 +83,7 @@ public class JavaTeamTranslator extends PacketTranslator { )); return; } + NameTagVisibility oldVisibility = team.getNameTagVisibility(); team.setName(MessageTranslator.convertMessage(packet.getDisplayName())) .setColor(packet.getColor()) @@ -80,6 +91,9 @@ public class JavaTeamTranslator extends PacketTranslator { .setPrefix(MessageTranslator.convertMessage(packet.getPrefix(), session.getLocale())) .setSuffix(MessageTranslator.convertMessage(packet.getSuffix(), session.getLocale())) .setUpdateType(UpdateType.UPDATE); + if (!oldVisibility.equals(packet.getNameTagVisibility())) { + updatePlayerNameTags(session, team, team.getEntities(), false); + } break; case ADD_PLAYER: if (team == null) { @@ -90,6 +104,7 @@ public class JavaTeamTranslator extends PacketTranslator { return; } team.addEntities(packet.getPlayers()); + updatePlayerNameTags(session, team, packet.getPlayers(), false); break; case REMOVE_PLAYER: if (team == null) { @@ -100,9 +115,13 @@ public class JavaTeamTranslator extends PacketTranslator { return; } team.removeEntities(packet.getPlayers()); + updatePlayerNameTags(session, team, packet.getPlayers(), true); break; case REMOVE: scoreboard.removeTeam(packet.getTeamName()); + if (team != null) { + updatePlayerNameTags(session, team, team.getEntities(), true); + } break; } @@ -113,6 +132,31 @@ public class JavaTeamTranslator extends PacketTranslator { } } + private void updatePlayerNameTags(GeyserSession session, Team team, String[] teamEntities, boolean remove) { + for (PlayerEntity entity : session.getEntityCache().getPlayerEntities()) { + for (String name : teamEntities) { + if (entity.getUsername().equals(name)) { + String currentName = entity.getMetadata().getString(EntityData.NAMETAG); + String newName; + if (remove) { + // Set the nametag to their default + newName = entity.getUsername(); + } else { + newName = team.getDisplayNameFor(currentName, session.getPlayerEntity().getUsername()); + } + if (!newName.equals(currentName)) { + entity.getMetadata().put(EntityData.NAMETAG, newName); + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.getMetadata().put(EntityData.NAMETAG, newName); + packet.setRuntimeEntityId(entity.getGeyserId()); + session.sendUpstreamPacket(packet); + } + break; + } + } + } + } + private Set toPlayerSet(String[] players) { return new ObjectOpenHashSet<>(players); } 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 c293b7a88..997fad50b 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 @@ -48,6 +48,7 @@ public class JavaUpdateScoreTranslator extends PacketTranslator scores = new ConcurrentHashMap<>(); - // todo add a 'to add' map so that we don't have to use a concurrentHashMap private Objective(Scoreboard scoreboard) { this.id = scoreboard.getNextId().getAndIncrement(); diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java index 79c90b096..0361c55c0 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Score.java @@ -35,11 +35,13 @@ public final class Score { private final long id; private final String name; private ScoreInfo cachedInfo; + @Getter + private boolean hasSentBelowName = false; /** * Changes that have been made since the last cached data. */ - private Score.ScoreData currentData; + private final Score.ScoreData currentData; /** * The data that is currently displayed to the Bedrock client. */ @@ -72,14 +74,14 @@ public final class Score { if (currentData.team != null && team != null) { if (!currentData.team.equals(team)) { currentData.team = team; - currentData.updateType = UpdateType.UPDATE; + setUpdateType(UpdateType.UPDATE); } return this; } // simplified from (this.team != null && team == null) || (this.team == null && team != null) if (currentData.team != null || team != null) { currentData.team = team; - currentData.updateType = UpdateType.UPDATE; + setUpdateType(UpdateType.UPDATE); } return this; } @@ -124,6 +126,11 @@ public final class Score { cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name); } + public Score setHasSentBelowName(boolean hasSentBelowName) { + this.hasSentBelowName = hasSentBelowName; + return this; + } + @Getter public static final class ScoreData { protected UpdateType updateType; diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java index 28cf0f2ce..8c996e5f8 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Scoreboard.java @@ -27,15 +27,19 @@ package org.geysermc.connector.scoreboard; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.nukkitx.protocol.bedrock.data.ScoreInfo; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.RemoveObjectivePacket; import com.nukkitx.protocol.bedrock.packet.SetDisplayObjectivePacket; +import com.nukkitx.protocol.bedrock.packet.SetEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.SetScorePacket; import lombok.Getter; 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.utils.LanguageUtils; +import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; @@ -49,9 +53,12 @@ public final class Scoreboard { private final AtomicLong nextId = new AtomicLong(0); private final Map objectives = new HashMap<>(); + @Getter private final Map objectiveSlots = new HashMap<>(); - private final Map teams = new ConcurrentHashMap<>(); // updated on multiple threads - // todo add a 'to add' map so that we don't have to use a concurrentHashMap + /** + * Updated on multiple threads + */ + private final Map teams = new ConcurrentHashMap<>(); private int lastAddScoreCount = 0; private int lastRemoveScoreCount = 0; @@ -183,6 +190,7 @@ public final class Scoreboard { setScorePacket.setAction(SetScorePacket.Action.REMOVE); setScorePacket.setInfos(removeScores); session.sendUpstreamPacket(setScorePacket); + System.out.println(setScorePacket); } if (!addScores.isEmpty()) { @@ -190,6 +198,7 @@ public final class Scoreboard { setScorePacket.setAction(SetScorePacket.Action.SET); setScorePacket.setInfos(addScores); session.sendUpstreamPacket(setScorePacket); + System.out.println(setScorePacket); } // prevents crashes in some cases @@ -241,6 +250,8 @@ public final class Scoreboard { boolean objectiveAdd = objective.getUpdateType() == ADD; boolean objectiveRemove = objective.getUpdateType() == REMOVE; + boolean isBelowName = objective.getDisplaySlot() == ScoreboardPosition.BELOW_NAME; + for (Score score : objective.getScores().values()) { Team team = score.getTeam(); @@ -285,6 +296,15 @@ public final class Scoreboard { objective.removeScore0(score.getName()); } + if (score.getUpdateType() != REMOVE && !objectiveRemove && isBelowName && add) { + PlayerEntity entity = session.getEntityCache().getPlayerEntity(score.getName()); + if (entity != null) { + sendBelowNameUpdate(objective, score, entity); + } + } else if ((objectiveRemove || score.getUpdateType() == REMOVE) && (isBelowName || score.isHasSentBelowName())) { + removeBelowName(score); + } + score.setUpdateType(NOTHING); } @@ -296,6 +316,7 @@ public final class Scoreboard { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); + System.out.println(removeObjectivePacket); } if ((objectiveAdd || objectiveUpdate) && !objectiveRemove) { @@ -306,12 +327,14 @@ public final class Scoreboard { displayObjectivePacket.setDisplaySlot(objective.getDisplaySlotName()); displayObjectivePacket.setSortOrder(1); // ?? session.sendUpstreamPacket(displayObjectivePacket); + System.out.println(displayObjectivePacket); } objective.setUpdateType(NOTHING); } private void deactivateObjective(Objective objective) { + System.out.println("Deactivating objective " + objective.getObjectiveName()); // Scoreboard has been removed already if (objective.getScores() == null) { return; @@ -320,6 +343,9 @@ public final class Scoreboard { List removedScores = new ArrayList<>(objective.getScores().size()); for (Score score : objective.getScores().values()) { removedScores.add(score.getCachedInfo()); + if (score.isHasSentBelowName()) { + removeBelowName(score); + } } objective.deactivate(); @@ -328,13 +354,16 @@ public final class Scoreboard { scorePacket.setAction(SetScorePacket.Action.REMOVE); scorePacket.setInfos(removedScores); session.sendUpstreamPacket(scorePacket); + System.out.println(scorePacket); RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); + System.out.println(removeObjectivePacket); } public void deleteObjective(Objective objective) { + System.out.println("Deleting objective " + objective.getObjectiveName()); objectives.remove(objective.getObjectiveName()); Objective storedSlot = objectiveSlots.get(objective.getDisplaySlot()); @@ -349,6 +378,7 @@ public final class Scoreboard { RemoveObjectivePacket removeObjectivePacket = new RemoveObjectivePacket(); removeObjectivePacket.setObjectiveId(objective.getObjectiveName()); session.sendUpstreamPacket(removeObjectivePacket); + System.out.println(removeObjectivePacket); } public Team getTeamFor(String entity) { @@ -359,4 +389,30 @@ public final class Scoreboard { } return null; } + + public void sendBelowNameUpdate(Objective objective, @Nullable Score score, PlayerEntity entity) { + // Show the belowname information this player + // Even if this player doesn't have a score, the display string is still updated for them + String displayString = score == null ? "0" : score.getCurrentData().getScore() + " " + objective.getDisplayName(); + entity.getMetadata().put(EntityData.SCORE_TAG, displayString); + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(entity.getGeyserId()); + packet.getMetadata().put(EntityData.SCORE_TAG, displayString); + session.sendUpstreamPacket(packet); + if (score != null) { + score.setHasSentBelowName(true); + } + } + + public void removeBelowName(Score score) { + // Clear the tag from the player + PlayerEntity entity = session.getEntityCache().getPlayerEntity(score.getName()); + if (entity != null) { + entity.getMetadata().remove(EntityData.SCORE_TAG); + SetEntityDataPacket packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(entity.getGeyserId()); + packet.getMetadata().put(EntityData.SCORE_TAG, ""); + session.sendUpstreamPacket(packet); + } + } } diff --git a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java index 6b2d54e8a..0963a1a2b 100644 --- a/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java +++ b/connector/src/main/java/org/geysermc/connector/scoreboard/Team.java @@ -32,6 +32,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import org.geysermc.connector.network.translators.chat.MessageTranslator; import java.util.ArrayList; import java.util.List; @@ -76,6 +77,10 @@ public final class Team { } } + public String[] getEntities() { + return entities.toArray(new String[0]); + } + public Team addEntities(String... names) { List added = new ArrayList<>(); for (String name : names) { @@ -198,6 +203,22 @@ public final class Team { return true; } + /** + * Get the display name of an entity on this team in relation to another entity. + * + * @param username the username of the player on this team + * @param otherEntity the other entity, for determining visibility + * @return the final display name + */ + public String getDisplayNameFor(String username, String otherEntity) { + String displayName = ""; + if (isVisibleFor(otherEntity)) { + displayName = MessageTranslator.toChatColor(color) + username; + displayName = currentData.getDisplayName(displayName); + } + return displayName; + } + @Override public int hashCode() { return id.hashCode();