Add below name scoreboard support

Fixes #1927
This commit is contained in:
Camotoy 2021-10-01 16:24:25 -04:00
parent 50896a24e7
commit 86c0c009e7
No known key found for this signature in database
GPG key ID: 7EEFB66FE798081F
5 changed files with 145 additions and 10 deletions

View file

@ -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);
}
}
}
}

View file

@ -138,6 +138,10 @@ public class EntityCache {
return playerEntities.remove(uuid);
}
public Collection<PlayerEntity> getAllPlayerEntities() {
return playerEntities.values();
}
public void addBossBar(UUID uuid, BossBar bossBar) {
bossBars.put(uuid, bossBar);
bossBar.addBossBar();

View file

@ -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<ServerSc
switch (packet.getAction()) {
case ADD, UPDATE -> 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()) {

View file

@ -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<ServerUpdateScor
return;
}
// If this is the objective that is in use to show the below name text, we need to update the player
// attached to this score.
boolean isBelowName = objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME);
switch (packet.getAction()) {
case ADD_OR_UPDATE:
case ADD_OR_UPDATE -> {
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<ServerUpdateScor
scoreboard.onUpdate();
}
}
/**
* @param objective the objective that currently resides on the below name display slot
*/
private void setBelowName(GeyserSession session, Objective objective, String username, int count) {
PlayerEntity entity = getPlayerEntity(session, username);
if (entity == null) {
return;
}
String displayString = count + " " + objective.getDisplayName();
// Of note: unlike Bedrock, if there is an objective in the below name slot, everyone has a display
entity.getMetadata().put(EntityData.SCORE_TAG, displayString);
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().put(EntityData.SCORE_TAG, displayString);
session.sendUpstreamPacket(entityDataPacket);
}
private PlayerEntity getPlayerEntity(GeyserSession session, String username) {
// We don't care about the session player, because... they're not going to be seeing their own score
if (session.getPlayerEntity().getUsername().equals(username)) {
return null;
}
for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) {
if (entity.getUsername().equals(username)) {
if (entity.isValid()) {
return entity;
} else {
// The below name text will be applied on spawn
return null;
}
}
}
return null;
}
}

View file

@ -33,6 +33,7 @@ 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;
@ -49,7 +50,8 @@ public final class Scoreboard {
private final AtomicLong nextId = new AtomicLong(0);
private final Map<String, Objective> objectives = new ConcurrentHashMap<>();
private final Map<ScoreboardPosition, Objective> objectiveSlots = new HashMap<>();
@Getter
private final Map<ScoreboardPosition, Objective> objectiveSlots = new EnumMap<>(ScoreboardPosition.class);
private final Map<String, Team> 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) {