Compare commits

...
62 Commits
Author SHA1 Message Date
NEZNAMY 9da0db5f74 Disable creation of blank issues 2023-11-04 11:55:58 +01:00
NEZNAMY 562a973c84 Create new issue template 2023-11-04 11:53:10 +01:00
NEZNAMY 967980ddf7 Update bug_report.yml 2023-11-03 21:27:24 +01:00
NEZNAMY 81d3675a87 Check for bundle packet on 1.19.4+ when reading entity packets for unlimited nametags 2023-11-03 21:25:21 +01:00
NEZNAMY 7be786bbaf Fix vanish change being called on join even if vanish status did not change 2023-11-02 17:24:02 +01:00
NEZNAMY f41c9be1f7 Fix race condition on Velocity causing errors on shutdown 2023-11-02 17:21:48 +01:00
NEZNAMY e88062d028 Fix unvanish method not being called on server switch when clearing vanish status 2023-11-02 16:08:15 +01:00
NEZNAMY edb2828643 Fix wrong java instruction 2023-11-02 15:58:34 +01:00
NEZNAMY 58c189cd0e Catch all exceptions/errors on load/unload to prevent players from getting kicked on bungeecord if something changes again in the future 2023-10-31 16:41:29 +01:00
NEZNAMY 012994847e Unregister teams of players who vanished for those who cannot see vanished players to prevent 3rd party clients from revealing vanished players by finding out who is vanished and who actually left. 2023-10-30 20:39:21 +01:00
NEZNAMY d87801be5f Add support for latest BungeeCord (#1123) 2023-10-30 16:02:31 +01:00
NEZNAMY 6d78c0c104 Create a class storing values of minecraft limitations 2023-10-29 15:25:00 +01:00
NEZNAMY 3a83519122 Fix tablist entry related issues on both BungeeCord and Velocity (each one was affected by a different issue) 2023-10-27 22:27:42 +02:00
NEZNAMY a9f940ebed Avoid attempts to send packets to offline players on bungeecord 2023-10-27 19:17:56 +02:00
NEZNAMY db73d9ef81 Simplify 2023-10-24 17:46:25 +02:00
NEZNAMY e2bd21c717 Cache adventure components & use Paper API for header/footer by default. This has reduced CPU usage of the feature by 50% in the test I have done. 2023-10-24 17:40:22 +02:00
NEZNAMY 871215b7b5 Do not attempt to send scoreboard-related packets between login packet send and process 2023-10-24 15:18:17 +02:00
NEZNAMY a6153ef46f Force-fix NPE that doesn't make sense 2023-10-24 13:42:55 +02:00
NEZNAMY 35475a6103 Fix the previous error on Fabric & Sponge as well 2023-10-24 13:42:35 +02:00
NEZNAMY bf77ffc1a5 Fix error (#1117) 2023-10-22 23:01:53 +02:00
NEZNAMY 7a64a9a534 Code style 2023-10-22 20:08:27 +02:00
NEZNAMY 68e057b99c Fabric - Implement world name recognition and everything related to it (#1115) 2023-10-22 17:44:34 +02:00
NEZNAMY 7338afff96 Fix error when using 3 years old version of PlaceholderAPI 2023-10-21 11:27:38 +02:00
NEZNAMY 97ae7813aa Add vanish status updating to RedisBungee (#1109) 2023-10-18 16:48:01 +02:00
NEZNAMY c0f3667ac1 4.0.8-SNAPSHOT 2023-10-18 16:47:37 +02:00
NEZNAMY d5c5502abe 4.0.7 2023-10-16 20:45:35 +02:00
NEZNAMY 194e8ffbd9 Code style 2023-10-15 20:39:06 +02:00
NEZNAMY ef39458abd With all the problems on 1.20.2 BungeeCord it turns out that Velocity isn't that bad. 2023-10-15 17:39:21 +02:00
NEZNAMY 6a6907fb0a Process login packet with a delay to avoid problems 2023-10-15 17:33:06 +02:00
NEZNAMY 8bb7c4a4a6 Adapt redis support subfeatures to recent changes 2023-10-15 17:00:55 +02:00
NEZNAMY 91700e7ed0 Fix global playerlist not readding players for 1.20.2 clients on server switch 2023-10-15 17:00:16 +02:00
NEZNAMY 661c2e6fc3 Simplify 2023-10-15 15:37:38 +02:00
NEZNAMY 3d053a6149 Implement backup solutions if header/footer fields fail to initialize 2023-10-15 15:35:25 +02:00
NEZNAMY 34179e4f62 Fix startup on old versions due to wrong enum names 2023-10-15 14:56:40 +02:00
NEZNAMY a23e5d9f50 Remove toComponent method from Platform interface and only keep it in platforms that actually use it 2023-10-15 14:26:09 +02:00
NEZNAMY 1cbcbf70d9 Fix aligned playerlist attempting to send packets to players who just left 2023-10-15 14:08:35 +02:00
NEZNAMY 0a7371aaec Micro optimizations with enum conversions 2023-10-14 13:55:52 +02:00
NEZNAMY da13e2d861 Fix Bridge still injecting placeholders that are not used on proxy anymore 2023-10-13 12:27:12 +02:00
NEZNAMY 5b18fa8c25 Fix PingSpoof spoofing everything instead of just online players (or their respective layout entries) 2023-10-13 11:34:38 +02:00
NEZNAMY 347231850a Fix error 2023-10-13 09:51:40 +02:00
NEZNAMY b1f9bdc26c Resend BossBar on server switch for 1.20.2 players as Login packet now clears it 2023-10-12 20:51:50 +02:00
NEZNAMY 03c9b95041 Listen to login packet instead of server switch delay for 1.20.2 players for better consistency (also fix layout not being resent on server switch on 1.20.2) 2023-10-12 19:20:22 +02:00
NEZNAMY 6e7e1d783b Fix rare placeholder formatting error 2023-10-12 16:33:58 +02:00
NEZNAMY cfb9b0a029 Fix compatibility with some random 1.8.8 overpriced fork (#1105) 2023-10-12 14:32:21 +02:00
NEZNAMY b7ea6e5af6 Fix wrong exception being caught causing the try/catch to not work (#1092) 2023-10-12 09:33:50 +02:00
NEZNAMY 44104df712 Change error message about invalid team/objective actions to a debug message. They don't cause any issues as they are properly caught and debugging is hard, especially if reporter has no interest in cooperating (#1083) 2023-10-11 19:35:57 +02:00
NEZNAMY 5de20d1c58 Add try/catch for BungeeCord sendPacket method to avoid a NPE in BungeeCord code (#1090, #1092) 2023-10-11 16:24:43 +02:00
NEZNAMY d156303740 Remove warn when placeholders use too much CPU. Since they have been moved to another thread, they will no longer prevent the plugin from working. While the warning can be useful for more advanced users, this is a public plugin used by many and most of them don't care about performance at all. This has resulted in having to answer people who would have never contacted me otherwise. 2023-10-10 15:04:00 +02:00
NEZNAMY 6e36eb3554 Add back fabric class for multi-version code for easier backport 2023-10-09 21:49:21 +02:00
NEZNAMY f7f3891817 Avoid attempts to send plugin message to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-08 12:41:12 +02:00
NEZNAMY 1ec28856ff Avoid attempts to send packets to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-06 11:41:32 +02:00
NEZNAMY 9658e0b74d Add debug message showing how long bridge took to respond to player join plugin message 2023-10-06 11:31:09 +02:00
NEZNAMY 86cd8d4151 Make feature loading more readable 2023-10-05 17:39:27 +02:00
NEZNAMY 2418eb206e Improve unknown version handling code & error messages 2023-10-05 13:57:56 +02:00
NEZNAMY 08fa715d26 Fix runtime warns false triggering on proxy when placeholders are not loaded due to bridge not having a chance to respond yet (#1088) 2023-10-04 14:16:08 +02:00
NEZNAMY 52426de283 Remove double method call 2023-10-04 14:04:57 +02:00
NEZNAMY 64b3da14c4 It's called disable condition, not display condition 2023-10-02 20:38:21 +02:00
NEZNAMY bf7341c52c Change way getProfile method is retrieved (#1089) 2023-10-02 19:09:53 +02:00
NEZNAMY e3490564a2 4.0.7-SNAPSHOT 2023-10-02 19:07:05 +02:00
NEZNAMY 6218f0ed16 4.0.6 2023-10-01 16:35:39 +02:00
NEZNAMY 54e8e1ea98 Add a temporary workaround for https://github.com/SpigotMC/BungeeCord/issues/3542 2023-10-01 16:35:33 +02:00
NEZNAMY 4cd50fb3d5 Use sendPacketQueued method on BungeeCord to avoid error on 1.20.2 clients 2023-10-01 16:31:25 +02:00
117 changed files with 2032 additions and 796 deletions

No files matched your search

+2
View File
@@ -60,4 +60,6 @@ body:
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
required: true
@@ -0,0 +1,38 @@
name: Compatibility issue
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
labels: ["Type: Compatibility issue"]
body:
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 4.0.0
validations:
required: true
- type: textarea
attributes:
label: Software causing compatibility issue
description: plugin/mod/platform version causing the issue
validations:
required: true
- type: textarea
attributes:
label: Additional info
description: Anything else you want to provide that will help in resolving the issue faster.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: The compatibility wiki page does not mention this incompatibility
required: true
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
required: true
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
@@ -5,5 +5,12 @@ package me.neznamy.tab.api.bossbar;
* Calling ordinal() will return color's network ID.
*/
public enum BarColor {
PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE
PINK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
WHITE
}
@@ -1,22 +1,14 @@
package me.neznamy.tab.api.bossbar;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
@AllArgsConstructor
public enum BarStyle {
PROGRESS("SOLID"),
NOTCHED_6("SEGMENTED_6"),
NOTCHED_10("SEGMENTED_10"),
NOTCHED_12("SEGMENTED_12"),
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
@Getter private final String bukkitName;
PROGRESS,
NOTCHED_6,
NOTCHED_10,
NOTCHED_12,
NOTCHED_20
}
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.5"
version = "4.0.8-SNAPSHOT"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -111,7 +111,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = TAB.getInstance().getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
@@ -139,6 +139,11 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
@@ -44,12 +44,13 @@ public class BukkitTAB extends JavaPlugin {
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
Bukkit.getConsoleSender().sendMessage(String.format(
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5, ProtocolVersion.LATEST_KNOWN_VERSION
));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug.");
}
return false;
}
@@ -5,6 +5,7 @@ import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
@@ -33,10 +34,6 @@ import java.util.*;
public class BukkitTabList implements TabList {
// NMS Fields
private static Class<?> HeaderFooterClass;
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
@@ -76,7 +73,6 @@ public class BukkitTabList implements TabList {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@@ -92,7 +88,6 @@ public class BukkitTabList implements TabList {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@@ -107,25 +102,14 @@ public class BukkitTabList implements TabList {
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
// Header & Footer
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newHeaderFooter = HeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
}
// Info packet
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(playerInfoDataClass);
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
@@ -190,15 +174,7 @@ public class BukkitTabList implements TabList {
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (HeaderFooterClass == null) return;
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(toComponent(header), toComponent(footer)));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, toComponent(header));
FOOTER.set(packet, toComponent(footer));
player.sendPacket(packet);
}
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
}
@SneakyThrows
@@ -62,11 +62,7 @@ public class BukkitUtils {
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
}
}
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
sb.append(component.getModifier().getMagicCodes());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
@@ -21,6 +21,26 @@ import java.util.UUID;
@RequiredArgsConstructor
public class BukkitBossBar implements BossBar {
/** Color array for fast access */
private static final org.bukkit.boss.BarColor[] colors = new org.bukkit.boss.BarColor[] {
org.bukkit.boss.BarColor.PINK,
org.bukkit.boss.BarColor.BLUE,
org.bukkit.boss.BarColor.RED,
org.bukkit.boss.BarColor.GREEN,
org.bukkit.boss.BarColor.YELLOW,
org.bukkit.boss.BarColor.PURPLE,
org.bukkit.boss.BarColor.WHITE
};
/** Style array for fast access */
private static final org.bukkit.boss.BarStyle[] styles = new org.bukkit.boss.BarStyle[] {
org.bukkit.boss.BarStyle.SOLID,
org.bukkit.boss.BarStyle.SEGMENTED_6,
org.bukkit.boss.BarStyle.SEGMENTED_10,
org.bukkit.boss.BarStyle.SEGMENTED_12,
org.bukkit.boss.BarStyle.SEGMENTED_20
};
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@@ -34,8 +54,9 @@ public class BukkitBossBar implements BossBar {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
colors[color.ordinal()],
styles[style.ordinal()]
);
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
@@ -53,12 +74,12 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bossBars.get(id).setStyle(styles[style.ordinal()]);
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
bossBars.get(id).setColor(colors[color.ordinal()]);
}
@Override
@@ -21,6 +21,26 @@ import java.util.UUID;
@RequiredArgsConstructor
public class ViaBossBar implements BossBar {
/** Color array for fast access */
private static final BossColor[] colors = new BossColor[] {
BossColor.PINK,
BossColor.BLUE,
BossColor.RED,
BossColor.GREEN,
BossColor.YELLOW,
BossColor.PURPLE,
BossColor.WHITE
};
/** Style array for fast access */
private static final BossStyle[] styles = new BossStyle[] {
BossStyle.SOLID,
BossStyle.SEGMENTED_6,
BossStyle.SEGMENTED_10,
BossStyle.SEGMENTED_12,
BossStyle.SEGMENTED_20
};
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@@ -35,8 +55,9 @@ public class ViaBossBar implements BossBar {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
colors[color.ordinal()],
styles[style.ordinal()]
);
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@@ -53,12 +74,12 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
viaBossBars.get(id).setStyle(styles[style.ordinal()]);
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
viaBossBars.get(id).setColor(colors[color.ordinal()]);
}
@Override
@@ -7,6 +7,7 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -112,4 +113,15 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
}
@Override
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
public void unregisterExpansion() {
if (ReflectionUtils.methodExists(PlaceholderExpansion.class, "unregister")) {
// Added in 2.10.7 (Jul 28, 2020)
unregister();
} else {
PlaceholderAPI.unregisterExpansion(this);
}
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Header/footer sender using Bukkit API. Despite this feature getting
* added into the game in 1.8, Bukkit team took 4 entire years to add
* it to the API, which eventually made it there in 1.13. <p>
* The input is just a String, meaning Bukkit will have to deserialize it
* into components, which is a massive performance loss. It also does not
* seem to support fonts.
*/
public class BukkitHeaderFooter extends HeaderFooter {
/** Flag tracking whether this class can be used on current server */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class);
@Override
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setPlayerListHeaderFooter(
BukkitUtils.toBukkitFormat(header, player.getVersion().getMinorVersion() >= 16),
BukkitUtils.toBukkitFormat(footer, player.getVersion().getMinorVersion() >= 16)
);
}
}
@@ -0,0 +1,55 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A class responsible for sending header/footer to players on request.
*/
public abstract class HeaderFooter {
/** Instance of the class, null if no implementation is available */
@Getter
@Nullable
private static HeaderFooter instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
if (PaperHeaderFooter.isAvailable()) {
instance = new PaperHeaderFooter();
} else if (PacketHeaderFooter.isAvailable()) {
instance = new PacketHeaderFooter();
} else if (BukkitHeaderFooter.isAvailable()) {
instance = new BukkitHeaderFooter();
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
"Please update the plugin with to version proper support for your server version.");
} else if (BukkitReflection.getMinorVersion() >= 8) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
"result in the feature not working. " +
"Please update the plugin with to version proper support for your server version.");
}
}
/**
* Sends header/footer to player.
*
* @param player
* Player to send header/footer to.
* @param header
* Header to use.
* @param footer
* Footer to use.
*/
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer);
}
@@ -0,0 +1,70 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Header/footer sender that uses NMS to send packets. Available
* on all versions since 1.8 when the feature was added into the game.
*/
public class PacketHeaderFooter extends HeaderFooter {
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
@Getter
private static boolean available;
static {
try {
if (BukkitReflection.getMinorVersion() >= 8) {
Class<?> IChatBaseComponent;
Class<?> HeaderFooterClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
}
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newHeaderFooter = HeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
}
available = true;
}
} catch (Exception ignored) {
// Print exception to find out what went wrong
}
}
@SneakyThrows
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
Object headerComponent = player.getPlatform().toComponent(header, player.getVersion());
Object footerComponent = player.getPlatform().toComponent(footer, player.getVersion());
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(headerComponent, footerComponent));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, headerComponent);
FOOTER.set(packet, footerComponent);
player.sendPacket(packet);
}
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Header/footer sender using Paper's API added in 1.16.5.
* Due to lack of component cache for adventure components in TAB,
* there is a performance loss compared to using NMS. However, we do not
* need to serialize/deserialize anything ourselves thanks to direct component
* conversion, so in the end it is roughly the same.
*/
public class PaperHeaderFooter extends HeaderFooter {
/** Flag tracking whether this class can be used on current server */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class);
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().sendPlayerListHeaderAndFooter(
AdventureHook.toAdventureComponent(header, player.getVersion()),
AdventureHook.toAdventureComponent(footer, player.getVersion())
);
}
}
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
@@ -73,17 +74,21 @@ public class DataWatcher implements EntityData {
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> Entity;
if (BukkitReflection.isMojangMapped()) {
Entity = Class.forName("net.minecraft.world.entity.Entity");
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
} else if (minorVersion >= 17) {
Entity = Class.forName("net.minecraft.world.entity.Entity");
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
} else {
Entity = BukkitReflection.getLegacyClass("Entity");
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
@@ -91,7 +96,11 @@ public class DataWatcher implements EntityData {
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
}
}
newDataWatcher = ReflectionUtils.getOnlyConstructor(DataWatcher);
if (minorVersion >= 7) {
newDataWatcher = DataWatcher.getConstructor(Entity);
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
@@ -167,7 +176,7 @@ public class DataWatcher implements EntityData {
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(TAB.getInstance().getPlatform().toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
Optional.of(((BukkitPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
@@ -6,6 +6,7 @@ import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
@@ -74,11 +75,12 @@ public class NMSStorage {
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = ReflectionUtils.getMethod(EntityHuman, new String[] {"getProfile"});
}
// There is only supposed to be one, however there are exceptions:
// #1 - CatServer adds another method
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
@@ -86,6 +88,7 @@ public class NMSStorage {
DataWatcher.load();
PacketEntityView.load();
PacketScoreboard.load();
HeaderFooter.findInstance();
if (minorVersion < 17) {
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
@@ -66,6 +66,9 @@ public class PacketEntityView implements EntityView {
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Class<?> ClientboundBundlePacket;
private static Field ClientboundBundlePacket_packets;
private static Object dummyEntity;
@Getter
@@ -86,6 +89,10 @@ public class PacketEntityView implements EntityView {
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
if (BukkitReflection.is1_19_4Plus()) {
ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
}
available = true;
}
@@ -366,6 +373,17 @@ public class PacketEntityView implements EntityView {
}
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return BukkitReflection.is1_19_4Plus() && ClientboundBundlePacket.isInstance(packet);
}
@Override
@SneakyThrows
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
}
/**
* Returns class from given potential names. Supports modded servers, such as Thermos.
*
@@ -47,7 +47,7 @@ import java.lang.reflect.Field;
* Implementation of Platform interface for Bukkit platform
*/
@Getter
public class BukkitPlatform implements BackendPlatform<Object> {
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
@NotNull
@@ -142,7 +142,7 @@ public class BukkitPlatform implements BackendPlatform<Object> {
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
if (!placeholderAPI) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
registerDummyPlaceholder(identifier);
return;
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
@@ -233,7 +233,15 @@ public class BukkitPlatform implements BackendPlatform<Object> {
return plugin.getDataFolder();
}
@Override
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return NMSStorage.getInstance().componentCache.get(component, version);
}
@@ -4,6 +4,7 @@ import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
@@ -155,22 +156,22 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, 32), RenderType.valueOf(display.name()));
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13), RenderType.valueOf(display.name()));
} else {
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
}
}
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.setDisplayName(transform(displayName, 128, 32));
objective.setDisplayName(transform(displayName, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13));
}
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.setPrefix(transform(prefix, 64, 16));
team.setPrefix(transform(prefix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.setSuffix(transform(suffix, 64, 16));
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
/**
@@ -37,7 +37,6 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
public static Enum[] DisplaySlot_values;
// PacketPlayOutScoreboardScore
private static Class<Enum> EnumScoreboardAction;
@@ -67,7 +66,6 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public static Field TeamPacket_PLAYERS;
private static Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Class<Enum> EnumChatFormatClass;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
private static Method ScoreboardTeam_setNameTagVisibility;
@@ -78,6 +76,13 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static Enum[] scoreboardActions;
private static Enum[] displaySlots;
private static Enum[] nameVisibilities;
private static Enum[] collisionRules;
private static Enum[] healthDisplays;
private static Enum[] chatFormats;
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam;
@@ -87,9 +92,10 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
Class<?> scoreboard;
Class<?> scorePacketClass;
Class<?> IChatBaseComponent = null;
Class<Enum> enumChatFormatClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
enumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
@@ -105,7 +111,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
} else if (minorVersion >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
enumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
@@ -120,7 +126,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
} else {
EnumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
enumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
@@ -148,7 +154,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
DisplaySlot_values = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
displaySlots = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
} else {
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
@@ -168,7 +174,12 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumChatFormatClass);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
scoreboardActions = new Enum[] {
Enum.valueOf(EnumScoreboardAction, "CHANGE"),
Enum.valueOf(EnumScoreboardAction, "REMOVE")
};
chatFormats = (Enum[]) enumChatFormatClass.getMethod("values").invoke(null);
} else {
newScorePacket_String = scorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
@@ -180,8 +191,21 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.class);
}
}
if (minorVersion >= 8) {
nameVisibilities = new Enum[] {
Enum.valueOf(EnumNameTagVisibility, "ALWAYS"),
Enum.valueOf(EnumNameTagVisibility, "NEVER"),
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OTHER_TEAMS"),
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OWN_TEAM")
};
healthDisplays = new Enum[] {
Enum.valueOf(EnumScoreboardHealthDisplay, "INTEGER"),
Enum.valueOf(EnumScoreboardHealthDisplay, "HEARTS")
};
}
if (minorVersion >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
collisionRules = (Enum[]) EnumTeamPush.getMethod("values").invoke(null); // <1.13 has wrong enum constant names
}
if (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
@@ -246,7 +270,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
Object displaySlot;
if (BukkitReflection.is1_20_2Plus()) {
displaySlot = DisplaySlot_values[slot.ordinal()];
displaySlot = displaySlots[slot.ordinal()];
} else {
displaySlot = slot.ordinal();
}
@@ -273,9 +297,13 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private Object buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
toComponent(IChatBaseComponent.optimizedComponent(title)),
Enum.valueOf(EnumScoreboardHealthDisplay, display.name())),
newScoreboardObjective.newInstance(
null,
objectiveName,
null,
toComponent(title),
healthDisplays[display.ordinal()]
),
action
);
}
@@ -283,7 +311,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, display.name()));
Objective_RENDER_TYPE.set(nmsPacket, healthDisplays[display.ordinal()]);
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
@@ -333,15 +361,17 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(prefix)));
ScoreboardTeam_setSuffix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(suffix)));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(EnumChatFormatClass, EnumChatFormat.lastColorsOf(prefix).toString()));
ScoreboardTeam_setPrefix.invoke(team, toComponent(prefix));
ScoreboardTeam_setSuffix.invoke(team, toComponent(suffix));
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (BukkitReflection.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (BukkitReflection.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
if (BukkitReflection.getMinorVersion() >= 8)
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
if (BukkitReflection.getMinorVersion() >= 9)
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
return team;
}
@@ -349,7 +379,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@SneakyThrows
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score));
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[0], objective, playerName, score));
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
@@ -365,7 +395,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[1], objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
@@ -385,7 +415,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
null,
objectiveName,
null,
toComponent(new IChatBaseComponent("")),
toComponent(""),
null
);
}
@@ -396,7 +426,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
);
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
private Object toComponent(String text) {
return player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
@@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -35,10 +34,10 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
packet.setHealth(progress);
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
packet.setColor(color.ordinal());
packet.setDivision(style.ordinal());
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
colors.put(id, color.ordinal());
styles.put(id, style.ordinal());
@@ -49,8 +48,8 @@ public class BungeeBossBar implements BossBar {
if (player.getVersion().getMinorVersion() < 9) return;
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
player.getPlayer().unsafe().sendPacket(packet);
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
player.sendPacket(packet);
}
@Override
@@ -59,7 +58,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
packet.setHealth(progress);
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
}
@Override
@@ -69,7 +68,7 @@ public class BungeeBossBar implements BossBar {
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));
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
styles.put(id, style.ordinal());
}
@@ -81,7 +80,7 @@ public class BungeeBossBar implements BossBar {
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());
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
colors.put(id, color.ordinal());
}
@@ -90,7 +89,7 @@ public class BungeeBossBar implements BossBar {
public void remove(@NotNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
player.getPlayer().unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
colors.remove(id);
styles.remove(id);
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.EventListener;
@@ -25,7 +26,29 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
@EventHandler
public void onSwitch(ServerSwitchEvent e) {
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
TAB tab = TAB.getInstance();
if (tab.isPluginDisabled()) return;
tab.getCPUManager().runTask(() -> {
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
if (player == null) {
player = createPlayer(e.getPlayer());
// I don't think this is actually needed, but someone said it fixed geyser warn in console
// Doing this won't hurt, so whatever
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getScoreboard().freeze();
}
tab.getFeatureManager().onJoin(player);
} else {
tab.getFeatureManager().onServerChange(player.getUniqueId(), e.getPlayer().getServer().getInfo().getName());
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_20_2.getNetworkId()) {
// For versions below 1.20.2 the tablist is already clean when this event is called
// For 1.20.2+ this event is called before, so we listen to Login packet instead
tab.getFeatureManager().onTabListClear(player);
}
}
});
}
@EventHandler
@@ -0,0 +1,200 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.util.stream.Collectors;
public class BungeeMultiVersion {
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1732.
* This build changes "ScoreboardDisplay" field "position" from "byte" to "int".
*/
private static final boolean b1732Plus = ReflectionUtils.getFields(ScoreboardDisplay.class, int.class).size() > 0;
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1738.
* This build adds "sendPacketQueued" method to UserConnection class.
*/
private static final boolean b1738Plus = ReflectionUtils.methodExists(UserConnection.class, "sendPacketQueued", DefinedPacket.class);
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1760.
* This build changes all packet fields for displaying information from String to BaseComponent.
*/
private static final boolean b1760Plus = ReflectionUtils.methodExists(BossBar.class, "setTitle", BaseComponent.class);
@SneakyThrows
@NotNull
public static ScoreboardDisplay newScoreboardDisplay(int position, @NotNull String name) {
if (b1732Plus) {
return ScoreboardDisplay.class.getConstructor(int.class, String.class).newInstance(position, name);
} else {
return new ScoreboardDisplay((byte) position, name);
}
}
@SneakyThrows
public static void sendPacket(@NotNull BungeeTabPlayer player, @NotNull DefinedPacket packet) {
if (b1738Plus) {
try {
UserConnection.class.getDeclaredMethod("sendPacketQueued", DefinedPacket.class).invoke(player.getPlayer(), packet);
} catch (InvocationTargetException BungeeCordBug) { // Since we use reflection we must catch this one
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + player.getName() +
" (online = " + player.getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
} else {
player.getPlayer().unsafe().sendPacket(packet);
}
}
@SneakyThrows
public static void setDisplayName(@NotNull Item item, @NotNull IChatBaseComponent displayName, @NotNull ProtocolVersion version) {
if (b1760Plus) {
Item.class.getMethod("setDisplayName", BaseComponent.class).invoke(item, toComponent(displayName, version));
} else {
item.setDisplayName(displayName.toString(version));
}
}
@SneakyThrows
@NotNull
public static PlayerListHeaderFooter newPlayerListHeaderFooter(@NotNull IChatBaseComponent header,
@NotNull IChatBaseComponent footer, ProtocolVersion version) {
if (b1760Plus) {
return PlayerListHeaderFooter.class.getConstructor(BaseComponent.class, BaseComponent.class).newInstance(
toComponent(header, version),
toComponent(footer, version)
);
} else {
return new PlayerListHeaderFooter(
header.toString(version),
footer.toString(version)
);
}
}
@SneakyThrows
public static void setTitle(@NotNull BossBar bossBar, @NotNull String title, @NotNull ProtocolVersion version) {
if (b1760Plus) {
BossBar.class.getMethod("setTitle", BaseComponent.class).invoke(bossBar, toComponent(IChatBaseComponent.optimizedComponent(title), version));
} else {
bossBar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(version));
}
}
@NotNull
@SneakyThrows
public static ScoreboardObjective newScoreboardObjective(@NotNull String objective, @NotNull IChatBaseComponent title,
@Nullable ScoreboardObjective.HealthDisplay healthDisplay, byte action,
@NotNull ProtocolVersion version) {
if (b1760Plus) {
return ScoreboardObjective.class
.getConstructor(String.class, BaseComponent.class, ScoreboardObjective.HealthDisplay.class, byte.class)
.newInstance(objective, toComponent(title, version), healthDisplay, action);
} else {
return new ScoreboardObjective(objective, title.toString(version), healthDisplay, action);
}
}
@NotNull
@SneakyThrows
public static Team newTeam(@NotNull String name, byte mode, @NotNull String prefix, @NotNull String suffix,
@NotNull String nameTagVisibility, @NotNull String collisionRule, int color,
byte options, @Nullable String[] players, @NotNull ProtocolVersion version) {
if (b1760Plus) {
return Team.class.getConstructor(String.class, byte.class, BaseComponent.class, BaseComponent.class,
BaseComponent.class, String.class, String.class, int.class, byte.class, String[].class)
.newInstance(
name,
mode,
toComponent(IChatBaseComponent.optimizedComponent(name), version),
toComponent(IChatBaseComponent.optimizedComponent(prefix), version),
toComponent(IChatBaseComponent.optimizedComponent(suffix), version),
nameTagVisibility,
collisionRule,
color,
options,
players
);
} else {
return new Team(
name,
mode,
jsonOrRaw(name, version),
jsonOrRaw(prefix, version),
jsonOrRaw(suffix, version),
nameTagVisibility,
collisionRule,
color,
options,
players
);
}
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @param version
* Protocol version to create json string for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private static String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion version) {
if (version.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(version);
} else {
return text;
}
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
private static BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
TextComponent textComponent = new TextComponent(component.getText());
ChatModifier modifier = component.getModifier();
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -163,7 +163,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, newDisplayName, receiver.getVersion());
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
@@ -177,7 +177,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, newDisplayName, receiver.getVersion());
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
@@ -189,6 +189,11 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return packet instanceof Login;
}
/**
* Constructs new instance of the feature
*/
@@ -3,22 +3,16 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
@@ -26,22 +20,16 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.stream.Collectors;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform<BaseComponent> {
public class BungeePlatform extends ProxyPlatform {
@NotNull
private final BungeeTAB plugin;
/** Component cache for BungeeCord components */
@NotNull
private final ComponentCache<IChatBaseComponent, BaseComponent> bungeeCache =
new ComponentCache<>(1000, this::toComponent0);
@Override
public void loadPlayers() {
ViaVersionHook.getInstance().printProxyWarn();
@@ -98,27 +86,6 @@ public class BungeePlatform extends ProxyPlatform<BaseComponent> {
return plugin.getDataFolder();
}
@Override
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return bungeeCache.get(component, version);
}
private BaseComponent toComponent0(IChatBaseComponent component, ProtocolVersion version) {
ChatModifier modifier = component.getModifier();
TextComponent textComponent = new TextComponent(component.getText());
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (modifier.getFont() != null) textComponent.setFont(modifier.getFont());
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent0(c, version)).collect(Collectors.toList()));
return textComponent;
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
@@ -4,14 +4,11 @@ import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
/**
@@ -21,11 +18,6 @@ import java.util.Collection;
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
/** Constructor to support both <1.20.2 and 1.20.2+ builds */
@SuppressWarnings("unchecked")
private static final Constructor<ScoreboardDisplay> newScoreboardDisplay = (Constructor<ScoreboardDisplay>)
Arrays.stream(ScoreboardDisplay.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findAny().orElse(null);
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
super(player);
}
@@ -33,36 +25,39 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.getPlayer().unsafe().sendPacket(newScoreboardDisplay.newInstance((byte)slot.ordinal(), objective));
player.sendPacket(BungeeMultiVersion.newScoreboardDisplay(slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
objectiveName,
jsonOrRaw(title),
IChatBaseComponent.optimizedComponent(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0
(byte) 0,
player.getVersion()
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
objectiveName,
"", // Empty string to prevent kick on 1.7
new IChatBaseComponent(), // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) 1
(byte) 1,
player.getVersion()
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
objectiveName,
jsonOrRaw(title),
IChatBaseComponent.optimizedComponent(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2
(byte) 2,
player.getVersion()
));
}
@@ -74,13 +69,23 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 0, jsonOrRaw(name), jsonOrRaw(prefix), jsonOrRaw(suffix),
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
player.sendPacket(BungeeMultiVersion.newTeam(
name,
(byte) 0,
prefix,
suffix,
visibility.toString(),
collision.toString(),
color,
(byte)options,
players.toArray(new String[0]),
player.getVersion()
));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.getPlayer().unsafe().sendPacket(new Team(name));
player.sendPacket(new Team(name));
}
@Override
@@ -90,36 +95,27 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 2, jsonOrRaw(name), jsonOrRaw(prefix),
jsonOrRaw(suffix), visibility.toString(), collision.toString(), color, (byte)options, null));
player.sendPacket(BungeeMultiVersion.newTeam(
name,
(byte) 2,
prefix,
suffix,
visibility.toString(),
collision.toString(),
color,
(byte)options,
null,
player.getVersion()
));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private String jsonOrRaw(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(player.getVersion());
} else {
return text;
}
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
}
@@ -14,7 +14,7 @@ public class BungeeTAB extends Plugin {
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
"(with 1.19.3 support) and up (or an equivalent fork) to work."));
"(released on December 7th, 2022) and up (or an equivalent fork) to work."));
return;
}
TAB.create(new BungeePlatform(this));
@@ -2,10 +2,12 @@ package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
@@ -14,8 +16,10 @@ import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -71,7 +75,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
}
@Override
@@ -102,11 +106,19 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
errorNoServer(message);
return;
if (!getPlayer().isConnected()) return;
try {
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.ServerConnection.sendPacketQueued(ServerConnection.java:48)
// at net.md_5.bungee.ServerConnection.sendData(ServerConnection.java:70)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver plugin message to player " + getName() +
" (online = " + getPlayer().isConnected() + ")", BungeeCordBug);
}
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
}
/**
@@ -141,4 +153,17 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
return tabList1_7;
}
/**
* Sends packet to the player. If BungeeCord supports 1.20.2+, new packet queue method is used
* to avoid error when sending packet in configuration phase.
*
* @param packet
* Packet to send
*/
@SneakyThrows
public void sendPacket(@NotNull DefinedPacket packet) {
if (!getPlayer().isConnected()) return;
BungeeMultiVersion.sendPacket(this, packet);
}
}
@@ -0,0 +1,71 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.tab.ServerUnique;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.UUID;
public abstract class BungeeTabList implements TabList {
/** Player this TabList belongs to */
@NotNull
protected final BungeeTabPlayer player;
/** Pointer to UUIDs in player's TabList */
private final Collection<UUID> uuids;
@SneakyThrows
@SuppressWarnings("unchecked")
public BungeeTabList(@NotNull BungeeTabPlayer player) {
this.player = player;
uuids = (Collection<UUID>) ReflectionUtils.getField(ServerUnique.class, "uuids").get(((UserConnection)player.getPlayer()).getTabListHandler());
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.sendPacket(BungeeMultiVersion.newPlayerListHeaderFooter(header, footer, player.getVersion()));
}
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
}
@NotNull
public Item entryToItem(Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) {
BungeeMultiVersion.setDisplayName(item, entry.getDisplayName(), player.getVersion());
}
item.setGamemode(entry.getGameMode());
item.setListed(true);
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
return item;
}
public void addUuid(UUID id) {
uuids.add(id);
}
public void removeUuid(UUID id) {
uuids.remove(id);
}
}
@@ -1,11 +1,8 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
@@ -18,27 +15,25 @@ import java.util.UUID;
/**
* TabList handler for 1.19.3+ players using the new tab list packets.
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList1193 implements TabList {
public class BungeeTabList1193 extends BungeeTabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
public BungeeTabList1193(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
removeUuid(entry);
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(new UUID[]{entry});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
player.sendPacket(remove);
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
}
@@ -58,39 +53,14 @@ public class BungeeTabList1193 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
item.setGamemode(entry.getGameMode());
item.setListed(true);
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
}
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
addUuid(entry.getUniqueId());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), entryToItem(entry));
}
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(actions);
packet.setItems(new Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
player.sendPacket(packet);
}
}
@@ -1,10 +1,9 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -19,17 +18,10 @@ import java.util.UUID;
* names are changed by removing old entry and adding a new one, which
* requires further tracking than just the UUID.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
* While BungeeCord itself does not support 1.7, some of its forks do.
* This was tested on FlameCord fork.
*/
@RequiredArgsConstructor
public class BungeeTabList17 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
public class BungeeTabList17 extends BungeeTabList {
/** Because entries are identified by names and not uuids on 1.7 */
@NotNull
@@ -38,9 +30,14 @@ public class BungeeTabList17 implements TabList {
@NotNull
private final Map<UUID, String> displayNames = new HashMap<>();
public BungeeTabList17(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
removeUuid(entry);
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
// Remove from map
@@ -66,8 +63,10 @@ public class BungeeTabList17 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
addUuid(entry.getUniqueId());
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
// Add to map
@@ -79,19 +78,14 @@ public class BungeeTabList17 implements TabList {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new PlayerListItem.Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
player.sendPacket(packet);
}
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUsername(username);
item.setDisplayName(displayName);
BungeeMultiVersion.setDisplayName(item, IChatBaseComponent.optimizedComponent(displayName), player.getVersion());
item.setPing(latency);
return item;
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Added in 1.8
}
}
@@ -1,11 +1,8 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
@@ -16,25 +13,23 @@ import java.util.UUID;
/**
* TabList handler for players between 1.8 and 1.19.2.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList18 implements TabList {
public class BungeeTabList18 extends BungeeTabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
public BungeeTabList18(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
removeUuid(entry);
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
}
@@ -54,38 +49,14 @@ public class BungeeTabList18 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
item.setGamemode(entry.getGameMode());
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
}
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
addUuid(entry.getUniqueId());
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(entry));
}
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
player.sendPacket(packet);
}
}
+4 -7
View File
@@ -1,6 +1,5 @@
val minecraftVersion = "1.20.2"
val v114 = "0.28.5+1.14"
val v115 = "0.28.5+1.15"
val v116 = "0.42.0+1.16"
@@ -27,11 +26,7 @@ val fabricApiVersions = mapOf(
"1.15.2" to v115,
"1.15.1" to v115,
"1.15" to v115,
"1.14.4" to v114,
"1.14.3" to v114,
"1.14.2" to v114,
"1.14.1" to v114,
"1.14" to v114,
"1.14.4" to "0.28.5+1.14",
)
plugins {
@@ -57,8 +52,10 @@ dependencies {
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
if (minecraftVersion.split(".")[1].toInt() >= 16) {
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
}
}
loom {
@@ -9,12 +9,33 @@ import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
import net.minecraft.world.BossEvent.BossBarOverlay;
import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
/** Color array for fast access */
private static final BossBarColor[] colors = new BossBarColor[] {
BossBarColor.PINK,
BossBarColor.BLUE,
BossBarColor.RED,
BossBarColor.GREEN,
BossBarColor.YELLOW,
BossBarColor.PURPLE,
BossBarColor.WHITE
};
/** Overlay array for fast access */
private static final BossBarOverlay[] overlays = new BossBarOverlay[] {
BossBarOverlay.PROGRESS,
BossBarOverlay.NOTCHED_6,
BossBarOverlay.NOTCHED_10,
BossBarOverlay.NOTCHED_12,
BossBarOverlay.NOTCHED_20
};
@NotNull
private final FabricTabPlayer player;
@@ -25,10 +46,10 @@ public class FabricBossBar implements BossBar {
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
BossEvent.BossBarColor.valueOf(color.name()),
BossEvent.BossBarOverlay.valueOf(style.name())
colors[color.ordinal()],
overlays[style.ordinal()]
);
bar.setProgress(progress);
FabricMultiVersion.setProgress(bar, progress);
bars.put(id, bar);
bar.addPlayer(player.getPlayer());
}
@@ -40,17 +61,17 @@ public class FabricBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, float progress) {
bars.get(id).setProgress(progress);
FabricMultiVersion.setProgress(bars.get(id), progress);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bars.get(id).setOverlay(BossEvent.BossBarOverlay.valueOf(style.name()));
bars.get(id).setOverlay(overlays[style.ordinal()]);
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bars.get(id).setColor(BossEvent.BossBarColor.valueOf(color.name()));
bars.get(id).setColor(colors[color.ordinal()]);
}
@Override
@@ -4,14 +4,10 @@ import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@@ -22,16 +18,13 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
@NotNull EntityData data) {
player.sendPacket(new ClientboundAddEntityPacket(entityId, id,
location.getX(), location.getY(), location.getZ(), 0, 0,
(EntityType<?>) entityType, 0, Vec3.ZERO, 0));
player.sendPacket(FabricMultiVersion.addEntity(entityId, id, entityType, location, data));
updateEntityMetadata(entityId, data);
}
@Override
@SuppressWarnings("unchecked")
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build()));
player.sendPacket(FabricMultiVersion.newEntityMetadata(entityId, data));
}
@@ -44,12 +37,12 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public void destroyEntities(int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
FabricMultiVersion.destroyEntities(player, entities);
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return packet instanceof ClientboundRemoveEntitiesPacket;
return FabricMultiVersion.isEntityDestroyPacket(packet);
}
@Override
@@ -89,6 +82,17 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
return FabricMultiVersion.getDestroyedEntities(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricMultiVersion.isBundlePacket(packet);
}
@SuppressWarnings("unchecked")
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) (Object) FabricMultiVersion.getPackets(bundlePacket);
}
}
@@ -3,8 +3,6 @@ package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
@@ -14,10 +12,8 @@ public class FabricEventListener extends EventListener<ServerPlayer> {
public void register() {
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) ->
worldChange(player.getUUID(), destination.dimension().location().toString()));
//TODO command
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
FabricMultiVersion.registerEntityEvents(this::replacePlayer, this::worldChange);
//TODO command preprocess
}
@Override
@@ -0,0 +1,324 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.BiConsumer;
/**
* Class that contains (almost) all code that needs to be changed
* to set compatibility with any MC version that fabric supports (1.14.4+).
*/
public class FabricMultiVersion {
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private static int getArmorStandFlagsPosition() {
int minorVersion = ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName()).getMinorVersion();
if (minorVersion >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (minorVersion >= 15) {
//1.15.x, 1.16.x
return 14;
} else {
//1.14.x
return 13;
}
}
public static String getWorldName(Level level) {
// 1.15.2-
// return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
// 1.16+
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
public static void sendSystemMessage(CommandSourceStack source, Component message) {
// 1.19.4-
// source.sendSuccess(message, false);
// 1.19.1+
source.sendSystemMessage(message);
}
public static boolean isSneaking(ServerPlayer player) {
// 1.14.4-
// return player.isSneaking();
// 1.15+
return player.isCrouching();
}
public static void registerNameTagXEvents(FabricNameTagX nameTagX) {
// 1.16+
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (nameTagX.enabled) nameTagX.respawn(oldPlayer.getUUID());
});
// TODO sneaking
}
public static void registerEntityEvents(BiConsumer<UUID, ServerPlayer> respawnAction, BiConsumer<UUID, String> worldChangeAction) {
// 1.16+
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register(
(oldPlayer, newPlayer, alive) -> {
respawnAction.accept(newPlayer.getUUID(), newPlayer);
// respawning from death & taking end portal in the end do not call world change event
worldChangeAction.accept(newPlayer.getUUID(), getWorldName(newPlayer.level));
});
net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChangeAction.accept(player.getUUID(), getWorldName(destination)));
}
public static ClientboundTabListPacket setHeaderFooter(Component header, Component footer) {
// 1.16.5-
/*ClientboundTabListPacket packet = new ClientboundTabListPacket();
packet.header = header;
packet.footer = footer;
return packet;*/
// 1.17+
return new ClientboundTabListPacket(header, footer);
}
public static ClientboundSetPlayerTeamPacket createTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 0);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
public static ClientboundSetPlayerTeamPacket removeTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 1);
// 1.17+
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
public static ClientboundSetPlayerTeamPacket updateTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 2);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
public static void setProgress(ServerBossEvent bar, float progress) {
// 1.16.5-
// bar.setPercent(progress);
// 1.17+
bar.setProgress(progress);
}
public static int[] getDestroyedEntities(Object destroyPacket) {
// 1.16.5-
// return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds();
// 1.17
// return new int[]{((ClientboundRemoveEntityPacket)destroyPacket).getEntityId()};
// 1.17.1+
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
}
public static void destroyEntities(FabricTabPlayer player, int... entities) {
// 1.17
/*for (int i : entities) {
player.sendPacket(new ClientboundRemoveEntityPacket(i));
}*/
// every other version
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
public static boolean isEntityDestroyPacket(Object packet) {
// 1.17
// return packet instanceof ClientboundRemoveEntityPacket;
// every other version
return packet instanceof ClientboundRemoveEntitiesPacket;
}
public static void registerCommand() {
// 1.18.2-
// net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
// 1.19+
net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
public static void sendMessage(FabricTabPlayer player, Component message) {
// 1.15.2-
// player.getPlayer().sendMessage(message);
// 1.16 - 1.18.2
// player.getPlayer().sendMessage(message, new UUID(0, 0));
// 1.19+
player.getPlayer().sendSystemMessage(message);
}
public static ClientboundAddEntityPacket addEntity(int entityId, @NotNull UUID id, @NotNull Object entityType,
@NotNull Location location, @NotNull EntityData data) {
// 1.18.2-
// return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(), 0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
// 1.19+
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(), 0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
public static boolean isPlayerInfo(Object packet) {
// 1.19.2-
// return packet instanceof ClientboundPlayerInfoPacket;
// 1.19.3+
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
public static Packet<?> build(FabricTabList.Action action, FabricTabList.Builder entry) {
// 1.16.5-
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName()
));
return packet;*/
// 1.17 - 1.18.2
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName()
));
return packet;*/
// 1.19 - 1.19.2
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName(), null
));
return packet;*/
// 1.19.3+
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
));
return packet;
}
public static EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
// 1.19.2-
/*SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;*/
// 1.19.3+
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(armorStandFlagsPosition, EntityDataSerializers.BYTE, (byte)16)
);
}
@SuppressWarnings("unchecked")
public static ClientboundSetEntityDataPacket newEntityMetadata(int entityId, EntityData data) {
// 1.19.2-
// return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
// 1.19.3+
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
public static boolean isBundlePacket(Object packet) {
// 1.19.4+
return packet instanceof ClientboundBundlePacket;
}
public static Iterable<Packet<ClientGamePacketListener>> getPackets(Object bundlePacket) {
// 1.19.4+
return ((ClientboundBundlePacket)bundlePacket).subPackets();
}
public static ClientboundSetDisplayObjectivePacket newDisplayObjective(int slot, Objective objective) {
// 1.20.1-
// return new ClientboundSetDisplayObjectivePacket(slot, objective);
// 1.20.2+
return new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[slot], objective);
}
public static int getSlot(ClientboundSetDisplayObjectivePacket packet) {
// 1.20.1-
// return packet.getSlot();
// 1.20.2+
return packet.getSlot().ordinal();
}
public static int getPing(FabricTabPlayer player) {
// 1.20.1-
// return player.getPlayer().latency;
// 1.20.2+
return player.getPlayer().connection.latency();
}
public static TabList.Skin propertyToSkin(Property property) {
// 1.20.1-
// return new TabList.Skin(property.getValue(), property.getSignature());
// 1.20.2+
return new TabList.Skin(property.value(), property.signature());
}
}
@@ -11,8 +11,6 @@ import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
@@ -39,7 +37,7 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket)packet).getSlot().ordinal(),
FabricMultiVersion.getSlot((ClientboundSetDisplayObjectivePacket) packet),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@@ -67,33 +65,41 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
return FabricMultiVersion.isPlayerInfo(packet);
}
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
// Comment this entire method out when compiling with 1.19.2-, adding compatibility would be tough and not worth,
// as nobody needs these features on Fabric anyway
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<Action> actions = packet.actions();
List<Entry> updatedList = new ArrayList<>();
for (Entry nmsData : packet.entries()) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(Action.UPDATE_DISPLAY_NAME)) {
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(Action.UPDATE_LATENCY)) {
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(Action.ADD_PLAYER)) {
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
packet.entries = updatedList;
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
@@ -13,30 +13,23 @@ import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.ChatFormatting;
import net.minecraft.SharedConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.contents.LiteralContents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Platform implementation for Fabric
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform<Component> {
public record FabricPlatform(MinecraftServer server) implements BackendPlatform {
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
registerDummyPlaceholder(identifier);
}
@Override
@@ -113,34 +106,17 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform<
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
}
@Override
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
// Text
MutableComponent comp = MutableComponent.create(new LiteralContents(component.getText()));
// Color and style
List<ChatFormatting> formats = new ArrayList<>();
if (component.getModifier().isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
if (component.getModifier().isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
if (component.getModifier().isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
if (component.getModifier().isItalic()) formats.add(ChatFormatting.ITALIC);
if (component.getModifier().isBold()) formats.add(ChatFormatting.BOLD);
Style style = comp.getStyle().applyFormats(formats.toArray(new ChatFormatting[0]));
if (component.getModifier().getColor() != null) {
if (version.getMinorVersion() >= 16) {
style = style.withColor(component.getModifier().getColor().getRgb());
} else {
style = style.withColor(ChatFormatting.valueOf(component.getModifier().getColor().getLegacyColor().name()));
}
}
if (component.getModifier().getFont() != null)
style = style.withFont(new ResourceLocation(component.getModifier().getFont()));
comp.withStyle(style);
// Extra
comp.getSiblings().addAll(component.getExtra().stream().map(c -> toComponent(c, version)).toList());
return comp;
return Component.Serializer.fromJson(component.toString(version));
}
@Override
@@ -1,15 +1,14 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import java.util.Objects;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.world.scores.Objective;
@@ -23,6 +22,9 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
@NotNull
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
@NotNull
private static final Component EMPTY_COMPONENT = Objects.requireNonNull(Component.Serializer.fromJson(IChatBaseComponent.EMPTY_COMPONENT));
public FabricScoreboard(FabricTabPlayer player) {
super(player);
}
@@ -30,13 +32,13 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[slot.ordinal()],
FabricMultiVersion.newDisplayObjective(
slot.ordinal(),
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
Component.empty(),
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
)
)
@@ -67,7 +69,7 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
Component.empty(),
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
),
1
@@ -104,12 +106,12 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.getPlayers().addAll(players);
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
player.sendPacket(FabricMultiVersion.createTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket(new PlayerTeam(dummyScoreboard, name)));
player.sendPacket(FabricMultiVersion.removeTeam(new PlayerTeam(dummyScoreboard, name)));
}
@Override
@@ -123,7 +125,7 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
player.sendPacket(FabricMultiVersion.updateTeam(team));
}
@Override
@@ -3,7 +3,6 @@ package me.neznamy.tab.platforms.fabric;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.shared.TAB;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
@@ -15,7 +14,7 @@ public class FabricTAB implements DedicatedServerModInitializer {
@Override
public void onInitializeServer() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
FabricMultiVersion.registerCommand();
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
@@ -51,7 +51,8 @@ public class FabricTabCommand {
boolean hasReloadPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
source.sendSystemMessage(((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
FabricMultiVersion.sendSystemMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
}
} else {
if (source.getEntity() == null) {
@@ -7,11 +7,6 @@ import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -21,71 +16,58 @@ public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList
@Override
public void removeEntry(@NotNull UUID entry) {
player.sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
player.sendPacket(FabricMultiVersion.build(Action.REMOVE_PLAYER, new Builder(entry)));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME),
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))));
player.sendPacket(FabricMultiVersion.build(Action.UPDATE_DISPLAY_NAME,
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))
));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), new Builder(entry).setLatency(latency)));
player.sendPacket(FabricMultiVersion.build(Action.UPDATE_LATENCY, new Builder(entry).setLatency(latency)));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE), new Builder(entry).setGameMode(gameMode)));
player.sendPacket(FabricMultiVersion.build(Action.UPDATE_GAME_MODE, new Builder(entry).setGameMode(gameMode)));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(build(EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class), new Builder(entry.getUniqueId())
player.sendPacket(FabricMultiVersion.build(Action.ADD_PLAYER, new Builder(entry.getUniqueId())
.setName(entry.getName())
.setSkin(entry.getSkin())
.setGameMode(entry.getGameMode())
.setLatency(entry.getLatency())
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))));
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))
));
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().connection.send(new ClientboundTabListPacket(
player.getPlayer().connection.send(FabricMultiVersion.setHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion()))
);
}
@NotNull
private Packet<?> build(@NotNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, @NotNull FabricTabList.Builder entry) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
));
return packet;
}
@RequiredArgsConstructor
@Getter
public static class Builder {
@NotNull private final UUID id;
@Nullable private String name;
@NotNull private String name = ""; // Avoid nullability issues as things are changing over versions
@Nullable private Skin skin;
private int latency;
private int gameMode;
@Nullable private Component displayName;
@NotNull
public Builder setName(@Nullable String name) {
public Builder setName(@NotNull String name) {
this.name = name;
return this;
}
@@ -114,9 +96,8 @@ public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList
return this;
}
@Nullable
@NotNull
public GameProfile createProfile() {
if (name == null) return null;
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
@@ -31,7 +31,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
private final EntityView entityView = new FabricEntityView(this);
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(), player.level().dimension().location().toString());
super(platform, player, player.getUUID(), player.getGameProfile().getName(), FabricMultiVersion.getWorldName(player.level));
}
@Override
@@ -41,12 +41,12 @@ public class FabricTabPlayer extends BackendTabPlayer {
@Override
public int getPing() {
return getPlayer().connection.latency();
return FabricMultiVersion.getPing(this);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendSystemMessage(getPlatform().toComponent(message, getVersion()));
FabricMultiVersion.sendMessage(this, getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -65,7 +65,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property skinProperty = properties.iterator().next();
return new TabList.Skin(skinProperty.value(), skinProperty.signature());
return FabricMultiVersion.propertyToSkin(skinProperty);
}
@Override
@@ -1,33 +1,25 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricMultiVersion;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class FabricNameTagX extends BackendNameTagX {
private boolean enabled = true;
public boolean enabled = true;
public FabricNameTagX() {
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
// no sneaking for fabric //TODO
FabricMultiVersion.registerNameTagXEvents(this);
}
@Override
@@ -39,7 +31,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((FabricTabPlayer)player1).getPlayer().level() == ((FabricTabPlayer)player2).getPlayer().level();
return ((FabricTabPlayer)player1).getPlayer().level == ((FabricTabPlayer)player2).getPlayer().level;
}
@Override
@@ -77,7 +69,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isCrouching();
return FabricMultiVersion.isSneaking(((FabricTabPlayer)player).getPlayer());
}
@Override
@@ -119,13 +111,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return () -> Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
return FabricMultiVersion.createDataWatcher(viewer, flags, displayName, nameVisible);
}
@Override
@@ -8,6 +8,9 @@ accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPac
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
# 1.20+
accessible field net/minecraft/world/entity/Entity level Lnet/minecraft/world/level/Level;
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
@@ -69,7 +69,7 @@ public class FeatureManager {
((UnLoadable) f).unload();
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed unload in " + (System.currentTimeMillis()-time) + "ms");
}
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregister();
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregisterExpansion();
if (TAB.getInstance().getPlatform() instanceof ProxyPlatform) {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
((ProxyTabPlayer)player).sendPluginMessage("Unload");
@@ -164,7 +164,6 @@ public class FeatureManager {
if (changed == null) return;
String from = changed.getServer();
changed.setServer(to);
changed.getScoreboard().clearRegisteredObjectives();
((ProxyTabPlayer)changed).sendJoinPluginMessage();
for (TabFeature f : values) {
if (!(f instanceof ServerSwitchListener)) continue;
@@ -271,6 +270,25 @@ public class FeatureManager {
return newLatency;
}
public void onLoginPacket(TabPlayer packetReceiver) {
packetReceiver.getScoreboard().unfreeze();
for (TabFeature f : values) {
if (!(f instanceof LoginPacketListener)) continue;
long time = System.nanoTime();
((LoginPacketListener)f).onLoginPacket(packetReceiver);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_LOGIN, System.nanoTime() - time);
}
}
public void onTabListClear(TabPlayer packetReceiver) {
for (TabFeature f : values) {
if (!(f instanceof TabListClearListener)) continue;
long time = System.nanoTime();
((TabListClearListener)f).onTabListClear(packetReceiver);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.TABLIST_CLEAR, System.nanoTime() - time);
}
}
public void registerFeature(@NotNull String featureName, @NotNull TabFeature featureHandler) {
features.put(featureName, featureHandler);
values = features.values().toArray(new TabFeature[0]);
@@ -304,51 +322,50 @@ public class FeatureManager {
FeatureManager featureManager = TAB.getInstance().getFeatureManager();
int minorVersion = TAB.getInstance().getServerVersion().getMinorVersion();
if (configuration.getConfig().getBoolean("ping-spoof.enabled", false))
featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
boolean pingSpoof = configuration.getConfig().getBoolean("ping-spoof.enabled", false);
boolean bossbar = configuration.getConfig().getBoolean("bossbar.enabled", false);
boolean headerFooter = configuration.getConfig().getBoolean("header-footer.enabled", true);
boolean spectatorFix = configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false);
boolean scoreboard = configuration.getConfig().getBoolean("scoreboard.enabled", false);
boolean perWorldPlayerList = configuration.getConfig().getBoolean("per-world-playerlist.enabled", false);
boolean layout = configuration.getConfig().getBoolean("layout.enabled", false);
boolean yellowNumber = configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true);
boolean belowName = configuration.getConfig().getBoolean("belowname-objective.enabled", false);
boolean teams = configuration.getConfig().getBoolean("scoreboard-teams.enabled", true);
boolean unlimitedTags = configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false);
boolean globalPlayerList = configuration.getConfig().getBoolean("global-playerlist.enabled", false);
boolean tablistFormatting = configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true);
boolean alignedSuffix = configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false);
if (configuration.getConfig().getBoolean("bossbar.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : TAB.getInstance().getPlatform().getLegacyBossBar());
}
if (configuration.getConfig().getBoolean("header-footer.enabled", true))
featureManager.registerFeature(TabConstants.Feature.HEADER_FOOTER, new HeaderFooter());
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (perWorldPlayerList && layout) TAB.getInstance().getMisconfigurationHelper().bothPerWorldPlayerListAndLayoutEnabled();
if (yellowNumber && layout) TAB.getInstance().getMisconfigurationHelper().layoutBreaksYellowNumber();
if (spectatorFix && layout) TAB.getInstance().getMisconfigurationHelper().layoutIncludesPreventSpectatorEffect();
if (globalPlayerList && layout) TAB.getInstance().getMisconfigurationHelper().bothGlobalPlayerListAndLayoutEnabled();
if (configuration.isPipelineInjection()) {
PipelineInjector inj = TAB.getInstance().getPlatform().createPipelineInjector();
if (inj != null) featureManager.registerFeature(TabConstants.Feature.PIPELINE_INJECTION, inj);
}
if (configuration.getConfig().getBoolean("scoreboard.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (configuration.getConfig().getBoolean("per-world-playerlist.enabled", false)) {
if (perWorldPlayerList) {
TabFeature pwp = TAB.getInstance().getPlatform().getPerWorldPlayerList();
if (pwp != null) featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, pwp);
if (configuration.getConfig().getBoolean("layout.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().bothPerWorldPlayerListAndLayoutEnabled();
}
}
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true))
featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (configuration.getConfig().getBoolean("belowname-objective.enabled", true))
featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
// No requirements, but due to chicken vs egg, the feature uses NameTags, Layout and RedisBungee
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true) ||
configuration.getConfig().getBoolean("layout.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
if (bossbar) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : TAB.getInstance().getPlatform().getLegacyBossBar());
}
if (pingSpoof) featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
if (headerFooter) featureManager.registerFeature(TabConstants.Feature.HEADER_FOOTER, new HeaderFooter());
if (spectatorFix) featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (scoreboard) featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (yellowNumber) featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (belowName) featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
if (teams || layout) featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
// Must be loaded after: Sorting
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true)) {
if (configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && minorVersion >= 8) {
if (teams) {
if (unlimitedTags && minorVersion >= 8) {
NameTag unlimited = TAB.getInstance().getPlatform().getUnlimitedNameTags();
if (unlimited instanceof NameTagX) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, unlimited);
@@ -361,19 +378,11 @@ public class FeatureManager {
}
// Must be loaded after: Sorting
if (minorVersion >= 8 && configuration.getConfig().getBoolean("layout.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true)) {
TAB.getInstance().getMisconfigurationHelper().layoutBreaksYellowNumber();
}
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().layoutIncludesPreventSpectatorEffect();
}
}
if (layout) featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
// Must be loaded after: Layout
if (minorVersion >= 8 && configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true)) {
if (configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false)) {
if (tablistFormatting) {
if (alignedSuffix) {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new AlignedPlayerList());
} else {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new PlayerList());
@@ -381,12 +390,8 @@ public class FeatureManager {
}
// Must be loaded after: PlayerList
if (configuration.getConfig().getBoolean("global-playerlist.enabled", false) &&
TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY) {
if (globalPlayerList && TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY) {
featureManager.registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST, new GlobalPlayerList());
if (configuration.getConfig().getBoolean("layout.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().bothGlobalPlayerListAndLayoutEnabled();
}
}
// Must be loaded after: Global PlayerList, PlayerList, NameTags, YellowNumber, BelowName
@@ -0,0 +1,27 @@
package me.neznamy.tab.shared;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* Class containing constants representing value length
* limits on different versions.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Limitations {
/** Player name length limit for scoreboard score on 1.7 and lower */
public static final int SCOREBOARD_SCORE_LENGTH_1_7 = 16;
/** Player name length limit for scoreboard score on 1.8 and higher */
public static final int SCOREBOARD_SCORE_LENGTH_1_8 = 40;
/** Max scoreboard title length for versions below 1.13 */
public static final int SCOREBOARD_TITLE_PRE_1_13 = 32;
/** Max team prefix/suffix length for versions below 1.13 */
public static final int TEAM_PREFIX_SUFFIX_PRE_1_13 = 16;
/** Max tablist entry length for 1.7 and lower */
public static final int MAX_DISPLAY_NAME_LENGTH_1_7 = 16;
}
@@ -9,6 +9,7 @@ import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.features.sorting.types.SortingType;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -361,7 +362,10 @@ public class MisconfigurationHelper {
// Runtime Errors
// ------------------------
public void invalidNumberForBossBarProgress(@NotNull String bossBar, @NotNull String input, @NotNull String configuredValue) {
public void invalidNumberForBossBarProgress(@NotNull String bossBar, @NotNull String input, @NotNull String configuredValue, TabPlayer player) {
// Placeholders are not initialized, because bridge did not respond yet (typically on join)
if (player instanceof ProxyTabPlayer && !((ProxyTabPlayer)player).isBridgeConnected()) return;
if (configuredValue.contains("%")) {
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Placeholder \"" + configuredValue +
"\" used in BossBar progress of \"" + bossBar + "\" returned value, which cannot be evaluated to a number between 0 and 100 (\"" + input + "\")"));
@@ -373,6 +377,9 @@ public class MisconfigurationHelper {
}
public void invalidInputForNumericSorting(SortingType type, String placeholder, String output, TabPlayer player) {
// Placeholders are not initialized, because bridge did not respond yet (typically on join)
if (player instanceof ProxyTabPlayer && !((ProxyTabPlayer)player).isBridgeConnected()) return;
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Placeholder " + placeholder + " used in sorting type " +
type + " returned \"" + output + "\" for player " + player.getName() + ", which is not a valid number."));
}
@@ -103,7 +103,7 @@ public class Property {
}
String rawFormattedValue0 = value;
for (String placeholder : placeholders0) {
rawFormattedValue0 = rawFormattedValue0.replace(placeholder, "%s");
rawFormattedValue0 = replaceFirst(rawFormattedValue0, placeholder, "%s");
}
if (placeholders0.size() > 0 && rawFormattedValue0.contains("%")) {
int index = rawFormattedValue0.lastIndexOf('%');
@@ -129,6 +129,15 @@ public class Property {
}
}
private String replaceFirst(String original, String searchString, String replacement) {
int index = original.indexOf(searchString);
if (index != -1) {
return original.substring(0, index) + replacement + original.substring(index + searchString.length());
} else {
return original;
}
}
/**
* Changes raw value to new provided value and performs all
* operations related to it. Changes source as well.
@@ -74,6 +74,9 @@ public enum ProtocolVersion {
V1_5_1 (60),
V1_5 (60);
/** Newest MC version this plugin jar knows */
public static final ProtocolVersion LATEST_KNOWN_VERSION = ProtocolVersion.V1_20_2;
/** Version's network id found at https://wiki.vg/Protocol_version_numbers */
private final int networkId;
@@ -55,7 +55,7 @@ public class TAB extends TabAPI {
@Getter private final DisabledCommand disabledCommand = new DisabledCommand();
/** Implementation of platform the plugin is installed on for platform-specific calls */
@Getter private final Platform<?> platform;
@Getter private final Platform platform;
/**
* CPU manager for thread and task management as well as
@@ -107,7 +107,7 @@ public class TAB extends TabAPI {
* @param platform
* Platform interface
*/
public static void create(@NotNull Platform<?> platform) {
public static void create(@NotNull Platform platform) {
instance = new TAB(platform);
instance.load();
}
@@ -119,7 +119,7 @@ public class TAB extends TabAPI {
* @param platform
* Platform interface
*/
private TAB(@NotNull Platform<?> platform) {
private TAB(@NotNull Platform platform) {
this.platform = platform;
this.serverVersion = platform.getServerVersion();
this.dataFolder = platform.getDataFolder();
@@ -134,7 +134,7 @@ public class TAB extends TabAPI {
platform.registerCommand();
platform.startMetrics();
if (platform instanceof ProxyPlatform) {
((ProxyPlatform<?>) platform).registerChannel();
((ProxyPlatform) platform).registerChannel();
}
}
@@ -181,7 +181,7 @@ public class TAB extends TabAPI {
kill();
return (configuration == null ? "&4Failed to reload, file %file% has broken syntax. Check console for more info."
: configuration.getMessages().getReloadFailBrokenFile()).replace("%file%", brokenFile);
} catch (Exception e) {
} catch (Throwable e) {
errorManager.criticalError("Failed to enable. Did you just invent a new way to break the plugin by misconfiguring it?", e);
kill();
return "&cFailed to enable due to an internal plugin error. Check console for more info.";
@@ -199,7 +199,7 @@ public class TAB extends TabAPI {
if (configuration.getMysql() != null) configuration.getMysql().closeConnection();
featureManager.unload();
platform.logInfo(IChatBaseComponent.fromColoredText("&aDisabled in " + (System.currentTimeMillis()-time) + "ms"));
} catch (Exception | NoClassDefFoundError e) {
} catch (Throwable e) {
errorManager.criticalError("Failed to disable", e);
}
kill();
@@ -52,6 +52,8 @@ public class TabConstants {
public static final String PACKET_ENTITY_SPAWN = "PacketPlayOutNamedEntitySpawn";
public static final String PACKET_ENTITY_DESTROY = "PacketPlayOutEntityDestroy";
public static final String RAW_PACKET_OUT = "Packet reading (out)";
public static final String PACKET_LOGIN = "Login packet";
public static final String TABLIST_CLEAR = "TabList entry re-add";
public static final String PLACEHOLDER_REFRESHING = "Refreshing placeholders";
@@ -10,7 +10,7 @@ import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import org.jetbrains.annotations.NotNull;
public interface BackendPlatform<T> extends Platform<T> {
public interface BackendPlatform extends Platform {
@Override
@NotNull default GroupManager detectPermissionPlugin() {
@@ -37,6 +37,21 @@ public interface BackendPlatform<T> extends Platform<T> {
registry.registerPlaceholders(manager);
}
/**
* Registers a dummy placeholder implementation for specified identifier in case
* no placeholder plugin was found.
*
* @param identifier
* Placeholder identifier to register
*/
default void registerDummyPlaceholder(@NotNull String identifier) {
if (identifier.startsWith("%rel_")) { // To prevent placeholder identifier check from throwing
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, -1, (viewer, target) -> identifier);
} else {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
}
}
/**
* Returns server's TPS for {@link TabConstants.Placeholder#TPS} placeholder
*
@@ -26,7 +26,7 @@ public abstract class BackendTabPlayer extends TabPlayer {
* @param world
* Player's world
*/
protected BackendTabPlayer(@NotNull BackendPlatform<?> platform, @NotNull Object player, @NotNull UUID uniqueId,
protected BackendTabPlayer(@NotNull BackendPlatform platform, @NotNull Object player, @NotNull UUID uniqueId,
@NotNull String name, @NotNull String world) {
super(platform, player, uniqueId, name, TAB.getInstance().getConfiguration().getServerName(),
world, ViaVersionHook.getInstance().getPlayerVersion(uniqueId, name), true);
@@ -72,4 +72,14 @@ public class DummyEntityView implements EntityView {
public int[] getDestroyedEntities(Object destroyPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isBundlePacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
}
@@ -33,4 +33,8 @@ public interface EntityView {
int getSpawnedPlayer(Object playerSpawnPacket);
int[] getDestroyedEntities(Object destroyPacket);
boolean isBundlePacket(Object packet);
Iterable<Object> getPackets(Object bundlePacket);
}
@@ -163,6 +163,16 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
BackendTabPlayer player = (BackendTabPlayer) receiver;
if (player.getEntityView().isBundlePacket(packet)) {
for (Object wrappedPacket : player.getEntityView().getPackets(packet)) {
checkPacket(player, wrappedPacket);
}
} else {
checkPacket(player, packet);
}
}
private void checkPacket(@NotNull BackendTabPlayer player, @NotNull Object packet) {
if (player.getEntityView().isMovePacket(packet) && !player.getEntityView().isLookPacket(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(player, player.getEntityView().getMoveEntityId(packet));
} else if (player.getEntityView().isTeleportPacket(packet)) {
@@ -43,14 +43,20 @@ public class ChatModifier {
return json;
}
/**
* Returns a String consisting of magic codes (color symbol + character) of
* each magic code used. If none are used, empty String is returned.
*
* @return Magic codes of this modifier as String
*/
@NotNull
public String getMagicCodes() {
StringBuilder builder = new StringBuilder();
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
if (bold) builder.append(EnumChatFormat.BOLD.getFormat());
if (italic) builder.append(EnumChatFormat.ITALIC.getFormat());
if (underlined) builder.append(EnumChatFormat.UNDERLINE.getFormat());
if (strikethrough) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (obfuscated) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
return builder.toString();
}
}
@@ -12,14 +12,11 @@ public class MiniMessageFormat implements RGBFormatter {
@Override
public @NotNull String reformat(@NotNull String text) {
if (
!text.contains("<") // No MiniMessage syntax is used
|| text.contains(EnumChatFormat.COLOR_STRING) // MiniMessage throws errors when using legacy color code
) return text;
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text));
} catch (Throwable ignored) {
return text;
if (text.contains("<") && !text.contains(EnumChatFormat.COLOR_STRING)) {
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text));
} catch (Throwable ignored) {}
}
return text;
}
}
@@ -6,10 +6,8 @@ import java.util.concurrent.*;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.Getter;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.types.TabFeature;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -52,11 +50,6 @@ public class CpuManager {
lastReport = new CpuReport(UPDATE_RATE_SECONDS, featureUsageCurrent, placeholderUsageCurrent);
featureUsageCurrent = new ConcurrentHashMap<>();
placeholderUsageCurrent = new ConcurrentHashMap<>();
if (lastReport.getPlaceholderUsageTotal() > 50) {
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("CPU usage of placeholders is " + (int) lastReport.getPlaceholderUsageTotal() +
"%. See /" + (TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY ? TabConstants.COMMAND_PROXY : TabConstants.COMMAND_BACKEND) +
" cpu for more info. Try increasing refresh intervals."));
}
});
}
@@ -18,7 +18,7 @@ import java.util.Map;
* Feature handler for BelowName feature
*/
public class BelowName extends TabFeature implements JoinListener, Loadable, UnLoadable,
ServerSwitchListener, Refreshable {
Refreshable, LoginPacketListener {
public static final String OBJECTIVE_NAME = "TAB-BelowName";
@@ -98,20 +98,6 @@ public class BelowName extends TabFeature implements JoinListener, Loadable, UnL
if (redis != null) redis.updateBelowName(connectedPlayer, number);
}
@Override
public void onServerChange(@NotNull TabPlayer player, @NotNull String from, @NotNull String to) {
if (disableChecker.isDisabledPlayer(player)) return;
player.getScoreboard().registerObjective(
OBJECTIVE_NAME,
player.getProperty(TabConstants.Property.BELOWNAME_TEXT).updateAndGet(),
Scoreboard.HealthDisplay.INTEGER
);
player.getScoreboard().setDisplaySlot(Scoreboard.DisplaySlot.BELOW_NAME, OBJECTIVE_NAME);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
player.getScoreboard().setScore(OBJECTIVE_NAME, all.getNickname(), getValue(all));
}
}
public void onDisableConditionChange(TabPlayer p, boolean disabledNow) {
if (disabledNow) {
p.getScoreboard().unregisterObjective(OBJECTIVE_NAME);
@@ -134,6 +120,16 @@ public class BelowName extends TabFeature implements JoinListener, Loadable, UnL
if (redis != null) redis.updateBelowName(refreshed, number);
}
@Override
public void onLoginPacket(TabPlayer player) {
if (disableChecker.isDisabledPlayer(player) || !player.isLoaded()) return;
player.getScoreboard().registerObjective(OBJECTIVE_NAME, player.getProperty(TabConstants.Property.BELOWNAME_TEXT).updateAndGet(), Scoreboard.HealthDisplay.INTEGER);
player.getScoreboard().setDisplaySlot(Scoreboard.DisplaySlot.BELOW_NAME, OBJECTIVE_NAME);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all.isLoaded()) player.getScoreboard().setScore(OBJECTIVE_NAME, all.getNickname(), getValue(all));
}
}
@RequiredArgsConstructor
public static class TextRefresher extends TabFeature implements Refreshable {
@@ -2,6 +2,11 @@ package me.neznamy.tab.shared.features;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.features.layout.LayoutView;
import me.neznamy.tab.shared.features.layout.ParentGroup;
import me.neznamy.tab.shared.features.layout.PlayerSlot;
import me.neznamy.tab.shared.features.types.*;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
@@ -20,13 +25,26 @@ public class PingSpoof extends TabFeature implements JoinListener, LatencyListen
/** Value to display as ping instead of real ping */
private final int value = TAB.getInstance().getConfig().getInt("ping-spoof.value", 0);
private LayoutManagerImpl layoutManager;
@Override
public int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency) {
return value;
public int onLatencyChange(@NotNull TabPlayer packetReceiver, @NotNull UUID id, int latency) {
if (layoutManager != null) {
LayoutView layout = layoutManager.getViews().get(packetReceiver);
if (layout != null) {
for (ParentGroup group : layout.getGroups()) {
PlayerSlot slot = group.getPlayerSlots().get((int) id.getLeastSignificantBits());
if (slot != null && slot.getPlayer() != null) return value;
}
}
}
if (TAB.getInstance().getPlayer(id) != null) return value;
return latency;
}
@Override
public void load() {
layoutManager = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.LAYOUT);
updateAll(false);
}
@@ -232,6 +232,8 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
tabExpansion.setPlaceholderValue(connectedPlayer, p.getIdentifier(), ((ServerPlaceholderImpl) p).getLastValue());
}
}
// Initialize to avoid onVanishStatusChange being called in the loop after joining because previous value was null
((PlayerPlaceholderImpl)registeredPlaceholders.get(TabConstants.Placeholder.VANISHED)).update(connectedPlayer);
}
@Override
@@ -295,4 +297,8 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
placeholderUsage.remove(identifier);
recalculateUsedPlaceholders();
}
public boolean isPlaceholderRegistered(@NotNull String identifier) {
return registeredPlaceholders.containsKey(identifier);
}
}
@@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
* PLAYER_LIST display slot (in tablist).
*/
public class YellowNumber extends TabFeature implements JoinListener, Loadable, UnLoadable,
ServerSwitchListener, Refreshable {
Refreshable, LoginPacketListener {
@Getter private final String featureName = "Yellow Number";
@Getter private final String refreshDisplayName = "Updating value";
@@ -107,16 +107,6 @@ public class YellowNumber extends TabFeature implements JoinListener, Loadable,
if (redis != null) redis.updateYellowNumber(connectedPlayer, value);
}
@Override
public void onServerChange(@NotNull TabPlayer p, @NotNull String from, @NotNull String to) {
if (disableChecker.isDisabledPlayer(p) || p.isBedrockPlayer()) return;
p.getScoreboard().registerObjective(OBJECTIVE_NAME, TITLE, displayType);
p.getScoreboard().setDisplaySlot(Scoreboard.DisplaySlot.PLAYER_LIST, OBJECTIVE_NAME);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
p.getScoreboard().setScore(OBJECTIVE_NAME, all.getNickname(), getValue(all));
}
}
public void onDisableConditionChange(TabPlayer p, boolean disabledNow) {
if (disabledNow) {
p.getScoreboard().unregisterObjective(OBJECTIVE_NAME);
@@ -134,4 +124,14 @@ public class YellowNumber extends TabFeature implements JoinListener, Loadable,
}
if (redis != null) redis.updateYellowNumber(refreshed, value);
}
@Override
public void onLoginPacket(TabPlayer p) {
if (disableChecker.isDisabledPlayer(p) || p.isBedrockPlayer() || !p.isLoaded()) return;
p.getScoreboard().registerObjective(OBJECTIVE_NAME, TITLE, displayType);
p.getScoreboard().setDisplaySlot(Scoreboard.DisplaySlot.PLAYER_LIST, OBJECTIVE_NAME);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all.isLoaded()) p.getScoreboard().setScore(OBJECTIVE_NAME, all.getNickname(), getValue(all));
}
}
}
@@ -102,8 +102,8 @@ public class AlignedPlayerList extends PlayerList implements QuitListener {
@Override
public void onQuit(@NotNull TabPlayer p) {
playerViews.values().forEach(v -> v.processPlayerQuit(p));
playerViews.remove(p);
playerViews.values().forEach(v -> v.processPlayerQuit(p));
}
@Override
@@ -161,7 +161,12 @@ public class BossBarLine implements BossBar {
if (value > 100) value = 100;
return value;
} catch (NumberFormatException e) {
TAB.getInstance().getMisconfigurationHelper().invalidNumberForBossBarProgress(name, progress, player.getProperty(propertyProgress).getCurrentRawValue());
TAB.getInstance().getMisconfigurationHelper().invalidNumberForBossBarProgress(
name,
progress,
player.getProperty(propertyProgress).getCurrentRawValue(),
player
);
return 100;
}
}
@@ -230,6 +235,10 @@ public class BossBarLine implements BossBar {
player.setProperty(progressRefresher, propertyProgress, progress);
player.setProperty(colorRefresher, propertyColor, color);
player.setProperty(styleRefresher, propertyStyle, style);
sendToPlayerRaw(player);
}
public void sendToPlayerRaw(TabPlayer player) {
player.getBossBar().create(
uniqueId,
player.getProperty(propertyTitle).updateAndGet(),
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.bossbar;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
@@ -19,7 +20,7 @@ import java.util.stream.Collectors;
* Class for handling BossBar feature
*/
public class BossBarManagerImpl extends TabFeature implements BossBarManager, JoinListener, CommandListener, Loadable,
UnLoadable, Refreshable {
UnLoadable, Refreshable, LoginPacketListener {
//default BossBars
private final List<String> defaultBars = new ArrayList<>();
@@ -255,4 +256,16 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager, Jo
announcedBossBars.remove(line);
});
}
@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()) {
for (BossBar bar : lineValues) {
if (bar.containsPlayer(player)) {
((BossBarLine)bar).sendToPlayerRaw(player);
}
}
}
}
}
@@ -16,7 +16,7 @@ import org.jetbrains.annotations.NotNull;
* Feature handler for global PlayerList feature
*/
public class GlobalPlayerList extends TabFeature implements JoinListener, QuitListener, VanishListener, GameModeListener,
Loadable, UnLoadable, ServerSwitchListener {
Loadable, UnLoadable, ServerSwitchListener, TabListClearListener {
// config options
private final List<String> spyServers = TAB.getInstance().getConfiguration().getConfig().getStringList("global-playerlist.spy-servers", Collections.singletonList("spyserver1"));
@@ -102,14 +102,6 @@ public class GlobalPlayerList extends TabFeature implements JoinListener, QuitLi
@Override
public void onServerChange(@NotNull TabPlayer changed, @NotNull String from, @NotNull String to) {
// Event is fired after all entries are removed from switched player's tablist, ready to re-add immediately
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
// Ignore players on the same server, since the server already sends add packet
if (!all.getServer().equals(changed.getServer()) && shouldSee(changed, all)) {
changed.getTabList().addEntry(getAddInfoData(all, changed));
}
}
// Player who switched server is removed from tablist of other players in ~70-110ms (depending on online count), re-add with a delay
TAB.getInstance().getCPUManager().runTaskLater(200, featureName, TabConstants.CpuUsageCategory.SERVER_SWITCH, () -> {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
@@ -125,6 +117,16 @@ public class GlobalPlayerList extends TabFeature implements JoinListener, QuitLi
});
}
@Override
public void onTabListClear(TabPlayer player) {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
// Ignore players on the same server, since the server already sends add packet
if (!all.getServer().equals(player.getServer()) && shouldSee(player, all)) {
player.getTabList().addEntry(getAddInfoData(all, player));
}
}
}
public @NotNull TabList.Entry getAddInfoData(@NotNull TabPlayer p, @NotNull TabPlayer viewer) {
IChatBaseComponent format = null;
if (playerlist != null && !playerlist.getDisableChecker().isDisabledPlayer(p)) {
@@ -9,6 +9,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -84,6 +85,8 @@ public abstract class NettyPipelineInjector extends PipelineInjector {
public abstract void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet);
public abstract boolean isLogin(@NotNull Object packet);
/**
* Removes all real players from team if packet does not come from TAB and reports this to override log
*
@@ -110,6 +113,19 @@ public abstract class NettyPipelineInjector extends PipelineInjector {
modifyPlayers(packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
}
if (isLogin(packet)) {
player.getScoreboard().freeze();
super.write(context, packet, channelPromise);
TAB.getInstance().getCPUManager().runTaskLater(200, getFeatureName(),
TabConstants.CpuUsageCategory.PACKET_LOGIN, () -> {
TAB.getInstance().getFeatureManager().onLoginPacket(player);
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
// For 1.20.2+ we need to do this, because server switch event is called before tablist is cleared
TAB.getInstance().getFeatureManager().onTabListClear(player);
}
});
return;
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
@@ -24,7 +24,7 @@ import org.jetbrains.annotations.Nullable;
@Getter
public class LayoutManagerImpl extends TabFeature implements LayoutManager, JoinListener, QuitListener, VanishListener, Loadable,
UnLoadable, Refreshable, ServerSwitchListener {
UnLoadable, Refreshable, TabListClearListener {
/** Config options */
private final Direction direction = parseDirection(TAB.getInstance().getConfig().getString("layout.direction", "COLUMNS"));
@@ -181,12 +181,6 @@ public class LayoutManagerImpl extends TabFeature implements LayoutManager, Join
views.values().forEach(LayoutView::tick);
}
@Override
public void onServerChange(@NotNull TabPlayer changed, @NotNull String from, @NotNull String to) {
LayoutView view = views.get(changed);
if (view != null) view.send();
}
@Override
public Layout createNewLayout(String name) {
return new LayoutPattern(this, name, Collections.emptyMap());
@@ -204,6 +198,12 @@ public class LayoutManagerImpl extends TabFeature implements LayoutManager, Join
refresh((TabPlayer) player, false);
}
@Override
public void onTabListClear(TabPlayer player) {
LayoutView view = views.get(player);
if (view != null) view.send();
}
@RequiredArgsConstructor
public enum Direction {
@@ -21,7 +21,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class NameTag extends TabFeature implements NameTagManager, JoinListener, QuitListener,
Loadable, UnLoadable, WorldSwitchListener, ServerSwitchListener, Refreshable {
Loadable, UnLoadable, WorldSwitchListener, ServerSwitchListener, Refreshable, LoginPacketListener,
VanishListener {
@Getter private final String featureName = "NameTags";
@Getter private final String refreshDisplayName = "Updating prefix/suffix";
@@ -77,7 +78,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
public void unload() {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (hasTeamHandlingPaused(target) || disableChecker.isDisabledPlayer(target)) return;
if (hasTeamHandlingPaused(target) || disableChecker.isDisabledPlayer(target)) continue;
viewer.getScoreboard().unregisterTeam(sorting.getShortTeamName(target));
}
}
@@ -100,7 +101,6 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
@Override
public void onJoin(@NotNull TabPlayer connectedPlayer) {
sorting.constructTeamNames(connectedPlayer);
updateProperties(connectedPlayer);
hiddenNameTagFor.put(connectedPlayer, new ArrayList<>());
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
@@ -122,6 +122,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
if (!disableChecker.isDisabledPlayer(disconnectedPlayer) && !hasTeamHandlingPaused(disconnectedPlayer)) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer == disconnectedPlayer) continue; //player who just disconnected
if (disconnectedPlayer.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) continue;
viewer.getScoreboard().unregisterTeam(sorting.getShortTeamName(disconnectedPlayer));
}
}
@@ -135,9 +136,6 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
@Override
public void onServerChange(@NonNull TabPlayer p, @NonNull String from, @NonNull String to) {
if (updateProperties(p) && !disableChecker.isDisabledPlayer(p)) updateTeamData(p);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (!disableChecker.isDisabledPlayer(all)) registerTeam(all, p);
}
}
@Override
@@ -231,6 +229,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
}
public void updateTeamData(@NonNull TabPlayer p, @NonNull TabPlayer viewer) {
if (p.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return;
boolean visible = getTeamVisibility(p, viewer);
String currentPrefix = p.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
String currentSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
@@ -247,6 +246,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
public void unregisterTeam(@NonNull TabPlayer p, @NonNull String teamName) {
if (hasTeamHandlingPaused(p)) return;
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (p.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) continue;
viewer.getScoreboard().unregisterTeam(teamName);
}
}
@@ -259,6 +259,7 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
private void registerTeam(@NonNull TabPlayer p, @NonNull TabPlayer viewer) {
if (hasTeamHandlingPaused(p)) return;
if (p.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return;
String replacedPrefix = p.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
String replacedSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
viewer.getScoreboard().registerTeam(
@@ -341,4 +342,24 @@ public class NameTag extends TabFeature implements NameTagManager, JoinListener,
public boolean hasHiddenNameTagVisibilityView(@NonNull me.neznamy.tab.api.TabPlayer player) {
return playersWithInvisibleNameTagView.contains(player);
}
@Override
public void onLoginPacket(TabPlayer player) {
if (!player.isLoaded()) return;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (!disableChecker.isDisabledPlayer(all) && all.isLoaded()) registerTeam(all, player);
}
}
@Override
public void onVanishStatusChange(@NotNull TabPlayer player) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) continue;
if (player.isVanished()) {
viewer.getScoreboard().unregisterTeam(sorting.getShortTeamName(player));
} else {
registerTeam(player, viewer);
}
}
}
}
@@ -29,7 +29,8 @@ import java.util.function.Supplier;
@SuppressWarnings("UnstableApiUsage")
@Getter
public abstract class RedisSupport extends TabFeature implements JoinListener, QuitListener,
DisplayNameListener, Loadable, UnLoadable, ServerSwitchListener {
DisplayNameListener, Loadable, UnLoadable, ServerSwitchListener, LoginPacketListener,
VanishListener, TabListClearListener {
@NotNull private final String featureName = "RedisSupport";
@@ -56,6 +57,7 @@ public abstract class RedisSupport extends TabFeature implements JoinListener, Q
registerMessage("join", PlayerJoin.class, PlayerJoin::new);
registerMessage("quit", PlayerQuit.class, PlayerQuit::new);
registerMessage("server", ServerSwitch.class, ServerSwitch::new);
registerMessage("vanish", UpdateVanishStatus.class, UpdateVanishStatus::new);
}
public void updateTabFormat(@NotNull TabPlayer p, @NotNull String format) {
@@ -207,4 +209,19 @@ public abstract class RedisSupport extends TabFeature implements JoinListener, Q
messages.put(name, supplier);
classStringMap.put(clazz, name);
}
@Override
public void onLoginPacket(TabPlayer player) {
features.forEach(f -> f.onLoginPacket(player));
}
@Override
public void onTabListClear(TabPlayer player) {
features.forEach(f -> f.onTabListClear(player));
}
@Override
public void onVanishStatusChange(@NotNull TabPlayer player) {
sendMessage(new UpdateVanishStatus(player.getUniqueId(), player.isVanished()));
}
}
@@ -42,21 +42,6 @@ public class RedisBelowName extends RedisFeature {
}
}
@Override
public void onServerSwitch(@NotNull TabPlayer player) {
onJoin(player);
}
@Override
public void onServerSwitch(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void onQuit(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void write(@NotNull ByteArrayDataOutput out, @NotNull TabPlayer player) {
out.writeInt(TAB.getInstance().getErrorManager().parseInteger(
@@ -68,6 +53,11 @@ public class RedisBelowName extends RedisFeature {
values.put(player, in.readInt());
}
@Override
public void onLoginPacket(@NotNull TabPlayer player) {
onJoin(player);
}
@NoArgsConstructor
@AllArgsConstructor
public class Update extends RedisMessage {
@@ -12,13 +12,19 @@ public abstract class RedisFeature {
public abstract void onJoin(@NotNull RedisPlayer player);
public abstract void onServerSwitch(@NotNull TabPlayer player);
public void onServerSwitch(@NotNull TabPlayer player) {/* Do nothing by default */}
public abstract void onServerSwitch(@NotNull RedisPlayer player);
public void onServerSwitch(@NotNull RedisPlayer player) {/* Do nothing by default */}
public abstract void onQuit(@NotNull RedisPlayer player);
public void onQuit(@NotNull RedisPlayer player) {/* Do nothing by default */}
public abstract void write(@NotNull ByteArrayDataOutput out, @NotNull TabPlayer player);
public abstract void read(@NotNull ByteArrayDataInput in, @NotNull RedisPlayer player);
public void onLoginPacket(@NotNull TabPlayer player) {/* Do nothing by default */}
public void onTabListClear(@NotNull TabPlayer player) {/* Do nothing by default */}
public void onVanishStatusChange(@NotNull RedisPlayer player) {/* Do nothing by default */}
}
@@ -41,11 +41,6 @@ public class RedisGlobalPlayerList extends RedisFeature {
}
}
@Override
public void onServerSwitch(@NotNull TabPlayer player) {
onJoin(player);
}
@Override
public void onServerSwitch(@NotNull RedisPlayer player) {
TAB.getInstance().getCPUManager().runTaskLater(200, redisSupport.getFeatureName(), TabConstants.CpuUsageCategory.SERVER_SWITCH, () -> {
@@ -93,6 +88,11 @@ public class RedisGlobalPlayerList extends RedisFeature {
}
}
@Override
public void onTabListClear(@NotNull TabPlayer player) {
onJoin(player);
}
private boolean shouldSee(@NotNull TabPlayer viewer, @NotNull RedisPlayer target) {
if (target.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return false;
if (globalPlayerList.isSpyServer(viewer.getServer())) return true;
@@ -104,4 +104,21 @@ public class RedisGlobalPlayerList extends RedisFeature {
redisSupport.getRedisPlayerList() == null ? null :
IChatBaseComponent.optimizedComponent(redisSupport.getRedisPlayerList().getFormat(player)));
}
@Override
public void onVanishStatusChange(@NotNull RedisPlayer player) {
if (player.isVanished()) {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (!shouldSee(all, player)) {
all.getTabList().removeEntry(player.getUniqueId());
}
}
} else {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (shouldSee(viewer, player)) {
viewer.getTabList().addEntry(getEntry(player));
}
}
}
}
}
@@ -50,16 +50,6 @@ public class RedisPlayerList extends RedisFeature {
onJoin(player);
}
@Override
public void onServerSwitch(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void onQuit(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void write(@NotNull ByteArrayDataOutput out, @NotNull TabPlayer player) {
out.writeUTF(player.getProperty(TabConstants.Property.TABPREFIX).get() +
@@ -76,6 +66,15 @@ public class RedisPlayerList extends RedisFeature {
return values.get(player);
}
@Override
public void onVanishStatusChange(@NotNull RedisPlayer player) {
if (player.isVanished()) return;
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer.getVersion().getMinorVersion() < 8) continue;
viewer.getTabList().updateDisplayName(player.getUniqueId(), IChatBaseComponent.optimizedComponent(getFormat(player)));
}
}
@NoArgsConstructor
@AllArgsConstructor
public class Update extends RedisMessage {
@@ -52,16 +52,6 @@ public class RedisTeams extends RedisFeature {
}
}
@Override
public void onServerSwitch(@NotNull TabPlayer player) {
onJoin(player);
}
@Override
public void onServerSwitch(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void onQuit(@NotNull RedisPlayer player) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
@@ -87,6 +77,11 @@ public class RedisTeams extends RedisFeature {
nameVisibilities.put(player, NameVisibility.getByName(in.readUTF()));
}
@Override
public void onLoginPacket(@NotNull TabPlayer player) {
onJoin(player);
}
private @NotNull String checkTeamName(@NotNull RedisPlayer player, @NotNull String currentName15, int id) {
String potentialTeamName = currentName15 + (char)id;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
@@ -42,21 +42,6 @@ public class RedisYellowNumber extends RedisFeature {
}
}
@Override
public void onServerSwitch(@NotNull TabPlayer player) {
onJoin(player);
}
@Override
public void onServerSwitch(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void onQuit(@NotNull RedisPlayer player) {
// No action is needed
}
@Override
public void write(@NotNull ByteArrayDataOutput out, @NotNull TabPlayer player) {
out.writeInt(TAB.getInstance().getErrorManager().parseInteger(
@@ -68,6 +53,11 @@ public class RedisYellowNumber extends RedisFeature {
values.put(player, in.readInt());
}
@Override
public void onLoginPacket(@NotNull TabPlayer player) {
onJoin(player);
}
@NoArgsConstructor
@AllArgsConstructor
public class Update extends RedisMessage {
@@ -0,0 +1,39 @@
package me.neznamy.tab.shared.features.redis.message;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import me.neznamy.tab.shared.features.redis.RedisPlayer;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@NoArgsConstructor
@AllArgsConstructor
public class UpdateVanishStatus extends RedisMessage {
private UUID playerId;
private boolean vanished;
@Override
public void write(@NotNull ByteArrayDataOutput out) {
writeUUID(out, playerId);
out.writeBoolean(vanished);
}
@Override
public void read(@NotNull ByteArrayDataInput in) {
playerId = readUUID(in);
vanished = in.readBoolean();
}
@Override
public void process(@NotNull RedisSupport redisSupport) {
RedisPlayer target = redisSupport.getRedisPlayers().get(playerId);
if (target == null) return; // Print warn?
target.setVanished(vanished);
redisSupport.getFeatures().forEach(f -> f.onVanishStatusChange(target));
}
}
@@ -19,7 +19,7 @@ import java.util.Map.Entry;
*/
public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManager, JoinListener,
CommandListener, DisplayObjectiveListener, ObjectiveListener, Loadable, UnLoadable, Refreshable,
ServerSwitchListener, QuitListener {
QuitListener, LoginPacketListener {
public static final String OBJECTIVE_NAME = "TAB-Scoreboard";
@@ -305,16 +305,6 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
return activeScoreboards.get(player);
}
@Override
public void onServerChange(@NotNull TabPlayer changed, @NotNull String from, @NotNull String to) {
otherPluginScoreboards.remove(changed);
ScoreboardImpl scoreboard = activeScoreboards.get(changed);
if (scoreboard != null) {
scoreboard.removePlayerFromSet(changed);
scoreboard.addPlayer(changed);
}
}
@Override
public void onQuit(@NotNull TabPlayer disconnectedPlayer) {
ScoreboardImpl sb = activeScoreboards.get(disconnectedPlayer);
@@ -322,4 +312,15 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
sb.removePlayerFromSet(disconnectedPlayer);
}
}
@Override
public void onLoginPacket(TabPlayer player) {
if (!player.isLoaded()) return;
otherPluginScoreboards.remove(player);
ScoreboardImpl scoreboard = activeScoreboards.get(player);
if (scoreboard != null) {
scoreboard.removePlayerFromSet(player);
scoreboard.addPlayer(player);
}
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.scoreboard.lines;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -46,7 +47,11 @@ public class LongLine extends ScoreboardLine implements Refreshable {
"", Scoreboard.NameVisibility.ALWAYS, Scoreboard.CollisionRule.ALWAYS, 0);
} else {
removeLine(refreshed, refreshed.getProperty(nameProperty).get());
String[] values = splitText(getPlayerName(lineNumber), RGBUtils.getInstance().convertRGBtoLegacy(refreshed.getProperty(textProperty).get()), refreshed.getVersion().getMinorVersion() >= 8 ? 40 : 16);
String[] values = splitText(
getPlayerName(lineNumber),
RGBUtils.getInstance().convertRGBtoLegacy(refreshed.getProperty(textProperty).get()),
refreshed.getVersion().getMinorVersion() >= 8 ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
);
addLine(refreshed, values[1], values[0], values[2]);
refreshed.setProperty(this, nameProperty, values[1]);
}
@@ -61,7 +66,11 @@ public class LongLine extends ScoreboardLine implements Refreshable {
addLine(p, playerName, value, "");
p.setProperty(this, nameProperty, playerName);
} else {
String[] values = splitText(playerName, RGBUtils.getInstance().convertRGBtoLegacy(value), p.getVersion().getMinorVersion() >= 8 ? 40 : 16);
String[] values = splitText(
playerName,
RGBUtils.getInstance().convertRGBtoLegacy(value),
p.getVersion().getMinorVersion() >= 8 ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
);
addLine(p, values[1], values[0], values[2]);
p.setProperty(this, nameProperty, values[1]);
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.scoreboard.lines;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.api.scoreboard.Line;
import me.neznamy.tab.shared.features.types.TabFeature;
@@ -164,7 +165,7 @@ public abstract class ScoreboardLine extends TabFeature implements Line {
* @param text
* text to display
* @param maxNameLength
* maximum length of name field, used values are 16 characters for &lt;1.8 and 40 for 1.8+
* maximum length of name field
* @return Split text as an array of 3 elements
*/
protected String[] splitText(@NonNull String playerNameStart, @NonNull String text, int maxNameLength) {
@@ -176,7 +177,7 @@ public abstract class ScoreboardLine extends TabFeature implements Line {
nameValue = playerNameStart + text;
suffixValue = "";
} else {
String[] prefixOther = split(text, 16);
String[] prefixOther = split(text, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
prefixValue = prefixOther[0];
String other = prefixOther[1];
if (playerNameStart.length() > 0) {
@@ -1,6 +1,7 @@
package me.neznamy.tab.shared.features.scoreboard.lines;
import lombok.NonNull;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
@@ -15,12 +16,12 @@ import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
*/
public class StaticLine extends ScoreboardLine {
//values for 1.7 clients with 16-character limit for player name
//values for 1.7 clients
protected String prefix17;
protected String name17;
protected String suffix17;
//values for 1.8-1.12 clients with 40-character limit for player name
//values for 1.8-1.12 clients
protected String prefix;
protected String name;
protected String suffix;
@@ -35,12 +36,12 @@ public class StaticLine extends ScoreboardLine {
super.text = text;
String legacy = RGBUtils.getInstance().convertRGBtoLegacy(this.text);
//1.8+
String[] v18 = splitText(getPlayerName(lineNumber), legacy, 40);
String[] v18 = splitText(getPlayerName(lineNumber), legacy, Limitations.SCOREBOARD_SCORE_LENGTH_1_8);
prefix = v18[0];
name = v18[1];
suffix = v18[2];
//1.7-
String[] v17 = splitText(getPlayerName(lineNumber), legacy, 16);
String[] v17 = splitText(getPlayerName(lineNumber), legacy, Limitations.SCOREBOARD_SCORE_LENGTH_1_7);
prefix17 = v17[0];
name17 = v17[1];
suffix17 = v17[2];
@@ -15,7 +15,7 @@ import java.util.function.BiConsumer;
public class DisableChecker extends TabFeature implements Refreshable {
@Getter private final String featureName;
@Getter private final String refreshDisplayName = "Updating display condition";
@Getter private final String refreshDisplayName = "Refreshing disable condition";
private final @Nullable Condition disableCondition;
private final BiConsumer<TabPlayer, Boolean> action;
@@ -0,0 +1,18 @@
package me.neznamy.tab.shared.features.types;
import me.neznamy.tab.shared.platform.TabPlayer;
/**
* Interface for features that resend content on Login packet.
*/
public interface LoginPacketListener {
/**
* Called after Login packet is received by the player to resend all
* scoreboard content as the packet has cleared it.
*
* @param player
* Player who received the packet
*/
void onLoginPacket(TabPlayer player);
}
@@ -0,0 +1,19 @@
package me.neznamy.tab.shared.features.types;
import me.neznamy.tab.shared.platform.TabPlayer;
/**
* Interface for features listening to tablist entry clear
* on server switch to re-add entries.
*/
public interface TabListClearListener {
/**
* Called after player's TabList is cleared by the proxy
* on server switch.
*
* @param player
* Player who got their TabList entries cleared
*/
void onTabListClear(TabPlayer player);
}
@@ -3,8 +3,10 @@ package me.neznamy.tab.shared.hook;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.jetbrains.annotations.NotNull;
@@ -14,6 +16,10 @@ import java.util.stream.Collectors;
public class AdventureHook {
/** Component cache for adventure components */
private static final ComponentCache<IChatBaseComponent, Component> cache =
new ComponentCache<>(1000, AdventureHook::toAdventureComponent0);
/**
* Converts component to adventure component
*
@@ -25,13 +31,27 @@ public class AdventureHook {
*/
@NotNull
public static Component toAdventureComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return cache.get(component, clientVersion);
}
/**
* Converts component to adventure component
*
* @param component
* Component to convert
* @param clientVersion
* Version to create component for
* @return Adventure component from this component.
*/
@NotNull
private static Component toAdventureComponent0(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
ChatModifier modifier = component.getModifier();
net.kyori.adventure.text.format.TextColor color = null;
TextColor color = null;
if (modifier.getColor() != null) {
if (clientVersion.getMinorVersion() >= 16) {
color = net.kyori.adventure.text.format.TextColor.color(modifier.getColor().getRgb());
color = TextColor.color(modifier.getColor().getRgb());
} else {
color = net.kyori.adventure.text.format.TextColor.color(modifier.getColor().getLegacyColor().getHexCode());
color = TextColor.color(modifier.getColor().getLegacyColor().getHexCode());
}
}
Set<TextDecoration> decorations = new HashSet<>();
@@ -40,8 +60,14 @@ public class AdventureHook {
if (modifier.isObfuscated()) decorations.add(TextDecoration.OBFUSCATED);
if (modifier.isStrikethrough()) decorations.add(TextDecoration.STRIKETHROUGH);
if (modifier.isUnderlined()) decorations.add(TextDecoration.UNDERLINED);
Component advComponent = Component.text(component.getText(), color, decorations);
if (modifier.getFont() != null) advComponent = advComponent.font(Key.key(modifier.getFont()));
return advComponent.children(component.getExtra().stream().map(c -> toAdventureComponent(c, clientVersion)).collect(Collectors.toList()));
Component adventureComponent = Component.text(component.getText(), color, decorations);
if (modifier.getFont() != null) {
adventureComponent = adventureComponent.font(Key.key(modifier.getFont()));
}
if (!component.getExtra().isEmpty()) {
adventureComponent = adventureComponent.children(component.getExtra().stream().map(
c -> toAdventureComponent(c, clientVersion)).collect(Collectors.toList()));
}
return adventureComponent;
}
}
@@ -42,8 +42,11 @@ public class ViaVersionHook {
}
ProtocolVersion protocol = ProtocolVersion.fromNetworkId(version);
if (protocol == ProtocolVersion.UNKNOWN_CLIENT_VERSION) {
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("ViaVersion returned unknown protocol version " + version +
" for player " + playerName + ". This may result in plugin not working correctly for them."));
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(String.format(
"ViaVersion returned unknown protocol version %d for player %s. " +
"Latest version recognized by this plugin version is %d (%s). " +
"Did a new MC version come out without you updating the plugin? This may result in plugin not working correctly for them.",
version, playerName, ProtocolVersion.LATEST_KNOWN_VERSION.getNetworkId(), ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName())));
} else {
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + playerName);
}
@@ -129,11 +129,12 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
* @return last known value for entered player duo
*/
public String getLastValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target) {
if (!lastValues.computeIfAbsent(viewer, v -> new WeakHashMap<>()).containsKey(target)) {
lastValues.get(viewer).put(target, getReplacements().findReplacement(identifier));
WeakHashMap<me.neznamy.tab.api.TabPlayer, String> viewerMap = lastValues.computeIfAbsent(viewer, v -> new WeakHashMap<>());
if (!viewerMap.containsKey(target)) {
viewerMap.put(target, getReplacements().findReplacement(identifier));
update(viewer, target);
}
return setPlaceholders(EnumChatFormat.color(lastValues.computeIfAbsent(viewer, v -> new WeakHashMap<>()).get(target)), target);
return setPlaceholders(EnumChatFormat.color(viewerMap.get(target)), target);
}
@Override
@@ -12,5 +12,5 @@ public class EmptyTabExpansion implements TabExpansion {
public void setValue(@NonNull TabPlayer player, @NonNull String key, @NonNull String value) {/*Do nothing*/}
@Override
public boolean unregister() { return false; }
public void unregisterExpansion() {/* Do nothing */}
}
@@ -39,5 +39,5 @@ public interface TabExpansion {
void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value);
boolean unregister();
void unregisterExpansion();
}
@@ -50,27 +50,6 @@ public abstract class EventListener<T> {
TAB.getInstance().getFeatureManager().onWorldChange(player, world));
}
/**
* Processes server change by forwarding it to all features.
*
* @param player
* Player who switched server or joined
* @param uuid
* UUID of the player
* @param server
* New server
*/
public void serverChange(@NotNull T player, @NotNull UUID uuid, @NotNull String server) {
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> {
if (TAB.getInstance().getPlayer(uuid) == null) {
TAB.getInstance().getFeatureManager().onJoin(createPlayer(player));
} else {
TAB.getInstance().getFeatureManager().onServerChange(uuid, server);
}
});
}
/**
* Processes plugin message.
*
@@ -82,7 +61,7 @@ public abstract class EventListener<T> {
public void pluginMessage(@NotNull UUID player, byte[] message) {
TAB.getInstance().getCPUManager().runMeasuredTask("Plugin message handling",
TabConstants.CpuUsageCategory.PLUGIN_MESSAGE, () ->
((ProxyPlatform<?>)TAB.getInstance().getPlatform()).getPluginMessageHandler().onPluginMessage(player, message));
((ProxyPlatform)TAB.getInstance().getPlatform()).getPluginMessageHandler().onPluginMessage(player, message));
}
/**
@@ -18,7 +18,7 @@ import java.io.File;
* An interface with methods that are called in universal code,
* but require platform-specific API calls
*/
public interface Platform<T> {
public interface Platform {
/**
* Detects permission plugin and returns its representing object
@@ -144,15 +144,4 @@ public interface Platform<T> {
* @return plugin's data folder
*/
File getDataFolder();
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
T toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version);
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.platform;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
@@ -23,7 +24,11 @@ public abstract class Scoreboard<T extends TabPlayer> {
/** Scoreboard objectives player has registered */
private final Set<String> registeredObjectives = new HashSet<>();
/** Flag tracking time between Login packet send and its processing */
private boolean frozen;
public void setScore(@NotNull String objective, @NotNull String playerName, int score) {
if (frozen) return;
if (!registeredObjectives.contains(objective)) {
error("Tried to update score (%s) without the existence of its requested objective '%s' to player ", playerName, objective);
return;
@@ -32,6 +37,7 @@ public abstract class Scoreboard<T extends TabPlayer> {
}
public void removeScore(@NotNull String objective, @NotNull String playerName) {
if (frozen) return;
if (!registeredObjectives.contains(objective)) {
error("Tried to remove score (%s) without the existence of its requested objective '%s' to player ", playerName, objective);
return;
@@ -40,14 +46,16 @@ public abstract class Scoreboard<T extends TabPlayer> {
}
public void registerObjective(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
if (frozen) return;
if (!registeredObjectives.add(objectiveName)) {
error("Tried to register duplicated objective %s to player ", objectiveName);
return;
}
registerObjective0(objectiveName, cutTo(title, 32), display);
registerObjective0(objectiveName, cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13), display);
}
public void unregisterObjective(@NotNull String objectiveName) {
if (frozen) return;
if (!registeredObjectives.remove(objectiveName)) {
error("Tried to unregister non-existing objective %s for player ", objectiveName);
return;
@@ -56,23 +64,34 @@ public abstract class Scoreboard<T extends TabPlayer> {
}
public void updateObjective(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
if (frozen) return;
if (!registeredObjectives.contains(objectiveName)) {
error("Tried to modify non-existing objective %s for player ", objectiveName);
return;
}
updateObjective0(objectiveName, cutTo(title, 32), display);
updateObjective0(objectiveName, cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13), display);
}
public void registerTeam(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility,
@NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
if (frozen) return;
if (!registeredTeams.add(name)) {
error("Tried to register duplicated team %s to player ", name);
return;
}
registerTeam0(name, cutTo(prefix, 16), cutTo(suffix, 16), visibility, collision, players, options);
registerTeam0(
name,
cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
visibility,
collision,
players,
options
);
}
public void unregisterTeam(@NotNull String name) {
if (frozen) return;
if (!registeredTeams.remove(name)) {
error("Tried to unregister non-existing team %s for player ", name);
return;
@@ -82,23 +101,39 @@ public abstract class Scoreboard<T extends TabPlayer> {
public void updateTeam(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility,
@NotNull CollisionRule collision, int options) {
if (frozen) return;
if (!registeredTeams.contains(name)) {
error("Tried to modify non-existing team %s for player ", name);
return;
}
updateTeam0(name, cutTo(prefix, 16), cutTo(suffix, 16), visibility, collision, options);
updateTeam0(
name,
cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
visibility,
collision,
options
);
}
private void error(@NotNull String format, @NotNull Object... args) {
TAB.getInstance().getErrorManager().printError(String.format(format, args) + player.getName());
TAB.getInstance().debug(String.format(format, args) + player.getName());
}
/**
* Clears maps of registered teams and objectives on server switch, as proxy sends Login packet
* Marks for freeze.
*/
public void clearRegisteredObjectives() {
public void freeze() {
frozen = true;
}
/**
* Clears frozen flag and clears maps of registered teams and objectives.
*/
public void unfreeze() {
registeredTeams.clear();
registeredObjectives.clear();
frozen = false;
}
/**
@@ -21,7 +21,7 @@ import java.util.*;
public abstract class TabPlayer implements me.neznamy.tab.api.TabPlayer {
/** Platform reference */
protected final Platform<?> platform;
protected final Platform platform;
/** Platform-specific player object instance */
@Setter protected Object player;
@@ -86,7 +86,7 @@ public abstract class TabPlayer implements me.neznamy.tab.api.TabPlayer {
* @param useRealId
* Whether tablist uses real uuid or offline
*/
protected TabPlayer(@NotNull Platform<?> platform, @NotNull Object player, @NotNull UUID uniqueId, @NotNull String name,
protected TabPlayer(@NotNull Platform platform, @NotNull Object player, @NotNull UUID uniqueId, @NotNull String name,
@NotNull String server, @NotNull String world, int protocolVersion, boolean useRealId) {
this.platform = platform;
this.player = player;
@@ -345,5 +345,5 @@ public abstract class TabPlayer implements me.neznamy.tab.api.TabPlayer {
*
* @return Server platform
*/
public abstract Platform<?> getPlatform();
public abstract Platform getPlatform();
}
@@ -10,23 +10,44 @@ import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.bossbar.BossBar.Color;
import net.kyori.adventure.bossbar.BossBar.Overlay;
import org.jetbrains.annotations.NotNull;
@AllArgsConstructor
public class AdventureBossBar implements BossBar {
/** Color array for fast access */
private static final Color[] colors = new Color[] {
Color.PINK,
Color.BLUE,
Color.RED,
Color.GREEN,
Color.YELLOW,
Color.PURPLE,
Color.WHITE
};
/** Overlay array for fast access */
private static final Overlay[] overlays = new Overlay[] {
Overlay.PROGRESS,
Overlay.NOTCHED_6,
Overlay.NOTCHED_10,
Overlay.NOTCHED_12,
Overlay.NOTCHED_20
};
private final TabPlayer player;
private final Map<UUID, net.kyori.adventure.bossbar.BossBar> bossBars = new LinkedHashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
net.kyori.adventure.bossbar.BossBar bar = net.kyori.adventure.bossbar.BossBar.bossBar(
AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
progress,
net.kyori.adventure.bossbar.BossBar.Color.valueOf(color.toString()),
net.kyori.adventure.bossbar.BossBar.Overlay.valueOf(style.toString())
colors[color.ordinal()],
overlays[style.ordinal()]
);
bossBars.put(id, bar);
((Audience)player.getPlayer()).showBossBar(bar);
@@ -44,12 +65,12 @@ public class AdventureBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).overlay(net.kyori.adventure.bossbar.BossBar.Overlay.valueOf(style.toString()));
bossBars.get(id).overlay(overlays[style.ordinal()]);
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).color(net.kyori.adventure.bossbar.BossBar.Color.valueOf(color.toString()));
bossBars.get(id).color(colors[color.ordinal()]);
}
@Override
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff. Show more