Compare commits

...
20 Commits
Author SHA1 Message Date
NEZNAMY cf065ba27d 4.1.6 2024-06-13 19:19:54 +02:00
NEZNAMY f96e263bd7 Add 1.21 support 2024-06-13 19:19:41 +02:00
NEZNAMY a22f8eccaf Update message about unlimited nametags 2024-06-13 15:28:44 +02:00
NEZNAMY 718ee89211 [BungeeCord] Fix another issue leading to "Network Protocol Error" disconnect on 1.20.5+ 2024-06-13 11:33:04 +02:00
NEZNAMY 54b44ce6cd [Unlimited nametags] Print console warn if the feature is enabled 2024-06-13 11:30:26 +02:00
NEZNAMY 29da28e955 [BungeeCord] Fix error in pipeline injector (#1310) 2024-06-12 17:44:24 +02:00
NEZNAMY fa195389ab [Sorting] Fix sorting by pre-defined values space trimming inconsistency (#1308) 2024-06-11 15:39:39 +02:00
NEZNAMY 9e3864be53 [Bukkit] Add missing support for mojang mapped jars 2024-06-06 15:16:17 +02:00
NEZNAMY 29b89b3bb7 [Commands] Fix error when setting property value to ' or " 2024-06-01 16:50:02 +02:00
NEZNAMY e741608261 [Placeholders] Fix server placeholders not returning proper value on request sometimes 2024-05-30 16:10:38 +02:00
NEZNAMY 8e8a4cc9e8 [Teams] [Bukkit] Fix team action not being taken correctly on 1.8 - 1.12, causing issues with previous commit 2024-05-29 13:34:35 +02:00
NEZNAMY 3bb6e0c3f2 [Teams] Fix another instance of "Network Protocol Error" disconnect on 1.20.5+ 2024-05-28 14:40:17 +02:00
NEZNAMY 4c3b13a81c 4.1.6-SNAPSHOT 2024-05-28 14:40:04 +02:00
NEZNAMY 6b9063c789 4.1.5 2024-05-27 15:58:52 +02:00
NEZNAMY ce0a45b76c [Bukkit] Add support for latest 1.21 snapshot 2024-05-27 13:46:47 +02:00
NEZNAMY 41a0bd37ce [Sorting] Fix number compression bug (#1297) 2024-05-26 12:38:28 +02:00
NEZNAMY 449d3cb3ee [BossBar] Fix error when trying to display bossbar that was not visible on join 2024-05-22 10:14:52 +02:00
NEZNAMY a1a3852b08 [BossBar] Fix Network Protocol Error disconnect on server switch 2024-05-20 12:41:07 +02:00
NEZNAMY ef63df5175 [BossBar] Make %countdown% return 0 after announcement is over instead of going to negative numbers 2024-05-20 11:08:08 +02:00
NEZNAMY 17e57f9d0c [Bug fix] Fix disabled tablist formatting referencing wrong variable 2024-05-16 21:00:01 +02:00
26 changed files with 212 additions and 92 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.1.5-SNAPSHOT"
version = "4.1.6"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -97,7 +97,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
}
if (player == null) return "<Player cannot be null>";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
if (p == null) return "<Player is not loaded>";
return p.expansionValues.values.get(identifier);
}
@@ -29,8 +29,8 @@ public class ComponentConverter {
private final FunctionWithException<String, Object> newTextComponent;
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.ChatModifier", "ChatModifier");
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("EnumChatFormat");
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier");
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
private final Constructor<?> newChatModifier;
private final Method ChatBaseComponent_addSibling;
private final Field Component_modifier;
@@ -41,7 +41,7 @@ public class ComponentConverter {
// 1.16+
private Method ChatHexColor_fromRGB;
private Constructor<?> newMinecraftKey;
private Method ResourceLocation_tryParse;
/**
* Constructs new instance and loads all NMS classes, constructors and methods.
@@ -50,30 +50,30 @@ public class ComponentConverter {
* If something failed
*/
private ComponentConverter() throws ReflectiveOperationException {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
if (BukkitReflection.getMinorVersion() >= 19) {
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.IChatMutableComponent", "IChatMutableComponent");
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent", "IChatMutableComponent");
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
} else {
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.ChatComponentText", "ChatComponentText");
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText");
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
newTextComponent = newChatComponentText::newInstance;
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.ChatBaseComponent", "ChatBaseComponent");
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
}
if (BukkitReflection.getMinorVersion() >= 16) {
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.ChatHexColor", "ChatHexColor");
Class<?> MinecraftKey = BukkitReflection.getClass("resources.MinecraftKey", "MinecraftKey");
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ChatClickable", "ChatClickable");
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.ChatHoverable", "ChatHoverable");
newMinecraftKey = MinecraftKey.getConstructor(String.class);
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey", "MinecraftKey");
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable", "ChatClickable");
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable", "ChatHoverable");
ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
ChatHexColor_fromRGB = ReflectionUtils.getOnlyMethod(chatHexColor, chatHexColor, int.class);
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, MinecraftKey));
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
convertModifier = this::createModifierModern;
} else {
newChatModifier = ChatModifier.getConstructor();
@@ -124,7 +124,7 @@ public class ComponentConverter {
null,
null,
null,
modifier.getFont() == null ? null : newMinecraftKey.newInstance(modifier.getFont())
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
);
}
@@ -16,6 +16,7 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
/**
* Class storing all team related fields and methods.
@@ -70,7 +71,12 @@ public class TeamPacketData {
emptyScoreboard = Scoreboard.getConstructor().newInstance();
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
List<Field> intFields = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class);
if (minorVersion >= 8 && minorVersion <= 12) {
TeamPacket_ACTION = intFields.get(1);
} else {
TeamPacket_ACTION = intFields.get(0);
}
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
@@ -302,7 +308,7 @@ public class TeamPacketData {
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
if (!TeamPacketClass.isInstance(packet)) return;
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.REMOVE || action == TeamAction.UPDATE) return;
if (action == TeamAction.UPDATE) return;
TeamPacket_PLAYERS.set(packet, player.getScoreboard().onTeamPacket(
action, (String) TeamPacket_NAME.get(packet), (Collection<String>) TeamPacket_PLAYERS.get(packet)));
}
+1 -1
View File
@@ -1,7 +1,7 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bungeecord:3.0.1")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.2-SNAPSHOT")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.3-SNAPSHOT")
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -29,10 +29,12 @@ public class BungeeBossBar implements BossBar {
@NotNull
private final Map<UUID, Integer> styles = new HashMap<>();
/** Flag tracking whether boss bars should be frozen or not */
private boolean frozen;
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
packet.setHealth(progress);
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
@@ -46,8 +48,7 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
player.sendPacket(packet);
@@ -55,8 +56,7 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, float progress) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
packet.setHealth(progress);
player.sendPacket(packet);
@@ -64,8 +64,7 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
packet.setDivision(style.ordinal());
packet.setColor(colors.get(id));
@@ -76,8 +75,7 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
packet.setDivision(styles.get(id));
packet.setColor(color.ordinal());
@@ -88,11 +86,20 @@ public class BungeeBossBar implements BossBar {
@Override
public void remove(@NotNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
colors.remove(id);
styles.remove(id);
}
}
@Override
public void freeze() {
frozen = true;
}
@Override
public void unfreeze() {
frozen = false;
}
}
@@ -42,10 +42,12 @@ public class BungeeEventListener implements EventListener<ProxiedPlayer>, Listen
TAB tab = TAB.getInstance();
if (tab.isPluginDisabled()) return;
// Avoid 1.20.3 client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
// Avoid 1.20.3+ client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
// Avoid 1.20.5+ client disconnect with "Network Protocol Error"
TabPlayer p = tab.getPlayer(e.getPlayer().getUniqueId());
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
p.getScoreboard().freeze();
p.getBossBar().freeze();
}
tab.getCPUManager().runTask(() -> {
@@ -57,6 +59,7 @@ public class BungeeEventListener implements EventListener<ProxiedPlayer>, Listen
// Sending these packets before login packet will also crash the client on 1.20.3
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getScoreboard().freeze();
player.getBossBar().freeze();
}
tab.getFeatureManager().onJoin(player);
@@ -14,6 +14,8 @@ import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Scoreboard handler for BungeeCord. Because it does not offer
@@ -158,8 +160,9 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer, BaseComponent>
}
if (isAntiOverrideTeams() && packet instanceof Team) {
Team team = (Team) packet;
if (team.getMode() == TeamAction.REMOVE || team.getMode() == TeamAction.UPDATE) return;
team.setPlayers(onTeamPacket(team.getMode(), team.getName(), Lists.newArrayList(team.getPlayers())).toArray(new String[0]));
if (team.getMode() == TeamAction.UPDATE) return;
List<String> players = team.getPlayers() == null ? Collections.emptyList() : Lists.newArrayList(team.getPlayers());
team.setPlayers(onTeamPacket(team.getMode(), team.getName(), players).toArray(new String[0]));
}
}
}
+2 -2
View File
@@ -12,11 +12,11 @@ repositories {
dependencies {
api(projects.shared)
minecraft("com.mojang:minecraft:24w19a")
minecraft("com.mojang:minecraft:1.21")
mappings(loom.officialMojangMappings())
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("net.fabricmc:fabric-loader:0.15.9")
val version = "0.97.8+1.20.6"
val version = "0.100.1+1.21"
modImplementation(fabricApi.module("fabric-api-base", version))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
modImplementation(fabricApi.module("fabric-networking-api-v1", version))
@@ -4,7 +4,7 @@ import com.mojang.authlib.properties.Property;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.loader.Loader;
import me.neznamy.tab.platforms.fabric.loader.Loader_1_20_5;
import me.neznamy.tab.platforms.fabric.loader.Loader_1_21;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
@@ -38,7 +38,7 @@ public class FabricMultiVersion {
private static final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(FabricTAB.minecraftVersion);
/** Method loader using latest supported MC version */
private static final Loader loaderNew = new Loader_1_20_5();
private static final Loader loaderNew = new Loader_1_21();
/** Method loader using 1.18.2 */
private static final Loader loader1_18_2 = createLoader("1_18_2");
@@ -42,14 +42,14 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Method loader compiled using Minecraft 1.20.5.
* Method loader compiled using Minecraft 1.21.
*/
@SuppressWarnings({
"unchecked", // Java generic types
"DataFlowIssue", // Profile is not null on add action
"unused" // Actually used, just via reflection
})
public class Loader_1_20_5 implements Loader {
public class Loader_1_21 implements Loader {
@Override
@NotNull
@@ -96,7 +96,7 @@ public class Loader_1_20_5 implements Loader {
null,
null,
null,
modifier.getFont() == null ? null : new ResourceLocation(modifier.getFont())
modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont())
);
}
@@ -147,7 +147,7 @@ public class Loader_1_20_5 implements Loader {
@Override
public void checkTeamPacket(@NotNull Packet<?> packet, @NotNull FabricScoreboard scoreboard) {
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
if (team.method == Scoreboard.TeamAction.REMOVE || team.method == Scoreboard.TeamAction.UPDATE) return;
if (team.method == Scoreboard.TeamAction.UPDATE) return;
team.players = scoreboard.onTeamPacket(team.method, team.getName(), team.players);
}
}
+1 -1
View File
@@ -20,7 +20,7 @@
"1.18", "1.18.1", "1.18.2",
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
"1.21-alpha.24.19.a"
"1.21"
]
},
"recommends": {
@@ -147,7 +147,7 @@ public class Loader_1_14_4 implements Loader {
public void checkTeamPacket(@NotNull Packet<?> packet, @NotNull FabricScoreboard scoreboard) {
if (packet instanceof ClientboundSetPlayerTeamPacket) {
int action = ReflectionUtils.getInstanceFields(packet.getClass(), int.class).get(0).getInt(packet);
if (action == Scoreboard.TeamAction.REMOVE || action == Scoreboard.TeamAction.UPDATE) return;
if (action == Scoreboard.TeamAction.UPDATE) return;
Field playersField = ReflectionUtils.getFields(packet.getClass(), Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(packet.getClass(), String.class).get(0).get(packet));
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared;
import java.util.*;
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.config.Configs;
import me.neznamy.tab.shared.config.mysql.MySQLUserConfiguration;
import me.neznamy.tab.shared.features.*;
@@ -487,6 +488,18 @@ public class FeatureManager {
NameTag unlimited = TAB.getInstance().getPlatform().getUnlimitedNameTags();
if (unlimited instanceof NameTagX) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, unlimited);
for (String message : new String[]{
"---------------------------------------------------------------------",
"You have unlimited nametag mode feature enabled.",
"This feature is scheduled for removal in a future TAB release.",
"Please considering the alternative solutions available to achieve your desired result.",
"No bug reports with this feature will be accepted anymore.",
"No support with this feature will be provided anymore.",
"See https://gist.github.com/NEZNAMY/f4cabf2fd9251a836b5eb877720dee5c/ for more info.",
"---------------------------------------------------------------------"
}) {
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText(message));
}
} else {
featureManager.registerFeature(TabConstants.Feature.NAME_TAGS, unlimited);
}
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
public enum ProtocolVersion {
UNKNOWN,
V1_21 (767),
V1_20_6 (766),
V1_20_5 (766),
V1_20_4 (765),
@@ -81,7 +82,7 @@ public enum ProtocolVersion {
private static final ProtocolVersion[] VALUES = values();
/** Newest MC version this plugin jar knows */
public static final ProtocolVersion LATEST_KNOWN_VERSION = V1_20_6;
public static final ProtocolVersion LATEST_KNOWN_VERSION = VALUES[1];
/** Version's network id found at <a href="https://wiki.vg/Protocol_version_numbers">wiki.vg</a> */
private final int networkId;
@@ -42,7 +42,7 @@ public abstract class PropertyCommand extends SubCommand {
server = args[args.length-1];
value = value.startsWith("-s") ? "" : value.substring(0, value.length()-server.length()-4);
}
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
if (((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) && value.length() > 1) {
value = value.substring(1, value.length()-1);
}
String property = args[1].toLowerCase();
@@ -237,7 +237,7 @@ public class PlayerList extends TabFeature implements TabListFormatManager, Join
connectedPlayer.getTabList().setAntiOverride(antiOverrideTabList);
updateProperties(connectedPlayer);
if (disableChecker.isDisableConditionMet(connectedPlayer)) {
connectedPlayer.disabledHeaderFooter.set(true);
connectedPlayer.disabledPlayerList.set(true);
return;
}
Runnable r = () -> {
@@ -264,12 +264,12 @@ public class BossBarLine implements BossBar {
public void addPlayer(@NonNull me.neznamy.tab.api.TabPlayer p) {
TabPlayer player = (TabPlayer) p;
if (players.contains(player)) return;
players.add(player);
player.setProperty(textRefresher, propertyTitle, title);
player.setProperty(progressRefresher, propertyProgress, progress);
player.setProperty(colorRefresher, propertyColor, color);
player.setProperty(styleRefresher, propertyStyle, style);
sendToPlayerRaw(player);
players.add(player);
}
@Override
@@ -83,8 +83,11 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager, Jo
@Override
public void load() {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.COUNTDOWN, 100,
() -> TimeUnit.MILLISECONDS.toSeconds(announceEndTime - System.currentTimeMillis()));
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.COUNTDOWN, 100, () -> {
long seconds = TimeUnit.MILLISECONDS.toSeconds(announceEndTime - System.currentTimeMillis());
if (seconds < 0) return 0;
return seconds;
});
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
onJoin(p);
}
@@ -165,12 +168,11 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager, Jo
}
}
@Override
public void onLoginPacket(TabPlayer player) {
// Since 1.20.2, Login packet clears BossBars as well
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getBossBar().unfreeze();
for (BossBar bar : lineValues) {
if (bar.containsPlayer(player)) {
((BossBarLine)bar).sendToPlayerRaw(player);
@@ -63,12 +63,13 @@ public class Placeholder extends SortingType {
String output = EnumChatFormat.color(setPlaceholders(p));
p.sortingData.teamNameNote += "\n-> " + sortingPlaceholder.getIdentifier() + " returned \"&e" + output + "&r\"";
int position;
if (!sortingMap.containsKey(output.toLowerCase(Locale.US))) {
TAB.getInstance().getConfigHelper().runtime().valueNotInPredefinedValues(sortingPlaceholder.getIdentifier(), sortingMap.keySet(), output, p);
String cleanOutput = output.trim().toLowerCase(Locale.US);
if (!sortingMap.containsKey(cleanOutput)) {
TAB.getInstance().getConfigHelper().runtime().valueNotInPredefinedValues(sortingPlaceholder.getIdentifier(), sortingMap.keySet(), cleanOutput, p);
position = sortingMap.size()+1;
p.sortingData.teamNameNote += "&c (not in list)&r. ";
} else {
position = sortingMap.get(output.toLowerCase(Locale.US));
position = sortingMap.get(cleanOutput);
p.sortingData.teamNameNote += "&r &a(#" + position + " in list). &r";
}
return String.valueOf((char) (position + 47));
@@ -80,27 +80,27 @@ public abstract class SortingType {
/**
* Compresses a number to ### format, where # is a character symbol representing
* a number in a base of 65536. The first two represent the whole part, the third one decimal part.
* a number in a base of 65534. The first two represent the whole part, the third one decimal part.
* The maximum number it will work properly with is {@link Integer#MAX_VALUE}.
*
* @param number
* Number to convert
* @return 3 characters long String of converted number with a base of 65536.
* @return 3 characters long String of converted number with a base of 65534.
*/
public String compressNumber(double number) {
int wholePart = (int) number;
char decimalChar = (char) ((number - wholePart) * (Character.MAX_VALUE - 1));
int base = Character.MAX_VALUE - 1;
char decimalChar = (char) ((number - wholePart) * base);
// The \ symbol breaks json syntax, skip it (and reduce range) (why is it not being escaped by json writer?)
if (decimalChar >= '\\') decimalChar++;
StringBuilder sb = new StringBuilder();
while (wholePart > 0) {
char digit = (char) (wholePart % (Character.MAX_VALUE - 1));
char digit = (char) (wholePart % base);
if (digit >= '\\') digit++;
sb.append(digit);
wholePart /= Character.MAX_VALUE;
sb.insert(0, digit);
wholePart /= base;
}
sb.reverse();
if (sb.length() == 1) sb.insert(0, (char) 0); // Avoid a single # if number is < 65535
if (sb.length() == 1) sb.insert(0, (char) 0); // Avoid a single # if number is < base
sb.append(decimalChar);
return sb.toString();
}
@@ -90,7 +90,7 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
@Override
@NotNull
public String getLastValueSafe(@NotNull TabPlayer player) {
return identifier;
return lastValue;
}
/**
@@ -74,4 +74,19 @@ public interface BossBar {
* Unique identifier to match the bossbar with other functions
*/
void remove(@NotNull UUID id);
/**
* Freezes the class, not letting any packets through.
*/
default void freeze() {
// Empty by default
}
/**
* Unfreezes the class, enabling it back.
*/
default void unfreeze() {
// Empty by default
}
}
@@ -37,6 +37,12 @@ public abstract class Scoreboard<T extends TabPlayer, C> {
/** Player-to-Team map of expected teams of players */
private final Map<String, String> expectedTeams = new HashMap<>();
/** Map of blocked team adds, key is player and value is team name */
private final Map<String, String> blockedTeamAdds = new HashMap<>();
/** Map of allowed team adds, key is player and value is team name */
private final Map<String, String> allowedTeamAdds = new HashMap<>();
/** Flag tracking time between Login packet send and its processing */
private boolean frozen;
@@ -372,26 +378,48 @@ public abstract class Scoreboard<T extends TabPlayer, C> {
@NotNull
public Collection<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
String expectedTeam = expectedTeams.get(entry);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!teamName.equals(expectedTeam)) {
// on REMOVE_PLAYER don't warn, just remove the player (required to prevent FPS drop, #1193)
// will still throw error and freeze if removing after team handling was paused (API, disable condition),
// since expected team is null, but add action was blocked previously but remove will not be blocked anymore,
// but that is a very specific case that people are unlikely to even reproduce
// workaround would be complicated, just let it be for now
if (action == TeamAction.CREATE || action == TeamAction.ADD_PLAYER) {
if (action == TeamAction.CREATE || action == TeamAction.ADD_PLAYER) {
for (String entry : players) {
String expectedTeam = expectedTeams.get(entry);
if (expectedTeam == null) {
blockedTeamAdds.remove(entry);
allowedTeamAdds.put(entry, teamName);
newList.add(entry);
continue;
}
if (teamName.equals(expectedTeam)) {
newList.add(entry);
allowedTeamAdds.remove(entry);
} else {
blockedTeamAdds.put(entry, teamName);
logTeamOverride(teamName, entry, expectedTeam);
}
} else {
newList.add(entry);
}
return newList;
}
return newList;
if (action == TeamAction.REMOVE_PLAYER) {
// TAB does not send remove player, making checks easier
for (String entry : players) {
String expectedTeam = expectedTeams.get(entry);
if (expectedTeam != null) {
allowedTeamAdds.remove(entry);
blockedTeamAdds.remove(entry);
continue;
}
if (allowedTeamAdds.containsKey(entry)) {
allowedTeamAdds.remove(entry);
newList.add(entry);
continue;
}
blockedTeamAdds.remove(entry);
}
return newList;
}
if (action == TeamAction.REMOVE) {
allowedTeamAdds.entrySet().removeIf(entry -> entry.getValue().equals(teamName));
blockedTeamAdds.entrySet().removeIf(entry -> entry.getValue().equals(teamName));
}
return players;
}
/**
@@ -403,7 +431,7 @@ public abstract class Scoreboard<T extends TabPlayer, C> {
* @param player
* Player who was about to be added into the team
* @param expectedTeam
* Expected name of the team
* Expected team
*/
public static void logTeamOverride(@NonNull String team, @NonNull String player, @NonNull String expectedTeam) {
String message = "Blocked attempt to add player " + player + " into team " + team + " (expected team: " + expectedTeam + ")";
@@ -1,14 +1,17 @@
package me.neznamy.tab.shared.platform.impl;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar.Color;
import net.kyori.adventure.bossbar.BossBar.Overlay;
@@ -17,23 +20,25 @@ import org.jetbrains.annotations.NotNull;
/**
* Shared BossBar implementation using Adventure API.
*/
@AllArgsConstructor
@RequiredArgsConstructor
public class AdventureBossBar implements BossBar {
/** Flag tracking whether this implementation is available on the server or not */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.bossbar.BossBar");
/** Player this BossBar belongs to */
private final TabPlayer player;
/** BossBars currently visible to the player */
private final Map<UUID, net.kyori.adventure.bossbar.BossBar> bossBars = new HashMap<>();
/** Flag tracking whether boss bars should be frozen or not */
private boolean frozen;
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) {
// Can happen on 1.20.2+ on Velocity on server switch
((Audience)player.getPlayer()).hideBossBar(bossBars.get(id));
((Audience)player.getPlayer()).showBossBar(bossBars.get(id));
return;
}
if (frozen) return; // Server switch
net.kyori.adventure.bossbar.BossBar bar = net.kyori.adventure.bossbar.BossBar.bossBar(
TabComponent.optimized(title).convert(player.getVersion()),
progress,
@@ -46,26 +51,42 @@ public class AdventureBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
if (frozen) return; // Server switch
bossBars.get(id).name(TabComponent.optimized(title).convert(player.getVersion()));
}
@Override
public void update(@NotNull UUID id, float progress) {
if (frozen) return; // Server switch
bossBars.get(id).progress(progress);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
if (frozen) return; // Server switch
bossBars.get(id).overlay(Overlay.valueOf(style.name()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
if (frozen) return; // Server switch
bossBars.get(id).color(Color.valueOf(color.name()));
}
@Override
public void remove(@NotNull UUID id) {
if (frozen) return; // Server switch
((Audience)player.getPlayer()).hideBossBar(bossBars.remove(id));
}
@Override
public void freeze() {
bossBars.clear();
frozen = true;
}
@Override
public void unfreeze() {
frozen = false;
}
}
@@ -1,12 +1,15 @@
package me.neznamy.tab.platforms.velocity;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.proxy.Player;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
@@ -32,6 +35,23 @@ public class VelocityEventListener implements EventListener<Player> {
quit(e.getPlayer().getUniqueId());
}
/**
* Freezes Boss bar for 1.20.2+ players due to bug with adventure that causes disconnect
* on 1.20.5+ with "Network Protocol Error"
*
* @param e
* Event fired before player switches server for proper freezing
*/
@Subscribe(order = PostOrder.LAST)
public void preConnect(@NotNull ServerPreConnectEvent e) {
if (e.getResult().isAllowed()) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
p.getBossBar().freeze();
}
}
}
/**
* Listens to player connecting to a backend server. This handles
* both initial connections and server switch.
@@ -97,4 +117,4 @@ public class VelocityEventListener implements EventListener<Player> {
public TabPlayer createPlayer(@NotNull Player player) {
return new VelocityTabPlayer((VelocityPlatform) TAB.getInstance().getPlatform(), player);
}
}
}