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;
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.Pose;
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.TeamColor;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
import com.github.steveice10.opennbt.tag.builtin.StringTag;
import lombok.Getter;
import lombok.Setter;
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.UpdateType;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
@ -415,14 +422,36 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
public void setBelowNameText(Objective objective) {
if (objective != null && objective.getUpdateType() != UpdateType.REMOVE) {
int amount;
Score score = objective.getScores().get(username);
String numberString;
NumberFormat numberFormat;
int amount;
if (score != null) {
amount = score.getCurrentData().getScore();
amount = score.getScore();
numberFormat = score.getNumberFormat();
if (numberFormat == null) {
numberFormat = objective.getNumberFormat();
}
} else {
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) {
// 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.getMetadata().put(EntityDataTypes.SCORE, displayString);
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;
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.TeamColor;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@Getter
@ -47,6 +50,7 @@ public final class Objective {
private ScoreboardPosition displaySlot;
private String displaySlotName;
private String displayName = "unknown";
private NumberFormat numberFormat;
private int type = 0; // 0 = integer, 1 = heart
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)) {
long scoreId = scoreboard.getNextId().getAndIncrement();
Score scoreObject = new Score(scoreId, id)
.setScore(score)
.setTeam(scoreboard.getTeamFor(id))
.setDisplayName(displayName)
.setNumberFormat(numberFormat)
.setUpdateType(UpdateType.ADD);
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);
if (stored != null) {
stored.setScore(score)
.setDisplayName(displayName)
.setNumberFormat(numberFormat)
.setUpdateType(UpdateType.UPDATE);
return;
}
registerScore(id, score);
registerScore(id, score, displayName, numberFormat);
}
public void removeScore(String id) {
@ -128,6 +136,26 @@ public final class Objective {
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) {
this.type = type;
if (updateType == UpdateType.NOTHING) {

View File

@ -25,9 +25,16 @@
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 lombok.Getter;
import lombok.experimental.Accessors;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.translator.text.MessageTranslator;
import java.util.Objects;
@Getter
@Accessors(chain = true)
@ -52,6 +59,10 @@ public final class Score {
}
public String getDisplayName() {
String displayName = cachedData.displayName;
if (displayName != null) {
return displayName;
}
Team team = cachedData.team;
if (team != null) {
return team.getDisplayName(name);
@ -88,6 +99,35 @@ public final class Score {
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() {
return currentData.updateType;
}
@ -105,7 +145,7 @@ public final class Score {
(currentData.team != null && currentData.team.shouldUpdate());
}
public void update(String objectiveName) {
public void update(Objective objective) {
if (cachedData == null) {
cachedData = new ScoreData();
cachedData.updateType = UpdateType.ADD;
@ -119,13 +159,26 @@ public final class Score {
currentData.changed = false;
cachedData.team = currentData.team;
cachedData.score = currentData.score;
cachedData.displayName = currentData.displayName;
cachedData.numberFormat = currentData.numberFormat;
String name = this.name;
if (cachedData.team != null) {
if (cachedData.displayName != null) {
name = cachedData.displayName;
} else if (cachedData.team != null) {
cachedData.team.prepareUpdate();
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
@ -136,6 +189,9 @@ public final class Score {
private Team team;
private int score;
private String displayName;
private NumberFormat numberFormat;
private ScoreData() {
updateType = UpdateType.ADD;
}

View File

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

View File

@ -54,7 +54,7 @@ public class JavaResetScorePacket extends PacketTranslator<ClientboundResetScore
// as described below
if (belowName != null) {
JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner(), 0);
JavaSetScoreTranslator.setBelowName(session, belowName, packet.getOwner());
}
} else {
Objective objective = scoreboard.getObjective(packet.getObjective());
@ -64,7 +64,7 @@ public class JavaResetScorePacket extends PacketTranslator<ClientboundResetScore
// attached to this score.
if (objective == belowName) {
// 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
public void translate(GeyserSession session, ClientboundSetObjectivePacket packet) {
// todo 1.20.3 unused NumberFormat ?
WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
@ -64,8 +63,19 @@ public class JavaSetObjectiveTranslator extends PacketTranslator<ClientboundSetO
}
switch (packet.getAction()) {
case ADD, UPDATE -> objective.setDisplayName(MessageTranslator.convertMessage(packet.getDisplayName()))
.setType(packet.getType().ordinal());
case ADD, UPDATE -> {
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 -> {
scoreboard.unregisterObjective(packet.getName());
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.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
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.GeyserLogger;
import org.geysermc.geyser.entity.type.player.PlayerEntity;
@ -54,7 +52,6 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
@Override
public void translate(GeyserSession session, ClientboundSetScorePacket packet) {
// todo 1.20.3 unused display and number format?
WorldCache worldCache = session.getWorldCache();
Scoreboard scoreboard = worldCache.getScoreboard();
int pps = worldCache.increaseAndGetScoreboardPacketsPerSecond();
@ -71,10 +68,10 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
// attached to this score.
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) {
// 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
@ -87,20 +84,13 @@ public class JavaSetScoreTranslator extends PacketTranslator<ClientboundSetScore
/**
* @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);
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.getDirtyMetadata().put(EntityDataTypes.SCORE, displayString);
SetEntityDataPacket entityDataPacket = new SetEntityDataPacket();
entityDataPacket.setRuntimeEntityId(entity.getGeyserId());
entityDataPacket.getMetadata().put(EntityDataTypes.SCORE, displayString);
session.sendUpstreamPacket(entityDataPacket);
entity.setBelowNameText(objective);
}
private static @Nullable PlayerEntity getOtherPlayerEntity(GeyserSession session, String username) {