Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-11-20 15:00:11 +01:00
Fixed some issues and added some initial tests
Dieser Commit ist enthalten in:
Ursprung
a5345b37fb
Commit
859d8e281f
@ -57,6 +57,7 @@ dependencies {
|
||||
|
||||
// Test
|
||||
testImplementation(libs.junit)
|
||||
testImplementation(libs.mockito)
|
||||
|
||||
// Annotation Processors
|
||||
compileOnly(projects.ap)
|
||||
|
@ -41,6 +41,7 @@ import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.GeyserDirtyMetadata;
|
||||
import org.geysermc.geyser.entity.properties.GeyserEntityPropertyManager;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.scoreboard.Team;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.EntityUtils;
|
||||
@ -102,7 +103,7 @@ public class Entity implements GeyserEntity {
|
||||
@Setter(AccessLevel.NONE)
|
||||
private float boundingBoxWidth;
|
||||
@Setter(AccessLevel.NONE)
|
||||
private String displayName = "";
|
||||
private String displayName;
|
||||
@Setter(AccessLevel.NONE)
|
||||
protected boolean silent = false;
|
||||
/* Metadata end */
|
||||
@ -131,6 +132,7 @@ public class Entity implements GeyserEntity {
|
||||
|
||||
public Entity(GeyserSession session, int entityId, long geyserId, UUID uuid, EntityDefinition<?> definition, Vector3f position, Vector3f motion, float yaw, float pitch, float headYaw) {
|
||||
this.session = session;
|
||||
this.displayName = EntityUtils.entityTypeName(definition.entityType());
|
||||
|
||||
this.entityId = entityId;
|
||||
this.geyserId = geyserId;
|
||||
@ -346,7 +348,7 @@ public class Entity implements GeyserEntity {
|
||||
* Sends the Bedrock metadata to the client
|
||||
*/
|
||||
public void updateBedrockMetadata() {
|
||||
if (!valid) {
|
||||
if (!isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -415,27 +417,42 @@ public class Entity implements GeyserEntity {
|
||||
return 300;
|
||||
}
|
||||
|
||||
public String teamIdentifier() {
|
||||
return uuid.toString();
|
||||
}
|
||||
|
||||
public void setDisplayName(EntityMetadata<Optional<Component>, ?> entityMetadata) {
|
||||
// the difference between displayName and nametag aren't important for non-living entities and for players,
|
||||
// but the displayName is needed for living entities that are part of a scoreboard team.
|
||||
// For them the nametag is prefix + displayName + suffix
|
||||
// displayName is shown when always display name is enabled. Either with or without team.
|
||||
// That's why there are both a displayName and a nametag variable.
|
||||
// Displayname is ignored for players, and is always their username.
|
||||
Optional<Component> name = entityMetadata.getValue();
|
||||
if (name.isPresent()) {
|
||||
var displayName = MessageTranslator.convertMessage(name.get(), session.locale());
|
||||
this.displayName = displayName;
|
||||
setNametag(displayName, true);
|
||||
} else {
|
||||
this.displayName = "";
|
||||
setNametag(null, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// if no displayName is set, use entity name (ENDER_DRAGON -> Ender Dragon)
|
||||
// maybe we can/should use a translatable here instead?
|
||||
this.displayName = EntityUtils.entityTypeName(definition.entityType());
|
||||
setNametag(null, true);
|
||||
}
|
||||
|
||||
protected void setNametag(@Nullable String nametag, boolean fromDisplayName) {
|
||||
var hide = nametag == null;
|
||||
if (hide) {
|
||||
// ensure that the team format is used when nametag changes
|
||||
if (nametag != null && fromDisplayName) {
|
||||
var team = session.getWorldCache().getScoreboard().getTeamFor(teamIdentifier());
|
||||
if (team != null) {
|
||||
updateNametag(team);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (nametag == null) {
|
||||
nametag = "";
|
||||
}
|
||||
var changed = Objects.equals(this.nametag, nametag);
|
||||
var changed = !Objects.equals(this.nametag, nametag);
|
||||
this.nametag = nametag;
|
||||
// we only update metadata if the value has changed
|
||||
if (!changed) {
|
||||
@ -444,7 +461,29 @@ public class Entity implements GeyserEntity {
|
||||
|
||||
dirtyMetadata.put(EntityDataTypes.NAME, nametag);
|
||||
// if nametag (player with team) is hidden for player, so should the score (belowname)
|
||||
scoreVisibility(!hide);
|
||||
scoreVisibility(!nametag.isEmpty());
|
||||
}
|
||||
|
||||
public void updateNametag(@Nullable Team team) {
|
||||
// allow LivingEntity+ to have a different visibility check
|
||||
updateNametag(team, true);
|
||||
}
|
||||
|
||||
protected void updateNametag(@Nullable Team team, boolean visible) {
|
||||
if (team != null) {
|
||||
String newNametag;
|
||||
// (team) visibility is LivingEntity+, team displayName is Entity+
|
||||
if (visible) {
|
||||
newNametag = team.displayName(getDisplayName());
|
||||
} else {
|
||||
// The name is not visible to the session player; clear name
|
||||
newNametag = "";
|
||||
}
|
||||
setNametag(newNametag, false);
|
||||
return;
|
||||
}
|
||||
// The name has reset, if it was previously something else
|
||||
setNametag(null, false);
|
||||
}
|
||||
|
||||
protected void scoreVisibility(boolean show) {}
|
||||
|
@ -25,6 +25,11 @@
|
||||
|
||||
package org.geysermc.geyser.entity.type;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@ -47,7 +52,6 @@ import org.geysermc.geyser.registry.type.ItemMapping;
|
||||
import org.geysermc.geyser.scoreboard.Team;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.item.ItemTranslator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.AttributeUtils;
|
||||
import org.geysermc.geyser.util.InteractionResult;
|
||||
import org.geysermc.geyser.util.MathUtils;
|
||||
@ -66,9 +70,6 @@ import org.geysermc.mcprotocollib.protocol.data.game.level.particle.EntityEffect
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.Particle;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.level.particle.ParticleType;
|
||||
|
||||
import java.util.*;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class LivingEntity extends Entity {
|
||||
@ -146,45 +147,16 @@ public class LivingEntity extends Entity {
|
||||
dirtyMetadata.put(EntityDataTypes.STRUCTURAL_INTEGRITY, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateNametag(@Nullable Team team) {
|
||||
if (team != null) {
|
||||
String newNametag;
|
||||
if (team.isVisibleFor(session.getPlayerEntity().getUsername())) {
|
||||
TeamColor color = team.color();
|
||||
String chatColor = MessageTranslator.toChatColor(color);
|
||||
// We have to emulate what modern Java text already does for us and add the color to each section
|
||||
newNametag = chatColor + team.prefix() + chatColor + getDisplayName() + chatColor + team.suffix();
|
||||
} else {
|
||||
// The name is not visible to the session player; clear name
|
||||
newNametag = "";
|
||||
}
|
||||
setNametag(newNametag, false);
|
||||
return;
|
||||
}
|
||||
// The name has reset, if it was previously something else
|
||||
setNametag(null, false);
|
||||
// if name not visible, don't mark it as visible
|
||||
updateNametag(team, team == null || team.isVisibleFor(session.getPlayerEntity().getUsername()));
|
||||
}
|
||||
|
||||
public void hideNametag() {
|
||||
setNametag("", false);
|
||||
}
|
||||
|
||||
public String teamIdentifier() {
|
||||
return uuid.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setNametag(@Nullable String nametag, boolean fromDisplayName) {
|
||||
if (nametag != null && fromDisplayName) {
|
||||
var team = session.getWorldCache().getScoreboard().getTeamFor(teamIdentifier());
|
||||
if (team != null) {
|
||||
updateNametag(team);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.setNametag(nametag, fromDisplayName);
|
||||
}
|
||||
|
||||
public void setLivingEntityFlags(ByteEntityMetadata entityMetadata) {
|
||||
byte xd = entityMetadata.getPrimitiveValue();
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.entity.type.player;
|
||||
|
||||
import java.util.Objects;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
@ -37,12 +38,14 @@ import org.cloudburstmc.protocol.bedrock.data.AbilityLayer;
|
||||
import org.cloudburstmc.protocol.bedrock.data.GameType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.PlayerPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityFlag;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.*;
|
||||
import org.geysermc.geyser.api.entity.type.player.GeyserPlayerEntity;
|
||||
import org.geysermc.geyser.entity.EntityDefinition;
|
||||
import org.geysermc.geyser.entity.EntityDefinitions;
|
||||
import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
@ -61,6 +64,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.type.EntityType;
|
||||
|
||||
@Getter @Setter
|
||||
public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
@ -78,7 +82,7 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
|
||||
private String username;
|
||||
|
||||
private String cachedScore;
|
||||
private String cachedScore = "";
|
||||
private boolean scoreVisible = true;
|
||||
|
||||
/**
|
||||
@ -108,6 +112,31 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
this.texturesProperty = texturesProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not use! For testing purposes only
|
||||
*/
|
||||
public PlayerEntity(GeyserSession session, long geyserId, UUID uuid, String username) {
|
||||
super(
|
||||
session,
|
||||
-1,
|
||||
geyserId,
|
||||
uuid,
|
||||
EntityDefinition.builder(null).type(EntityType.PLAYER).build(false),
|
||||
Vector3f.ZERO,
|
||||
Vector3f.ZERO,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
this.username = username;
|
||||
this.nametag = username;
|
||||
this.texturesProperty = null;
|
||||
|
||||
// clear initial metadata
|
||||
dirtyMetadata.apply(new EntityDataMap());
|
||||
setFlagsDirty(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
@ -391,19 +420,26 @@ public class PlayerEntity extends LivingEntity implements GeyserPlayerEntity {
|
||||
text = "";
|
||||
}
|
||||
|
||||
var changed = !Objects.equals(cachedScore, text);
|
||||
cachedScore = text;
|
||||
if (scoreVisible) {
|
||||
if (isScoreVisible() && changed) {
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scoreVisibility(boolean show) {
|
||||
var changed = scoreVisible != show;
|
||||
var visibilityChanged = scoreVisible != show;
|
||||
scoreVisible = show;
|
||||
if (changed) {
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, show ? cachedScore : "");
|
||||
if (!visibilityChanged) {
|
||||
return;
|
||||
}
|
||||
// if the player has no cachedScore, we never have to change the score.
|
||||
// hide = set to "" (does nothing), show = change from "" (does nothing)
|
||||
if (cachedScore.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dirtyMetadata.put(EntityDataTypes.SCORE, show ? cachedScore : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,7 +83,7 @@ public final class Objective {
|
||||
}
|
||||
|
||||
public void updateProperties(Component displayNameComponent, ScoreType type, NumberFormat format) {
|
||||
var displayName = MessageTranslator.convertMessage(displayNameComponent, scoreboard.session().locale());
|
||||
var displayName = MessageTranslator.convertMessageRaw(displayNameComponent, scoreboard.session().locale());
|
||||
var changed = !Objects.equals(this.displayName, displayName) || this.type != type;
|
||||
|
||||
this.displayName = displayName;
|
||||
|
@ -31,8 +31,8 @@ import java.util.Set;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.LivingEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
|
||||
@ -45,7 +45,7 @@ public final class Team {
|
||||
private final String id;
|
||||
|
||||
private final Set<String> entities;
|
||||
private final Set<LivingEntity> managedEntities;
|
||||
private final Set<Entity> managedEntities;
|
||||
@NonNull private NameTagVisibility nameTagVisibility = NameTagVisibility.ALWAYS;
|
||||
private TeamColor color;
|
||||
|
||||
@ -68,23 +68,23 @@ public final class Team {
|
||||
this.id = id;
|
||||
this.entities = new ObjectOpenHashSet<>();
|
||||
this.managedEntities = new ObjectOpenHashSet<>();
|
||||
this.lastUpdate = LAST_UPDATE_DEFAULT;
|
||||
|
||||
addEntitiesNoUpdate(players);
|
||||
// this calls the update
|
||||
// doesn't call entity update
|
||||
updateProperties(name, prefix, suffix, visibility, color);
|
||||
// calls entitity update
|
||||
addEntities(players);
|
||||
lastUpdate = LAST_UPDATE_DEFAULT;
|
||||
}
|
||||
|
||||
public void addEntities(String... names) {
|
||||
addAddedEntities(addEntitiesNoUpdate(names));
|
||||
}
|
||||
|
||||
private Set<String> addEntitiesNoUpdate(String... names) {
|
||||
Set<String> added = new HashSet<>();
|
||||
for (String name : names) {
|
||||
if (entities.add(name)) {
|
||||
added.add(name);
|
||||
// go to next score if score is already present
|
||||
if (!entities.add(name)) {
|
||||
continue;
|
||||
}
|
||||
added.add(name);
|
||||
scoreboard.getPlayerToTeam().compute(name, (player, oldTeam) -> {
|
||||
if (oldTeam != null) {
|
||||
// Remove old team from this map, and from the set of players of the old team.
|
||||
@ -96,12 +96,12 @@ public final class Team {
|
||||
}
|
||||
|
||||
if (added.isEmpty()) {
|
||||
return added;
|
||||
return;
|
||||
}
|
||||
// we don't have to change our updateType,
|
||||
// because the scores itself need updating, not the team
|
||||
// because the scores themselves need updating, not the team
|
||||
scoreboard.setTeamFor(this, added);
|
||||
return added;
|
||||
addAddedEntities(added);
|
||||
}
|
||||
|
||||
public void removeEntities(String... names) {
|
||||
@ -120,7 +120,14 @@ public final class Team {
|
||||
}
|
||||
|
||||
public String displayName(String score) {
|
||||
return prefix + score + suffix;
|
||||
var chatColor = ChatColor.chatColorFor(color);
|
||||
// most sidebar plugins will use the reset color, because they don't want color
|
||||
// skip the unneeded double reset color in that case
|
||||
if (ChatColor.RESET.equals(chatColor)) {
|
||||
chatColor = "";
|
||||
}
|
||||
// also add reset because setting the color does not reset the formatting, unlike Java
|
||||
return chatColor + prefix + ChatColor.RESET + chatColor + score + ChatColor.RESET + chatColor + suffix;
|
||||
}
|
||||
|
||||
public boolean isVisibleFor(String entity) {
|
||||
@ -148,9 +155,9 @@ public final class Team {
|
||||
var oldVisible = isVisibleFor(playerName());
|
||||
var oldColor = this.color;
|
||||
|
||||
this.name = MessageTranslator.convertMessage(name, session().locale());
|
||||
this.prefix = MessageTranslator.convertMessage(prefix, session().locale());
|
||||
this.suffix = MessageTranslator.convertMessage(suffix, session().locale());
|
||||
this.name = MessageTranslator.convertMessageRaw(name, session().locale());
|
||||
this.prefix = MessageTranslator.convertMessageRaw(prefix, session().locale());
|
||||
this.suffix = MessageTranslator.convertMessageRaw(suffix, session().locale());
|
||||
// matches vanilla behaviour, the visibility is not reset (to ALWAYS) if it is null.
|
||||
// instead the visibility is not altered
|
||||
if (visibility != null) {
|
||||
@ -159,24 +166,9 @@ public final class Team {
|
||||
this.color = color;
|
||||
|
||||
if (lastUpdate == LAST_UPDATE_DEFAULT) {
|
||||
if (entities.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var hidden = false;
|
||||
if (nameTagVisibility != NameTagVisibility.ALWAYS && !isVisibleFor(playerName())) {
|
||||
// while the team has technically changed, we don't mark it as changed because the visibility
|
||||
// doesn't influence any of the display slots
|
||||
hideEntities();
|
||||
hidden = true;
|
||||
}
|
||||
|
||||
// addEntities is called after the initial updateProperties, so no need to do any entity updates here
|
||||
if (this.color != TeamColor.RESET || !this.prefix.isEmpty() || !this.suffix.isEmpty()) {
|
||||
markChanged();
|
||||
// we've already hidden the entities, so we don't have to update them again
|
||||
if (!hidden) {
|
||||
updateEntities();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -220,36 +212,32 @@ public final class Team {
|
||||
refreshAllEntities();
|
||||
return;
|
||||
}
|
||||
for (LivingEntity entity : managedEntities) {
|
||||
for (Entity entity : managedEntities) {
|
||||
entity.updateNametag(null);
|
||||
entity.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideEntities() {
|
||||
for (LivingEntity entity : managedEntities) {
|
||||
entity.hideNametag();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateEntities() {
|
||||
for (LivingEntity entity : managedEntities) {
|
||||
for (Entity entity : managedEntities) {
|
||||
entity.updateNametag(this);
|
||||
entity.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
private void addAddedEntities(Set<String> names) {
|
||||
// can't contain self if none are added
|
||||
if (names.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
var containsSelf = names.contains(playerName());
|
||||
|
||||
for (Entity entity : session().getEntityCache().getEntities().values()) {
|
||||
if (!(entity instanceof LivingEntity living)) {
|
||||
continue;
|
||||
}
|
||||
if (names.contains(living.teamIdentifier())) {
|
||||
managedEntities.add(living);
|
||||
if (names.contains(entity.teamIdentifier())) {
|
||||
managedEntities.add(entity);
|
||||
if (!containsSelf) {
|
||||
living.updateNametag(this);
|
||||
living.updateBedrockMetadata();
|
||||
entity.updateNametag(this);
|
||||
entity.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,11 +269,8 @@ public final class Team {
|
||||
|
||||
private void refreshAllEntities() {
|
||||
for (Entity entity : session().getEntityCache().getEntities().values()) {
|
||||
if (!(entity instanceof LivingEntity living)) {
|
||||
continue;
|
||||
}
|
||||
living.updateNametag(scoreboard.getTeamFor(living.teamIdentifier()));
|
||||
living.updateBedrockMetadata();
|
||||
entity.updateNametag(scoreboard.getTeamFor(entity.teamIdentifier()));
|
||||
entity.updateBedrockMetadata();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ public final class SidebarDisplayScore extends DisplayScore {
|
||||
private ScoreInfo cachedInfo;
|
||||
private Team team;
|
||||
private String order;
|
||||
private boolean onlyScoreValueChanged;
|
||||
|
||||
public SidebarDisplayScore(DisplaySlot slot, long scoreId, ScoreReference reference) {
|
||||
super(slot, scoreId, reference);
|
||||
@ -81,6 +82,9 @@ public final class SidebarDisplayScore extends DisplayScore {
|
||||
finalName = order + ChatColor.RESET + finalName;
|
||||
}
|
||||
|
||||
if (cachedInfo != null) {
|
||||
onlyScoreValueChanged = finalName.equals(cachedInfo.getName());
|
||||
}
|
||||
cachedInfo = new ScoreInfo(id, slot.objectiveId(), reference.score(), finalName);
|
||||
}
|
||||
|
||||
@ -128,4 +132,8 @@ public final class SidebarDisplayScore extends DisplayScore {
|
||||
public boolean exists() {
|
||||
return cachedInfo != null;
|
||||
}
|
||||
|
||||
public boolean onlyScoreValueChanged() {
|
||||
return onlyScoreValueChanged;
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,6 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
|
||||
public BelownameDisplaySlot(GeyserSession session, Objective objective) {
|
||||
super(session, objective, ScoreboardPosition.BELOW_NAME);
|
||||
setAndAddBelownameForExisting();
|
||||
updateType = UpdateType.NOTHING;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -61,7 +59,13 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
// when the objective is added, updated or removed we thus have to update the belowname for every player
|
||||
// when an individual score is updated (score or number format) we have to update the individual player
|
||||
|
||||
// add is handled in the constructor and remove is handled in #remove()
|
||||
// remove is handled in #remove()
|
||||
if (updateType == UpdateType.ADD) {
|
||||
for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) {
|
||||
playerRegistered(player);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (updateType == UpdateType.UPDATE) {
|
||||
for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) {
|
||||
setBelowNameText(player, scoreFor(player.getUsername()));
|
||||
@ -118,12 +122,6 @@ public class BelownameDisplaySlot extends DisplaySlot {
|
||||
displayScores.remove(player.getGeyserId());
|
||||
}
|
||||
|
||||
private void setAndAddBelownameForExisting() {
|
||||
for (PlayerEntity player : session.getEntityCache().getAllPlayerEntities()) {
|
||||
playerRegistered(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDisplayScore(ScoreReference reference) {
|
||||
var players = session.getEntityCache().getPlayersByName(reference.name());
|
||||
for (PlayerEntity player : players) {
|
||||
|
@ -147,7 +147,7 @@ public final class SidebarDisplaySlot extends DisplaySlot {
|
||||
// we need this as long as MCPE-143063 hasn't been fixed.
|
||||
// the checks after 'add' are there to prevent removing scores that
|
||||
// are going to be removed anyway / don't need to be removed
|
||||
if (add && exists && !(objectiveUpdate || objectiveAdd)) {
|
||||
if (add && exists && !(objectiveUpdate || objectiveAdd) && !score.onlyScoreValueChanged()) {
|
||||
removeScores.add(score.cachedInfo());
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.text;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
|
||||
|
||||
public class ChatColor {
|
||||
public static final String ANSI_RESET = (char) 0x1b + "[0m";
|
||||
|
||||
@ -86,6 +88,8 @@ public class ChatColor {
|
||||
}
|
||||
|
||||
public static String styleOrder(int index) {
|
||||
// https://bugs.mojang.com/browse/MCPE-41729
|
||||
// strikethrough and underlined do not exist on Bedrock
|
||||
return switch (index) {
|
||||
case 0 -> BLACK;
|
||||
case 1 -> DARK_BLUE;
|
||||
@ -105,9 +109,35 @@ public class ChatColor {
|
||||
case 15 -> WHITE;
|
||||
case 16 -> OBFUSCATED;
|
||||
case 17 -> BOLD;
|
||||
case 18 -> STRIKETHROUGH;
|
||||
case 19 -> UNDERLINE;
|
||||
default -> ITALIC;
|
||||
};
|
||||
}
|
||||
|
||||
public static String chatColorFor(TeamColor teamColor) {
|
||||
// https://bugs.mojang.com/browse/MCPE-41729
|
||||
// strikethrough and underlined do not exist on Bedrock
|
||||
return switch (teamColor) {
|
||||
case BLACK -> BLACK;
|
||||
case DARK_BLUE -> DARK_BLUE;
|
||||
case DARK_GREEN -> DARK_GREEN;
|
||||
case DARK_AQUA -> DARK_AQUA;
|
||||
case DARK_RED -> DARK_RED;
|
||||
case DARK_PURPLE -> DARK_PURPLE;
|
||||
case GOLD -> GOLD;
|
||||
case GRAY -> GRAY;
|
||||
case DARK_GRAY -> DARK_GRAY;
|
||||
case BLUE -> BLUE;
|
||||
case GREEN -> GREEN;
|
||||
case AQUA -> AQUA;
|
||||
case RED -> RED;
|
||||
case LIGHT_PURPLE -> LIGHT_PURPLE;
|
||||
case YELLOW -> YELLOW;
|
||||
case WHITE -> WHITE;
|
||||
case OBFUSCATED -> OBFUSCATED;
|
||||
case BOLD -> BOLD;
|
||||
case STRIKETHROUGH, UNDERLINED -> "";
|
||||
case ITALIC -> ITALIC;
|
||||
default -> RESET;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.translator.text;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ScoreComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
@ -39,14 +41,16 @@ import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.text.*;
|
||||
import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.DummyLegacyHoverEventSerializer;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.text.GsonComponentSerializerWrapper;
|
||||
import org.geysermc.geyser.text.MinecraftTranslationRegistry;
|
||||
import org.geysermc.geyser.text.TextDecoration;
|
||||
import org.geysermc.mcprotocollib.protocol.data.DefaultComponentSerializer;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class MessageTranslator {
|
||||
// These are used for handling the translations of the messages
|
||||
@ -59,9 +63,6 @@ public class MessageTranslator {
|
||||
private static final LegacyComponentSerializer BEDROCK_SERIALIZER;
|
||||
private static final String BEDROCK_COLORS;
|
||||
|
||||
// Store team colors for player names
|
||||
private static final Map<TeamColor, String> TEAM_COLORS = new EnumMap<>(TeamColor.class);
|
||||
|
||||
// Legacy formatting character
|
||||
private static final String BASE = "\u00a7";
|
||||
|
||||
@ -69,31 +70,6 @@ public class MessageTranslator {
|
||||
private static final String RESET = BASE + "r";
|
||||
|
||||
static {
|
||||
TEAM_COLORS.put(TeamColor.RESET, RESET);
|
||||
|
||||
TEAM_COLORS.put(TeamColor.BLACK, BASE + "0");
|
||||
TEAM_COLORS.put(TeamColor.DARK_BLUE, BASE + "1");
|
||||
TEAM_COLORS.put(TeamColor.DARK_GREEN, BASE + "2");
|
||||
TEAM_COLORS.put(TeamColor.DARK_AQUA, BASE + "3");
|
||||
TEAM_COLORS.put(TeamColor.DARK_RED, BASE + "4");
|
||||
TEAM_COLORS.put(TeamColor.DARK_PURPLE, BASE + "5");
|
||||
TEAM_COLORS.put(TeamColor.GOLD, BASE + "6");
|
||||
TEAM_COLORS.put(TeamColor.GRAY, BASE + "7");
|
||||
TEAM_COLORS.put(TeamColor.DARK_GRAY, BASE + "8");
|
||||
TEAM_COLORS.put(TeamColor.BLUE, BASE + "9");
|
||||
TEAM_COLORS.put(TeamColor.GREEN, BASE + "a");
|
||||
TEAM_COLORS.put(TeamColor.AQUA, BASE + "b");
|
||||
TEAM_COLORS.put(TeamColor.RED, BASE + "c");
|
||||
TEAM_COLORS.put(TeamColor.LIGHT_PURPLE, BASE + "d");
|
||||
TEAM_COLORS.put(TeamColor.YELLOW, BASE + "e");
|
||||
TEAM_COLORS.put(TeamColor.WHITE, BASE + "f");
|
||||
|
||||
// Formats, not colors
|
||||
TEAM_COLORS.put(TeamColor.OBFUSCATED, BASE + "k");
|
||||
TEAM_COLORS.put(TeamColor.BOLD, BASE + "l");
|
||||
TEAM_COLORS.put(TeamColor.STRIKETHROUGH, BASE + "m");
|
||||
TEAM_COLORS.put(TeamColor.ITALIC, BASE + "o");
|
||||
|
||||
// Temporary fix for https://github.com/KyoriPowered/adventure/issues/447 - TODO resolve properly
|
||||
GsonComponentSerializer source = DefaultComponentSerializer.get()
|
||||
.toBuilder()
|
||||
@ -145,13 +121,31 @@ public class MessageTranslator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Java message to the legacy format ready for bedrock
|
||||
* Convert a Java message to the legacy format ready for bedrock. Unlike
|
||||
* {@link #convertMessageRaw(Component, String)} this adds a leading color reset. In Bedrock
|
||||
* some places have build-in colors.
|
||||
*
|
||||
* @param message Java message
|
||||
* @param locale Locale to use for translation strings
|
||||
* @return Parsed and formatted message for bedrock
|
||||
*/
|
||||
public static String convertMessage(Component message, String locale) {
|
||||
return convertMessage(message, locale, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Java message to the legacy format ready for bedrock. Unlike {@link #convertMessage(Component, String)}
|
||||
* this version does not add a leading color reset. In Bedrock some places have build-in colors.
|
||||
*
|
||||
* @param message Java message
|
||||
* @param locale Locale to use for translation strings
|
||||
* @return Parsed and formatted message for bedrock
|
||||
*/
|
||||
public static String convertMessageRaw(Component message, String locale) {
|
||||
return convertMessage(message, locale, false);
|
||||
}
|
||||
|
||||
private static String convertMessage(Component message, String locale, boolean addLeadingResetFormat) {
|
||||
try {
|
||||
// Translate any components that require it
|
||||
message = RENDERER.render(message, locale);
|
||||
@ -160,7 +154,7 @@ public class MessageTranslator {
|
||||
|
||||
StringBuilder finalLegacy = new StringBuilder();
|
||||
char[] legacyChars = legacy.toCharArray();
|
||||
boolean lastFormatReset = false;
|
||||
boolean lastFormatReset = !addLeadingResetFormat;
|
||||
for (int i = 0; i < legacyChars.length; i++) {
|
||||
char legacyChar = legacyChars[i];
|
||||
if (legacyChar != ChatColor.ESCAPE || i >= legacyChars.length - 1) {
|
||||
@ -173,7 +167,7 @@ public class MessageTranslator {
|
||||
|
||||
char next = legacyChars[++i];
|
||||
if (BEDROCK_COLORS.indexOf(next) != -1) {
|
||||
// Append this color code, as well as a necessary reset code
|
||||
// Unlike Java Edition, the ChatFormatting is not reset when a ChatColor is added
|
||||
if (!lastFormatReset) {
|
||||
finalLegacy.append(RESET);
|
||||
}
|
||||
@ -366,16 +360,6 @@ public class MessageTranslator {
|
||||
session.sendUpstreamPacket(textPacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a team color to a chat color
|
||||
*
|
||||
* @param teamColor Color or format to convert
|
||||
* @return The chat color character
|
||||
*/
|
||||
public static String toChatColor(TeamColor teamColor) {
|
||||
return TEAM_COLORS.getOrDefault(teamColor, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given message is over 256 characters (Java edition server chat limit) and sends a message to the user if it is
|
||||
*
|
||||
|
@ -290,6 +290,28 @@ public final class EntityUtils {
|
||||
};
|
||||
}
|
||||
|
||||
public static String entityTypeName(EntityType type) {
|
||||
var typeName = type.name();
|
||||
var builder = new StringBuilder();
|
||||
|
||||
boolean upNext = true;
|
||||
for (int i = 0; i < typeName.length(); i++) {
|
||||
char c = typeName.charAt(i);
|
||||
if ('_' == c) {
|
||||
upNext = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// enums are upper case by default
|
||||
if (!upNext) {
|
||||
c = Character.toLowerCase(c);
|
||||
}
|
||||
builder.append(c);
|
||||
upNext = false;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private EntityUtils() {
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class NameVisibilityScoreboardTest {
|
||||
@Test
|
||||
void playerVisibilityNever() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team1",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.NEVER,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"player1"}
|
||||
)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void playerVisibilityHideForOtherTeam() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team1",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.HIDE_FOR_OTHER_TEAMS,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"player1"}
|
||||
)
|
||||
);
|
||||
// only hidden if session player (Tim203) is in a team as well
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// create another team and add Tim203 to it
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team2",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.NEVER,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"Tim203"}
|
||||
)
|
||||
);
|
||||
// Tim203 is now in another team, so it should be hidden
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// add Tim203 to same team as player1, score should be visible again
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"})
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void playerVisibilityHideForOwnTeam() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team1",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.HIDE_FOR_OWN_TEAM,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"player1"}
|
||||
)
|
||||
);
|
||||
// Tim203 is not in a team (let alone the same team), so should be visible
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// Tim203 is now in the same team as player1, so should be hidden
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"})
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// create another team and add Tim203 to there, score should be visible again
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team2",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.NEVER,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"Tim203"}
|
||||
)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void playerVisibilityAlways() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team1",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.ALWAYS,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"player1"}
|
||||
)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.NAME, "§4prefix§r§4player1§r§4suffix");
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// adding self to another team shouldn't make a difference
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team2",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.ALWAYS,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{"Tim203"}
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// adding self to player1 team shouldn't matter
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket("team1", TeamAction.ADD_PLAYER, new String[]{"Tim203"})
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.belowname;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockAndAddPlayerEntity;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class BasicBelownameScoreboardTests {
|
||||
@Test
|
||||
void displayWithNoPlayersAndRemove() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective")
|
||||
);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "")
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayColorWithOnePlayer() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective", NamedTextColor.BLUE),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §r§9objective");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayWithOnePlayerAndRemove() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideAndRemove() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
mockAndAddPlayerEntity(context, "player1", 2);
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective1",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective1"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective2",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective2"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective2")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective2");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "objective1")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "0 §robjective1");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.BELOW_NAME, "")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetEntityDataPacket();
|
||||
packet.setRuntimeEntityId(2);
|
||||
packet.getMetadata().put(EntityDataTypes.SCORE, "");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.playerlist;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/*
|
||||
Identical to sidebar
|
||||
*/
|
||||
public class BasicPlayerlistScoreboardTests {
|
||||
@Test
|
||||
void display() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("list");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayNameColors() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective", Style.style(NamedTextColor.AQUA, TextDecoration.BOLD)),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("§b§lobjective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("list");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideWithOneScore() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective1",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective1"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective2",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective2"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective1", 1));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective2", 2));
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective2")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective2");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("list");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
// session player name is Tim203
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, ScoreInfo.ScorerType.PLAYER, 1)));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective1")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new RemoveObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("2");
|
||||
packet.setDisplayName("objective1");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("list");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
// session player name is Tim203
|
||||
packet.setInfos(List.of(new ScoreInfo(3, "2", 1, ScoreInfo.ScorerType.PLAYER, 1)));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.sidebar;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/*
|
||||
Identical to playerlist
|
||||
*/
|
||||
public class BasicSidebarScoreboardTests {
|
||||
@Test
|
||||
void displayAndRemove() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("list");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.PLAYER_LIST, "")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new RemoveObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayNameColors() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective", Style.style(NamedTextColor.AQUA, TextDecoration.BOLD)),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("§b§lobjective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void override() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective1",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective1"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective2",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective2"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective1", 1));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("Tim203", "objective2", 2));
|
||||
assertNoNextPacket(context);
|
||||
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective2")
|
||||
);
|
||||
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective2");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "Tim203")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective1")
|
||||
);
|
||||
|
||||
assertNextPacket(() -> {
|
||||
var packet = new RemoveObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("2");
|
||||
packet.setDisplayName("objective1");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(3, "2", 1, "Tim203")));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,533 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.sidebar;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaResetScorePacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetPlayerTeamTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.CollisionRule;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.NameTagVisibility;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundResetScorePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetPlayerTeamPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class OrderAndLimitSidebarScoreboardTests {
|
||||
@Test
|
||||
void aboveDisplayLimit() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
var resetScoreTranslator = new JavaResetScorePacket();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
// some are in an odd order to make sure that there is no bias for which score is send first,
|
||||
// and to make sure that the score value also doesn't influence the order
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("a", "objective", 1));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("b", "objective", 2));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("c", "objective", 3));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("d", "objective", 5));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("e", "objective", 4));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("f", "objective", 6));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("g", "objective", 9));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("h", "objective", 8));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("i", "objective", 7));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("p", "objective", 10));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("o", "objective", 11));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("n", "objective", 12));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("m", "objective", 13));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("k", "objective", 14));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("l", "objective", 15));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("j", "objective", 16));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("q", "objective", 17));
|
||||
assertNoNextPacket(context);
|
||||
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(1, "0", 17, "q"),
|
||||
new ScoreInfo(2, "0", 16, "j"),
|
||||
new ScoreInfo(3, "0", 15, "l"),
|
||||
new ScoreInfo(4, "0", 14, "k"),
|
||||
new ScoreInfo(5, "0", 13, "m"),
|
||||
new ScoreInfo(6, "0", 12, "n"),
|
||||
new ScoreInfo(7, "0", 11, "o"),
|
||||
new ScoreInfo(8, "0", 10, "p"),
|
||||
new ScoreInfo(9, "0", 9, "g"),
|
||||
new ScoreInfo(10, "0", 8, "h"),
|
||||
new ScoreInfo(11, "0", 7, "i"),
|
||||
new ScoreInfo(12, "0", 6, "f"),
|
||||
new ScoreInfo(13, "0", 5, "d"),
|
||||
new ScoreInfo(14, "0", 4, "e"),
|
||||
new ScoreInfo(15, "0", 3, "c")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// remove a score
|
||||
context.translate(
|
||||
resetScoreTranslator,
|
||||
new ClientboundResetScorePacket("m", "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(new ScoreInfo(5, "0", 13, "m")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// add a score
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("aa", "objective", 13)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(17, "0", 13, "aa")));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// add score with same score value (after)
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("ga", "objective", 9)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(15, "0", 3, "c"),
|
||||
new ScoreInfo(9, "0", 9, "§0§rg")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(9, "0", 9, "§0§rg"),
|
||||
new ScoreInfo(18, "0", 9, "§1§rga")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// add another score with same score value (before all)
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("ag", "objective", 9)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(14, "0", 4, "e"),
|
||||
new ScoreInfo(9, "0", 9, "§1§rg"),
|
||||
new ScoreInfo(18, "0", 9, "§2§rga")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(19, "0", 9, "§0§rag"),
|
||||
new ScoreInfo(9, "0", 9, "§1§rg"),
|
||||
new ScoreInfo(18, "0", 9, "§2§rga")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// remove score with same value
|
||||
context.translate(
|
||||
resetScoreTranslator,
|
||||
new ClientboundResetScorePacket("g", "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(9, "0", 9, "§1§rg"),
|
||||
new ScoreInfo(18, "0", 9, "§1§rga")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(18, "0", 9, "§1§rga"),
|
||||
new ScoreInfo(20, "0", 4, "e")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// remove the other score with the same value
|
||||
context.translate(
|
||||
resetScoreTranslator,
|
||||
new ClientboundResetScorePacket("ga", "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(18, "0", 9, "§1§rga"),
|
||||
new ScoreInfo(19, "0", 9, "ag")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(19, "0", 9, "ag"),
|
||||
new ScoreInfo(21, "0", 3, "c")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void aboveDisplayLimitWithTeam() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
var resetScoreTranslator = new JavaResetScorePacket();
|
||||
var setPlayerTeamTranslator = new JavaSetPlayerTeamTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
// some are in an odd order to make sure that there is no bias for which score is send first,
|
||||
// and to make sure that the score value also doesn't influence the order
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("a", "objective", 1));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("b", "objective", 2));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("c", "objective", 3));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("d", "objective", 5));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("e", "objective", 4));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("f", "objective", 6));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("g", "objective", 9));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("h", "objective", 8));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("i", "objective", 7));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("p", "objective", 10));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("o", "objective", 11));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("n", "objective", 12));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("m", "objective", 13));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("k", "objective", 14));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("l", "objective", 15));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("j", "objective", 16));
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("q", "objective", 17));
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team1",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.ALWAYS,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_RED,
|
||||
new String[]{ "f", "o" }
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(1, "0", 17, "q"),
|
||||
new ScoreInfo(2, "0", 16, "j"),
|
||||
new ScoreInfo(3, "0", 15, "l"),
|
||||
new ScoreInfo(4, "0", 14, "k"),
|
||||
new ScoreInfo(5, "0", 13, "m"),
|
||||
new ScoreInfo(6, "0", 12, "n"),
|
||||
new ScoreInfo(7, "0", 11, "§4prefix§r§4o§r§4suffix"),
|
||||
new ScoreInfo(8, "0", 10, "p"),
|
||||
new ScoreInfo(9, "0", 9, "g"),
|
||||
new ScoreInfo(10, "0", 8, "h"),
|
||||
new ScoreInfo(11, "0", 7, "i"),
|
||||
new ScoreInfo(12, "0", 6, "§4prefix§r§4f§r§4suffix"),
|
||||
new ScoreInfo(13, "0", 5, "d"),
|
||||
new ScoreInfo(14, "0", 4, "e"),
|
||||
new ScoreInfo(15, "0", 3, "c")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// remove a score
|
||||
context.translate(
|
||||
resetScoreTranslator,
|
||||
new ClientboundResetScorePacket("m", "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(new ScoreInfo(5, "0", 13, "m")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// add a score
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("aa", "objective", 13)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(new ScoreInfo(16, "0", 2, "b")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(17, "0", 13, "aa")));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// add some teams for the upcoming score adds
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team2",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.ALWAYS,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_AQUA,
|
||||
new String[]{ "oa" }
|
||||
)
|
||||
);
|
||||
context.translate(
|
||||
setPlayerTeamTranslator,
|
||||
new ClientboundSetPlayerTeamPacket(
|
||||
"team3",
|
||||
Component.text("displayName"),
|
||||
Component.text("prefix"),
|
||||
Component.text("suffix"),
|
||||
false,
|
||||
false,
|
||||
NameTagVisibility.ALWAYS,
|
||||
CollisionRule.NEVER,
|
||||
TeamColor.DARK_PURPLE,
|
||||
new String[]{ "ao" }
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
// add a score that on Java should be after 'o', but would be before on Bedrock without manual order
|
||||
// due to the team color
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("oa", "objective", 11)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(15, "0", 3, "c"),
|
||||
new ScoreInfo(7, "0", 11, "§0§r§4prefix§r§4o§r§4suffix")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(7, "0", 11, "§0§r§4prefix§r§4o§r§4suffix"),
|
||||
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// add a score that on Java should be before 'o', but would be after on Bedrock without manual order
|
||||
// due to the team color
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("ao", "objective", 11)
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(14, "0", 4, "e"),
|
||||
new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"),
|
||||
new ScoreInfo(18, "0", 11, "§2§r§3prefix§r§3oa§r§3suffix")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(19, "0", 11, "§0§r§5prefix§r§5ao§r§5suffix"),
|
||||
new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"),
|
||||
new ScoreInfo(18, "0", 11, "§2§r§3prefix§r§3oa§r§3suffix")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// remove original 'o' score
|
||||
context.translate(
|
||||
resetScoreTranslator,
|
||||
new ClientboundResetScorePacket("o", "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(7, "0", 11, "§1§r§4prefix§r§4o§r§4suffix"),
|
||||
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix"),
|
||||
new ScoreInfo(20, "0", 4, "e")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
|
||||
// remove the other score with the same value as 'o'
|
||||
context.translate(
|
||||
resetScoreTranslator,
|
||||
new ClientboundResetScorePacket("oa", "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(18, "0", 11, "§1§r§3prefix§r§3oa§r§3suffix"),
|
||||
new ScoreInfo(19, "0", 11, "§5prefix§r§5ao§r§5suffix")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(
|
||||
new ScoreInfo(19, "0", 11, "§5prefix§r§5ao§r§5suffix"),
|
||||
new ScoreInfo(21, "0", 3, "c")
|
||||
));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.sidebar;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContextScoreboard.mockContextScoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.cloudburstmc.protocol.bedrock.data.ScoreInfo;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetDisplayObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetObjectiveTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.java.scoreboard.JavaSetScoreTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ObjectiveAction;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.ScoreboardPosition;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetDisplayObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetObjectivePacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.scoreboard.ClientboundSetScorePacket;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class VanillaSidebarScoreboardTests {
|
||||
@Test
|
||||
void displayAndAddScore() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayAndChangeScoreValue() {
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 2));
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "owner")));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayAndChangeScoreDisplayName() {
|
||||
// this ensures that MCPE-143063 is properly handled
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("owner", "objective", 1).withDisplay(Component.text("hi"))
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "hi")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "hi")));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayAndChangeScoreDisplayNameAndValue() {
|
||||
// this ensures that MCPE-143063 is properly handled
|
||||
mockContextScoreboard(context -> {
|
||||
var setObjectiveTranslator = new JavaSetObjectiveTranslator();
|
||||
var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator();
|
||||
var setScoreTranslator = new JavaSetScoreTranslator();
|
||||
|
||||
context.translate(
|
||||
setObjectiveTranslator,
|
||||
new ClientboundSetObjectivePacket(
|
||||
"objective",
|
||||
ObjectiveAction.ADD,
|
||||
Component.text("objective"),
|
||||
ScoreType.INTEGER,
|
||||
null
|
||||
)
|
||||
);
|
||||
context.translate(setScoreTranslator, new ClientboundSetScorePacket("owner", "objective", 1));
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setDisplayObjectiveTranslator,
|
||||
new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "objective")
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetDisplayObjectivePacket();
|
||||
packet.setObjectiveId("0");
|
||||
packet.setDisplayName("objective");
|
||||
packet.setCriteria("dummy");
|
||||
packet.setDisplaySlot("sidebar");
|
||||
packet.setSortOrder(1);
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 1, "owner")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNoNextPacket(context);
|
||||
|
||||
context.translate(
|
||||
setScoreTranslator,
|
||||
new ClientboundSetScorePacket("owner", "objective", 2).withDisplay(Component.text("hi"))
|
||||
);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.REMOVE);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "hi")));
|
||||
return packet;
|
||||
}, context);
|
||||
assertNextPacket(() -> {
|
||||
var packet = new SetScorePacket();
|
||||
packet.setAction(SetScorePacket.Action.SET);
|
||||
packet.setInfos(List.of(new ScoreInfo(1, "0", 2, "hi")));
|
||||
return packet;
|
||||
}, context);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.util;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
public class AssertUtils {
|
||||
public static <T> void assertContextEquals(Supplier<? extends T> expected, T actual) {
|
||||
if (actual == null) {
|
||||
Assertions.fail("Expected another packet! " + expected.get());
|
||||
}
|
||||
Assertions.assertEquals(expected.get(), actual);
|
||||
}
|
||||
|
||||
public static void assertNextPacket(Supplier<BedrockPacket> expected, GeyserMockContext context) {
|
||||
assertContextEquals(expected, context.nextPacket());
|
||||
}
|
||||
|
||||
public static void assertNoNextPacket(GeyserMockContext context) {
|
||||
Assertions.assertEquals(
|
||||
Collections.emptyList(),
|
||||
context.packets(),
|
||||
"Expected no remaining packets, got " + context.packetCount()
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.util;
|
||||
|
||||
import org.geysermc.geyser.GeyserLogger;
|
||||
|
||||
public class EmptyGeyserLogger implements GeyserLogger {
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message, Throwable error) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String message, Throwable error) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDebug(boolean debug) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDebug() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.util;
|
||||
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.configuration.GeyserConfiguration;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class GeyserMockContext {
|
||||
private final List<Object> mocksAndSpies = new ArrayList<>();
|
||||
private final List<Object> storedObjects = new ArrayList<>();
|
||||
private final List<BedrockPacket> packets = Collections.synchronizedList(new ArrayList<>());
|
||||
private MockedStatic<GeyserImpl> geyserImplMock;
|
||||
|
||||
public static void mockContext(Consumer<GeyserMockContext> geyserContext) {
|
||||
var context = new GeyserMockContext();
|
||||
|
||||
var geyserImpl = context.mock(GeyserImpl.class);
|
||||
var config = context.mock(GeyserConfiguration.class);
|
||||
|
||||
when(config.getScoreboardPacketThreshold()).thenReturn(1_000);
|
||||
|
||||
when(geyserImpl.getConfig()).thenReturn(config);
|
||||
|
||||
var logger = context.storeObject(new EmptyGeyserLogger());
|
||||
when(geyserImpl.getLogger()).thenReturn(logger);
|
||||
|
||||
try (var mocked = mockStatic(GeyserImpl.class)) {
|
||||
mocked.when(GeyserImpl::getInstance).thenReturn(geyserImpl);
|
||||
context.geyserImplMock = mocked;
|
||||
geyserContext.accept(context);
|
||||
}
|
||||
}
|
||||
|
||||
public static void mockContext(Runnable runnable) {
|
||||
mockContext(context -> runnable.run());
|
||||
}
|
||||
|
||||
public <T> T mock(Class<T> type) {
|
||||
return addMockOrSpy(Mockito.mock(type));
|
||||
}
|
||||
|
||||
public <T> T spy(T object) {
|
||||
return addMockOrSpy(Mockito.spy(object));
|
||||
}
|
||||
|
||||
private <T> T addMockOrSpy(T mockOrSpy) {
|
||||
mocksAndSpies.add(mockOrSpy);
|
||||
return mockOrSpy;
|
||||
}
|
||||
|
||||
public <T> T storeObject(T object) {
|
||||
storedObjects.add(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries the mock or spy that is an instance of the specified type.
|
||||
* This is only really intended for classes where you only need a single instance of.
|
||||
*/
|
||||
public <T> T mockOrSpy(Class<T> type) {
|
||||
for (Object mock : mocksAndSpies) {
|
||||
if (type.isInstance(mock)) {
|
||||
return type.cast(mock);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> T storedObject(Class<T> type) {
|
||||
for (Object storedObject : storedObjects) {
|
||||
if (type.isInstance(storedObject)) {
|
||||
return type.cast(storedObject);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public GeyserSession session() {
|
||||
return mockOrSpy(GeyserSession.class);
|
||||
}
|
||||
|
||||
void addPacket(BedrockPacket packet) {
|
||||
packets.add(packet);
|
||||
}
|
||||
|
||||
public int packetCount() {
|
||||
return packets.size();
|
||||
}
|
||||
|
||||
public BedrockPacket nextPacket() {
|
||||
if (packets.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return packets.remove(0);
|
||||
}
|
||||
|
||||
public List<BedrockPacket> packets() {
|
||||
return Collections.unmodifiableList(packets);
|
||||
}
|
||||
|
||||
public <T> void translate(PacketTranslator<T> translator, T packet) {
|
||||
translator.translate(session(), packet);
|
||||
}
|
||||
|
||||
public MockedStatic<GeyserImpl> geyserImplMock() {
|
||||
return geyserImplMock;
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (c) 2024 GeyserMC. http://geysermc.org
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* @author GeyserMC
|
||||
* @link https://github.com/GeyserMC/Geyser
|
||||
*/
|
||||
|
||||
package org.geysermc.geyser.scoreboard.network.util;
|
||||
|
||||
import static org.geysermc.geyser.scoreboard.network.util.AssertUtils.assertNoNextPacket;
|
||||
import static org.geysermc.geyser.scoreboard.network.util.GeyserMockContext.mockContext;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataMap;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
|
||||
import org.geysermc.geyser.entity.type.player.PlayerEntity;
|
||||
import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.EntityCache;
|
||||
import org.geysermc.geyser.session.cache.WorldCache;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
public class GeyserMockContextScoreboard {
|
||||
public static void mockContextScoreboard(Consumer<GeyserMockContext> geyserContext) {
|
||||
mockContext(context -> {
|
||||
createSessionSpy(context);
|
||||
geyserContext.accept(context);
|
||||
|
||||
assertNoNextPacket(context);
|
||||
});
|
||||
}
|
||||
|
||||
private static void createSessionSpy(GeyserMockContext context) {
|
||||
// GeyserSession has so many dependencies, it's easier to just mock it
|
||||
var session = context.mock(GeyserSession.class);
|
||||
|
||||
when(session.locale()).thenReturn("en_US");
|
||||
doAnswer((Answer<Void>) invocation -> {
|
||||
context.addPacket(invocation.getArgument(0, BedrockPacket.class));
|
||||
return null;
|
||||
}).when(session).sendUpstreamPacket(any());
|
||||
|
||||
// SessionPlayerEntity loads stuff in like blocks, which is not what we want
|
||||
var playerEntity = context.mock(SessionPlayerEntity.class);
|
||||
when(playerEntity.getGeyserId()).thenReturn(1L);
|
||||
when(playerEntity.getUsername()).thenReturn("Tim203");
|
||||
when(session.getPlayerEntity()).thenReturn(playerEntity);
|
||||
|
||||
var entityCache = context.spy(new EntityCache(session));
|
||||
when(session.getEntityCache()).thenReturn(entityCache);
|
||||
|
||||
var worldCache = context.spy(new WorldCache(session));
|
||||
when(session.getWorldCache()).thenReturn(worldCache);
|
||||
|
||||
// disable global scoreboard updater
|
||||
when(worldCache.increaseAndGetScoreboardPacketsPerSecond()).thenReturn(0);
|
||||
}
|
||||
|
||||
public static PlayerEntity mockAndAddPlayerEntity(GeyserMockContext context, String username, long geyserId) {
|
||||
var playerEntity = spy(new PlayerEntity(context.session(), geyserId, UUID.randomUUID(), username));
|
||||
// fake the player being spawned
|
||||
when(playerEntity.isValid()).thenReturn(true);
|
||||
|
||||
var entityCache = context.mockOrSpy(EntityCache.class);
|
||||
entityCache.addPlayerEntity(playerEntity);
|
||||
// called when the player spawns
|
||||
entityCache.cacheEntity(playerEntity);
|
||||
return playerEntity;
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ neoforge-minecraft = "21.0.0-beta"
|
||||
mixin = "0.8.5"
|
||||
mixinextras = "0.3.5"
|
||||
minecraft = "1.21"
|
||||
mockito = "5.+"
|
||||
|
||||
# plugin versions
|
||||
indra = "3.1.3"
|
||||
@ -124,6 +125,8 @@ protocol-connection = { group = "org.cloudburstmc.protocol", name = "bedrock-con
|
||||
|
||||
math = { group = "org.cloudburstmc.math", name = "immutable", version = "2.0" }
|
||||
|
||||
mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||
|
||||
# plugins
|
||||
indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" }
|
||||
shadow = { group = "com.github.johnrengelman", name = "shadow", version.ref = "shadow" }
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren