Compare commits

..
17 Commits
Author SHA1 Message Date
NEZNAMY 827197f916 5.1.1 2025-03-31 16:52:47 +02:00
NEZNAMY 29a35cf1cc [ViaVersion hook] Decrease check interval from 100 to 50 2025-03-30 18:51:39 +02:00
NEZNAMY f26516ecae [Code style] Separate objective register and set display slot functions 2025-03-30 18:47:39 +02:00
NEZNAMY f26a7ad2b1 [Bukkit] Add direct NMS hook for v1_8_R3 and v1_12_R1 2025-03-30 17:46:23 +02:00
NEZNAMY 96e0a29573 Merge pull request #1464 from MineLAT/fix/viaversion-delay
Fix ViaVersion packets delay
2025-03-29 17:12:29 +01:00
NEZNAMY f30d667ca8 [Code style] Add return value annotations and remove unused methods in ConfigurationFile 2025-03-29 17:05:53 +01:00
Rubén 8db7a1d6d9 Fix ViaVersion packets delay 2025-03-29 10:04:46 -06:00
NEZNAMY d285a0512e [Multi-server support] Provide some feedback about connection status (success/fail) (doesn't work for redis) 2025-03-29 16:52:13 +01:00
NEZNAMY 256d0d5c84 Revert "[Placeholders] Parse nested PAPI placeholders using {}"
This reverts commit 6501480980.
2025-03-29 09:57:02 +01:00
NEZNAMY b808e1f9d1 [Bukkit] Refactor scoreboard / tablist implementation finding for players 2025-03-29 09:55:52 +01:00
NEZNAMY 07cd781ec1 [Sponge] Drop Sponge 7 module 2025-03-28 15:43:37 +01:00
NEZNAMY 9115b687f4 [Sponge] Drop support for sponge versions 1.16.5 - 1.20.5 2025-03-27 18:51:28 +01:00
NEZNAMY 7c51377203 [Bukkit] Update paper submodule for 1.21.5 and allow it there 2025-03-27 18:24:02 +01:00
NEZNAMY 193cd02072 [Configuration] Fix startup error when some config options were missing 2025-03-27 16:49:01 +01:00
NEZNAMY dbb6d91f0c [Configuration] Add missing playerlist-objective.render-type option to default config 2025-03-26 19:48:20 +01:00
NEZNAMY a82714e811 [BungeeCord] Fix error (#1462) 2025-03-26 19:00:53 +01:00
NEZNAMY bcd1bbac62 5.1.1-SNAPSHOT 2025-03-26 18:53:58 +01:00
66 changed files with 1506 additions and 1410 deletions

No files matched your search

+4 -3
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "5.1.0"
version = "5.1.1"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -15,11 +15,12 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bukkit.paper,
projects.bukkit.v18R3,
projects.bukkit.v112R1,
projects.bungeecord,
projects.component,
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.sponge,
projects.fabric
).map { it.dependencyProject }
+2 -2
View File
@@ -9,11 +9,11 @@ repositories {
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
val version = "1.21.4-R0.1-SNAPSHOT"
val version = "1.21.5-R0.1-SNAPSHOT"
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
paperweight.paperDevBundle("1.21.5-no-moonrise-SNAPSHOT") // Just temporary
compileOnly("io.papermc.paper:paper-api:${version}")
}
@@ -9,7 +9,6 @@ import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
@SuppressWarnings("unused") // Used via reflection
public class PaperComponentConverter extends ComponentConverter {
@Override
@@ -0,0 +1,40 @@
package me.neznamy.tab.platforms.paper;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code (1.20.5+).
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new PaperComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new PaperPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new PaperPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
}
@@ -1,17 +0,0 @@
package me.neznamy.tab.platforms.paper;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
/**
* Loader for Paper to speed up functions by using direct NMS code instead of reflection.
*/
@SuppressWarnings("unused") // Via reflection
public class PaperLoader {
/** Channel getter using direct NMS code */
public static final FunctionWithException<BukkitTabPlayer, Channel> getChannel =
player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
@@ -18,30 +18,21 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
@SuppressWarnings("unused") // Used via reflection
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field method;
private static final Field players;
static {
try {
method = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("method"));
players = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("players"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
private static final Field method = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).getFirst();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
/**
* Constructs new instance with given player.
@@ -66,7 +57,14 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
@@ -26,7 +26,6 @@ import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
@SuppressWarnings("unused") // Used via reflection
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
@@ -3,15 +3,11 @@ package me.neznamy.tab.platforms.bukkit;
import io.netty.channel.Channel;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Pipeline injection for Bukkit 1.8+.
*/
@@ -28,33 +24,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
super("packet_handler");
}
/**
* Attempts to load required classes, fields and methods and marks class as available.
* If something fails, an error message is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Field NETWORK_MANAGER;
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "network channel injection", null,
"Anti-override for tablist & nametags not working",
"Compatibility with nickname plugins changing player names will not work",
"Scoreboard will not be checking for other plugins");
}
}
/**
* Returns {@code true} if pipeline injection is available, {@code false} if not.
*
@@ -7,6 +7,7 @@ import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -23,6 +24,7 @@ public class BukkitReflection {
public static final Method CraftPlayer_getHandle = getHandle();
/** Server version data */
@Getter
private static final ServerVersion serverVersion = detectServerVersion();
/** Flag determining whether the server version is at least 1.20.2 or not */
@@ -59,11 +61,12 @@ public class BukkitReflection {
ClassLoader loader = BukkitReflection.class.getClassLoader();
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
}
return new ServerVersion(classFunction, minorVersion, serverPackage);
} else {
// Paper without CB relocation
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
return new ServerVersion(classFunction, minorVersion, null);
}
return new ServerVersion(classFunction, minorVersion);
}
/**
@@ -102,9 +105,12 @@ public class BukkitReflection {
*/
@RequiredArgsConstructor
@Getter
private static class ServerVersion {
public static class ServerVersion {
private final FunctionWithException<String, Class<?>> getClass;
private final int minorVersion;
@Nullable
private final String serverPackage;
}
}
@@ -1,7 +1,5 @@
package me.neznamy.tab.platforms.bukkit.platform;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
@@ -15,14 +13,9 @@ import me.neznamy.tab.platforms.bukkit.header.*;
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.platforms.bukkit.tablist.*;
import me.neznamy.tab.platforms.bukkit.provider.BukkitImplementationProvider;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.platforms.bukkit.provider.ViaVersionImplementationProvider;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
@@ -33,7 +26,6 @@ import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
@@ -43,10 +35,8 @@ import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.impl.AdventureBossBar;
import me.neznamy.tab.shared.platform.impl.DummyBossBar;
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
import me.neznamy.tab.shared.util.PerformanceUtil;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import net.kyori.adventure.audience.Audience;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
@@ -62,10 +52,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
@@ -94,25 +82,22 @@ public class BukkitPlatform implements BackendPlatform {
/** Detection for presence of Paper's MSPT getter */
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
/** Component converter from TAB to minecraft components */
@Nullable
private final ComponentConverter componentConverter = findComponentConverter();
/** Provider for scoreboard implementation */
@NotNull
private final FunctionWithException<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
/** Provider for tablist implementation */
@NotNull
private final FunctionWithException<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
/** Header/footer implementation */
@Getter
@NotNull
private final HeaderFooter headerFooter = findHeaderFooter();
private final HeaderFooter headerFooter;
/** Flag tracking if direct mojang-mapped code can be used or not */
private final boolean canUseDirectNMS = BukkitReflection.isMojangMapped() && serverVersion == ProtocolVersion.V1_21_4;
private final boolean canUseDirectNMS = BukkitReflection.isMojangMapped() &&
(serverVersion == ProtocolVersion.V1_21_4 || serverVersion == ProtocolVersion.V1_21_5);
/** Implementation for creating new instances using content available on the server */
@NotNull
private final ImplementationProvider serverImplementationProvider;
/** Implementation for sending new content to new players on old servers */
@NotNull
private final ViaVersionImplementationProvider viaVersionImplementationProvider = new ViaVersionImplementationProvider(serverVersion);
/**
* Constructs new instance with given plugin.
@@ -121,7 +106,6 @@ public class BukkitPlatform implements BackendPlatform {
* Plugin
*/
@SneakyThrows
@SuppressWarnings("unchecked")
public BukkitPlatform(@NotNull JavaPlugin plugin) {
this.plugin = plugin;
long time = System.currentTimeMillis();
@@ -134,105 +118,27 @@ public class BukkitPlatform implements BackendPlatform {
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
new BukkitPremiumVanishHook().register();
}
PingRetriever.tryLoad();
if (BukkitReflection.getMinorVersion() >= 8) {
if (canUseDirectNMS) {
BukkitPipelineInjector.setGetChannel((FunctionWithException<BukkitTabPlayer, Channel>) Class.forName("me.neznamy.tab.platforms.paper.PaperLoader").getDeclaredField("getChannel").get(null));
} else {
BukkitPipelineInjector.tryLoad();
}
if (canUseDirectNMS) {
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper.PaperImplementationProvider").getConstructor().newInstance();
} else if ("v1_12_R1".equals(BukkitReflection.getServerVersion().getServerPackage())) {
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_12_R1.NMSImplementationProvider").getConstructor().newInstance();
} else if ("v1_8_R3".equals(BukkitReflection.getServerVersion().getServerPackage())) {
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_8_R3.NMSImplementationProvider").getConstructor().newInstance();
} else {
serverImplementationProvider = new BukkitImplementationProvider();
}
headerFooter = findHeaderFooter();
PingRetriever.tryLoad();
BukkitPipelineInjector.setGetChannel(serverImplementationProvider.getChannelFunction());
BukkitUtils.sendCompatibilityMessage();
Bukkit.getConsoleSender().sendMessage("[TAB] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
}
@NotNull
@SneakyThrows
private FunctionWithException<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
final FunctionWithException<BukkitTabPlayer, Scoreboard> base;
if (ViaVersionHook.getInstance().isInstalled()) {
base = ViaVersionPlatform.findScoreboardProvider(serverVersion);
} else {
base = FunctionWithException.empty();
}
if (canUseDirectNMS) {
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketScoreboard").getConstructor(BukkitTabPlayer.class);
return base.fallback(player -> (Scoreboard) constructor.newInstance(player));
}
try {
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
PacketScoreboard.load();
return base.fallback(PacketScoreboard::new);
} catch (Exception e) {
if (PaperScoreboard.isAvailable()) {
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
return base.fallback(PaperScoreboard::new);
} else if (BukkitScoreboard.isAvailable()) {
List<String> missingFeatures = Lists.newArrayList(
"Compatibility with other plugins being reduced",
"Features receiving new artificial character limits"
);
if (BukkitReflection.is1_20_3Plus()) {
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
}
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
return base.fallback(BukkitScoreboard::new);
} else if (BukkitReflection.getMinorVersion() >= 5) {
BukkitUtils.compatibilityError(e, "Scoreboards", null,
"Scoreboard feature will not work",
"Belowname feature will not work",
"Player objective feature will not work",
"Scoreboard teams feature will not work (nametags & sorting)");
}
}
return base.fallback(DummyScoreboard::new);
}
@NotNull
@SneakyThrows
private FunctionWithException<BukkitTabPlayer, TabList> findTablistProvider() {
final FunctionWithException<BukkitTabPlayer, TabList> base;
if (ViaVersionHook.getInstance().isInstalled()) {
base = ViaVersionPlatform.findTablistProvider(serverVersion);
} else {
base = FunctionWithException.empty();
}
if (canUseDirectNMS) {
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketTabList").getConstructor(BukkitTabPlayer.class);
return base.fallback(player -> (TabList) constructor.newInstance(player));
}
try {
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
// 1.19.3+
Objects.requireNonNull(componentConverter);
PacketTabList1193.loadNew();
return base.fallback(PacketTabList1193::new);
} else if (BukkitReflection.getMinorVersion() >= 8) {
// 1.8 - 1.19.2
Objects.requireNonNull(componentConverter);
PacketTabList18.load();
return base.fallback(PacketTabList18::new);
} else {
// 1.7.10 and lower
PacketTabList17.load();
return base.fallback(PacketTabList17::new);
}
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
"Layout feature will not work",
"Prevent-spectator-effect feature will not work",
"Ping spoof feature will not work",
"Tablist formatting missing anti-override",
"Tablist formatting not supporting relational placeholders");
return base.fallback(BukkitTabList::new);
}
}
@NotNull
private HeaderFooter findHeaderFooter() {
if (BukkitReflection.getMinorVersion() >= 8) {
try {
Objects.requireNonNull(componentConverter);
Objects.requireNonNull(serverImplementationProvider.getComponentConverter());
return new PacketHeaderFooter();
} catch (Exception e) {
if (PaperHeaderFooter.isAvailable()) return new PaperHeaderFooter();
@@ -250,38 +156,6 @@ public class BukkitPlatform implements BackendPlatform {
return new DummyHeaderFooter();
}
/**
* Attempts to load component converter.
*
* @return Instance or {@code null} if not available
*/
@Nullable
@SneakyThrows
public ComponentConverter findComponentConverter() {
if (canUseDirectNMS) {
return (ComponentConverter) Class.forName("me.neznamy.tab.platforms.paper.PaperComponentConverter").getConstructor().newInstance();
}
try {
if (BukkitReflection.getMinorVersion() >= 19) {
// 1.19+
return new ModernComponentConverter();
} else if (BukkitReflection.getMinorVersion() >= 16) {
// 1.16 - 1.18.2
return new ModerateComponentConverter();
} else {
// 1.7 - 1.15.2
return new LegacyComponentConverter();
}
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
"This will negatively impact most features, see below.");
if (BukkitUtils.PRINT_EXCEPTIONS) {
e.printStackTrace();
}
return null;
}
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
@@ -338,19 +212,17 @@ public class BukkitPlatform implements BackendPlatform {
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
if (identifier.startsWith("%rel_")) {
// relational placeholder
pl.registerRelationalPlaceholder(identifier, (viewer, target) ->
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, (viewer, target) ->
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
} else if (identifier.startsWith("%sync:")) {
registerSyncPlaceholder(identifier);
} else if (identifier.contains("{") && identifier.contains("}")) {
// has nested bracket placeholders
pl.registerPlayerPlaceholder(identifier, p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), PlaceholderAPI.setBracketPlaceholders((Player) p.getPlayer(), identifier)));
} else if (identifier.startsWith("%server_")) {
// placeholder with the same output for all players, register as server for better performance
pl.registerServerPlaceholder(identifier, () -> PlaceholderAPI.setPlaceholders(null, identifier));
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier,
() -> PlaceholderAPI.setPlaceholders(null, identifier));
} else {
pl.registerPlayerPlaceholder(identifier, p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier,
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
}
}
@@ -366,7 +238,7 @@ public class BukkitPlatform implements BackendPlatform {
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), PlaceholderAPI.setBracketPlaceholders((Player) p.getPlayer(), syncedPlaceholder)) : identifier);
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
});
return null;
@@ -424,8 +296,8 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@NotNull
public Object convertComponent(@NotNull TabComponent component) {
if (componentConverter != null) {
return componentConverter.convert(component);
if (serverImplementationProvider.getComponentConverter() != null) {
return serverImplementationProvider.getComponentConverter().convert(component);
} else {
return component;
}
@@ -435,7 +307,9 @@ public class BukkitPlatform implements BackendPlatform {
@NotNull
@SneakyThrows
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
return scoreboardProvider.apply((BukkitTabPlayer) player);
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
if (scoreboard != null) return scoreboard;
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
}
@Override
@@ -458,7 +332,9 @@ public class BukkitPlatform implements BackendPlatform {
@NotNull
@SneakyThrows
public TabList createTabList(@NotNull TabPlayer player) {
return tablistProvider.apply((BukkitTabPlayer) player);
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
if (tabList != null) return tabList;
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
}
@Override
@@ -1,118 +0,0 @@
package me.neznamy.tab.platforms.bukkit.platform;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.jetbrains.annotations.NotNull;
public class ViaVersionPlatform {
@NotNull
public static FunctionWithException<BukkitTabPlayer, Scoreboard> findScoreboardProvider(@NotNull ProtocolVersion serverVersion) {
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3")) {
return FunctionWithException.empty();
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_13.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
return new ViaScoreboard1203(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
return new ViaScoreboard116(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId()) {
return new ViaScoreboard113(player);
}
return null;
};
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_16.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
return new ViaScoreboard1203(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
return new ViaScoreboard116(player);
}
return null;
};
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_20_3.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
return new ViaScoreboard1203(player);
}
return null;
};
}
return FunctionWithException.empty();
}
@NotNull
public static FunctionWithException<BukkitTabPlayer, TabList> findTablistProvider(@NotNull ProtocolVersion serverVersion) {
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4")) {
return FunctionWithException.empty();
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_16.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
return new ViaTabList1214(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
return new ViaTabList1212(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
return new ViaTabList1193(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
return new ViaTabList116(player);
}
return null;
};
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_19_3.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
return new ViaTabList1214(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
return new ViaTabList1212(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
return new ViaTabList1193(player);
}
return null;
};
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_21_2.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
return new ViaTabList1214(player);
}
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
return new ViaTabList1212(player);
}
return null;
};
}
if (serverVersion.getNetworkId() < ProtocolVersion.V1_21_4.getNetworkId()) {
return player -> {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
return new ViaTabList1214(player);
}
return null;
};
}
return FunctionWithException.empty();
}
}
@@ -0,0 +1,175 @@
package me.neznamy.tab.platforms.bukkit.provider;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
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.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.platforms.bukkit.tablist.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList1193;
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList17;
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* Implementation provider using reflection for NMS / Bukkit implementations as fallback options.
*/
@Getter
public class BukkitImplementationProvider implements ImplementationProvider {
@Nullable
private final FunctionWithException<BukkitTabPlayer, Channel> channelFunction = findChannelFunction();
@Nullable
private final ComponentConverter componentConverter = findComponentConverter();
@NotNull
private final Function<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
@NotNull
private final Function<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
@Nullable
private FunctionWithException<BukkitTabPlayer, Channel> findChannelFunction() {
if (BukkitReflection.getMinorVersion() < 8) return null;
try {
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Field NETWORK_MANAGER;
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
return player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "network channel injection", null,
"Anti-override for tablist & nametags not working",
"Compatibility with nickname plugins changing player names will not work",
"Scoreboard will not be checking for other plugins");
return null;
}
}
@Nullable
@SneakyThrows
private ComponentConverter findComponentConverter() {
try {
if (BukkitReflection.getMinorVersion() >= 19) {
// 1.19+
return new ModernComponentConverter();
} else if (BukkitReflection.getMinorVersion() >= 16) {
// 1.16 - 1.18.2
return new ModerateComponentConverter();
} else {
// 1.7 - 1.15.2
return new LegacyComponentConverter();
}
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
"This will negatively impact most features, see below.");
if (BukkitUtils.PRINT_EXCEPTIONS) {
e.printStackTrace();
}
return null;
}
}
@NotNull
private Function<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
try {
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
PacketScoreboard.load();
return PacketScoreboard::new;
} catch (Exception e) {
if (PaperScoreboard.isAvailable()) {
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
return PaperScoreboard::new;
} else if (BukkitScoreboard.isAvailable()) {
List<String> missingFeatures = Lists.newArrayList(
"Compatibility with other plugins being reduced",
"Features receiving new artificial character limits"
);
if (BukkitReflection.is1_20_3Plus()) {
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
}
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
return BukkitScoreboard::new;
} else if (BukkitReflection.getMinorVersion() >= 5) {
BukkitUtils.compatibilityError(e, "Scoreboards", null,
"Scoreboard feature will not work",
"Belowname feature will not work",
"Player objective feature will not work",
"Scoreboard teams feature will not work (nametags & sorting)");
}
}
return DummyScoreboard::new;
}
@NotNull
@SneakyThrows
private Function<BukkitTabPlayer, TabList> findTablistProvider() {
try {
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
// 1.19.3+
Objects.requireNonNull(componentConverter);
PacketTabList1193.loadNew();
return PacketTabList1193::new;
} else if (BukkitReflection.getMinorVersion() >= 8) {
// 1.8 - 1.19.2
Objects.requireNonNull(componentConverter);
PacketTabList18.load();
return PacketTabList18::new;
} else {
// 1.7.10 and lower
PacketTabList17.load();
return PacketTabList17::new;
}
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
"Layout feature will not work",
"Prevent-spectator-effect feature will not work",
"Ping spoof feature will not work",
"Tablist formatting missing anti-override",
"Tablist formatting not supporting relational placeholders");
return BukkitTabList::new;
}
}
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return scoreboardProvider.apply(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return tablistProvider.apply(player);
}
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.platforms.bukkit.provider;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ImplementationProvider {
@NotNull
Scoreboard newScoreboard(@NotNull BukkitTabPlayer player);
@NotNull
TabList newTabList(@NotNull BukkitTabPlayer player);
@Nullable
ComponentConverter getComponentConverter();
@Nullable
FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction();
}
@@ -0,0 +1,78 @@
package me.neznamy.tab.platforms.bukkit.provider;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Implementation for sending new features on old servers using ViaVersion API.
* If no implementation is required for a player, {@code null} is returned.
*/
@RequiredArgsConstructor
public class ViaVersionImplementationProvider {
/** Map of tablist implementations by each new version */
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, TabList>> TABLIST_IMPLEMENTATIONS = new LinkedHashMap<>();
/** Map of scoreboard implementations by each new version */
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> SCOREBOARD_IMPLEMENTATIONS = new LinkedHashMap<>();
static {
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_4, ViaTabList1214::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_2, ViaTabList1212::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_19_3, ViaTabList1193::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaTabList116::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_20_3, ViaScoreboard1203::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaScoreboard116::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
}
private final ProtocolVersion serverVersion;
@Nullable
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3")) {
return null;
}
int serverVer = serverVersion.getNetworkId();
int playerVer = player.getVersion().getNetworkId();
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
return entry.getValue().apply(player);
}
}
return null;
}
@Nullable
public TabList newTabList(@NotNull BukkitTabPlayer player) {
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4")) {
return null;
}
int serverVer = serverVersion.getNetworkId();
int playerVer = player.getVersion().getNetworkId();
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
return entry.getValue().apply(player);
}
}
return null;
}
}
@@ -41,7 +41,7 @@ import java.util.concurrent.CountDownLatch;
* making per-player view of teams, especially sidebar not working.
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
@SuppressWarnings({"deprecation", "DataFlowIssue"})
@SuppressWarnings("deprecation")
public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
/** Version in which team colors were added */
@@ -100,10 +100,16 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
checkPlayerScoreboard();
org.bukkit.scoreboard.Objective obj = newObjective(objective.getName(), "dummy", objective.getTitle(), objective.getHealthDisplay());
setObjectiveNumberFormat(obj, objective.getNumberFormat());
obj.setDisplaySlot(slots[objective.getDisplaySlot().ordinal()]);
objective.setPlatformObjective(obj);
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
checkPlayerScoreboard();
org.bukkit.scoreboard.Objective obj = (org.bukkit.scoreboard.Objective) objective.getPlatformObjective();
obj.setDisplaySlot(slots[objective.getDisplaySlot().ordinal()]);
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
checkPlayerScoreboard();
@@ -113,6 +113,10 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void registerObjective(@NonNull Objective objective) {
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.REGISTER, objective));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
packetSender.sendPacket(player, displayPacketData.setDisplaySlot(objective.getDisplaySlot().ordinal(), newObjective(objective)));
}
@@ -5,6 +5,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.type.Types;
import lombok.NonNull;
import me.neznamy.chat.component.SimpleTextComponent;
@@ -14,6 +15,7 @@ import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -23,19 +25,19 @@ import java.util.concurrent.TimeUnit;
*/
public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final long DELAY = 500;
private static final long CHECK_DELAY = 50;
private static final Object DUMMY = new Object();
protected final Class<? extends Protocol> protocol;
private final PacketType setDisplayObjective;
protected final PacketType setDisplayObjective;
private final PacketType setObjective;
protected final PacketType setScore;
private final PacketType setPlayerTeam;
/** User connection this tablist belongs to */
/** User connection this scoreboard belongs to */
protected final UserConnection connection;
private transient final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
private transient boolean delayed = true;
private transient ScheduledFuture<?> task;
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
/**
* Constructs new instance with given player.
@@ -62,25 +64,22 @@ public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
this.setPlayerTeam = setPlayerTeam;
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
// First-time delay
connection.getChannel().eventLoop().schedule(() -> {
Runnable task;
while ((task = queue.poll()) != null) {
task.run();
// Queue packets until connection is available
// Is important to use scheduleWithFixedDelay() to avoid task overlap
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
PacketWrapper packet;
while ((packet = queuedPackets.poll()) != null) {
packet.send(protocol);
}
task.cancel(true);
}
delayed = false;
}, DELAY, TimeUnit.MILLISECONDS);
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
}
@Override
public void registerObjective(@NonNull Objective objective) {
sendObjectiveUpdate(ObjectiveAction.REGISTER, objective);
final PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
writeObjective(packet, objective);
send(packet);
}
@Override
@@ -178,13 +177,11 @@ public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
protected abstract void writeObjective(@NonNull PacketWrapper packet, @NonNull Objective objective);
protected abstract void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective);
protected void send(@NonNull PacketWrapper packet) {
if (delayed) {
queue.add(() -> packet.send(protocol));
if (!task.isCancelled()) {
queuedPackets.add(packet);
} else {
packet.scheduleSend(protocol);
}
@@ -30,6 +30,16 @@ public class ViaScoreboard113 extends ViaScoreboard {
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
packet.write(Types.STRING, objective.getName());
send(packet);
}
@Override
public void setScore(@NonNull Score score) {
sendScoreUpdate(ScoreAction.CHANGE, score);
@@ -62,12 +72,6 @@ public class ViaScoreboard113 extends ViaScoreboard {
packet.write(Types.COMPONENT, ComponentUtil.legacyToJson(component.toLegacyText()));
}
@Override
protected void writeObjective(@NonNull PacketWrapper packet, @NonNull Objective objective) {
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
packet.write(Types.STRING, objective.getName());
}
@Override
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
// Objective value
@@ -33,6 +33,16 @@ public class ViaScoreboard1203 extends ViaScoreboard116 {
this.resetScore = resetScore;
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
packet.write(Types.STRING, objective.getName());
send(packet);
}
@Override
public void setScore(@NonNull Score score) {
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
@@ -68,12 +78,6 @@ public class ViaScoreboard1203 extends ViaScoreboard116 {
packet.write(Types.TAG, component.toViaVersionTag());
}
@Override
protected void writeObjective(@NonNull PacketWrapper packet, @NonNull Objective objective) {
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
packet.write(Types.STRING, objective.getName());
}
@Override
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
super.writeObjectiveDisplay(packet, objective);
@@ -5,6 +5,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.type.Types;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
@@ -18,6 +19,7 @@ import java.util.BitSet;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -27,7 +29,7 @@ import java.util.concurrent.TimeUnit;
*/
public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
private static final long DELAY = 500;
private static final long CHECK_DELAY = 50;
protected final Class<? extends Protocol> protocol;
private final PacketType playerInfoUpdate;
@@ -35,8 +37,8 @@ public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
/** User connection this tablist belongs to */
protected final UserConnection connection;
private transient final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
private transient boolean delayed = true;
private transient ScheduledFuture<?> task;
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
/**
*
@@ -56,14 +58,17 @@ public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
this.tabList = tabList;
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
// First-time delay
connection.getChannel().eventLoop().schedule(() -> {
Runnable task;
while ((task = queue.poll()) != null) {
task.run();
// Queue packets until connection is available
// Is important to use scheduleWithFixedDelay() to avoid task overlap
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
PacketWrapper packet;
while ((packet = queuedPackets.poll()) != null) {
packet.send(protocol);
}
task.cancel(true);
}
delayed = false;
}, DELAY, TimeUnit.MILLISECONDS);
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
}
@Override
@@ -164,8 +169,8 @@ public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
}
protected void send(@NonNull PacketWrapper packet) {
if (delayed) {
queue.add(() -> packet.send(protocol));
if (!task.isCancelled()) {
queuedPackets.add(packet);
} else {
packet.scheduleSend(protocol);
}
+4
View File
@@ -0,0 +1,4 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.spigotmc:spigot:1.12-R0.1-SNAPSHOT")
}
@@ -0,0 +1,48 @@
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import net.minecraft.server.v1_12_R1.*;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct NMS code.
*/
public class NMSComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return new ChatComponentText(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return new ChatMessage(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return new ChatComponentKeybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
((IChatBaseComponent)nmsComponent).setChatModifier(
new net.minecraft.server.v1_12_R1.ChatModifier()
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
.setBold(modifier.getBold())
.setItalic(modifier.getItalic())
.setUnderline(modifier.getUnderlined())
.setStrikethrough(modifier.getStrikethrough())
.setRandom(modifier.getObfuscated())
);
}
@Override
public void addSibling(@NotNull Object parent, @NotNull Object child) {
((IChatBaseComponent)parent).addSibling((IChatBaseComponent) child);
}
}
@@ -0,0 +1,45 @@
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Implementation provider using direct NMS code for 1.8.8.
*/
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
public @Nullable ComponentConverter getComponentConverter() {
return componentConverter;
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().playerConnection.networkManager.channel;
}
}
@@ -0,0 +1,159 @@
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.server.v1_12_R1.*;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), IScoreboardCriteria.b);
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
objective.setPlatformObjective(obj);
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
String title = objective.getTitle().toLegacyText();
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
title = cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13);
}
obj.setDisplayName(title);
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
s.setScore(score.getValue());
sendPacket(new PacketPlayOutScoreboardScore(s));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new ScoreboardTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.getPlayerNameSet().addAll(team.getPlayers());
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
}
private void updateTeamProperties(@NonNull Team team) {
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setPrefix(cutTo(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setSuffix(cutTo(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
if (isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
}
if (isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, ((SafeScoreboard<?>)player.getScoreboard()).onTeamPacket(
action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
}
}
@@ -0,0 +1,189 @@
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.server.v1_12_R1.*;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* TabList implementation using direct NMS code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
// PlayerInfoData subclass is broken, using reflection to get it
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class);
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
private static final Field HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(0);
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
// Added in 1.20.2
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.20.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName());
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
HEADER.set(packet, header.convert());
FOOTER.set(packet, footer.convert());
sendPacket(packet);
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.getValue(), property.getSignature());
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
String action = ACTION.get(packet).toString();
for (Object nmsData : (List<Object>) PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action.equals(Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
private void sendPacket(@NonNull PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(
packet,
createProfile(id, name, skin),
latency,
EnumGamemode.values()[gameMode],
displayName == null ? null : displayName.convert())
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
}
}
+4
View File
@@ -0,0 +1,4 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.bukkit:craftbukkit:1.8.8-R0.1-SNAPSHOT")
}
@@ -0,0 +1,51 @@
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import net.minecraft.server.v1_8_R3.ChatComponentText;
import net.minecraft.server.v1_8_R3.ChatMessage;
import net.minecraft.server.v1_8_R3.EnumChatFormat;
import net.minecraft.server.v1_8_R3.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct NMS code.
*/
public class NMSComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return new ChatComponentText(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return new ChatMessage(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
throw new UnsupportedOperationException("Keybind components were added in 1.12");
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
((IChatBaseComponent)nmsComponent).setChatModifier(
new net.minecraft.server.v1_8_R3.ChatModifier()
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
.setBold(modifier.getBold())
.setItalic(modifier.getItalic())
.setUnderline(modifier.getUnderlined())
.setStrikethrough(modifier.getStrikethrough())
.setRandom(modifier.getObfuscated())
);
}
@Override
public void addSibling(@NotNull Object parent, @NotNull Object child) {
((IChatBaseComponent)parent).addSibling((IChatBaseComponent) child);
}
}
@@ -0,0 +1,40 @@
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct NMS code for 1.8.8.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().playerConnection.networkManager.channel;
}
}
@@ -0,0 +1,157 @@
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.server.v1_8_R3.*;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), IScoreboardCriteria.b);
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
objective.setPlatformObjective(obj);
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
String title = objective.getTitle().toLegacyText();
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
title = cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13);
}
obj.setDisplayName(title);
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
s.setScore(score.getValue());
sendPacket(new PacketPlayOutScoreboardScore(s));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new ScoreboardTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.getPlayerNameSet().addAll(team.getPlayers());
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
}
private void updateTeamProperties(@NonNull Team team) {
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPrefix(cutTo(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setSuffix(cutTo(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
if (isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
}
if (isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, ((SafeScoreboard<?>)player.getScoreboard()).onTeamPacket(
action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
}
}
@@ -0,0 +1,178 @@
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.server.v1_8_R3.*;
import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo.PlayerInfoData;
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct NMS code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class);
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData.class, int.class).get(0);
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData.class, IChatBaseComponent.class);
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
// Added in 1.20.2
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.20.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName());
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(header.convert());
FOOTER.set(packet, footer.convert());
sendPacket(packet);
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.getValue(), property.getSignature());
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
String action = ACTION.get(packet).toString();
for (PlayerInfoData nmsData : (List<PlayerInfoData>) PLAYERS.get(packet)) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action.equals(Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
private void sendPacket(@NonNull PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
PLAYERS.set(packet, Collections.singletonList(packet.new PlayerInfoData(
createProfile(id, name, skin),
latency,
WorldSettings.EnumGamemode.values()[gameMode],
displayName == null ? null : displayName.convert())
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
}
}
@@ -44,6 +44,10 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
@Override
public void registerObjective(@NonNull Objective objective) {
sendObjectivePacket(objective, (byte) ObjectiveAction.REGISTER);
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
player.sendPacket(new ScoreboardDisplay(objective.getDisplaySlot().ordinal(), objective.getName()));
}
@@ -5,6 +5,7 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.protocol.DefinedPacket;
import org.jetbrains.annotations.NotNull;
@@ -53,7 +54,8 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendPluginMessage(byte[] message) {
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
Server server = getPlayer().getServer();
if (server != null) server.sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
}
/**
@@ -48,7 +48,11 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], (net.minecraft.world.scores.Objective) objective.getPlatformObjective()));
}
@Override
+3 -2
View File
@@ -7,10 +7,11 @@ plugins {
val platforms = setOf(
rootProject.projects.bukkit,
rootProject.projects.bukkit.paper,
rootProject.projects.bukkit.v18R3,
rootProject.projects.bukkit.v112R1,
rootProject.projects.bungeecord,
rootProject.projects.velocity,
rootProject.projects.sponge7,
rootProject.projects.sponge8
rootProject.projects.sponge
).map { it.dependencyProject }
tasks {
+4 -2
View File
@@ -12,6 +12,7 @@ dependencyResolutionManagement {
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist, RedisBungee
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
maven("https://repo.codemc.org/repository/nms/") // CraftBukkit + NMS
}
}
@@ -31,9 +32,10 @@ include(":shared")
include(":velocity")
include(":bukkit")
include(":bukkit:paper")
include(":bukkit:v1_8_R3")
include(":bukkit:v1_12_R1")
include(":bungeecord")
include(":component")
include(":sponge7")
include(":sponge8")
include(":sponge")
include(":fabric")
include(":jar")
@@ -569,20 +569,18 @@ public class FeatureManager {
// Load the feature first, because it will be processed in main thread (to make it run before feature threads)
if (config.isEnableProxySupport()) {
String type = config.getConfig().getString("proxy-support.type");
ProxySupport proxy;
String type = config.getConfig().getString("proxy-support.type", "PLUGIN");
ProxySupport proxy = null;
if (type.equalsIgnoreCase("PLUGIN")) {
String plugin = config.getConfig().getString("proxy-support.plugin.name", "RedisBungee");
proxy = TAB.getInstance().getPlatform().getProxySupport(plugin);
} else if (type.equalsIgnoreCase("REDIS")) {
String url = config.getConfig().getString("proxy-support.redis.url");
proxy = new ProxyMessengerSupport(() -> RedisBroker.of(url));
String url = config.getConfig().getString("proxy-support.redis.url", "redis://:password@localhost:6379/0");
proxy = new ProxyMessengerSupport("Redis", () -> RedisBroker.of(url));
} else if (type.equalsIgnoreCase("RABBITMQ")) {
String exchange = config.getConfig().getString("proxy-support.rabbitmq.exchange");
String url = config.getConfig().getString("proxy-support.rabbitmq.url");
proxy = new ProxyMessengerSupport(() -> RabbitMQBroker.of(url, exchange));
} else {
proxy = null;
String exchange = config.getConfig().getString("proxy-support.rabbitmq.exchange", "plugin");
String url = config.getConfig().getString("proxy-support.rabbitmq.url", "amqp://guest:guest@localhost:5672/%2F");
proxy = new ProxyMessengerSupport("RabbitMQ", () -> RabbitMQBroker.of(url, exchange));
}
if (proxy != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.PROXY_SUPPORT, proxy);
}
@@ -249,8 +249,8 @@ public class Converter {
private void convertHeaderFooter(@NotNull ConfigurationFile oldConfig, @NotNull ConfigurationFile newConfig) {
newConfig.set("header-footer.enabled", oldConfig.getBoolean("enable-header-footer", true));
newConfig.set("header-footer.header", oldConfig.getStringList("header"));
newConfig.set("header-footer.footer", oldConfig.getStringList("footer"));
newConfig.set("header-footer.header", oldConfig.getStringList("header", Collections.emptyList()));
newConfig.set("header-footer.footer", oldConfig.getStringList("footer", Collections.emptyList()));
newConfig.set("header-footer.disable-in-worlds", oldConfig.getStringList("disable-features-in-worlds.header-footer", Collections.singletonList("disabledworld")));
if (TAB.getInstance().getPlatform().isProxy())
newConfig.set("header-footer.disable-in-servers", oldConfig.getStringList("disable-features-in-servers.header-footer", Collections.singletonList("disabledserver")));
@@ -337,7 +337,7 @@ public class Converter {
}
private String translateWorldGroup(@NotNull ConfigurationFile oldConfig, @NotNull String group) {
String oldSeparator = oldConfig.hasConfigOption("multi-world-separator") ? oldConfig.getString("multi-world-separator") : "-";
String oldSeparator = oldConfig.getString("multi-world-separator", "-");
return group.replace(oldSeparator, ";");
}
@@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -17,17 +18,17 @@ import java.util.Map.Entry;
/**
* Abstract class for configuration file
*/
@Getter
@SuppressWarnings("unchecked")
public abstract class ConfigurationFile {
/** Comments on top of the file */
protected List<String> header;
/** Configuration file content */
@Getter @Setter protected Map<Object, Object> values;
@Setter
protected Map<Object, Object> values;
/** File to use */
@Getter protected final File file;
@NotNull
protected final File file;
/**
* Constructs new instance and attempts to load specified configuration file.
@@ -69,6 +70,7 @@ public abstract class ConfigurationFile {
* Value to be inserted and returned if option is not present
* @return value from configuration file
*/
@Nullable
public Object getObject(@NonNull String path, @Nullable Object defaultValue) {
if (path.isEmpty()) return values;
Object value = values;
@@ -108,10 +110,10 @@ public abstract class ConfigurationFile {
* @return Value at specified path
*/
@Nullable
public Object getObject(String[] path) {
public Object getObject(@NonNull String[] path) {
Object value = values;
for (String section : path) {
if (section == null || !(value instanceof Map)) {
if (!(value instanceof Map)) {
return null;
}
value = getIgnoreCase((Map<Object, Object>) value, section);
@@ -129,6 +131,7 @@ public abstract class ConfigurationFile {
* case-insensitive key name
* @return map value from case-insensitive key
*/
@Nullable
private Object getIgnoreCase(@NonNull Map<Object, Object> map, @NonNull String key) {
try {
for (Entry<Object, Object> entry : map.entrySet()) {
@@ -141,18 +144,6 @@ public abstract class ConfigurationFile {
}
}
/**
* Returns config option with specified path as {@code String}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public String getString(@NonNull String path) {
return getString(path, null);
}
/**
* Returns config option with specified path as {@code String}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
@@ -164,25 +155,13 @@ public abstract class ConfigurationFile {
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code String}
*/
@Contract("_, !null -> !null")
public String getString(@NonNull String path, @Nullable String defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
return String.valueOf(value);
}
/**
* Returns config option with specified path as {@code List<String>}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
@Nullable
public List<String> getStringList(@NonNull String path) {
return getStringList(path, null);
}
/**
* Returns config option with specified path as {@code List<String>}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
@@ -194,6 +173,7 @@ public abstract class ConfigurationFile {
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code List<String>}
*/
@Contract("_, !null -> !null")
public List<String> getStringList(@NonNull String path, @Nullable List<String> defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
@@ -207,18 +187,6 @@ public abstract class ConfigurationFile {
return fixedList;
}
/**
* Returns config option with specified path as {@code Integer}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public Integer getInt(@NonNull String path) {
return getInt(path, null);
}
/**
* Returns config option with specified path as {@code Integer}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
@@ -230,6 +198,7 @@ public abstract class ConfigurationFile {
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Integer}
*/
@Contract("_, !null -> !null")
public Integer getInt(@NonNull String path, @Nullable Integer defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
@@ -257,27 +226,6 @@ public abstract class ConfigurationFile {
return Boolean.parseBoolean(value.toString());
}
/**
* Returns config option with specified path as {@code Double}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Double}
*/
public Double getDouble(@NonNull String path, double defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try {
return Double.parseDouble(value.toString());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Returns config option with specified path as {@code Map<K, V>}. If the option
* is not present or value is not a {@code Map}, new empty {@code Map} is returned.
@@ -337,6 +285,7 @@ public abstract class ConfigurationFile {
* Value to save
* @return the first argument to allow chaining
*/
@NotNull
private Map<Object, Object> set(@NonNull Map<Object, Object> map, @NonNull String path, @Nullable Object value) {
if (path.contains(".")) {
String keyWord = getRealKey(map, path.split("\\.")[0]);
@@ -365,6 +314,7 @@ public abstract class ConfigurationFile {
* Key to find
* @return The real key name
*/
@NotNull
private String getRealKey(@NonNull Map<?, ?> map, @NonNull String key) {
for (Object mapKey : map.keySet()) {
if (mapKey.toString().equalsIgnoreCase(key)) return mapKey.toString();
@@ -208,12 +208,12 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
private void register(@NotNull TabPlayer player) {
player.getScoreboard().registerObjective(
Scoreboard.DisplaySlot.BELOW_NAME,
OBJECTIVE_NAME,
cache.get(player.belowNameData.text.updateAndGet()),
Scoreboard.HealthDisplay.INTEGER,
cache.get(player.belowNameData.defaultNumberFormat.updateAndGet())
);
player.getScoreboard().setDisplaySlot(OBJECTIVE_NAME, Scoreboard.DisplaySlot.BELOW_NAME);
}
/**
@@ -208,12 +208,12 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
private void register(@NotNull TabPlayer player) {
player.getScoreboard().registerObjective(
Scoreboard.DisplaySlot.PLAYER_LIST,
OBJECTIVE_NAME,
cache.get(player.playerlistObjectiveData.title.updateAndGet()),
configuration.getHealthDisplay(),
SimpleTextComponent.EMPTY
);
player.getScoreboard().setDisplaySlot(OBJECTIVE_NAME, Scoreboard.DisplaySlot.PLAYER_LIST);
}
/**
@@ -3,38 +3,56 @@ package me.neznamy.tab.shared.features.proxy;
import com.saicone.delivery4j.AbstractMessenger;
import com.saicone.delivery4j.Broker;
import lombok.RequiredArgsConstructor;
import me.neznamy.chat.TextColor;
import me.neznamy.chat.component.TextComponent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Supplier;
@RequiredArgsConstructor
public class ProxyMessengerSupport extends ProxySupport {
@NotNull
private final String messengerName;
@NotNull
private final Supplier<Broker> brokerSupplier;
@Nullable
private AbstractMessenger messenger;
@Override
public void sendMessage(@NotNull String message) {
if (messenger == null) return;
messenger.send(TabConstants.PROXY_CHANNEL_NAME, message);
}
@Override
public void register() {
messenger = new AbstractMessenger() {
@Override
protected @NotNull Broker loadBroker() {
return brokerSupplier.get();
}
};
messenger.subscribe(TabConstants.PROXY_CHANNEL_NAME).consume((channel, lines) -> {
processMessage(lines[0]);
}).cache(true);
messenger.start();
try {
Broker broker = brokerSupplier.get();
messenger = new AbstractMessenger() {
@Override
@NotNull
protected Broker loadBroker() {
return broker;
}
};
messenger.subscribe(TabConstants.PROXY_CHANNEL_NAME).consume((channel, lines) -> processMessage(lines[0])).cache(true);
messenger.start();
TAB.getInstance().getPlatform().logInfo(new TextComponent("Successfully connected to " + messengerName, TextColor.GREEN));
} catch (Exception e) {
TAB.getInstance().getErrorManager().criticalError("Failed to connect to " + messengerName + ": " + e.getClass().getName() + ": " + e.getMessage(), null);
}
}
@Override
public void unregister() {
if (messenger == null) return;
messenger.close();
messenger.clear();
}
@@ -150,12 +150,12 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
if (p.scoreboardData.activeScoreboard == this) return; // already registered
p.scoreboardData.titleProperty = new Property(this, p, title);
p.getScoreboard().registerObjective(
Scoreboard.DisplaySlot.SIDEBAR,
ScoreboardManagerImpl.OBJECTIVE_NAME,
manager.getCache().get(p.scoreboardData.titleProperty.get()),
Scoreboard.HealthDisplay.INTEGER,
SimpleTextComponent.EMPTY
);
p.getScoreboard().setDisplaySlot(ScoreboardManagerImpl.OBJECTIVE_NAME, Scoreboard.DisplaySlot.SIDEBAR);
for (Line s : lines) {
((ScoreboardLine)s).register(p);
}
@@ -20,8 +20,6 @@ public interface Scoreboard {
/**
* Registers new scoreboard objective.
*
* @param displaySlot
* Display slot
* @param objectiveName
* Objective name
* @param title
@@ -31,8 +29,16 @@ public interface Scoreboard {
* @param numberFormat
* Default number format for all scores in this objective (1.20.3+)
*/
void registerObjective(@NonNull DisplaySlot displaySlot, @NonNull String objectiveName, @NonNull TabComponent title,
@NonNull HealthDisplay display, @Nullable TabComponent numberFormat);
void registerObjective(@NonNull String objectiveName, @NonNull TabComponent title, @NonNull HealthDisplay display, @Nullable TabComponent numberFormat);
/**
* Sets display slot of an objective.
* @param objectiveName
* Objective name
* @param displaySlot
* Display slot
*/
void setDisplaySlot(@NonNull String objectiveName, @NonNull DisplaySlot displaySlot);
/**
* Unregisters scoreboard objective.
@@ -57,9 +57,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
private boolean antiOverrideScoreboard;
@Override
public synchronized void registerObjective(@NonNull DisplaySlot displaySlot, @NonNull String objectiveName, @NonNull TabComponent title,
public synchronized void registerObjective(@NonNull String objectiveName, @NonNull TabComponent title,
@NonNull HealthDisplay display, @Nullable TabComponent numberFormat) {
Objective objective = new Objective(displaySlot, objectiveName, title, display, numberFormat, null);
Objective objective = new Objective(objectiveName, title, display, numberFormat, null);
if (objectives.put(objectiveName, objective) != null) {
error("Tried to register duplicated objective %s to player ", objectiveName);
return;
@@ -68,6 +68,18 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
registerObjective(objective);
}
@Override
public synchronized void setDisplaySlot(@NonNull String objectiveName, @NonNull DisplaySlot displaySlot) {
Objective objective = objectives.get(objectiveName);
if (objective == null) {
error("Tried to set display slot for non-existing objective %s to player ", objectiveName);
return;
}
objective.setDisplaySlot(displaySlot);
if (frozen) return;
setDisplaySlot(objective);
}
@Override
public synchronized void unregisterObjective(@NonNull String objectiveName) {
Objective objective = objectives.remove(objectiveName);
@@ -202,6 +214,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
public synchronized void resend() {
for (Objective objective : objectives.values()) {
registerObjective(objective);
if (objective.getDisplaySlot() != null) {
setDisplaySlot(objective);
}
for (Score score : objective.getScores().values()) {
setScore(score);
}
@@ -370,6 +385,14 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
*/
public abstract void registerObjective(@NonNull Objective objective);
/**
* Sets display slot for an objective
*
* @param objective
* Objective to set display slot for
*/
public abstract void setDisplaySlot(@NonNull Objective objective);
/**
* Unregisters an objective
*
@@ -439,12 +462,11 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
/**
* Structure holding objective data.
*/
@AllArgsConstructor
@Getter
@Setter
public static class Objective {
@NonNull private final DisplaySlot displaySlot;
@Nullable private DisplaySlot displaySlot;
@NonNull private final String name;
@NonNull private TabComponent title;
@NonNull private HealthDisplay healthDisplay;
@@ -454,6 +476,15 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
/** Platform's objective object (if it has one) for fast access */
@Nullable private Object platformObjective;
private Objective(@NonNull String name, @NonNull TabComponent title,
@NonNull HealthDisplay healthDisplay, @Nullable TabComponent numberFormat, @Nullable Object platformObjective) {
this.name = name;
this.title = title;
this.healthDisplay = healthDisplay;
this.numberFormat = numberFormat;
this.platformObjective = platformObjective;
}
private void update(@NonNull TabComponent title, @NonNull HealthDisplay healthDisplay, @Nullable TabComponent numberFormat) {
this.title = title;
this.healthDisplay = healthDisplay;
@@ -51,7 +51,7 @@ public abstract class TrackedTabList<P extends TabPlayer> implements TabList {
* Not needed for platforms which support pipeline injection.
*/
public void checkDisplayNames() {
// Empty by default, overridden by Sponge7, Sponge8 and Velocity
// Empty by default, overridden Sponge and Velocity
}
/**
@@ -25,6 +25,11 @@ public class DummyScoreboard extends SafeScoreboard<TabPlayer> {
// Do nothing
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
// Do nothing
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
// Do nothing
@@ -57,6 +57,7 @@ playerlist-objective:
value: "%ping%"
fancy-value: "&7Ping: %ping%"
title: "Java Edition is better" # Only visible on Bedrock Edition
render-type: INTEGER
disable-condition: '%world%=disabledworld'
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname
@@ -17,8 +17,12 @@ dependencies {
implementation("org.bstats:bstats-sponge:3.1.0")
}
tasks.compileJava {
options.release.set(21)
}
sponge {
apiVersion("8.0.0")
apiVersion("12.0.0")
loader {
name(PluginLoaders.JAVA_PLAIN)
version("1.0")
@@ -28,7 +32,7 @@ sponge {
displayName(rootProject.name)
version(project.version.toString())
description(project.description)
entrypoint("me.neznamy.tab.platforms.sponge8.Sponge8TAB")
entrypoint("me.neznamy.tab.platforms.sponge.SpongeTAB")
links {
val website = rootProject.ext.get("website")!!.toString()
homepage(website)
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.TAB;
@@ -14,9 +14,12 @@ import org.spongepowered.api.event.entity.ChangeEntityWorldEvent;
import org.spongepowered.api.event.entity.living.player.RespawnPlayerEvent;
import org.spongepowered.api.event.filter.cause.First;
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
import org.spongepowered.api.profile.GameProfile;
import java.util.UUID;
/**
* Event listener for Sponge 8 to forward events to enabled features.
* Event listener for Sponge to forward events to enabled features.
*/
public class SpongeEventListener implements EventListener<ServerPlayer> {
@@ -29,7 +32,7 @@ public class SpongeEventListener implements EventListener<ServerPlayer> {
@Listener
@SneakyThrows
public void onQuit(ServerSideConnectionEvent.Disconnect event) {
quit(SpongeMultiVersion.getUniqueId.apply(event));
quit(event.profile().map(GameProfile::uuid).orElse(new UUID(0, 0)));
}
/**
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@@ -28,14 +28,14 @@ import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import java.io.File;
/**
* Platform implementation for Sponge 8 and up.
* Platform implementation for Sponge.
*/
@RequiredArgsConstructor
public class SpongePlatform implements BackendPlatform {
/** Main class reference */
@NotNull
private final Sponge8TAB plugin;
private final SpongeTAB plugin;
/** Server version */
@Getter
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import lombok.NonNull;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
@@ -14,7 +14,7 @@ import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayMo
import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayModes;
/**
* Scoreboard implementation for Sponge 8 using its API.
* Scoreboard implementation for Sponge using its API.
*/
public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
@@ -72,10 +72,14 @@ public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
.criterion(Criteria.DUMMY)
.build();
sb.addObjective(obj);
sb.updateDisplaySlot(obj, displaySlots[objective.getDisplaySlot().ordinal()]);
objective.setPlatformObjective(obj);
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sb.updateDisplaySlot((org.spongepowered.api.scoreboard.objective.Objective) objective.getPlatformObjective(), displaySlots[objective.getDisplaySlot().ordinal()]);
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sb.removeObjective((org.spongepowered.api.scoreboard.objective.Objective) objective.getPlatformObjective());
@@ -91,13 +95,13 @@ public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
@Override
public void setScore(@NonNull Score score) {
org.spongepowered.api.scoreboard.objective.Objective obj = (org.spongepowered.api.scoreboard.objective.Objective) score.getObjective().getPlatformObjective();
SpongeMultiVersion.findOrCreateScore(obj, score.getHolder()).setScore(score.getValue());
obj.findOrCreateScore(score.getHolder()).setScore(score.getValue());
}
@Override
public void removeScore(@NonNull Score score) {
org.spongepowered.api.scoreboard.objective.Objective obj = (org.spongepowered.api.scoreboard.objective.Objective) score.getObjective().getPlatformObjective();
obj.removeScore(SpongeMultiVersion.findOrCreateScore(obj, score.getHolder()));
obj.removeScore(score.getHolder());
}
@Override
@@ -1,13 +1,16 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import com.google.inject.Inject;
import lombok.Getter;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.apache.logging.log4j.Logger;
import org.bstats.sponge.Metrics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.api.Server;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
@@ -20,15 +23,16 @@ import org.spongepowered.plugin.builtin.jvm.Plugin;
import java.nio.file.Path;
/**
* Main class for Sponge 8.
* Main class for Sponge.
*/
@Getter
@Plugin(TabConstants.PLUGIN_ID)
public class Sponge8TAB {
public class SpongeTAB {
@Inject @ConfigDir(sharedRoot = false) private Path configDir;
@Inject private PluginContainer container;
@Inject private Metrics.Factory metricsFactory;
@Inject private Logger logger;
/**
* Enables the plugin.
@@ -38,6 +42,13 @@ public class Sponge8TAB {
*/
@Listener
public void onServerStart(@Nullable StartingEngineEvent<Server> event) {
ProtocolVersion serverVersion = ProtocolVersion.fromNetworkId(Sponge.platform().minecraftVersion().protocolVersion());
if (serverVersion.getNetworkId() < ProtocolVersion.V1_20_6.getNetworkId()) {
logger.warn("====================================================================================================");
logger.warn("This plugin version was made for Minecraft version 1.20.6 and above.");
logger.warn("====================================================================================================");
return;
}
TAB.create(new SpongePlatform(this));
}
@@ -60,6 +71,6 @@ public class Sponge8TAB {
*/
@Listener
public void onServerStop(@Nullable StoppingEngineEvent<Server> event) {
TAB.getInstance().unload();
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
@@ -22,7 +22,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
/**
* Command processor for plugin's command for Sponge 8.
* Command processor for plugin's command for Sponge.
*/
public class SpongeTabCommand implements Command.Raw {
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
@@ -15,7 +15,7 @@ import java.util.List;
import java.util.UUID;
/**
* TabList implementation for Sponge 8 and up
* TabList implementation for Sponge.
*/
public class SpongeTabList extends TrackedTabList<SpongeTabPlayer> {
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8;
package me.neznamy.tab.platforms.sponge;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
@@ -10,11 +10,12 @@ import org.spongepowered.api.effect.potion.PotionEffect;
import org.spongepowered.api.effect.potion.PotionEffectTypes;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.network.ServerConnectionState;
import java.util.Collections;
/**
* TabPlayer implementation for Sponge 8+.
* TabPlayer implementation for Sponge.
*/
public class SpongeTabPlayer extends BackendTabPlayer {
@@ -38,7 +39,7 @@ public class SpongeTabPlayer extends BackendTabPlayer {
@Override
@SneakyThrows
public int getPing() {
return SpongeMultiVersion.getPing.apply(getPlayer());
return getPlayer().connection().state().map(c -> ((ServerConnectionState.Game)c).latency()).orElse(0);
}
@Override
-4
View File
@@ -1,4 +0,0 @@
dependencies {
implementation(projects.shared)
compileOnly("org.spongepowered:spongeapi:5.0.0")
}
@@ -1,55 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import com.google.inject.Inject;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.event.game.state.GameStoppedServerEvent;
import org.spongepowered.api.plugin.Plugin;
import java.io.File;
/**
* Main class for Sponge 7.
*/
@Plugin(
id = TabConstants.PLUGIN_ID,
name = TabConstants.PLUGIN_NAME,
version = TabConstants.PLUGIN_VERSION,
description = TabConstants.PLUGIN_DESCRIPTION,
url = TabConstants.PLUGIN_WEBSITE,
authors = TabConstants.PLUGIN_AUTHOR
)
@Getter
public class Sponge7TAB {
@Inject @ConfigDir(sharedRoot = false) private File configDir;
@Inject private Logger logger;
/**
* Enables the plugin.
*
* @param event
* Server start event
*/
@Listener
public void onServerStart(@Nullable GameStartedServerEvent event) {
TAB.create(new SpongePlatform(this));
}
/**
* Disables the plugin.
*
* @param event
* Server stop event
*/
@Listener
public void onServerStop(@Nullable GameStoppedServerEvent event) {
TAB.getInstance().unload();
}
}
@@ -1,81 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.boss.*;
/**
* BossBar implementation for Sponge 7 using its API.
*/
@RequiredArgsConstructor
public class SpongeBossBar extends SafeBossBar<ServerBossBar> {
/** Color array for fast access */
private static final BossBarColor[] colors = {
BossBarColors.PINK,
BossBarColors.BLUE,
BossBarColors.RED,
BossBarColors.GREEN,
BossBarColors.YELLOW,
BossBarColors.PURPLE,
BossBarColors.WHITE
};
/** Style array for fast access */
private static final BossBarOverlay[] styles = {
BossBarOverlays.PROGRESS,
BossBarOverlays.NOTCHED_6,
BossBarOverlays.NOTCHED_10,
BossBarOverlays.NOTCHED_12,
BossBarOverlays.NOTCHED_20
};
/** Player to send boss bars to */
@NotNull
private final SpongeTabPlayer player;
@Override
@NotNull
public ServerBossBar constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
return ServerBossBar.builder()
.name(title.convert())
.color(colors[color.ordinal()])
.overlay(styles[style.ordinal()])
.percent(progress)
.build();
}
@Override
public void create(@NotNull BossBarInfo bar) {
bar.getBossBar().addPlayer(player.getPlayer());
}
@Override
public void updateTitle(@NotNull BossBarInfo bar) {
bar.getBossBar().setName(bar.getTitle().convert());
}
@Override
public void updateProgress(@NotNull BossBarInfo bar) {
bar.getBossBar().setPercent(bar.getProgress());
}
@Override
public void updateStyle(@NotNull BossBarInfo bar) {
bar.getBossBar().setOverlay(styles[bar.getStyle().ordinal()]);
}
@Override
public void updateColor(@NotNull BossBarInfo bar) {
bar.getBossBar().setColor(colors[bar.getColor().ordinal()]);
}
@Override
public void remove(@NotNull BossBarInfo bar) {
bar.getBossBar().removePlayer(player.getPlayer());
}
}
@@ -1,85 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.command.SendCommandEvent;
import org.spongepowered.api.event.entity.MoveEntityEvent;
import org.spongepowered.api.event.entity.living.humanoid.player.RespawnPlayerEvent;
import org.spongepowered.api.event.filter.cause.First;
import org.spongepowered.api.event.network.ClientConnectionEvent;
/**
* Event listener for Sponge 7 to forward events to enabled features.
*/
public class SpongeEventListener implements EventListener<Player> {
/**
* Listens to player quit event.
*
* @param event
* Disconnect event
*/
@Listener
public void onQuit(ClientConnectionEvent.Disconnect event) {
quit(event.getTargetEntity().getUniqueId());
}
/**
* Listens to player join event.
*
* @param event
* Join event
*/
@Listener
public void onJoin(ClientConnectionEvent.Join event) {
join(event.getTargetEntity());
}
/**
* Listens to world change event.
*
* @param event
* World change event
* @param player
* Player who changed world
*/
@Listener
public void onWorldChange(MoveEntityEvent.Teleport event, @First Player player) {
worldChange(event.getTargetEntity().getUniqueId(), event.getTargetEntity().getWorld().getName());
}
/**
* Listens to command event to potentially cancel it.
*
* @param event
* Command event
* @param player
* Player who executed the command
*/
@Listener
public void onCommand(SendCommandEvent event, @First Player player) {
if (command(player.getUniqueId(), event.getCommand())) event.setCancelled(true);
}
/**
* Listens to player respawn event.
*
* @param event
* Player respawn event
*/
@Listener(order = Order.PRE)
public void onRespawn(RespawnPlayerEvent event) {
replacePlayer(event.getTargetEntity().getUniqueId(), event.getTargetEntity());
}
@Override
@NotNull
public TabPlayer createPlayer(@NotNull Player player) {
return new SpongeTabPlayer((SpongePlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -1,151 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.text.Text;
import java.io.File;
/**
* Platform implementation for Sponge 7 and lower
*/
@RequiredArgsConstructor
public class SpongePlatform implements BackendPlatform {
/** Plugin reference */
@NotNull
private final Sponge7TAB plugin;
/** Server version */
@Getter
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Sponge.getGame().getPlatform().getMinecraftVersion().getName());
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
registerDummyPlaceholder(identifier);
}
@Override
public void loadPlayers() {
for (Player player : Sponge.getServer().getOnlinePlayers()) {
TAB.getInstance().addPlayer(new SpongeTabPlayer(this, player));
}
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return null;
}
@Override
@NotNull
public TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
@Nullable
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
return null;
}
@Override
public void logInfo(@NotNull TabComponent message) {
plugin.getLogger().info(message.toLegacyText());
}
@Override
public void logWarn(@NotNull TabComponent message) {
plugin.getLogger().warn("§c" + message.toLegacyText());
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Sponge] " + Sponge.getPlatform().getMinecraftVersion().getName();
}
@Override
public void registerListener() {
Sponge.getGame().getEventManager().registerListeners(plugin, new SpongeEventListener());
}
@Override
public void registerCommand() {
SpongeTabCommand cmd = new SpongeTabCommand();
Sponge.getGame().getCommandManager().register(plugin, CommandSpec.builder()
.arguments(cmd, GenericArguments.remainingJoinedStrings(Text.of("arguments"))) // GenericArguments.none() doesn't work, so rip no-arg
.executor(cmd)
.build(), getCommand());
}
@Override
public void startMetrics() {
// Not available
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getConfigDir();
}
@Override
@NotNull
public Text convertComponent(@NotNull TabComponent component) {
return Text.of(component.toLegacyText());
}
@Override
@NotNull
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
return new SpongeScoreboard((SpongeTabPlayer) player);
}
@Override
@NotNull
public BossBar createBossBar(@NotNull TabPlayer player) {
return new SpongeBossBar((SpongeTabPlayer) player);
}
@Override
@NotNull
public TabList createTabList(@NotNull TabPlayer player) {
return new SpongeTabList((SpongeTabPlayer) player);
}
@Override
public boolean supportsScoreboards() {
return true;
}
@Override
public double getTPS() {
return Sponge.getServer().getTicksPerSecond();
}
@Override
public double getMSPT() {
return -1; // Not available
}
}
@@ -1,143 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import lombok.NonNull;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.scoreboard.CollisionRules;
import org.spongepowered.api.scoreboard.Visibilities;
import org.spongepowered.api.scoreboard.Visibility;
import org.spongepowered.api.scoreboard.critieria.Criteria;
import org.spongepowered.api.scoreboard.displayslot.DisplaySlots;
import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayMode;
import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayModes;
import org.spongepowered.api.text.Text;
/**
* Scoreboard implementation for Sponge 7 using its API.
*/
public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
/** Collision rule array for fast access */
private static final org.spongepowered.api.scoreboard.CollisionRule[] collisionRules = {
CollisionRules.ALWAYS,
CollisionRules.NEVER,
CollisionRules.PUSH_OTHER_TEAMS,
CollisionRules.PUSH_OWN_TEAM
};
/** Visibility array for fast access */
private static final Visibility[] visibilities = {
Visibilities.ALWAYS,
Visibilities.NEVER,
Visibilities.HIDE_FOR_OTHER_TEAMS,
Visibilities.HIDE_FOR_OWN_TEAM
};
/** DisplaySlot array for fast access */
private static final org.spongepowered.api.scoreboard.displayslot.DisplaySlot[] displaySlots = {
DisplaySlots.LIST,
DisplaySlots.SIDEBAR,
DisplaySlots.BELOW_NAME
};
/** Health display array for fast access */
private static final ObjectiveDisplayMode[] healthDisplays = {
ObjectiveDisplayModes.INTEGER,
ObjectiveDisplayModes.HEARTS
};
/** Scoreboard of the player */
private final org.spongepowered.api.scoreboard.Scoreboard sb = org.spongepowered.api.scoreboard.Scoreboard.builder().build();
/**
* Constructs new instance and puts player into new scoreboard.
*
* @param player
* Player this scoreboard will belong to
*/
public SpongeScoreboard(@NonNull SpongeTabPlayer player) {
super(player);
// Make sure each player is in a different scoreboard for per-player view
player.getPlayer().setScoreboard(sb);
}
@Override
public void registerObjective(@NonNull Objective objective) {
org.spongepowered.api.scoreboard.objective.Objective obj = org.spongepowered.api.scoreboard.objective.Objective.builder()
.name(objective.getName())
.displayName(Text.of(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13)))
.objectiveDisplayMode(healthDisplays[objective.getHealthDisplay().ordinal()])
.criterion(Criteria.DUMMY)
.build();
sb.addObjective(obj);
sb.updateDisplaySlot(obj, displaySlots[objective.getDisplaySlot().ordinal()]);
objective.setPlatformObjective(obj);
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sb.removeObjective(((org.spongepowered.api.scoreboard.objective.Objective)objective.getPlatformObjective()));
}
@Override
public void updateObjective(@NonNull Objective objective) {
org.spongepowered.api.scoreboard.objective.Objective obj = ((org.spongepowered.api.scoreboard.objective.Objective)objective.getPlatformObjective());
obj.setDisplayName(Text.of(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13)));
obj.setDisplayMode(healthDisplays[objective.getHealthDisplay().ordinal()]);
}
@Override
public void setScore(@NonNull Score score) {
((org.spongepowered.api.scoreboard.objective.Objective)score.getObjective().getPlatformObjective())
.getOrCreateScore(Text.of(score.getHolder())).setScore(score.getValue());
}
@Override
public void removeScore(@NonNull Score score) {
((org.spongepowered.api.scoreboard.objective.Objective)score.getObjective().getPlatformObjective()).removeScore(Text.of(score.getHolder()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new Object(); // This implementation does not need teams tracked
}
@Override
public void registerTeam(@NonNull Team team) {
org.spongepowered.api.scoreboard.Team spongeTeam = org.spongepowered.api.scoreboard.Team.builder()
.name(team.getName())
.displayName(Text.of(team.getName()))
.prefix(Text.of(cutTo(team.getPrefix().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13)))
.suffix(Text.of(cutTo(team.getSuffix().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13)))
.allowFriendlyFire((team.getOptions() & 0x01) != 0)
.canSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0)
.collisionRule(collisionRules[team.getCollision().ordinal()])
.nameTagVisibility(visibilities[team.getVisibility().ordinal()])
.build();
for (String member : team.getPlayers()) {
spongeTeam.addMember(Text.of(member));
}
sb.registerTeam(spongeTeam);
team.setPlatformTeam(sb);
}
@Override
public void unregisterTeam(@NonNull Team team) {
((org.spongepowered.api.scoreboard.Team)team.getPlatformTeam()).unregister();
}
@Override
public void updateTeam(@NonNull Team team) {
org.spongepowered.api.scoreboard.Team spongeTeam = (org.spongepowered.api.scoreboard.Team) team.getPlatformTeam();
spongeTeam.setDisplayName(Text.of(team.getName()));
spongeTeam.setPrefix(Text.of(cutTo(team.getPrefix().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13)));
spongeTeam.setSuffix(Text.of(cutTo(team.getSuffix().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13)));
spongeTeam.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
spongeTeam.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
spongeTeam.setCollisionRule(collisionRules[team.getCollision().ordinal()]);
spongeTeam.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
}
}
@@ -1,74 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandArgs;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.args.CommandElement;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.serializer.TextSerializers;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Command processor for plugin's command for Sponge 7.
*/
public class SpongeTabCommand extends CommandElement implements CommandExecutor {
protected SpongeTabCommand() {
super(null);
}
@NotNull
public CommandResult execute(@NotNull CommandSource source, @NotNull CommandContext context) {
String[] args = context.<String>getOne(Text.of("arguments")).orElse("").split(" ");
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = source.hasPermission(TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = source.hasPermission(TabConstants.Permission.COMMAND_ALL);
List<String> messages = TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission);
for (String message : messages) {
source.sendMessage(TextSerializers.FORMATTING_CODE.deserialize(message));
}
return CommandResult.success();
}
TabPlayer player = null;
if (source instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) source).getUniqueId());
if (player == null) return CommandResult.success(); // Player not loaded correctly
}
TAB.getInstance().getCommand().execute(player, args);
return CommandResult.success();
}
@Override
@Nullable
protected Object parseValue(@NotNull CommandSource source, @NotNull CommandArgs args) {
return null;
}
@Override
@NotNull
public List<String> complete(@NotNull CommandSource source, @NotNull CommandArgs commandArgs, @NotNull CommandContext context) {
TabPlayer player = null;
if (source instanceof Player) {
player = TAB.getInstance().getPlayer(((Player)source).getUniqueId());
if (player == null) return Collections.emptyList(); // Player not loaded correctly
}
String[] args = commandArgs.getRaw().split(" ");
if (commandArgs.getRaw().endsWith(" ")) {
args = Arrays.copyOf(args, args.length+1);
args[args.length-1] = "";
}
return TAB.getInstance().getCommand().complete(player, args);
}
}
@@ -1,115 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import org.spongepowered.api.entity.living.player.tab.TabListEntry;
import org.spongepowered.api.profile.GameProfile;
import org.spongepowered.api.profile.property.ProfileProperty;
import java.util.Collection;
import java.util.UUID;
/**
* TabList implementation for Sponge 7 and lower
*/
public class SpongeTabList extends TrackedTabList<SpongeTabPlayer> {
/** Gamemode array for fast access */
private static final GameMode[] gameModes = {
GameModes.SURVIVAL, GameModes.CREATIVE, GameModes.ADVENTURE, GameModes.SPECTATOR
};
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public SpongeTabList(@NotNull SpongeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
player.getPlayer().getTabList().removeEntry(entry);
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
player.getPlayer().getTabList().getEntry(entry).ifPresent(e -> e.setDisplayName(displayName == null ? null : displayName.convert()));
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
player.getPlayer().getTabList().getEntry(entry).ifPresent(e -> e.setLatency(latency));
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
player.getPlayer().getTabList().getEntry(entry).ifPresent(e -> e.setGameMode(gameModes[gameMode]));
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
// Added in 1.21.2
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.21.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
GameProfile profile = GameProfile.of(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getPropertyMap().put(TEXTURES_PROPERTY, ProfileProperty.of(
TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
TabListEntry tabListEntry = TabListEntry.builder()
.list(player.getPlayer().getTabList())
.profile(profile)
.latency(entry.getLatency())
.gameMode(gameModes[entry.getGameMode()])
.displayName(entry.getDisplayName() == null ? null : entry.getDisplayName().convert())
.build();
player.getPlayer().getTabList().addEntry(tabListEntry);
}
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
player.getPlayer().getTabList().setHeaderAndFooter(header.convert(), footer.convert());
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return player.getPlayer().getTabList().getEntry(entry).isPresent();
}
@Override
@Nullable
public Skin getSkin() {
Collection<ProfileProperty> properties = player.getPlayer().getProfile().getPropertyMap().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
ProfileProperty property = properties.iterator().next();
return new Skin(property.getValue(), property.getSignature().orElse(null));
}
@Override
public void checkDisplayNames() {
for (TabListEntry entry : player.getPlayer().getTabList().getEntries()) {
TabComponent expectedComponent = getExpectedDisplayNames().get(entry.getProfile().getUniqueId());
if (expectedComponent != null && entry.getDisplayName().orElse(null) != expectedComponent.convert()) {
entry.setDisplayName(expectedComponent.convert());
}
}
}
}
@@ -1,93 +0,0 @@
package me.neznamy.tab.platforms.sponge7;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.data.manipulator.mutable.PotionEffectData;
import org.spongepowered.api.effect.potion.PotionEffectTypes;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import org.spongepowered.api.text.Text;
/**
* TabPlayer implementation for Sponge 7.
*/
public class SpongeTabPlayer extends BackendTabPlayer {
/**
* Constructs new instance with given parameters.
*
* @param platform
* Server platform
* @param player
* Platform's player object
*/
public SpongeTabPlayer(@NotNull SpongePlatform platform, @NotNull Player player) {
super(platform, player, player.getUniqueId(), player.getName(), player.getWorld().getName(), platform.getServerVersion().getNetworkId());
}
@Override
public boolean hasPermission(@NotNull String permission) {
return getPlayer().hasPermission(permission);
}
@Override
public int getPing() {
return getPlayer().getConnection().getLatency();
}
@Override
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendMessage((Text) message.convert());
}
@Override
public boolean hasInvisibilityPotion() {
PotionEffectData potionEffects = getPlayer().get(PotionEffectData.class).orElse(null);
if (potionEffects == null) return false;
return potionEffects.asList().stream().anyMatch(effect -> effect.getType().equals(PotionEffectTypes.INVISIBILITY));
}
@Override
public boolean isDisguised() {
return false;
}
@Override
@NotNull
public Player getPlayer() {
return (Player) player;
}
@Override
public SpongePlatform getPlatform() {
return (SpongePlatform) platform;
}
@Override
public boolean isVanished0() {
return getPlayer().get(Keys.VANISH).orElse(false);
}
@Override
public int getGamemode() {
GameMode gameMode = getPlayer().getGameModeData().type().get();
if (gameMode.equals(GameModes.CREATIVE)) return 1;
if (gameMode.equals(GameModes.ADVENTURE)) return 2;
if (gameMode.equals(GameModes.SPECTATOR)) return 3;
return 0;
}
@Override
public double getHealth() {
return getPlayer().health().get();
}
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayNameData().displayName().get().toPlain();
}
}
@@ -1,78 +0,0 @@
package me.neznamy.tab.platforms.sponge8;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.function.ToIntFunction;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
import org.spongepowered.api.profile.GameProfile;
import org.spongepowered.api.scoreboard.Score;
import org.spongepowered.api.scoreboard.objective.Objective;
import java.util.Optional;
import java.util.UUID;
/**
* Class managing code that varies based on server version.
*/
@SuppressWarnings("unchecked")
public class SpongeMultiVersion {
/** Function for getting score from an objective */
private static final BiFunctionWithException<Objective, String, Score> findOrCreateScore;
/** Function for getting player's ping*/
public static final ToIntFunction<ServerPlayer> getPing;
/** Function for getting UUID from disconnect event */
public static final FunctionWithException<ServerSideConnectionEvent.Disconnect, UUID> getUniqueId;
static {
if (ReflectionUtils.methodExists(Objective.class, "findOrCreateScore", String.class)) {
// Sponge 11+ (1.20.4+)
findOrCreateScore = (objective, holder) -> (Score) Objective.class.getMethod("findOrCreateScore", String.class).invoke(objective, holder);
} else {
// Sponge 8 - 10 (and early 11) (1.16.5 - 1.20.2)
findOrCreateScore = (objective, holder) -> objective.findOrCreateScore(Component.text(holder));
}
if (!ReflectionUtils.classExists("org.spongepowered.api.network.ServerPlayerConnection")) {
// Sponge 11+ (1.20.6+)
getPing = player -> {
Object connection = player.getClass().getMethod("connection").invoke(player);
Optional<Object> state = (Optional<Object>) connection.getClass().getMethod("state").invoke(connection);
return (int) state.get().getClass().getMethod("latency").invoke(state.get());
};
} else {
// Sponge 8 - 10 (and early 11) (1.16.5 - 1.20.4)
getPing = player -> player.connection().latency();
}
if (!ReflectionUtils.methodExists(ServerSideConnectionEvent.Disconnect.class, "player")) {
// Sponge 11+ (1.20.6+)
getUniqueId = event -> ((Optional<GameProfile>) ReflectionUtils.setAccessible(event.getClass().getMethod("profile")).invoke(event)).get().uuid();
} else {
// Sponge 8 - 10 (and early 11) (1.16.5 - 1.20.4)
getUniqueId = event -> event.player().uniqueId();
}
}
/**
* Returns score of holder in given objective. If it does not exist, it is created.
*
* @param objective
* Objective to get score from
* @param holder
* Score holder
* @return Score of given holder in objective
*/
@SneakyThrows
@NotNull
public static Score findOrCreateScore(@NotNull Objective objective, @NotNull String holder) {
return findOrCreateScore.apply(objective, holder);
}
}
@@ -41,7 +41,6 @@ public class VelocityScoreboard extends SafeScoreboard<VelocityTabPlayer> {
public void registerObjective(@NonNull Objective objective) {
try {
ProxyObjective.Builder builder = scoreboard.objectiveBuilder(objective.getName())
.displaySlot(com.velocitypowered.api.scoreboard.DisplaySlot.valueOf(objective.getDisplaySlot().name()))
.healthDisplay(com.velocitypowered.api.scoreboard.HealthDisplay.valueOf(objective.getHealthDisplay().name()))
.title(objective.getTitle().toTextHolder(textHolderFunction))
.numberFormat(objective.getNumberFormat() == null ? null : NumberFormat.fixed(objective.getNumberFormat().toAdventure()));
@@ -51,6 +50,11 @@ public class VelocityScoreboard extends SafeScoreboard<VelocityTabPlayer> {
}
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
((ProxyObjective)objective.getPlatformObjective()).setDisplaySlot(com.velocitypowered.api.scoreboard.DisplaySlot.valueOf(objective.getDisplaySlot().name()));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
try {