Translate scoreboard display name and number format (#4567)

The following caveats apply because selectively removing or modifying the score numbers on Bedrock cannot be done without removing all of them:
* "blank" and "styled" number formats are ignored for "list" and "sidebar" display slots
* The "fixed" number format is appended to the end of the "list" and "sidebar" entry names instead of replacing the score number
* The "below_name" slot has no limitations and displays identically to Java
This commit is contained in:
ookiegajwa 2024-04-18 20:23:33 -05:00 committed by GitHub
parent ae96ad2ec4
commit 0cc2921eda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 157 additions and 35 deletions

View File

@ -25,6 +25,11 @@
package org.geysermc.geyser.entity.type.player; package org.geysermc.geyser.entity.type.player;
import com.github.steveice10.mc.protocol.codec.NbtComponentSerializer;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.BlankFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.FixedFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.StyledFormat;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; 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.entity.metadata.Pose;
import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.BooleanEntityMetadata;
@ -33,6 +38,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.type.FloatEnt
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -64,6 +70,7 @@ import org.geysermc.geyser.scoreboard.Score;
import org.geysermc.geyser.scoreboard.Team; import org.geysermc.geyser.scoreboard.Team;
import org.geysermc.geyser.scoreboard.UpdateType; import org.geysermc.geyser.scoreboard.UpdateType;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator; import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils; import org.geysermc.geyser.util.ChunkUtils;
@ -415,14 +422,36 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public void setBelowNameText(Objective objective) { public void setBelowNameText(Objective objective) {
if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) { if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) {
int amount;
Score score = objective.getScores().get(username); Score score = objective.getScores().get(username);
String numberString;
NumberFormat numberFormat;
int amount;
if (score != null) { if (score != null) {
amount = score.getCurrentData().getScore(); amount = score.getScore();
numberFormat = score.getNumberFormat();
if (numberFormat == null) {
numberFormat = objective.getNumberFormat();
}
} else { } else {
amount = 0; amount = 0;
numberFormat = objective.getNumberFormat();
} }
String displayString = amount + " " + objective.getDisplayName();
if (numberFormat instanceof BlankFormat) {
numberString = "";
} else if (numberFormat instanceof FixedFormat fixedFormat) {
numberString = MessageTranslator.convertMessage(fixedFormat.getValue());
} else if (numberFormat instanceof StyledFormat styledFormat) {
CompoundTag styledAmount = styledFormat.getStyle().clone();
styledAmount.put(new StringTag("text", String.valueOf(amount)));
numberString = MessageTranslator.convertJsonMessage(
NbtComponentSerializer.tagComponentToJson(styledAmount).toString());
} else {
numberString = String.valueOf(amount);
}
String displayString = numberString + " " + ChatColor.RESET + objective.getDisplayName();
if (valid) { if (valid) {
// Already spawned - we still need to run the rest of this code because the spawn packet will be // Already spawned - we still need to run the rest of this code because the spawn packet will be
@ -431,13 +460,22 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
packet.setRuntimeEntityId(geyserId); packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, displayString); packet.getMetadata().put(EntityDataTypes.SCORE, displayString);
session.sendUpstreamPacket(packet); session.sendUpstreamPacket(packet);
} else {
// Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity
dirtyMetadata.put(EntityDataTypes.SCORE, displayString);
}
} else {
if (valid) {
SetEntityDataPacket packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
session.sendUpstreamPacket(packet);
} else {
// Not spawned yet, store score value in dirtyMetadata to be picked up by #spawnEntity
dirtyMetadata.put(EntityDataTypes.SCORE, "");
} }
} else if (valid) {
SetEntityDataPacket packet = new SetEntityDataPacket();
packet.setRuntimeEntityId(geyserId);
packet.getMetadata().put(EntityDataTypes.SCORE, "");
session.sendUpstreamPacket(packet);
} }
} }
/** /**

View File

@ -25,13 +25,16 @@
package org.geysermc.geyser.scoreboard; package org.geysermc.geyser.scoreboard;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@Getter @Getter
@ -47,6 +50,7 @@ public final class Objective {
private ScoreboardPosition displaySlot; private ScoreboardPosition displaySlot;
private String displaySlotName; private String displaySlotName;
private String displayName = "unknown"; private String displayName = "unknown";
private NumberFormat numberFormat;
private int type = 0; // 0 = integer, 1 = heart private int type = 0; // 0 = integer, 1 = heart
private Map<String, Score> scores = new ConcurrentHashMap<>(); private Map<String, Score> scores = new ConcurrentHashMap<>();
@ -85,25 +89,29 @@ public final class Objective {
}; };
} }
public void registerScore(String id, int score) { public void registerScore(String id, int score, Component displayName, NumberFormat numberFormat) {
if (!scores.containsKey(id)) { if (!scores.containsKey(id)) {
long scoreId = scoreboard.getNextId().getAndIncrement(); long scoreId = scoreboard.getNextId().getAndIncrement();
Score scoreObject = new Score(scoreId, id) Score scoreObject = new Score(scoreId, id)
.setScore(score) .setScore(score)
.setTeam(scoreboard.getTeamFor(id)) .setTeam(scoreboard.getTeamFor(id))
.setDisplayName(displayName)
.setNumberFormat(numberFormat)
.setUpdateType(UpdateType.ADD); .setUpdateType(UpdateType.ADD);
scores.put(id, scoreObject); scores.put(id, scoreObject);
} }
} }
public void setScore(String id, int score) { public void setScore(String id, int score, Component displayName, NumberFormat numberFormat) {
Score stored = scores.get(id); Score stored = scores.get(id);
if (stored != null) { if (stored != null) {
stored.setScore(score) stored.setScore(score)
.setDisplayName(displayName)
.setNumberFormat(numberFormat)
.setUpdateType(UpdateType.UPDATE); .setUpdateType(UpdateType.UPDATE);
return; return;
} }
registerScore(id, score); registerScore(id, score, displayName, numberFormat);
} }
public void removeScore(String id) { public void removeScore(String id) {
@ -128,6 +136,26 @@ public final class Objective {
return this; return this;
} }
public Objective setNumberFormat(NumberFormat numberFormat) {
if (Objects.equals(this.numberFormat, numberFormat)) {
return this;
}
this.numberFormat = numberFormat;
if (updateType == UpdateType.NOTHING) {
updateType = UpdateType.UPDATE;
}
// Update the number format for scores that are following this objective's number format
for (Score score : scores.values()) {
if (score.getNumberFormat() == null) {
score.setUpdateType(UpdateType.UPDATE);
}
}
return this;
}
public Objective setType(int type) { public Objective setType(int type) {
this.type = type; this.type = type;
if (updateType == UpdateType.NOTHING) { if (updateType == UpdateType.NOTHING) {

View File

@ -25,9 +25,16 @@
package org.geysermc.geyser.scoreboard; package org.geysermc.geyser.scoreboard;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.FixedFormat;
import com.github.steveice10.mc.protocol.data.game.chat.numbers.NumberFormat;
import net.kyori.adventure.text.Component;
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
import lombok.Getter; import lombok.Getter;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.Objects;
@Getter @Getter
@Accessors(chain = true) @Accessors(chain = true)
@ -52,6 +59,10 @@ public final class Score {
} }
public String getDisplayName() { public String getDisplayName() {
String displayName = cachedData.displayName;
if (displayName != null) {
return displayName;
}
Team team = cachedData.team; Team team = cachedData.team;
if (team != null) { if (team != null) {
return team.getDisplayName(name); return team.getDisplayName(name);
@ -88,6 +99,35 @@ public final class Score {
return this; return this;
} }
public Score setDisplayName(Component displayName) {
if (currentData.displayName != null && displayName != null) {
String convertedDisplayName = MessageTranslator.convertMessage(displayName);
if (!currentData.displayName.equals(convertedDisplayName)) {
currentData.displayName = convertedDisplayName;
setUpdateType(UpdateType.UPDATE);
}
return this;
}
// simplified from (this.displayName != null && displayName == null) || (this.displayName == null && displayName != null)
if (currentData.displayName != null || displayName != null) {
currentData.displayName = MessageTranslator.convertMessage(displayName);
setUpdateType(UpdateType.UPDATE);
}
return this;
}
public NumberFormat getNumberFormat() {
return currentData.numberFormat;
}
public Score setNumberFormat(NumberFormat numberFormat) {
if (!Objects.equals(currentData.numberFormat, numberFormat)) {
currentData.numberFormat = numberFormat;
setUpdateType(UpdateType.UPDATE);
}
return this;
}
public UpdateType getUpdateType() { public UpdateType getUpdateType() {
return currentData.updateType; return currentData.updateType;
} }
@ -105,7 +145,7 @@ public final class Score {
(currentData.team != null && currentData.team.shouldUpdate()); (currentData.team != null && currentData.team.shouldUpdate());
} }
public void update(String objectiveName) { public void update(Objective objective) {
if (cachedData == null) { if (cachedData == null) {
cachedData = new ScoreData(); cachedData = new ScoreData();
cachedData.updateType = UpdateType.ADD; cachedData.updateType = UpdateType.ADD;
@ -119,13 +159,26 @@ public final class Score {
currentData.changed = false; currentData.changed = false;
cachedData.team = currentData.team; cachedData.team = currentData.team;
cachedData.score = currentData.score; cachedData.score = currentData.score;
cachedData.displayName = currentData.displayName;
cachedData.numberFormat = currentData.numberFormat;
String name = this.name; String name = this.name;
if (cachedData.team != null) { if (cachedData.displayName != null) {
name = cachedData.displayName;
} else if (cachedData.team != null) {
cachedData.team.prepareUpdate(); cachedData.team.prepareUpdate();
name = cachedData.team.getDisplayName(name); name = cachedData.team.getDisplayName(name);
} }
cachedInfo = new ScoreInfo(id, objectiveName, cachedData.score, name);
NumberFormat numberFormat = cachedData.numberFormat;
if (numberFormat == null) {
numberFormat = objective.getNumberFormat();
}
if (numberFormat instanceof FixedFormat fixedFormat) {
name += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue());
}
cachedInfo = new ScoreInfo(id, objective.getObjectiveName(), cachedData.score, name);
} }
@Getter @Getter
@ -136,6 +189,9 @@ public final class Score {
private Team team; private Team team;
private int score; private int score;
private String displayName;
private NumberFormat numberFormat;
private ScoreData() { private ScoreData() {
updateType = UpdateType.ADD; updateType = UpdateType.ADD;
} }

View File

@ -240,7 +240,7 @@ public final class Scoreboard {
boolean update = score.shouldUpdate(); boolean update = score.shouldUpdate();
if (update) { if (update) {
score.update(objective.getObjectiveName()); score.update(objective);
} }
if (score.getUpdateType() != REMOVE && update) { if (score.getUpdateType() != REMOVE && update) {
@ -281,7 +281,7 @@ public final class Scoreboard {
} }
if (score.shouldUpdate()) { if (score.shouldUpdate()) {
score.update(objective.getObjectiveName()); score.update(objective);
add = true; add = true;
} }

View File

@ -54,7 +54,7 @@ public class JavaResetScorePacket extends PacketTranslator<ClientboundResetScore
// as described below // as described below
if (belowName != null) { if (belowName != null) {
JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner(), 0); JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner());
} }
} else { } else {
Objective objective = scoreboard.getObjective(packet.getObjective()); Objective objective = scoreboard.getObjective(packet.getObjective());
@ -64,7 +64,7 @@ public class JavaResetScorePacket extends PacketTranslator<ClientboundResetScore
// attached to this score. // attached to this score.
if (objective == belowName) { if (objective == belowName) {
// Update the score on this player to now reflect 0 // Update the score on this player to now reflect 0
JavaSetScoreTranslator.setBelowName(session, objective, packet.getOwner(), 0); JavaSetScoreTranslator.setBelowName(session, objective, packet.getOwner());
} }
} }

View File

@ -47,7 +47,6 @@ public class JavaSetObjectiveTranslator extends PacketTranslator<ClientboundSetO
@Override @Override
public void translate(GeyserSession session, ClientboundSetObjectivePacket packet) { public void translate(GeyserSession session, ClientboundSetObjectivePacket packet) {
// todo 1.20.3 unused NumberFormat ?
WorldCache worldCache = session.getWorldCache(); WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard(); Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
@ -64,8 +63,19 @@ public class JavaSetObjectiveTranslator extends PacketTranslator<ClientboundSetO
} }
switch (packet.getAction()) { switch (packet.getAction()) {
case ADD, UPDATE -> objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName())) case ADD, UPDATE -> {
.setType(packet.getType().ordinal()); objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setNumberFormat(packet.getNumberFormat())
.setType(packet.getType().ordinal());
if (objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) {
// Update the score tag of all players
for (PlayerEntity entity : session.getEntityCache().getAllPlayerEntities()) {
if (entity.isValid()) {
entity.setBelowNameText(objective);
}
}
}
}
case REMOVE -> { case REMOVE -> {
scoreboard.unregisterObjective(packet.getName()); scoreboard.unregisterObjective(packet.getName());
if (objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) { if (objective != null && objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME)) {

View File

@ -28,8 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.scoreboard;
import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition; import com.github.steveice10.mc.protocol.data.game.scoreboard.ScoreboardPosition;
import com.github.steveice10.mc.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket; import com.github.steveice10.mc.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.GeyserLogger; import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity;
@ -54,7 +52,6 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
@Override @Override
public void translate(GeyserSession session, ClientboundSetScorePacket packet) { public void translate(GeyserSession session, ClientboundSetScorePacket packet) {
// todo 1.20.3 unused display and number format?
WorldCache worldCache = session.getWorldCache(); WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard(); Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond(); int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
@ -71,10 +68,10 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
// attached to this score. // attached to this score.
boolean isBelowName = objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME); boolean isBelowName = objective == scoreboard.getObjectiveSlots().get(ScoreboardPosition.BELOW_NAME);
objective.setScore(packet.getOwner(), packet.getValue()); objective.setScore(packet.getOwner(), packet.getValue(), packet.getDisplay(), packet.getNumberFormat());
if (isBelowName) { if (isBelowName) {
// Update the below name score on this player // Update the below name score on this player
setBelowName(session, objective, packet.getOwner(), packet.getValue()); setBelowName(session, objective, packet.getOwner());
} }
// ScoreboardUpdater will handle it for us if the packets per second // ScoreboardUpdater will handle it for us if the packets per second
@ -87,20 +84,13 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
/** /**
* @param objective the objective that currently resides on the below name display slot * @param objective the objective that currently resides on the below name display slot
*/ */
static void setBelowName(GeyserSession session, Objective objective, String username, int count) { static void setBelowName(GeyserSession session, Objective objective, String username) {
PlayerEntity entity = getOtherPlayerEntity(session, username); PlayerEntity entity = getOtherPlayerEntity(session, username);
if (entity == null) { if (entity == null) {
return; return;
} }
String displayString = count + " " + objective.getDisplayName(); entity.setBelowNameText(objective);
// Of note: unlike Bedrock, if there is an objective in the below name slot, everyone has a display
entity.getDirtyMetadata().put(EntityDataTypes.SCORE, displayString);
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().put(EntityDataTypes.SCORE, displayString);
session.sendUpstreamPacket(entityDataPacket);
} }
private static @Nullable PlayerEntity getOtherPlayerEntity(GeyserSession session, String username) { private static @Nullable PlayerEntity getOtherPlayerEntity(GeyserSession session, String username) {