Improved serializer system for packet adapters & fixed bugs with nametags (#123)

This commit is contained in:
AlexDev_ 2023-11-30 16:15:18 +01:00 committed by GitHub
parent 4d6621c3c1
commit 3b1be4142f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 214 additions and 68 deletions

View File

@ -95,6 +95,8 @@ shadowJar {
destinationDirectory.set(file("$rootDir/target"))
archiveClassifier.set('')
minimize()
}
jar.dependsOn shadowJar
clean.delete "$rootDir/target"

View File

@ -128,7 +128,7 @@ public class LuckPermsHook extends Hook {
if (oldRole.equals(player.getRole())) {
return;
}
plugin.getTabList().updatePlayer(player);
plugin.getTabList().updatePlayer(player, false);
}
}

View File

@ -23,6 +23,8 @@ package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
@ -36,8 +38,11 @@ import java.util.Set;
@SuppressWarnings("DuplicatedCode")
public class Protocol340Adapter extends TeamsPacketAdapter {
private final LegacyComponentSerializer serializer;
public Protocol340Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_12_2));
serializer = LegacyComponentSerializer.legacySection();
}
@Override
@ -49,9 +54,13 @@ public class Protocol340Adapter extends TeamsPacketAdapter {
return;
}
if (mode == UpdateTeamsPacket.UpdateMode.CREATE_TEAM || mode == UpdateTeamsPacket.UpdateMode.UPDATE_INFO) {
ProtocolUtils.writeString(byteBuf, packet.displayName());
ProtocolUtils.writeString(byteBuf, packet.prefix());
ProtocolUtils.writeString(byteBuf, packet.suffix());
final String displayName = getChatString(packet.displayName());
final String prefix = getChatString(packet.prefix());
final String suffix = getChatString(packet.suffix());
ProtocolUtils.writeString(byteBuf, shrinkString(displayName));
ProtocolUtils.writeString(byteBuf, shrinkString(prefix));
ProtocolUtils.writeString(byteBuf, shrinkString(suffix));
byteBuf.writeByte(UpdateTeamsPacket.FriendlyFlag.toBitMask(packet.friendlyFlags()));
ProtocolUtils.writeString(byteBuf, packet.nametagVisibility().id());
ProtocolUtils.writeString(byteBuf, packet.collisionRule().id());
@ -65,4 +74,21 @@ public class Protocol340Adapter extends TeamsPacketAdapter {
}
}
}
/**
* Returns a shortened version of the given string, with a maximum length of 16 characters.
* This is used to ensure that the team name, display name, prefix and suffix are not too long for the client.
* @param string the string to be shortened
* @return the shortened string
*/
@NotNull
private String shrinkString(@NotNull String string) {
return string.substring(0, Math.min(string.length(), 16));
}
@NotNull
@Override
protected String getChatString(@NotNull Component component) {
return serializer.serialize(component);
}
}

View File

@ -0,0 +1,90 @@
/*
* This file is part of Velocitab, licensed under the Apache License 2.0.
*
* Copyright (c) William278 <will27528@gmail.com>
* Copyright (c) contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* Adapter for handling the UpdateTeamsPacket for Minecraft 1.13.2 - 1.15.2
*/
@SuppressWarnings("DuplicatedCode")
public class Protocol404Adapter extends TeamsPacketAdapter {
private final GsonComponentSerializer serializer;
public Protocol404Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_13_2,
ProtocolVersion.MINECRAFT_1_14,
ProtocolVersion.MINECRAFT_1_14_1,
ProtocolVersion.MINECRAFT_1_14_2,
ProtocolVersion.MINECRAFT_1_14_3,
ProtocolVersion.MINECRAFT_1_14_4,
ProtocolVersion.MINECRAFT_1_15,
ProtocolVersion.MINECRAFT_1_15_1,
ProtocolVersion.MINECRAFT_1_15_2
));
serializer = GsonComponentSerializer.colorDownsamplingGson();
}
@Override
public void encode(@NotNull ByteBuf byteBuf, @NotNull UpdateTeamsPacket packet) {
ProtocolUtils.writeString(byteBuf, packet.teamName());
UpdateTeamsPacket.UpdateMode mode = packet.mode();
byteBuf.writeByte(mode.id());
if (mode == UpdateTeamsPacket.UpdateMode.REMOVE_TEAM) {
return;
}
if (mode == UpdateTeamsPacket.UpdateMode.CREATE_TEAM || mode == UpdateTeamsPacket.UpdateMode.UPDATE_INFO) {
ProtocolUtils.writeString(byteBuf, getChatString(packet.displayName()));
byteBuf.writeByte(UpdateTeamsPacket.FriendlyFlag.toBitMask(packet.friendlyFlags()));
ProtocolUtils.writeString(byteBuf, packet.nametagVisibility().id());
ProtocolUtils.writeString(byteBuf, packet.collisionRule().id());
byteBuf.writeByte(packet.color());
ProtocolUtils.writeString(byteBuf, getChatString(packet.prefix()));
ProtocolUtils.writeString(byteBuf, getChatString(packet.suffix()));
}
if (mode == UpdateTeamsPacket.UpdateMode.CREATE_TEAM || mode == UpdateTeamsPacket.UpdateMode.ADD_PLAYERS || mode == UpdateTeamsPacket.UpdateMode.REMOVE_PLAYERS) {
List<String> entities = packet.entities();
ProtocolUtils.writeVarInt(byteBuf, entities != null ? entities.size() : 0);
for (String entity : entities != null ? entities : new ArrayList<String>()) {
ProtocolUtils.writeString(byteBuf, entity);
}
}
}
@NotNull
@Override
protected String getChatString(@NotNull Component component) {
return serializer.serialize(component);
}
}

View File

@ -23,6 +23,8 @@ package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
@ -36,22 +38,29 @@ import java.util.Set;
@SuppressWarnings("DuplicatedCode")
public class Protocol48Adapter extends TeamsPacketAdapter {
private final LegacyComponentSerializer serializer;
public Protocol48Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_8));
serializer = LegacyComponentSerializer.legacySection();
}
@Override
public void encode(@NotNull ByteBuf byteBuf, @NotNull UpdateTeamsPacket packet) {
ProtocolUtils.writeString(byteBuf, packet.teamName().substring(0, Math.min(packet.teamName().length(), 16)));
ProtocolUtils.writeString(byteBuf, shrinkString(packet.teamName()));
UpdateTeamsPacket.UpdateMode mode = packet.mode();
byteBuf.writeByte(mode.id());
if (mode == UpdateTeamsPacket.UpdateMode.REMOVE_TEAM) {
return;
}
if (mode == UpdateTeamsPacket.UpdateMode.CREATE_TEAM || mode == UpdateTeamsPacket.UpdateMode.UPDATE_INFO) {
ProtocolUtils.writeString(byteBuf, packet.displayName());
ProtocolUtils.writeString(byteBuf, packet.prefix());
ProtocolUtils.writeString(byteBuf, packet.suffix());
final String displayName = getChatString(packet.displayName());
final String prefix = getChatString(packet.prefix());
final String suffix = getChatString(packet.suffix());
ProtocolUtils.writeString(byteBuf, shrinkString(displayName));
ProtocolUtils.writeString(byteBuf, shrinkString(prefix));
ProtocolUtils.writeString(byteBuf, shrinkString(suffix));
byteBuf.writeByte(UpdateTeamsPacket.FriendlyFlag.toBitMask(packet.friendlyFlags()));
ProtocolUtils.writeString(byteBuf, packet.nametagVisibility().id());
byteBuf.writeByte(packet.color());
@ -64,4 +73,21 @@ public class Protocol48Adapter extends TeamsPacketAdapter {
}
}
}
/**
* Returns a shortened version of the given string, with a maximum length of 16 characters.
* This is used to ensure that the team name, display name, prefix and suffix are not too long for the client.
* @param string the string to be shortened
* @return the shortened string
*/
@NotNull
private String shrinkString(@NotNull String string) {
return string.substring(0, Math.min(string.length(), 16));
}
@NotNull
@Override
protected String getChatString(@NotNull Component component) {
return serializer.serialize(component);
}
}

View File

@ -25,9 +25,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Formatter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -35,21 +33,15 @@ import java.util.List;
import java.util.Set;
/**
* Adapter for handling the UpdateTeamsPacket for Minecraft 1.13.2+
* Adapter for handling the UpdateTeamsPacket for Minecraft 1.16 - 1.20.2
*/
@SuppressWarnings("DuplicatedCode")
public class Protocol403Adapter extends TeamsPacketAdapter {
public class Protocol735Adapter extends TeamsPacketAdapter {
public Protocol403Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_13_2,
ProtocolVersion.MINECRAFT_1_14,
ProtocolVersion.MINECRAFT_1_14_1,
ProtocolVersion.MINECRAFT_1_14_2,
ProtocolVersion.MINECRAFT_1_14_3,
ProtocolVersion.MINECRAFT_1_14_4,
ProtocolVersion.MINECRAFT_1_15,
ProtocolVersion.MINECRAFT_1_15_1,
ProtocolVersion.MINECRAFT_1_15_2,
private final GsonComponentSerializer serializer;
public Protocol735Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(
ProtocolVersion.MINECRAFT_1_16,
ProtocolVersion.MINECRAFT_1_16_1,
ProtocolVersion.MINECRAFT_1_16_2,
@ -58,16 +50,15 @@ public class Protocol403Adapter extends TeamsPacketAdapter {
ProtocolVersion.MINECRAFT_1_17,
ProtocolVersion.MINECRAFT_1_17_1,
ProtocolVersion.MINECRAFT_1_18,
//ProtocolVersion.MINECRAFT_1_18_1,
ProtocolVersion.MINECRAFT_1_18_2,
ProtocolVersion.MINECRAFT_1_19,
ProtocolVersion.MINECRAFT_1_19_1,
//ProtocolVersion.MINECRAFT_1_19_2,
ProtocolVersion.MINECRAFT_1_19_3,
ProtocolVersion.MINECRAFT_1_19_4,
ProtocolVersion.MINECRAFT_1_20,
ProtocolVersion.MINECRAFT_1_20_2
));
serializer = GsonComponentSerializer.gson();
}
@Override
@ -84,8 +75,8 @@ public class Protocol403Adapter extends TeamsPacketAdapter {
ProtocolUtils.writeString(byteBuf, packet.nametagVisibility().id());
ProtocolUtils.writeString(byteBuf, packet.collisionRule().id());
byteBuf.writeByte(packet.color());
ProtocolUtils.writeString(byteBuf, getRGBChat(packet.prefix()));
ProtocolUtils.writeString(byteBuf, getRGBChat(packet.suffix()));
ProtocolUtils.writeString(byteBuf, getChatString(packet.prefix()));
ProtocolUtils.writeString(byteBuf, getChatString(packet.suffix()));
}
if (mode == UpdateTeamsPacket.UpdateMode.CREATE_TEAM || mode == UpdateTeamsPacket.UpdateMode.ADD_PLAYERS || mode == UpdateTeamsPacket.UpdateMode.REMOVE_PLAYERS) {
List<String> entities = packet.entities();
@ -97,13 +88,9 @@ public class Protocol403Adapter extends TeamsPacketAdapter {
}
@NotNull
private String getRGBChat(@NotNull String input) {
if (!getPlugin().getFormatter().equals(Formatter.LEGACY)) {
return getChatString(input);
}
final Component component = LegacyComponentSerializer.builder().hexColors().character('&').build().deserialize(input);
return GsonComponentSerializer.gson().serialize(component);
@Override
protected String getChatString(@NotNull Component component) {
return serializer.serialize(component);
}
}

View File

@ -53,7 +53,8 @@ public class ScoreboardManager {
}
private void registerVersions() {
versions.add(new Protocol403Adapter(plugin));
versions.add(new Protocol735Adapter(plugin));
versions.add(new Protocol404Adapter(plugin));
versions.add(new Protocol340Adapter(plugin));
versions.add(new Protocol48Adapter(plugin));
}
@ -128,14 +129,21 @@ public class ScoreboardManager {
cachedTag.ifPresent(nametag -> {
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(
plugin, createdTeams.get(player.getUniqueId()),
"", nametag, player.getUsername()
nametag, player.getUsername()
);
siblings.forEach(server -> server.getPlayersConnected().stream().filter(p -> p != player)
.forEach(connected -> dispatchPacket(packet, connected)));
});
}
public void updateRole(@NotNull Player player, @NotNull String role) {
/**
* Updates the role of the player in the scoreboard.
*
* @param player The player whose role will be updated. Must not be null.
* @param role The new role of the player. Must not be null.
* @param force Whether to force the update even if the player's nametag is the same.
*/
public void updateRole(@NotNull Player player, @NotNull String role, boolean force) {
if (!player.isActive()) {
plugin.getTabList().removeOfflinePlayer(player);
return;
@ -155,10 +163,10 @@ public class ScoreboardManager {
createdTeams.put(player.getUniqueId(), role);
this.nametags.put(role, newTag);
dispatchGroupPacket(
UpdateTeamsPacket.create(plugin, role, "", newTag, name),
UpdateTeamsPacket.create(plugin, role, newTag, name),
player
);
} else if (this.nametags.containsKey(role) && this.nametags.get(role).equals(newTag)) {
} else if (force || (this.nametags.containsKey(role) && !this.nametags.get(role).equals(newTag))) {
this.nametags.put(role, newTag);
dispatchGroupPacket(
UpdateTeamsPacket.changeNametag(plugin, role, newTag),
@ -215,7 +223,7 @@ public class ScoreboardManager {
if (tag != null) {
final TabPlayer.Nametag nametag = nametags.get(role);
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(
plugin, role, "", nametag, p.getUsername()
plugin, role, nametag, p.getUsername()
);
dispatchPacket(packet, player);
}
@ -314,7 +322,7 @@ public class ScoreboardManager {
final TabPlayer.Nametag tag = nametags.get(team);
if (tag != null) {
final UpdateTeamsPacket addTeam = UpdateTeamsPacket.create(
plugin, team, "", tag, target.getPlayer().getUsername()
plugin, team, tag, target.getPlayer().getUsername()
);
dispatchPacket(addTeam, player);
}

View File

@ -24,8 +24,8 @@ import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab;
import org.apache.commons.text.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
@ -40,9 +40,6 @@ public abstract class TeamsPacketAdapter {
public abstract void encode(@NotNull ByteBuf byteBuf, @NotNull UpdateTeamsPacket packet);
@NotNull
protected String getChatString(@NotNull String string) {
return String.format("{\"text\":\"%s\"}", StringEscapeUtils.escapeJson(string));
}
protected abstract String getChatString(@NotNull Component component);
}

View File

@ -44,19 +44,19 @@ import java.util.stream.Collectors;
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(fluent = true)
@SuppressWarnings("unused")
public class UpdateTeamsPacket implements MinecraftPacket {
private final Velocitab plugin;
private String teamName;
private UpdateMode mode;
private String displayName;
private Component displayName;
private List<FriendlyFlag> friendlyFlags;
private NametagVisibility nametagVisibility;
private CollisionRule collisionRule;
private int color;
private String prefix;
private String suffix;
private Component prefix;
private Component suffix;
private List<String> entities;
public UpdateTeamsPacket(@NotNull Velocitab plugin) {
@ -65,18 +65,18 @@ public class UpdateTeamsPacket implements MinecraftPacket {
@NotNull
protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull String teamName,
@NotNull String displayName, @NotNull TabPlayer.Nametag nametag,
@NotNull TabPlayer.Nametag nametag,
@NotNull String... teamMembers) {
return new UpdateTeamsPacket(plugin)
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
.mode(UpdateMode.CREATE_TEAM)
.displayName(displayName)
.displayName(Component.empty())
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS)
.color(getLastColor(nametag.getPrefix()))
.prefix(nametag.getPrefix() == null ? "" : nametag.getPrefix())
.suffix(nametag.getSuffix() == null ? "" : nametag.getSuffix())
.prefix(nametag.getPrefixComponent(plugin))
.suffix(nametag.getSuffixComponent(plugin))
.entities(Arrays.asList(teamMembers));
}
@ -85,8 +85,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
return true;
}
return nametag.getPrefix() != null && !nametag.getPrefix().isEmpty()
|| nametag.getSuffix() != null && !nametag.getSuffix().isEmpty();
return !nametag.getPrefix().isEmpty() || !nametag.getSuffix().isEmpty();
}
@NotNull
@ -95,13 +94,13 @@ public class UpdateTeamsPacket implements MinecraftPacket {
return new UpdateTeamsPacket(plugin)
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
.mode(UpdateMode.UPDATE_INFO)
.displayName(teamName)
.displayName(Component.empty())
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nametagVisibility(isNametagPresent(nametag, plugin) ? NametagVisibility.ALWAYS : NametagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS)
.color(getLastColor(nametag.getPrefix()))
.prefix(nametag.getPrefix() == null ? "" : nametag.getPrefix())
.suffix(nametag.getSuffix() == null ? "" : nametag.getSuffix());
.prefix(nametag.getPrefixComponent(plugin))
.suffix(nametag.getSuffixComponent(plugin));
}
@NotNull

View File

@ -200,8 +200,10 @@ public final class TabPlayer implements Comparable<TabPlayer> {
*/
@Getter
@AllArgsConstructor
public static class Nametag {
public class Nametag {
@NotNull
private final String prefix;
@NotNull
private final String suffix;
private Nametag(@NotNull String tag, @NotNull Player player) {
@ -210,13 +212,22 @@ public final class TabPlayer implements Comparable<TabPlayer> {
this.suffix = split.length > 1 ? split[1] : "";
}
@NotNull
public Component getPrefixComponent(@NotNull Velocitab plugin) {
return plugin.getFormatter().format(prefix, TabPlayer.this, plugin);
}
@NotNull
public Component getSuffixComponent(@NotNull Velocitab plugin) {
return plugin.getFormatter().format(suffix, TabPlayer.this, plugin);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Nametag other)) {
return false;
}
return (prefix != null && prefix.equals(other.prefix)) &&
(suffix != null && suffix.equals(other.suffix));
return (prefix.equals(other.prefix)) && (suffix.equals(other.suffix));
}
}

View File

@ -203,7 +203,7 @@ public class PlayerTabList {
plugin.getScoreboardManager().ifPresent(s -> {
s.resendAllTeams(joined);
tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t));
tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t, false));
});
// Fire event without listening for result
@ -286,8 +286,8 @@ public class PlayerTabList {
);
}
// Update a player's name in the tab list
public void updatePlayer(@NotNull TabPlayer tabPlayer) {
// Update a player's name in the tab list and scoreboard team
public void updatePlayer(@NotNull TabPlayer tabPlayer, boolean force) {
if (!tabPlayer.getPlayer().isActive()) {
removeOfflinePlayer(tabPlayer.getPlayer());
return;
@ -298,7 +298,7 @@ public class PlayerTabList {
return;
}
plugin.getScoreboardManager().ifPresent(manager -> manager.updateRole(
tabPlayer.getPlayer(), teamName
tabPlayer.getPlayer(), teamName, force
));
});
}
@ -355,7 +355,7 @@ public class PlayerTabList {
return;
}
players.values().forEach(player -> {
this.updatePlayer(player);
this.updatePlayer(player, false);
player.sendHeaderAndFooter(this);
});
updateDisplayNames();
@ -380,7 +380,7 @@ public class PlayerTabList {
this.updatePeriodically(plugin.getSettings().getUpdateRate());
} else {
players.values().forEach(player -> {
this.updatePlayer(player);
this.updatePlayer(player, true);
player.sendHeaderAndFooter(this);
});
updateDisplayNames();