From 6470f25d9a1b3a93eaeb086393e44133a62791c8 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 26 Sep 2024 15:36:39 +0200 Subject: [PATCH] Added CubeCraft's scoreboard as a test, and fixed a discovered bug --- .../geysermc/geyser/entity/type/Entity.java | 2 +- .../geyser/scoreboard/Scoreboard.java | 15 + .../org/geysermc/geyser/scoreboard/Team.java | 15 + .../display/score/SidebarDisplayScore.java | 2 +- .../display/slot/SidebarDisplaySlot.java | 4 +- .../geyser/session/cache/EntityCache.java | 31 +- .../org/geysermc/geyser/util/EntityUtils.java | 5 +- .../server/CubecraftScoreboardTest.java | 756 ++++++++++++++++++ 8 files changed, 815 insertions(+), 15 deletions(-) create mode 100644 core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java index 7e5ce0797..dc76e9925 100644 --- a/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java +++ b/core/src/main/java/org/geysermc/geyser/entity/type/Entity.java @@ -132,12 +132,12 @@ 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.definition = definition; this.displayName = standardDisplayName(); this.entityId = entityId; this.geyserId = geyserId; this.uuid = uuid; - this.definition = definition; this.motion = motion; this.yaw = yaw; this.pitch = pitch; diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java index 273f3c01b..02e2f3a82 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Scoreboard.java @@ -35,6 +35,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumConstraint; import org.cloudburstmc.protocol.bedrock.packet.SetScorePacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.GeyserLogger; +import org.geysermc.geyser.entity.type.Entity; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.scoreboard.display.slot.BelownameDisplaySlot; import org.geysermc.geyser.scoreboard.display.slot.DisplaySlot; @@ -302,6 +303,20 @@ public final class Scoreboard { } } + public void entityRegistered(Entity entity) { + var team = getTeamFor(entity.teamIdentifier()); + if (team != null) { + team.onEntitySpawn(entity); + } + } + + public void entityRemoved(Entity entity) { + var team = getTeamFor(entity.teamIdentifier()); + if (team != null) { + team.onEntityRemove(entity); + } + } + public void setTeamFor(Team team, Set entities) { for (DisplaySlot slot : objectiveSlots.values()) { // only sidebar slots use teams diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java index 117115abb..28efb02cb 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/Team.java @@ -225,6 +225,21 @@ public final class Team { } } + public void onEntitySpawn(Entity entity) { + // I've basically ported addAddedEntities + if (entities.contains(entity.teamIdentifier())) { + managedEntities.add(entity); + // onEntitySpawn includes all entities but players, so it cannot contain self + entity.updateNametag(this); + entity.updateBedrockMetadata(); + } + } + + public void onEntityRemove(Entity entity) { + // we don't have to update anything, since the player is removed. + managedEntities.remove(entity); + } + private void addAddedEntities(Set names) { // can't contain self if none are added if (names.isEmpty()) { diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java index 16b8f2c66..57bebd22f 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/score/SidebarDisplayScore.java @@ -75,7 +75,7 @@ public final class SidebarDisplayScore extends DisplayScore { numberFormat = objective.getNumberFormat(); } if (numberFormat instanceof FixedFormat fixedFormat) { - finalName += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue()); + finalName += " " + ChatColor.RESET + MessageTranslator.convertMessage(fixedFormat.getValue(), objective.getScoreboard().session().locale()); } if (order != null) { diff --git a/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java index 54875528c..24cc81f78 100644 --- a/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java +++ b/core/src/main/java/org/geysermc/geyser/scoreboard/display/slot/SidebarDisplaySlot.java @@ -78,7 +78,7 @@ public final class SidebarDisplaySlot extends DisplaySlot { return new SidebarDisplayScore(this, objective.getScoreboard().nextId(), reference); }).collect(Collectors.toList()); - // in newDisplayScores we removed the items that were already present, + // in newDisplayScores we removed the items that were already present from displayScores, // meaning that the items that remain are items that are no longer displayed for (var score : this.displayScores) { removeScores.add(score.cachedInfo()); @@ -164,7 +164,7 @@ public final class SidebarDisplaySlot extends DisplaySlot { @Override public void addScore(ScoreReference reference) { - // we handle them a bit different, we sort the scores and we add them ourselves + // we handle them a bit different: we sort the scores, and we add them ourselves } @Override diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java index 07507a18d..24f2eb50c 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/EntityCache.java @@ -68,6 +68,10 @@ public class EntityCache { if (cacheEntity(entity)) { entity.spawnEntity(); + // start tracking newly spawned entities. + // This is however not called for players, that's done in addPlayerEntity + session.getWorldCache().getScoreboard().entityRegistered(entity); + if (entity instanceof Tickable) { // Start ticking it tickableEntities.add((Tickable) entity); @@ -86,21 +90,24 @@ public class EntityCache { } public void removeEntity(Entity entity) { + if (entity == null) { + return; + } + if (entity instanceof PlayerEntity player) { session.getPlayerWithCustomHeads().remove(player.getUuid()); } - if (entity != null) { - if (entity.isValid()) { - entity.despawnEntity(); - } + if (entity.isValid()) { + entity.despawnEntity(); + } + entities.remove(entityIdTranslations.remove(entity.getEntityId())); - long geyserId = entityIdTranslations.remove(entity.getEntityId()); - entities.remove(geyserId); + // don't track the entity anymore, now that it's removed + session.getWorldCache().getScoreboard().entityRemoved(entity); - if (entity instanceof Tickable) { - tickableEntities.remove(entity); - } + if (entity instanceof Tickable) { + tickableEntities.remove(entity); } } @@ -130,8 +137,12 @@ public class EntityCache { if (exists) { return; } + // notify scoreboard for new entity - session.getWorldCache().getScoreboard().playerRegistered(entity); + var scoreboard = session.getWorldCache().getScoreboard(); + scoreboard.playerRegistered(entity); + // spawnPlayer's entityRegistered is not called for players + scoreboard.entityRegistered(entity); } public PlayerEntity getPlayerEntity(UUID uuid) { diff --git a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java index 7f18d6623..b7f4e7d76 100644 --- a/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java +++ b/core/src/main/java/org/geysermc/geyser/util/EntityUtils.java @@ -302,8 +302,11 @@ public final class EntityUtils { } public static String translatedEntityName(EntityType type, GeyserSession session) { + if (type == EntityType.PLAYER) { + return "Player"; // the player's name is always shown instead + } // this works at least with all 1.20.5 entities, except the killer bunny since that's not an entity type. - var typeName = type.name().toLowerCase(Locale.ROOT); + String typeName = type.name().toLowerCase(Locale.ROOT); return translatedEntityName("minecraft", typeName, session); } diff --git a/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java b/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java new file mode 100644 index 000000000..dd693022c --- /dev/null +++ b/core/src/test/java/org/geysermc/geyser/scoreboard/network/server/CubecraftScoreboardTest.java @@ -0,0 +1,756 @@ +/* + * 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.server; + +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 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.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.cloudburstmc.protocol.bedrock.data.ScoreInfo; +import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; +import org.cloudburstmc.protocol.bedrock.packet.RemoveObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetDisplayObjectivePacket; +import org.cloudburstmc.protocol.bedrock.packet.SetEntityDataPacket; +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.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.TeamAction; +import org.geysermc.mcprotocollib.protocol.data.game.scoreboard.TeamColor; +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 CubecraftScoreboardTest { + @Test + void test() { + mockContextScoreboard(context -> { + var setTeamTranslator = new JavaSetPlayerTeamTranslator(); + var setObjectiveTranslator = new JavaSetObjectiveTranslator(); + var setDisplayObjectiveTranslator = new JavaSetDisplayObjectiveTranslator(); + var setScoreTranslator = new JavaSetScoreTranslator(); + + // unused + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("SB_NoName", Component.text("SB_NoName"), Component.empty(), Component.empty(), true, true, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.RESET, new String[0])); + assertNoNextPacket(context); + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "sidebar", + ObjectiveAction.ADD, + Component.text("sidebar"), + ScoreType.INTEGER, + null + ) + ); + assertNoNextPacket(context); + + context.translate( + setDisplayObjectiveTranslator, + new ClientboundSetDisplayObjectivePacket(ScoreboardPosition.SIDEBAR, "sidebar") + ); + assertNextPacket(() -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("sidebar"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, context); + + + // Now they're going to create a bunch of teams and add players to those teams in a very inefficient way. + // Presumably this is a leftover from an old system, as these don't seem to do anything but hide their nametags. + // For which you could just use a single team. + + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", Component.text("2i|1"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.DARK_GRAY)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "A_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", Component.text("1y|11"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", TeamAction.ADD_PLAYER, new String[] { "B_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "C_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "D_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1y|11", TeamAction.ADD_PLAYER, new String[] { "E_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "F_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "G_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", Component.text("2e|3"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.BLUE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", TeamAction.ADD_PLAYER, new String[] { "H_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "I_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", Component.text("22|9"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("22|9", TeamAction.ADD_PLAYER, new String[] { "J_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "K_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", Component.text("26|7"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.AQUA)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("26|7", TeamAction.ADD_PLAYER, new String[] { "L_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2e|3", TeamAction.ADD_PLAYER, new String[] { "M_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "N_Player" })); + + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.RESET, new String[0])); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), true, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, true, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.ALWAYS, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.ALWAYS, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", Component.text("1u|13"), Component.empty(), Component.empty(), false, false, NameTagVisibility.NEVER, CollisionRule.NEVER, TeamColor.LIGHT_PURPLE)); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("1u|13", TeamAction.ADD_PLAYER, new String[] { "O_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "P_Player" })); + context.translate(setTeamTranslator, new ClientboundSetPlayerTeamPacket("2i|1", TeamAction.ADD_PLAYER, new String[] { "Q_Player" })); + + assertNoNextPacket(context); + + + // Now that those teams are created and people added to it, they set the final sidebar name and add the lines to it. + // They're also not doing this efficiently, because they don't add the players when the team is created. + // Instead, they send an additional packet. + + + context.translate( + setObjectiveTranslator, + new ClientboundSetObjectivePacket( + "sidebar", + ObjectiveAction.UPDATE, + Component.empty() + .append(Component.text( + "CubeCraft", Style.style(NamedTextColor.WHITE, TextDecoration.BOLD))), + ScoreType.INTEGER, + null)); + assertNextPacket( + () -> { + var packet = new RemoveObjectivePacket(); + packet.setObjectiveId("0"); + return packet; + }, + context); + assertNextPacket( + () -> { + var packet = new SetDisplayObjectivePacket(); + packet.setObjectiveId("0"); + packet.setDisplayName("§f§lCubeCraft"); + packet.setCriteria("dummy"); + packet.setDisplaySlot("sidebar"); + packet.setSortOrder(1); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-0", + Component.text("SB_l-0"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-0", TeamAction.ADD_PLAYER, new String[] {"§0§0"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-0", + Component.text("SB_l-0"), + Component.empty().append(Component.text("", Style.style(NamedTextColor.BLACK))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§0", "sidebar", 10)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(1, "0", 10, "§r§0§0§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-1", + Component.text("SB_l-1"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-1", TeamAction.ADD_PLAYER, new String[] {"§0§1"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-1", + Component.text("SB_l-1"), + Component.empty() + .append(Component.textOfChildren( + Component.text("User: ", TextColor.color(0x3aa9ff)), + Component.text("Tim203", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§1", "sidebar", 9)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(2, "0", 9, "§bUser: §r§fTim203§r§0§1§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-2", + Component.text("SB_l-2"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-2", TeamAction.ADD_PLAYER, new String[] {"§0§2"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-2", + Component.text("SB_l-2"), + Component.empty() + .append(Component.textOfChildren( + Component.text("Rank: ", TextColor.color(0x3aa9ff)), + Component.text("\uE1AB ", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§2", "sidebar", 8)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(3, "0", 8, "§bRank: §r§f\uE1AB §r§0§2§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-3", + Component.text("SB_l-3"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-3", TeamAction.ADD_PLAYER, new String[] {"§0§3"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-3", + Component.text("SB_l-3"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§3", "sidebar", 7)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(4, "0", 7, "§r§0§3§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-4", + Component.text("SB_l-4"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-4", TeamAction.ADD_PLAYER, new String[] {"§0§4"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-4", + Component.text("SB_l-4"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§4", "sidebar", 6)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(5, "0", 6, "§r§0§4§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-5", + Component.text("SB_l-5"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-5", TeamAction.ADD_PLAYER, new String[] {"§0§5"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-5", + Component.text("SB_l-5"), + Component.empty().append(Component.text("", NamedTextColor.DARK_BLUE)), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§5", "sidebar", 5)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(6, "0", 5, "§r§0§5§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-6", + Component.text("SB_l-6"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-6", TeamAction.ADD_PLAYER, new String[] {"§0§6"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-6", + Component.text("SB_l-6"), + Component.empty() + .append(Component.textOfChildren( + Component.text("Lobby: ", TextColor.color(0x3aa9ff)), + Component.text("EU #10", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§6", "sidebar", 4)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(7, "0", 4, "§bLobby: §r§fEU #10§r§0§6§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-7", + Component.text("SB_l-7"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-7", TeamAction.ADD_PLAYER, new String[] {"§0§7"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-7", + Component.text("SB_l-7"), + Component.empty() + .append(Component.textOfChildren( + Component.text("Players: ", TextColor.color(0x3aa9ff)), + Component.text("783", NamedTextColor.WHITE))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§7", "sidebar", 3)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(8, "0", 3, "§bPlayers: §r§f783§r§0§7§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-8", + Component.text("SB_l-8"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-8", TeamAction.ADD_PLAYER, new String[] {"§0§8"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-8", + Component.text("SB_l-8"), + Component.empty().append(Component.text("", NamedTextColor.DARK_GREEN)), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§8", "sidebar", 2)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(9, "0", 2, "§r§0§8§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-9", + Component.text("SB_l-9"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-9", TeamAction.ADD_PLAYER, new String[] {"§0§9"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-9", + Component.text("SB_l-9"), + Component.empty().append(Component.text("24/09/24 (g2208)", TextColor.color(0x777777))), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§9", "sidebar", 1)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(10, "0", 1, "§824/09/24 (g2208)§r§0§9§r"))); + return packet; + }, + context); + + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-10", + Component.text("SB_l-10"), + Component.empty(), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET, + new String[0])); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket("SB_l-10", TeamAction.ADD_PLAYER, new String[] {"§0§a"})); + context.translate( + setTeamTranslator, + new ClientboundSetPlayerTeamPacket( + "SB_l-10", + Component.text("SB_l-10"), + Component.empty().append(Component.text("play.cubecraft.net", NamedTextColor.GOLD)), + Component.empty(), + true, + true, + NameTagVisibility.ALWAYS, + CollisionRule.ALWAYS, + TeamColor.RESET)); + assertNoNextPacket(context); + + context.translate(setScoreTranslator, new ClientboundSetScorePacket("§0§a", "sidebar", 0)); + assertNextPacket( + () -> { + var packet = new SetScorePacket(); + packet.setAction(SetScorePacket.Action.SET); + packet.setInfos(List.of(new ScoreInfo(11, "0", 0, "§6play.cubecraft.net§r§0§a§r"))); + return packet; + }, + context); + + // after this we get a ClientboundPlayerInfoUpdatePacket with the action UPDATE_DISPLAY_NAME, + // but that one is only shown in the tablist so we don't have to handle that. + // And after that we get each player's ClientboundPlayerInfoUpdatePacket with also a UPDATE_DISPLAY_NAME, + // which is also not interesting for us. + // CubeCraft seems to use two armor stands per player: 1 for the rank badge and 1 for the player name. + // So the only thing we have to verify is that the nametag is hidden + + mockAndAddPlayerEntity(context, "A_Player", 2); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(2); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "B_Player", 3); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(3); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "E_Player", 4); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(4); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "H_Player", 5); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(5); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "J_Player", 6); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(6); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "K_Player", 7); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(7); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "L_Player", 8); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(8); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + + mockAndAddPlayerEntity(context, "O_Player", 9); + assertNextPacket( + () -> { + var packet = new SetEntityDataPacket(); + packet.setRuntimeEntityId(9); + packet.getMetadata().put(EntityDataTypes.NAME, ""); + return packet; + }, + context); + }); + } +}