Compare commits

...
146 Commits
Author SHA1 Message Date
NEZNAMY 590286a6c2 4.0.5 2023-09-27 21:12:58 +02:00
NEZNAMY 6c96650dcc Add support for new Spigot build that uses mojang mappings for fields and methods 2023-09-27 21:12:22 +02:00
NEZNAMY 7c83a74fd8 Fix plugin trying to send scoreboard packets to players who left already 2023-09-27 19:20:32 +02:00
NEZNAMY 7b3f60cb3b 4.0.5-SNAPSHOT 2023-09-23 16:37:02 +02:00
NEZNAMY bfc845088a Merge pull request #1084 from GsTio86/master
Fix Arclight compatibility
2023-09-23 12:36:32 +02:00
Galen Huang e67d93a592 Update DataWatcher.java 2023-09-23 16:58:19 +08:00
gstio86 b518b980ae Fix Arclight compatibility 2023-09-23 14:35:06 +08:00
NEZNAMY 744bc1403a Remove tps placeholder from config entirely, the negation was wrong as well 2023-09-22 11:01:35 +02:00
NEZNAMY e11113073f 4.0.4 2023-09-22 09:00:37 +02:00
NEZNAMY 3615818812 Add condition to default config to prevent %tps% being displayed on BungeeCord in scoreboard, as the message is too long 2023-09-22 08:59:31 +02:00
NEZNAMY 0cdef12caf Add support for BungeeCord 1.20.2 2023-09-22 08:56:19 +02:00
NEZNAMY dda8c6c053 Fix another error on Bukkit 1.20.2 2023-09-21 22:21:46 +02:00
NEZNAMY 8c3327f553 Fix error on Bukkit 1.20.2 2023-09-21 20:49:29 +02:00
NEZNAMY c037a14e1f Update Bukkit to 1.20.2 2023-09-21 19:45:06 +02:00
NEZNAMY bd661360ee Delete %server_uptime% from default config as it's a PlaceholderAPI placeholder, which is not always guaranteed to work. (#1077) 2023-09-21 18:33:27 +02:00
NEZNAMY 317fabbc87 Update Fabric to 1.20.2 2023-09-21 18:31:49 +02:00
NEZNAMY 87161446c5 Add 1.20.2 to protocol versions 2023-09-21 18:25:22 +02:00
NEZNAMY 7db8b2e84b Send warn if trying to use parse command from the console with "me" player name argument 2023-09-21 15:04:03 +02:00
NEZNAMY 5f3fa2b317 Layout - Fix ROWS direction type not working properly & avoid real players appearing on top when teams are disabled 2023-09-19 22:14:37 +02:00
NEZNAMY 64c15730a6 Add missing correct Placeholder#update implementations (#1075) 2023-09-17 18:48:41 +02:00
NEZNAMY e5ab46b8ad Initialize scoreboard placeholders to avoid them rarely not parsing due to not being set 2023-09-17 15:01:51 +02:00
NEZNAMY 3060d8cc2b Add override for %player_ping% placeholder because 1.20.2 will have a breaking change to prevent github issues reporting server expansion not supporting new version 2023-09-16 10:30:05 +02:00
NEZNAMY f5d6ba462e Remove unnecessary fully qualified class names 2023-09-15 15:21:39 +02:00
NEZNAMY 81fdeece96 Port new Velocity anti-override to Sponge as well 2023-09-15 14:21:20 +02:00
NEZNAMY 8f60439ef3 Fix multiple sorting types with same priority separated with | not actually working 2023-09-14 16:13:44 +02:00
NEZNAMY c5420ead90 Send gamemode update packet on world switch when spectator fix is enabled as the server may resend gamemode on world switch back to spectator (#1069) 2023-09-13 16:55:21 +02:00
NEZNAMY 99d372d03c Fix refresh interval config conversion ignoring random invalid user-made maps and then causing error on load 2023-09-12 14:32:15 +02:00
NEZNAMY 1219a9822f Remove unused dependency 2023-09-10 10:42:37 +02:00
NEZNAMY d40c182af9 Clean up repositories 2023-09-10 10:39:12 +02:00
NEZNAMY 785e986a72 Fix component cache including child components 2023-09-09 21:04:41 +02:00
NEZNAMY e8b80f455b Replace converter methods with arrays for fast access 2023-09-09 11:33:16 +02:00
NEZNAMY 2d545f1bf1 Fix potential blocking of other plugins in new anti-override for Velocity 2023-09-09 08:22:45 +02:00
NEZNAMY d4713dc7f4 Use direct code for LibsDisguises hook instead of reflection and move it into a separate class 2023-09-08 23:54:21 +02:00
NEZNAMY cd96c13670 Remove unused imports 2023-09-08 23:07:13 +02:00
NEZNAMY 808607faf0 Add try/catch for Fabric loading to send warn on unsupported versions instead of throwing an error (#1068) 2023-09-08 21:30:54 +02:00
NEZNAMY bc403f5c23 Fix ConcurrentModificationException on Velocity 2023-09-08 19:04:00 +02:00
NEZNAMY bfc67e1953 Allow the new anti-override to be disabled with standard settings 2023-09-08 08:21:42 +02:00
NEZNAMY a186c16c34 Implement anti-override for tablist display names on Velocity 2023-09-07 21:37:33 +02:00
NEZNAMY f9be9d4c4b Improve check to reduce errors thrown 2023-09-07 18:09:38 +02:00
NEZNAMY 79fc3ce33f Fix BossBars getting resent when output of placeholder used in display condition changed even if condition result didn't change (#1065) 2023-09-06 21:14:35 +02:00
NEZNAMY 055ff8749e Add support for custom fonts in configuration 2023-09-05 21:38:38 +02:00
NEZNAMY d36410e851 Simplify 2023-09-05 21:32:01 +02:00
NEZNAMY 0fed5dca7f Add font field and conversion to component class and quality of life changes for component conversion 2023-09-05 15:30:42 +02:00
NEZNAMY dd34ffc5b2 Create internal enum for scoreboard display type instead of using a boolean 2023-09-04 21:00:17 +02:00
NEZNAMY 2cd5ac85f9 Simplify 2023-09-04 18:20:17 +02:00
NEZNAMY 571f949f50 Remove unnecessary player version check 2023-09-04 17:58:38 +02:00
NEZNAMY 81e32957b2 Fix startup error 2023-09-04 17:57:20 +02:00
NEZNAMY 4639016ffb Preload NMS GameModes instead of calling Enum#valueOf every time 2023-09-04 17:41:26 +02:00
NEZNAMY dabc2b49dc Remove unused fields and methods 2023-09-03 17:52:29 +02:00
NEZNAMY 2dec389c8c Load NMS method by parameter types instead of name 2023-09-01 11:26:05 +02:00
NEZNAMY bfe5b5960e Fix error in per world playerlist on <1.8 2023-08-31 21:04:36 +02:00
NEZNAMY e352967a17 Improve parse command and fix color-related issues on bukkit 2023-08-31 18:48:04 +02:00
NEZNAMY 930917f285 Fill auto-complete suggestions in plugin's PlaceholderAPI expansion 2023-08-30 21:04:49 +02:00
NEZNAMY b9747f7fbe Fix NPE when user deletes default-refresh-interval option from config (1052) 2023-08-28 13:39:39 +02:00
NEZNAMY 5ac9dcec37 Fix startup on Sponge 7 2023-08-28 13:00:11 +02:00
NEZNAMY 8328b43783 Fix NPE when using relational placeholder with multiple proxies 2023-08-25 22:28:23 +02:00
NEZNAMY 131fcfe144 Remove redundant annotation 2023-08-24 17:36:16 +02:00
NEZNAMY 709cb967b5 Fix plugin sometimes trying to format tablist entries for 1.7 players 2023-08-24 12:16:16 +02:00
NEZNAMY 3370b83087 Fix issue in config conversion 2023-08-24 12:06:04 +02:00
NEZNAMY 5c44bd500c Simplify 2023-08-24 10:05:21 +02:00
NEZNAMY ab8a345944 Small style changes 2023-08-24 09:45:23 +02:00
NEZNAMY 337dd56c87 Simplify 2023-08-24 09:38:54 +02:00
NEZNAMY de78ae30db Copy the previous fix to aligned playerlist as well 2023-08-22 14:29:04 +02:00
NEZNAMY 3f64185389 Fix disable condition not being respected in playerlist in one method (#1037) 2023-08-22 14:23:32 +02:00
NEZNAMY 5077cbbc9a Return empty list instead of new list 2023-08-22 14:01:50 +02:00
NEZNAMY 4d522ba5f5 Improve bossbar command to match scoreboard command features 2023-08-22 13:57:45 +02:00
NEZNAMY 4f1180b3f5 Boost performance of tablist getter on BungeeCord 2023-08-17 17:53:22 +02:00
NEZNAMY 577a3f2551 Add support for Mohist 1.18.2 2023-08-13 17:54:42 +02:00
NEZNAMY 8623ab053e Remove some unnecessary local variables 2023-08-12 20:51:25 +02:00
NEZNAMY 6d0f99eaf1 Change WeakHashMap key to use own player object instead of server's to prevent map getting big when another plugin has a memory leak 2023-08-12 19:04:30 +02:00
NEZNAMY f9490d2652 Move field into local variable 2023-08-12 15:08:15 +02:00
NEZNAMY 9d321e4a31 Remove unused parameter and simplify 2023-08-11 22:12:00 +02:00
NEZNAMY b8c2625b80 Only show placeholder retrieve time message if debug is enabled 2023-08-09 20:22:38 +02:00
NEZNAMY 8e42f328dc Register plugin's PlaceholderAPI expansion automatically without having to enable a config option 2023-08-09 20:13:26 +02:00
NEZNAMY 0c6b4efba5 Simplify 2023-08-09 17:02:25 +02:00
NEZNAMY 0821ed4dae Bukkit - Make NMS loading less dependent on the main NMS class 2023-08-09 17:00:20 +02:00
NEZNAMY 8bc38e25f9 Fix empty line detection sometimes not working 2023-08-07 21:51:43 +02:00
NEZNAMY 7a7a4e6da2 Global playerlist - Do not remove & add self on server switch 2023-08-07 14:04:25 +02:00
NEZNAMY f7d703ed1e Placeholders - Decrease return time warn threshold from 1000ms to 50ms 2023-08-07 13:40:35 +02:00
NEZNAMY 443d7fd082 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-08-07 13:38:47 +02:00
NEZNAMY b6114625c1 Remove duplicate check 2023-08-07 12:18:13 +02:00
NEZNAMY 4a3eb78f12 Add back ping spoof feature 2023-08-06 11:32:02 +02:00
NEZNAMY fa117a671a Simplify placeholder registration 2023-08-05 23:04:36 +02:00
NEZNAMY 6448fb975a Fix scoreboard title limit 2023-08-05 14:41:40 +02:00
NEZNAMY 1c685ce0bb Bukkit - Properly play around Bukkit API limits in scoreboard 2023-08-05 14:36:14 +02:00
NEZNAMY 1662fa8d60 Bukkit - Implement scoreboard handler using Bukkit API (still unused) 2023-08-05 13:23:03 +02:00
NEZNAMY 2787b1735b Header/Footer - Fix being sent on server switch even if disable condition is met & fix being sent twice 2023-08-04 22:07:06 +02:00
NEZNAMY e58390e30b Add some missing NotNull / Nullable annotations 2023-08-04 20:43:58 +02:00
NEZNAMY 41a10a61bf Small refactoring 2023-08-03 22:56:05 +02:00
NEZNAMY fdcc7d5e7c Global Playerlist - Add support for * symbol in server-groups 2023-08-01 13:35:06 +02:00
NEZNAMY 50b39e462f Remove unnecessary full manual import 2023-07-28 23:18:49 +02:00
NEZNAMY 4bc781f0da Refresh placeholders in a different thread, so they can no longer kill the plugin 2023-07-28 22:33:33 +02:00
NEZNAMY aca4c00083 Do not refresh placeholders in PlaceholderManager on join, as it can cause error when updating nested placeholder in a feature that's not loaded yet 2023-07-28 17:17:07 +02:00
NEZNAMY 77b820ad4d Do not spawn armor stands of dead players on join (#1005) 2023-07-28 17:02:42 +02:00
NEZNAMY 328f604236 Add pipeline injection for fabric 2023-07-28 17:02:13 +02:00
NEZNAMY 2f77bf3d10 Create a separate class for CPU report 2023-07-28 13:05:04 +02:00
NEZNAMY 23eb55aa06 Add back support for mojang-mapped server jars 2023-07-26 16:19:21 +02:00
NEZNAMY 32b7356bc0 Send console hint if both layout and prevent-spectator-effect are enabled 2023-07-25 18:22:34 +02:00
NEZNAMY 6091641ed9 Add warn if both layout and yellow-number features are enabled 2023-07-25 18:16:01 +02:00
NEZNAMY e368213d18 Add misconfiguration check for scoreboard chaining 2023-07-25 15:17:41 +02:00
NEZNAMY 58f9bb6fca Add unlimited nametag mode to Fabric (with a few missing things) 2023-07-24 22:26:24 +02:00
NEZNAMY 79798199b8 More abstraction of unlimited nametag mode 2023-07-24 20:53:14 +02:00
NEZNAMY c8d20f8479 Move NMS loading of classes 2023-07-24 17:59:17 +02:00
NEZNAMY c0498a60b4 Remove some console messages on startup 2023-07-24 16:46:00 +02:00
NEZNAMY faa01ddfc2 Add back compatibility attempt for nick plugins (#974) 2023-07-23 12:47:16 +02:00
NEZNAMY a2f628e267 Add try/catch for PremiumVanish hook due to its bug (#993) 2023-07-23 11:54:04 +02:00
NEZNAMY 66007b9121 Decrease component cache size to 1000 entries 2023-07-22 01:25:28 +02:00
NEZNAMY 9e0056b603 Improve source code parity between platforms (Sponge 7, Sponge 8 and Fabric) and improvements to Fabric 2023-07-22 01:22:13 +02:00
NEZNAMY b15cc3a6af Improve source code parity between platforms (BungeeCord and Velocity) 2023-07-21 22:33:55 +02:00
NEZNAMY 5393f869e7 Remove option to disable global playerlist latency update. The performance drop is almost zero and updating is actually needed as initial ping value can be invalid ((#990) 2023-07-20 18:05:39 +02:00
NEZNAMY f8088402e8 Make it more obvious that %tps% is a backend-only placeholder (#990) 2023-07-20 17:48:27 +02:00
NEZNAMY 979ff7d2b8 Move %mspt% placeholder registration into backend package 2023-07-19 23:29:07 +02:00
NEZNAMY bdbde58f42 Drop krypton platform as author abandoned it 2023-07-19 21:33:07 +02:00
NEZNAMY 1e26e0bd13 Change backend placeholder registration method 2023-07-19 19:34:36 +02:00
NEZNAMY be3c00d17e Fix colors in error messages 2023-07-16 12:53:46 +02:00
NEZNAMY d542bb67de Add a console warn if anti-override is disabled 2023-07-16 12:09:16 +02:00
NEZNAMY a79cdfaa1c Add a console warn if placeholder used in numeric sorting did not return a valid number 2023-07-16 11:46:07 +02:00
NEZNAMY 42db715421 Fix NPE 2023-07-15 21:16:10 +02:00
NEZNAMY 2228d5a35d Small cleanup 2023-07-15 21:08:18 +02:00
NEZNAMY c434d0eafd Remove component cache from Sponge 7 2023-07-15 21:01:27 +02:00
NEZNAMY 414a3a971f Optimize placeholder high_to_low and low_to_high to only take 3 characters instead of 10 and slightly increase range as well 2023-07-15 18:32:58 +02:00
NEZNAMY b8a22a2561 Fix layout fixed slot updating issue when switching between layouts 2023-07-15 15:06:25 +02:00
NEZNAMY bc1dc2f84f Fix layout skins not working on sponge 8 2023-07-15 14:24:01 +02:00
NEZNAMY 904c4f27aa Add check if a placeholder's refresh interval is configured same as default interval 2023-07-14 16:28:21 +02:00
NEZNAMY b19e5435ef Add check for redundant per-server / per-world overrides for header/footer 2023-07-14 15:45:18 +02:00
NEZNAMY 78fbcd829a Construct player object async 2023-07-14 14:22:56 +02:00
NEZNAMY a255844a92 Create separate methods for logging info/warn and use them 2023-07-14 14:19:22 +02:00
NEZNAMY c7a548c575 Fix placeholders in tablist formatting getting stuck when on bungeecord and joining into disabled server 2023-07-14 11:50:18 +02:00
NEZNAMY 90a4bab114 Fix global playerlist formatting tablist names for players meeting disable condition 2023-07-13 23:47:28 +02:00
NEZNAMY a1633d146a Fix NPE when using relational placeholders on bungeecord 2023-07-12 22:42:21 +02:00
NEZNAMY 87bf4b03df Add misconfiguration checks for bossbar properties 2023-07-12 17:58:17 +02:00
NEZNAMY 7731aea0d6 Create %player_ping% placeholder on bukkit to override the PAPI one and avoid issues like #972 when ping field changes 2023-07-10 21:37:33 +02:00
NEZNAMY e66350111e Make detectPlaceholders method 7x faster when text only consists of 1 placeholder 2023-07-09 22:27:13 +02:00
NEZNAMY 7e5df68626 Round animation change interval instead of decreasing it when invalid value is used 2023-07-09 21:25:10 +02:00
NEZNAMY 8e3e7de4ee Send warn if a placeholder took longer than 1000ms to return value 2023-07-09 18:35:03 +02:00
NEZNAMY 0ee535fbd5 Small code style improvements from PR 2023-07-09 10:45:05 +02:00
NEZNAMY 1b471d266c Merge pull request #970 from TFSMads/master
Added support for different default skins depending on the slot index in the layout feature
2023-07-09 10:38:44 +02:00
NEZNAMY 19a4f6ea1f Merge pull request #971 from misteeka/master
@NotNull marked function actually can return null
2023-07-08 19:10:36 +02:00
misteeka 7cc8d71224 Fix typo 2023-07-08 19:42:30 +03:00
_Mads b843475a0a Remove unnecessary imports and debug message 2023-07-08 15:18:05 +02:00
_Mads 027c0fa771 Added support for different default skins depending on the slot index 2023-07-08 15:09:50 +02:00
NEZNAMY c08e76f110 Fix startup error when refresh intervals are misconfigured (#969) 2023-07-08 12:46:39 +02:00
NEZNAMY b837fe7d15 4.0.4-SNAPSHOT 2023-07-08 12:34:21 +02:00
NEZNAMY d4d1bed408 4.0.3 2023-07-07 22:51:41 +02:00
NEZNAMY 8870295d54 Fix compatibility with bukkit/forge hybrids if they use different mappings 2023-07-07 22:50:32 +02:00
NEZNAMY 79cd6e4ae2 Add null check for messages form bridge (for future updates to avoid NPE / protocol break) 2023-07-07 17:36:44 +02:00
207 changed files with 5821 additions and 4008 deletions

No files matched your search

@@ -193,6 +193,20 @@ public interface BossBar {
*/
@NotNull List<TabPlayer> getPlayers();
/**
* Returns {@code true} if this bossbar is visible to specified player based on
* display condition or adding via {@link #addPlayer(TabPlayer)}, {@code false} if not.
* <p>
* This may return {@code true} even if player cannot physically see the bossbar due to
* having it disabled via toggle command or {@link BossBarManager#toggleBossBar(TabPlayer, boolean)}
* (or {@link BossBarManager#setBossBarVisible(TabPlayer, boolean, boolean)}).
*
* @param player
* Player to check
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
*/
boolean containsPlayer(TabPlayer player);
/**
* Returns {@code true} if this boss bar is configured as announcement-only bar,
* {@code false} if not.
@@ -6,6 +6,7 @@ import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface allowing work with scoreboards, such as creating,
@@ -164,5 +165,5 @@ public interface ScoreboardManager {
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
@NotNull Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
}
+1 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.3-SNAPSHOT"
version = "4.0.5"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -15,7 +15,6 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.krypton,
projects.velocity,
projects.sponge7,
projects.sponge8,
+6 -1
View File
@@ -4,5 +4,10 @@ dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.2")
compileOnly("com.mojang:authlib:1.5.25")
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
exclude("org.bukkit", "bukkit")
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Player;
@@ -10,6 +12,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
/**
* The core for bukkit forwarding events into all enabled features
@@ -37,7 +40,8 @@ public class BukkitEventListener extends EventListener<Player> implements Listen
}
@Override
public TabPlayer createPlayer(Player player) {
return new BukkitTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull Player player) {
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -3,17 +3,19 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
@@ -31,6 +33,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
@@ -45,8 +48,13 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@@ -75,13 +83,12 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
return BukkitTabList.PlayerInfoClass.isInstance(packet);
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
@@ -92,27 +99,42 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_getProfile.invoke(nmsData);
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id;
if (BukkitReflection.is1_19_3Plus()) {
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
} else {
id = profile.getId();
}
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayName = nms.toNMSComponent(newDisplayName, receiver.getVersion());
if (!nms.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = TAB.getInstance().getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (nms.is1_19_3Plus()) {
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
if (BukkitReflection.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
profile.getId(),
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
if (nms.is1_19_3Plus()) {
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@@ -130,7 +152,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = TAB.getInstance().getPlayer(entry);
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
@@ -1,82 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Bukkit registry to register bukkit-only and universal placeholders
*/
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
private Chat chat;
/** NMS server to get TPS from on spigot */
private Object server;
/** TPS field*/
private Field recentTps;
/** Detection for presence of Paper's TPS getter */
private Method paperTps;
/** Detection for presence of Paper's MSPT getter */
private Method paperMspt;
/**
* Constructs new instance and loads hooks
*/
public BukkitPlaceholderRegistry() {
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
} else if (recentTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
try {
return formatTPS(((double[]) recentTps.get(server))[0]);
} catch (IllegalAccessException e) {
return -1;
}
});
} else {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
}
if (paperMspt != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
}
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
if (chat != null) {
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
}
super.registerPlaceholders(manager);
}
}
@@ -1,51 +1,28 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Main class for Bukkit platform
* Main class for Bukkit.
*/
public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable() {
String version = Bukkit.getBukkitVersion().split("-")[0];
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
if (!isVersionSupported()) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
BukkitPlatform platform = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this);
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), getDataFolder()));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> platform.isPlaceholderAPI() ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
PluginCommand cmd = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (cmd == null) return;
BukkitTabCommand command = new BukkitTabCommand();
cmd.setExecutor(command);
cmd.setTabCompleter(command);
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this));
}
@Override
@@ -63,11 +40,7 @@ public class BukkitTAB extends JavaPlugin {
*/
private boolean isVersionSupported() {
try {
long time = System.currentTimeMillis();
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
NMSStorage.setInstance(minorVersion >= 17 ? new BukkitModernNMSStorage() : new BukkitLegacyNMSStorage());
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
NMSStorage.setInstance(new NMSStorage());
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
@@ -11,7 +11,7 @@ import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -20,7 +20,7 @@ import java.util.List;
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
@@ -37,11 +37,12 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
@NotNull
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
@@ -2,12 +2,13 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -15,7 +16,6 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
@@ -33,169 +33,224 @@ import java.util.*;
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PacketPlayOutPlayerListHeaderFooterClass;
public static Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
public static Field HEADER;
public static Field FOOTER;
private static Class<?> HeaderFooterClass;
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
public static Class<?> PacketPlayOutPlayerInfoClass;
public static Constructor<?> newPacketPlayOutPlayerInfo;
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
private static Class<Enum> ActionClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> RemoteChatSession$Data;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
public static Class<?> PlayerInfoDataClass;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
public static Field PlayerInfoData_UUID;
public static Field PlayerInfoData_Profile;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
private static Object[] gameModes;
@Getter
private static boolean available;
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported (yet?)
// Classes
Class<?> playerInfoDataClass;
Class<?> IChatBaseComponent;
Class<Enum> EnumGamemodeClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
if (nms.is1_19_3Plus()) {
// Header & Footer
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newHeaderFooter = HeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
}
// Info packet
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(playerInfoDataClass);
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
if (BukkitReflection.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumSet.class);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
} else {
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumPlayerInfoActionClass);
}
PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoDataClass);
PlayerInfoData_getProfile = ReflectionUtils.getOnlyMethod(PlayerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(PlayerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoDataClass, nms.IChatBaseComponent);
if (nms.is1_19_3Plus()) {
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(PlayerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(PlayerInfoDataClass, RemoteChatSession$Data);
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
}
gameModes = new Object[] {
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
available = true;
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (!available) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
} else {
//1.19.2-
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Entry.Builder(entry).build(), player.getVersion()));
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME,
new Entry.Builder(entry).displayName(displayName).build(), player.getVersion())
);
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
);
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
);
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(createPacket(Action.ADD_PLAYER, entry, player.getVersion()));
if (!available) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (PacketPlayOutPlayerListHeaderFooterClass == null) return;
NMSStorage nms = NMSStorage.getInstance();
Object packet;
if (nms.getMinorVersion() >= 17) {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance(
nms.toNMSComponent(header, player.getVersion()), nms.toNMSComponent(footer, player.getVersion()));
if (HeaderFooterClass == null) return;
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(toComponent(header), toComponent(footer)));
} else {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance();
HEADER.set(packet, nms.toNMSComponent(header, player.getVersion()));
FOOTER.set(packet, nms.toNMSComponent(footer, player.getVersion()));
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, toComponent(header));
FOOTER.set(packet, toComponent(footer));
player.sendPacket(packet);
}
player.sendPacket(packet);
}
@SneakyThrows
private Object createPacket(TabList.Action action, TabList.Entry entry, ProtocolVersion clientVersion) {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
@NotNull
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
if (BukkitReflection.is1_19_3Plus()) {
EnumSet<?> actions;
if (action == TabList.Action.ADD_PLAYER) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
if (action == Action.ADD_PLAYER) {
actions = EnumSet.allOf(ActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action.name()));
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
}
packet = newPacketPlayOutPlayerInfo.newInstance(actions, Collections.emptyList());
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
players.add(newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
true,
entry.getLatency(),
int2GameMode(entry.getGameMode()),
entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion),
gameModes[entry.getGameMode()],
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
null
));
} else {
packet = newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.name()),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoClass) {
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(null);
parameters.add(gameModes[entry.getGameMode()]);
parameters.add(entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()));
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
}
PLAYERS.set(packet, players);
return packet;
}
private Object int2GameMode(int gameMode) {
switch (gameMode) {
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
@NotNull
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name == null ? "" : name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
}
@@ -4,25 +4,22 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
@@ -30,7 +27,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
@@ -40,26 +36,37 @@ import java.util.UUID;
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
@NotNull
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
@NotNull
private final Object playerConnection;
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
@NotNull
private final TabList tabList = new BukkitTabList(this);
@NotNull
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@NotNull
private final EntityView entityView = new PacketEntityView(this);
/**
* Constructs new instance with given bukkit player and protocol version
* Constructs new instance with given bukkit player
*
* @param platform
* Server platform
* @param p
* bukkit player
*/
@SneakyThrows
public BukkitTabPlayer(Player p) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(),
p.getWorld().getName(), ViaVersionHook.getInstance().getPlayerVersion(p.getUniqueId(), p.getName()));
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@@ -70,27 +77,23 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
public int getPing() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
try {
return NMSStorage.getInstance().PING.getInt(handle);
} catch (IllegalAccessException e) {
return -1;
}
return NMSStorage.getInstance().PING.getInt(handle);
}
@SneakyThrows
public void sendPacket(@Nullable Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
@Override
@@ -100,28 +103,29 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, getPlayer());
} catch (LinkageError | ReflectiveOperationException e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
return false;
}
return LibsDisguisesHook.isDisguised(this);
}
@Override
@SneakyThrows
@Nullable
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
return new TabList.Skin(property.getValue(), property.getSignature());
if (BukkitReflection.is1_20_2Plus()) {
return new TabList.Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new TabList.Skin(property.getValue(), property.getSignature());
}
}
@Override
public @NotNull Player getPlayer() {
@NotNull
public Player getPlayer() {
return (Player) player;
}
@@ -130,6 +134,11 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return getPlayer().isOnline();
}
@Override
public BukkitPlatform getPlatform() {
return (BukkitPlatform) platform;
}
@Override
public boolean isVanished() {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
@@ -141,39 +150,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
}
@Override
@SneakyThrows
public void destroyEntities(int... entities) {
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
}
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName();
}
}
@@ -0,0 +1,76 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Class containing static util methods used on Bukkit.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BukkitUtils {
/**
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
* and we want to avoid errors.
*
* @return Online players from Bukkit API.
*/
@SuppressWarnings("unchecked")
@SneakyThrows
@NotNull
public static Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* If not, closest legacy color is used instead.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
*/
@NotNull
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
StringBuilder sb = new StringBuilder();
if (component.getModifier().getColor() != null) {
if (BukkitReflection.getMinorVersion() >= 16 && rgbClient) {
String hexCode = component.getModifier().getColor().getHexCode();
char c = EnumChatFormat.COLOR_CHAR;
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
} else {
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
}
}
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
}
return sb.toString();
}
}
@@ -3,10 +3,10 @@ package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -22,17 +22,18 @@ import java.util.UUID;
public class BukkitBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
/** Bukkit BossBars the player can currently see */
@NotNull
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bar.setProgress(progress);
@@ -42,8 +43,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
bossBars.get(id).setTitle(BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16));
}
@Override
@@ -5,7 +5,7 @@ import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
@@ -21,6 +21,7 @@ import java.util.UUID;
public class EntityBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@Override
@@ -28,18 +29,18 @@ public class EntityBossBar implements BossBar {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
w.getHelper().setCustomName(title, player.getVersion());
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
w.setHealth(health);
w.setCustomName(title, player.getVersion());
w.setEntityFlags((byte) 32);
w.setWitherInvulnerableTime(880); // Magic number
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
w.setCustomName(title, player.getVersion());
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
@@ -47,8 +48,8 @@ public class EntityBossBar implements BossBar {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
player.updateEntityMetadata(id.hashCode(), w);
w.setHealth(health);
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
@@ -59,6 +60,6 @@ public class EntityBossBar implements BossBar {
@Override
public void remove(@NotNull UUID id) {
player.destroyEntities(id.hashCode());
player.getEntityView().destroyEntities(id.hashCode());
}
}
@@ -4,10 +4,10 @@ import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
@@ -22,16 +22,18 @@ import java.util.UUID;
public class ViaBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
@NotNull
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
@@ -41,8 +43,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
viaBossBars.get(id).setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
}
@Override
@@ -1,18 +1,12 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.features.types.PacketSendListener;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
@@ -36,10 +30,7 @@ import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
public class BukkitNameTagX extends BackendNameTagX implements Listener {
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
@@ -47,45 +38,12 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
() -> getArmorStandManager(p).sneak(e.isSneaking()));
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
}
@EventHandler
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRespawn(PlayerRespawnEvent e) {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (respawned == null || isPlayerDisabled(respawned)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> getArmorStandManager(respawned).teleport());
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
respawn(e.getPlayer().getUniqueId());
}
@Override
@@ -112,7 +70,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@SuppressWarnings("deprecation")
@Override
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
@@ -126,7 +85,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@@ -136,7 +96,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull String getEntityType(@NotNull Object entity) {
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@@ -163,7 +124,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull Object getArmorStandType() {
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@@ -183,17 +145,23 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(nameVisible);
datawatcher.getHelper().setArmorStandFlags((byte)16);
datawatcher.setEntityFlags(flags);
datawatcher.setCustomName(displayName, viewer.getVersion());
datawatcher.setCustomNameVisible(nameVisible);
datawatcher.setArmorStandFlags((byte)16);
return datawatcher;
}
@Override
public void runInEntityScheduler(Object entity, Runnable task) {
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isDead();
}
}
@@ -11,7 +11,9 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -22,11 +24,36 @@ import java.util.WeakHashMap;
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@NotNull
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
private final String author = TabConstants.PLUGIN_AUTHOR;
private final String identifier = TabConstants.PLUGIN_ID;
private final String version = TabConstants.PLUGIN_VERSION;
/** List of all placeholders offered by the plugin for command suggestions */
@NotNull
private final List<String> placeholders = Arrays.asList(
"%tab_tabprefix%",
"%tab_tabsuffix%",
"%tab_tagprefix%",
"%tab_tagsuffix%",
"%tab_customtabname%",
"%tab_customtagname%",
"%tab_belowname%",
"%tab_abovename%",
"%tab_tabprefix_raw%",
"%tab_tabsuffix_raw%",
"%tab_tagprefix_raw%",
"%tab_tagsuffix_raw%",
"%tab_customtabname_raw%",
"%tab_customtagname_raw%",
"%tab_belowname_raw%",
"%tab_abovename_raw%",
"%tab_scoreboard_name%",
"%tab_scoreboard_visible%",
"%tab_bossbar_visible%",
"%tab_nametag_preview%",
"%tab_nametag_visibility%",
"%tab_replace_<placeholder>%",
"%tab_placeholder_<placeholder>%"
);
@Override
public boolean persist() {
@@ -34,6 +61,31 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
}
@Override
@NotNull
public String getAuthor() {
return TabConstants.PLUGIN_AUTHOR;
}
@Override
@NotNull
public String getIdentifier() {
return TabConstants.PLUGIN_ID;
}
@Override
@NotNull
public String getVersion() {
return TabConstants.PLUGIN_VERSION;
}
@Override
@NotNull
public List<String> getPlaceholders() {
return placeholders;
}
@Override
@Nullable
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
if (identifier.startsWith("replace_")) {
String text = "%" + identifier.substring(8) + "%";
@@ -50,11 +102,14 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
if (identifier.startsWith("placeholder_")) {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
}
return values.get(player).get(identifier);
if (player == null) return "<Player cannot be null>";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
}
@Override
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
}
}
@@ -5,8 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.UnLoadable;
import org.bukkit.Bukkit;
@@ -27,7 +26,6 @@ import org.jetbrains.annotations.NotNull;
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
@@ -35,21 +33,27 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/** Plugin reference*/
@NotNull private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@Override
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public PerWorldPlayerList(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
checkPlayer(p);
}
}
@Override
public void unload() {
for (Player p : Bukkit.getOnlinePlayers()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
for (Player p : BukkitUtils.getOnlinePlayers()) {
for (Player pl : BukkitUtils.getOnlinePlayers()) {
p.showPlayer(pl);
}
}
@@ -60,14 +64,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
public void onJoin(PlayerJoinEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
@@ -79,7 +83,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
* Player to update
*/
private void checkPlayer(@NotNull Player p) {
for (Player all : Bukkit.getOnlinePlayers()) {
for (Player all : BukkitUtils.getOnlinePlayers()) {
if (all == p) continue;
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
@@ -109,4 +113,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
}
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
public String getFeatureName() {
return "Per world PlayerList";
}
}
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
@@ -10,7 +11,6 @@ import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
@@ -24,15 +24,18 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/** Reference to plugin for registering listener */
private final @NotNull JavaPlugin plugin;
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public WitherBossBar(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
@@ -49,9 +52,10 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (BossBar line : getRegisteredBossBars().values()) {
if (!line.getPlayers().contains(p)) continue;
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(60)); // Wither distance
if (loc.getY() < 1) loc.setY(1);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
}
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.platforms.bukkit.hook;
import me.libraryaddict.disguise.DisguiseAPI;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
/**
* Class for hooking into LibsDisguises to get disguise status of players.
*/
public class LibsDisguisesHook {
/** Flag tracking if LibsDisguises is installed or not */
private static boolean installed = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
/**
* Returns {@code true} if LibsDisguises is installed and player is disguised,
* {@code false} otherwise.
*
* @param player
* Player to check
* @return {@code true} if disguised, {@code false} otherwise
*/
public static boolean isDisguised(TabPlayer player) {
try {
return installed && DisguiseAPI.isDisguised(((BukkitTabPlayer)player).getPlayer());
} catch (LinkageError e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
installed = false;
return false;
}
}
}
@@ -0,0 +1,83 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class BukkitReflection {
/** Server's NMS/CraftBukkit package */
@Getter
private static final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether this server jar is mojang mapped or not */
@Getter
private static final boolean mojangMapped = checkMojangMapped();
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
/** Flag determining whether the server version is at least 1.19.4 or not */
@Getter
private static final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Flag determining whether the server version is at least 1.20.2 or not */
@Getter
private static final boolean is1_20_2Plus = minorVersion >= 20 && !serverPackage.equals("v1_20_R1");
private static boolean checkMojangMapped() {
try {
Class.forName("net.minecraft.network.syncher.SynchedEntityData").getMethod("define",
Class.forName("net.minecraft.network.syncher.EntityDataAccessor"), Object.class);
return true;
} catch (ReflectiveOperationException | NullPointerException ex) {
return false;
}
}
/**
* Returns class with given potential names in same order
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
public static Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return BukkitReflection.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
}
@@ -0,0 +1,283 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> DataWatcher;
private static Constructor<?> newDataWatcher;
private static Method DataWatcher_register;
private static Method DataWatcher_markDirty;
public static Method DataWatcher_packDirty;
private static Class<?> DataWatcherObject;
private static Constructor<?> newDataWatcherObject;
private static Class<?> DataWatcherRegistry;
private static Class<?> DataWatcherSerializer;
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
private static Object DataWatcherSerializer_BOOLEAN;
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private static int getArmorStandFlagsPosition() {
if (BukkitReflection.getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (BukkitReflection.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (BukkitReflection.getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (BukkitReflection.getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
/**
* Loads all required Fields and throws Exception if something went wrong
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
if (BukkitReflection.isMojangMapped()) {
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
} else if (minorVersion >= 17) {
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
} else {
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
}
}
newDataWatcher = ReflectionUtils.getOnlyConstructor(DataWatcher);
if (minorVersion >= 9) {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Bukkit 1.20.2+, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
} else {
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang | 1.20.2+, 1.20.2-}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
if (minorVersion >= 9) {
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, DataWatcherSerializer);
DataWatcherSerializer_BYTE = ReflectionUtils.getField(DataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(DataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(DataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
if (BukkitReflection.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
if (BukkitReflection.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (minorVersion >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
}
}
}
}
/**
* Sets value into data values
*
* @param position
* data position
* @param serializer
* Object serializer (1.9+)
* @param value
* value
*/
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
dataValues.put(position, new Item(position, serializer, value));
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
setValue(0, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(TAB.getInstance().getPlatform().toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(10, null, cutName);
} else {
setValue(5, null, cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (BukkitReflection.getMinorVersion() >= 9) {
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
} else {
setValue(3, null, (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(6, DataWatcherSerializer_FLOAT, health);
} else {
setValue(16, null, (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (BukkitReflection.getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
setValue(20, null, time);
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
Object nmsWatcher;
if (newDataWatcher.getParameterCount() == 1) { //1.7+
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
} else {
nmsWatcher = newDataWatcher.newInstance();
}
for (Item item : dataValues.values()) {
Object nmsObject = item.createObject();
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
if (BukkitReflection.is1_19_3Plus()) DataWatcher_markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
@RequiredArgsConstructor
@Getter
public static class Item {
/** Item position */
private final int position;
/** Item serializer (1.9+) */
@Nullable
private final Object serializer;
/** Item value */
@NotNull
private final Object value;
/**
* Creates data watcher object with position and serializer
*
* @return NMS data watcher object
*/
@SneakyThrows
public Object createObject() {
if (BukkitReflection.getMinorVersion() >= 9) {
return newDataWatcherObject.newInstance(position, serializer);
} else {
return position;
}
}
}
}
@@ -0,0 +1,120 @@
package me.neznamy.tab.platforms.bukkit.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
public class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = ReflectionUtils.getMethod(EntityHuman, new String[] {"getProfile"});
}
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
BukkitTabList.load();
DataWatcher.load();
PacketEntityView.load();
PacketScoreboard.load();
if (minorVersion < 17) {
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
}
private void loadClasses() throws ClassNotFoundException {
if (minorVersion >= 17 && ReflectionUtils.classExists("net.minecraft.ChatFormatting")) {
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
} else if (minorVersion >= 17) {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
} else {
EntityHuman = BukkitReflection.getLegacyClass("EntityHuman");
Packet = BukkitReflection.getLegacyClass("Packet");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerConnection = BukkitReflection.getLegacyClass("PlayerConnection");
NetworkManager = BukkitReflection.getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
ChatSerializer = BukkitReflection.getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
}
}
}
@@ -0,0 +1,392 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
@SuppressWarnings("unchecked")
public class PacketEntityView implements EntityView {
/** PacketPlayOutEntityDestroy */
private static Class<?> EntityDestroyClass;
private static Constructor<?> newEntityDestroy;
private static Field EntityDestroy_Entities;
/** PacketPlayOutEntityMetadata */
private static Constructor<?> newEntityMetadata;
/** PacketPlayOutEntityTeleport */
private static Class<?> EntityTeleportClass;
private static Constructor<?> newEntityTeleport;
private static Field EntityTeleport_EntityId;
private static Field EntityTeleport_X;
private static Field EntityTeleport_Y;
private static Field EntityTeleport_Z;
/** PacketPlayOutSpawnEntityLiving */
private static Class<?> SpawnEntityClass;
private static Constructor<?> newSpawnEntity;
/** 1.17+ */
private static Object Vec3D_Empty;
private static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
private static Field SpawnEntity_EntityId;
private static Field SpawnEntity_EntityType;
private static Field SpawnEntity_UUID;
private static Field SpawnEntity_X;
private static Field SpawnEntity_Y;
private static Field SpawnEntity_Z;
private static Field SpawnEntity_DataWatcher;
private static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Other entity packets */
private static Class<?> PacketPlayOutEntity;
private static Field PacketPlayOutEntity_ENTITYID;
private static Class<?> PacketPlayOutEntityLook;
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Object dummyEntity;
@Getter
private static boolean available;
/** Player this view belongs to */
private final BukkitTabPlayer player;
/**
* Loads all required classes and fields and throws Exception if something went wrong
*
* @throws ReflectiveOperationException
* If something fails
*/
public static void load() throws ReflectiveOperationException {
loadEntityMetadata();
loadEntityDestroy();
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
available = true;
}
private static void loadEntityMetadata() throws ReflectiveOperationException {
// Class
Class<?> entityMetadataClass = getClass("network.protocol.game.ClientboundSetEntityDataPacket",
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
// Constructor
if (BukkitReflection.is1_19_3Plus()) {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, List.class);
} else {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
}
}
private static void loadEntityDestroy() throws ReflectiveOperationException {
// Class
EntityDestroyClass = getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
// Constructor
try {
newEntityDestroy = EntityDestroyClass.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
newEntityDestroy = EntityDestroyClass.getConstructor(int.class);
}
// Field
EntityDestroy_Entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
}
private static void loadEntityTeleport() throws ReflectiveOperationException {
// Class
EntityTeleportClass = getClass("network.protocol.game.ClientboundTeleportEntityPacket",
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
// Constructor
if (BukkitReflection.getMinorVersion() >= 17) {
newEntityTeleport = EntityTeleportClass.getConstructor(getClass("world.entity.Entity"));
} else {
newEntityTeleport = EntityTeleportClass.getConstructor();
}
// Fields
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
} else {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
}
// Dummy armor stand for constructor
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> world = getClass("world.level.Level", "world.level.World", "World");
Class<?> entityArmorStand = getClass("world.entity.decoration.ArmorStand",
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
}
}
private static void loadEntityMove() throws ReflectiveOperationException {
// Classes
PacketPlayOutEntity = getClass("network.protocol.game.ClientboundMoveEntityPacket",
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityLook = getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
"PacketPlayOutEntityLook", "Packet32EntityLook");
// Field
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
}
private static void loadEntitySpawn() throws ReflectiveOperationException {
SpawnEntityClass = getClass("network.protocol.game.ClientboundAddEntityPacket",
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> Vec3D = getClass("world.phys.Vec3", "world.phys.Vec3D");
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
Class<?> EntityTypes = getClass("world.entity.EntityType", "world.entity.EntityTypes");
if (BukkitReflection.getMinorVersion() >= 19) {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
} else {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
}
if (BukkitReflection.getMinorVersion() >= 19) {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
} else {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
}
} else {
newSpawnEntity = SpawnEntityClass.getConstructor();
if (BukkitReflection.getMinorVersion() >= 9) {
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
} else {
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
}
SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
if (BukkitReflection.getMinorVersion() <= 14) {
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
}
}
if (BukkitReflection.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (BukkitReflection.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (!BukkitReflection.is1_20_2Plus()) {
PacketPlayOutNamedEntitySpawn = getClass("network.protocol.game.ClientboundAddPlayerPacket",
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
}
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
@SneakyThrows
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
int minorVersion = BukkitReflection.getMinorVersion();
if (minorVersion >= 19) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d));
} else if (minorVersion >= 17) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty));
} else {
Object nmsPacket = newSpawnEntity.newInstance();
SpawnEntity_EntityId.set(nmsPacket, entityId);
if (minorVersion <= 14) {
SpawnEntity_DataWatcher.set(nmsPacket, data.build());
}
if (minorVersion >= 9) {
SpawnEntity_UUID.set(nmsPacket, id);
SpawnEntity_X.set(nmsPacket, l.getX());
SpawnEntity_Y.set(nmsPacket, l.getY());
SpawnEntity_Z.set(nmsPacket, l.getZ());
} else {
SpawnEntity_X.set(nmsPacket, floor(l.getX()*32));
SpawnEntity_Y.set(nmsPacket, floor(l.getY()*32));
SpawnEntity_Z.set(nmsPacket, floor(l.getZ()*32));
}
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
player.sendPacket(nmsPacket);
}
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@SneakyThrows
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (newEntityMetadata.getParameterCount() == 2) {
//1.19.3+
player.sendPacket(newEntityMetadata.newInstance(entityId, DataWatcher.DataWatcher_packDirty.invoke(data.build())));
} else {
player.sendPacket(newEntityMetadata.newInstance(entityId, data.build(), true));
}
}
@SneakyThrows
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
Object nmsPacket;
if (BukkitReflection.getMinorVersion() >= 17) {
nmsPacket = newEntityTeleport.newInstance(dummyEntity);
} else {
nmsPacket = newEntityTeleport.newInstance();
}
EntityTeleport_EntityId.set(nmsPacket, entityId);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X.set(nmsPacket, location.getX());
EntityTeleport_Y.set(nmsPacket, location.getY());
EntityTeleport_Z.set(nmsPacket, location.getZ());
} else {
EntityTeleport_X.set(nmsPacket, floor(location.getX()*32));
EntityTeleport_Y.set(nmsPacket, floor(location.getY()*32));
EntityTeleport_Z.set(nmsPacket, floor(location.getZ()*32));
}
player.sendPacket(nmsPacket);
}
@SneakyThrows
@Override
public void destroyEntities(int... entities) {
if (newEntityDestroy.getParameterTypes()[0] != int.class) {
player.sendPacket(newEntityDestroy.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
player.sendPacket(newEntityDestroy.newInstance(entity));
}
}
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return EntityDestroyClass.isInstance(packet);
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return EntityTeleportClass.isInstance(packet);
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntityClass.isInstance(packet);
} else {
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
}
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return PacketPlayOutEntity.isInstance(packet);
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return PacketPlayOutEntityLook.isInstance(packet);
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return EntityTeleport_EntityId.getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
}
@Override
@SneakyThrows
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntity_EntityId.getInt(playerSpawnPacket);
} else {
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
}
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
Object entities = PacketEntityView.EntityDestroy_Entities.get(destroyPacket);
if (BukkitReflection.getMinorVersion() >= 17) {
if (entities instanceof List) {
return ((List<Integer>)entities).stream().mapToInt(i -> i).toArray();
} else {
//1.17.0
return new int[]{(int) entities};
}
} else {
return (int[]) entities;
}
}
/**
* Returns class from given potential names. Supports modded servers, such as Thermos.
*
* @param names
* Potential class names
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
if (BukkitReflection.getMinorVersion() >= 17) {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft." + name);
} else {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft.server." + BukkitReflection.getServerPackage() + "." + name);
}
} catch (ClassNotFoundException | NullPointerException e) {
// Wrong class name
}
}
throw new ClassNotFoundException("No class found with potential names " + Arrays.toString(names));
}
}
@@ -1,89 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> CLASS;
private static Constructor<?> CONSTRUCTOR;
private static Method REGISTER;
public static Method markDirty;
public static Method packDirty;
/** Watched data */
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
/** Helper for easier data write */
@Getter private final DataWatcherHelper helper = new DataWatcherHelper(this);
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
if (nms.getMinorVersion() >= 9) {
REGISTER = ReflectionUtils.getMethod(CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
} else {
REGISTER = ReflectionUtils.getMethod(CLASS, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (nms.getMinorVersion() >= 19) {
packDirty = CLASS.getMethod("b");
}
if (nms.is1_19_3Plus()) {
markDirty = ReflectionUtils.getOnlyMethod(CLASS, void.class, DataWatcherObject.CLASS);
}
}
/**
* Sets value into data values
*
* @param type
* type of value
* @param value
* value
*/
public void setValue(@NotNull DataWatcherObject type, @NotNull Object value) {
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object nmsObject = item.getType().build();
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
}
@@ -1,160 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* A class to help to assign DataWatcher items as positions often change per-version
*/
@AllArgsConstructor
public class DataWatcherHelper {
/** NMS Fields */
public static Class<?> DataWatcherRegistry;
public static Class<?> DataWatcherSerializer;
public static Object DataWatcherSerializer_BYTE;
public static Object DataWatcherSerializer_FLOAT;
public static Object DataWatcherSerializer_STRING;
public static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
public static Object DataWatcherSerializer_BOOLEAN;
/** Instance fields */
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
@NotNull private final DataWatcher data;
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private int getArmorStandFlagsPosition() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x
return 15;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
public static void load(NMSStorage nms) throws NoSuchFieldException, IllegalAccessException {
if (nms.getMinorVersion() < 9) return;
DataWatcherSerializer_BYTE = DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherSerializer_FLOAT = DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherSerializer_STRING = DataWatcherRegistry.getDeclaredField("d").get(null);
if (nms.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("g").get(null);
if (nms.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
if (nms.getMinorVersion() >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("i").get(null);
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
}
}
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
data.setValue(new DataWatcherObject(0, DataWatcherSerializer_BYTE), flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 13) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_OPTIONAL_COMPONENT),
Optional.ofNullable(NMSStorage.getInstance().toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_STRING), customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(10, null), cutName);
} else {
data.setValue(new DataWatcherObject(5, null), cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, DataWatcherSerializer_BOOLEAN), visible);
} else {
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(6, DataWatcherSerializer_FLOAT), health);
} else {
data.setValue(new DataWatcherObject(16, null), (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
data.setValue(new DataWatcherObject(armorStandFlagsPosition, DataWatcherSerializer_BYTE), flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (TAB.getInstance().getServerVersion().getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
data.setValue(new DataWatcherObject(20, null), time);
}
}
@@ -1,39 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Data;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Class representing NMS Data Watcher Item
*/
@Data
public class DataWatcherItem {
/** NMS Fields */
public static Class<?> CLASS;
public static Field TYPE;
public static Field VALUE;
/** Instance fields */
@NotNull private final DataWatcherObject type;
@NotNull private final Object value;
/**
* Loads all required Fields
*
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) {
VALUE = ReflectionUtils.getFields(CLASS, Object.class).get(0);
if (nms.getMinorVersion() >= 9) {
TYPE = ReflectionUtils.getOnlyField(CLASS, DataWatcherObject.CLASS);
} else {
TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
}
}
}
@@ -1,54 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
*/
@Data @AllArgsConstructor
public class DataWatcherObject {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field SLOT;
public static Field SERIALIZER;
/** Instance fields */
private final int position;
@Nullable private final Object serializer;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @throws NoSuchMethodException
* If something fails
*/
public static void load() throws NoSuchMethodException {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcherHelper.DataWatcherSerializer);
SLOT = ReflectionUtils.getOnlyField(CLASS, int.class);
SERIALIZER = ReflectionUtils.getOnlyField(CLASS, DataWatcherHelper.DataWatcherSerializer);
}
/**
* Converts the object into NMS object
*
* @return NMS object
*/
@SneakyThrows
public Object build() {
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
return DataWatcherObject.CONSTRUCTOR.newInstance(position, serializer);
} else {
return position;
}
}
}
@@ -1,109 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* NMS loader for Minecraft 1.5.2 - 1.16.5 using Bukkit mapping.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitLegacyNMSStorage extends NMSStorage {
/**
* Returns class with given potential names in same order
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
private Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
EntityHuman = getLegacyClass("EntityHuman");
World = getLegacyClass("World");
Packet = getLegacyClass("Packet");
EnumChatFormat = (Class<Enum>) getLegacyClass("EnumChatFormat");
EntityPlayer = getLegacyClass("EntityPlayer");
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
NetworkManager = getLegacyClass("NetworkManager");
DataWatcher.CLASS = getLegacyClass("DataWatcher");
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
if (minorVersion >= 8) {
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
BukkitTabList.PacketPlayOutPlayerInfoClass = getLegacyClass("PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
}
@@ -1,72 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitModernNMSStorage extends NMSStorage {
@Override
public void loadClasses() throws ClassNotFoundException {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
World = Class.forName("net.minecraft.world.level.World");
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.EntityArmorStand");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher$Item");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
PacketPlayOutSpawnEntityLivingStorage.Vec3D = Class.forName("net.minecraft.world.phys.Vec3D");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
if (is1_19_3Plus()) {
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
}
}
@@ -1,159 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
@SuppressWarnings("rawtypes")
public abstract class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
@Getter protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
protected Class<?> EntityArmorStand;
protected Class<?> World;
public Class<Enum> EnumChatFormat;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
public Class<?> Entity;
public Class<?> EntityLiving;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
public Class<?> IChatBaseComponent;
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
/** Other entity packets */
public Class<?> PacketPlayOutEntity;
public Field PacketPlayOutEntity_ENTITYID;
public Class<?> PacketPlayOutEntityLook;
public Class<?> PacketPlayOutNamedEntitySpawn;
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
public Object dummyEntity;
private final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = EntityHuman.getMethod("getProfile");
}
Constructor<?> newEntityArmorStand = EntityArmorStand.getConstructor(World, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
BukkitTabList.load(this);
}
if (minorVersion >= 9) {
DataWatcherObject.load();
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
DataWatcher.load(this);
DataWatcherItem.load(this);
DataWatcherHelper.load(this);
PacketPlayOutEntityDestroyStorage.load();
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketScoreboard.load(this);
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion < 17) {
try {
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
} catch (NoSuchFieldException e) {
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
}
}
}
/**
* Loads all classes used by the plugin
*
* @throws ClassNotFoundException
* If a class was not found
*/
public abstract void loadClasses() throws ClassNotFoundException;
/**
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
* If {@code component} is {@code null}, returns {@code null}
*
* @param component
* component to convert
* @param clientVersion
* client version used to decide RGB conversion
* @return converted component or {@code null} if {@code component} is {@code null}
*/
public @Nullable Object toNMSComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
public class PacketPlayOutEntityDestroyStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @throws NoSuchMethodException
* If something fails
*/
public static void load() throws NoSuchMethodException {
ENTITIES = ReflectionUtils.getOnlyField(CLASS);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
public class PacketPlayOutEntityMetadataStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
} else {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
}
}
}
@@ -1,87 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
public class PacketPlayOutEntityTeleportStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field YAW;
public static Field PITCH;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
if (nms.getMinorVersion() >= 9) {
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = ReflectionUtils.getFields(CLASS, int.class).get(1);
Y = ReflectionUtils.getFields(CLASS, int.class).get(2);
Z = ReflectionUtils.getFields(CLASS, int.class).get(3);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int entityId, Location location) {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
if (nms.getMinorVersion() >= 9) {
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
return nmsPacket;
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,131 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
public class PacketPlayOutSpawnEntityLivingStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
/** 1.17+ */
public static Class<?> Vec3D;
public static Class<?> EntityTypes;
public static Object Vec3D_Empty;
public static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
public static Field ENTITY_ID;
public static Field ENTITY_TYPE;
public static Field YAW;
public static Field PITCH;
public static Field UUID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field DATA_WATCHER;
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
int minorVersion = nms.getMinorVersion();
if (minorVersion >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (minorVersion >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (minorVersion >= 19) {
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("d").get(null);
CONSTRUCTOR = CLASS.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
} else if (minorVersion >= 17) {
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("c").get(null);
CONSTRUCTOR = CLASS.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
} else {
CONSTRUCTOR = CLASS.getConstructor();
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (minorVersion >= 9) {
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = ReflectionUtils.getFields(CLASS, int.class).get(2);
Y = ReflectionUtils.getFields(CLASS, int.class).get(3);
Z = ReflectionUtils.getFields(CLASS, int.class).get(4);
}
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
if (minorVersion <= 14) {
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
}
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int id, UUID uuid, Object entityType, Location l, EntityData data) {
int minorVersion = NMSStorage.getInstance().getMinorVersion();
if (minorVersion >= 19) {
return CONSTRUCTOR.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d);
}
if (minorVersion >= 17) {
return CONSTRUCTOR.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
ENTITY_ID.set(nmsPacket, id);
YAW.set(nmsPacket, (byte)(l.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(l.getPitch() * 256.0f / 360.0f));
if (minorVersion <= 14) {
DATA_WATCHER.set(nmsPacket, data.build());
}
if (minorVersion >= 9) {
UUID.set(nmsPacket, uuid);
X.set(nmsPacket, l.getX());
Y.set(nmsPacket, l.getY());
Z.set(nmsPacket, l.getZ());
} else {
X.set(nmsPacket, floor(l.getX()*32));
Y.set(nmsPacket, floor(l.getY()*32));
Z.set(nmsPacket, floor(l.getZ()*32));
}
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
return nmsPacket;
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,24 +1,21 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.BukkitPipelineInjector;
import me.neznamy.tab.platforms.bukkit.BukkitPlaceholderRegistry;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.*;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
@@ -28,9 +25,14 @@ import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
@@ -38,50 +40,91 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.io.File;
import java.lang.reflect.Field;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
@Getter
public class BukkitPlatform implements BackendPlatform {
public class BukkitPlatform implements BackendPlatform<Object> {
/** Plugin instance for registering tasks and events */
@NotNull
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
@Setter private boolean libsDisguisesEnabled = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
public @NotNull BossBarManagerImpl getLegacyBossBar() {
/** NMS server to get TPS from on spigot */
@Nullable private Object server;
/** TPS field */
@Nullable private Field spigotTps;
/** Detection for presence of Paper's TPS getter */
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
/** Detection for presence of Paper's MSPT getter */
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
public BukkitPlatform(@NotNull JavaPlugin plugin) {
this.plugin = plugin;
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
spigotTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
}
@Override
@NotNull
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
for (Player p : BukkitUtils.getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) {
Chat chat = rspChat.getProvider();
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
}
}
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
p -> ((TabPlayer)p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
@Nullable
public PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
@NotNull
public NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
}
@Override
public @NotNull TabExpansion createTabExpansion() {
@NotNull
public TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
@@ -91,48 +134,35 @@ public class BukkitPlatform implements BackendPlatform {
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
@Nullable
public TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
*
* @return online players from Bukkit API
*/
@SuppressWarnings("unchecked")
@SneakyThrows
private @NotNull Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
if (!placeholderAPI) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
return;
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
int refresh = pl.getRefreshInterval(identifier);
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
} else if (identifier.startsWith("%sync:")) {
registerSyncPlaceholder(identifier, refresh);
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh,
() -> PlaceholderAPI.setPlaceholders(null, identifier));
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
}
}
public void registerSyncPlaceholder(String identifier, int refresh) {
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
@@ -146,18 +176,71 @@ public class BukkitPlatform implements BackendPlatform {
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
public void logInfo(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + BukkitUtils.toBukkitFormat(message, true));
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] [WARN] " + BukkitUtils.toBukkitFormat(message, true));
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
return "[Bukkit] " + Bukkit.getName() + " - " +
Bukkit.getBukkitVersion().split("-")[0] +
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
@NotNull public GroupManager detectPermissionPlugin() {
public void registerListener() {
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), plugin);
}
@Override
public void registerCommand() {
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (command != null) {
BukkitTabCommand cmd = new BukkitTabCommand();
command.setExecutor(cmd);
command.setTabCompleter(cmd);
} else {
logWarn(new IChatBaseComponent("Failed to register command, is it defined in plugin.yml?"));
}
}
@Override
public void startMetrics() {
Metrics metrics = new Metrics(plugin, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
() -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return NMSStorage.getInstance().componentCache.get(component, version);
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
@@ -170,7 +253,25 @@ public class BukkitPlatform implements BackendPlatform {
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
public void runEntityTask(Entity entity, Runnable task) {
@Override
@SneakyThrows
public double getTPS() {
if (paperTps) {
return Bukkit.getTPS()[0];
} else if (spigotTps != null) {
return ((double[]) spigotTps.get(server))[0];
} else {
return -1;
}
}
@Override
public double getMSPT() {
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
}
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
}
@@ -5,6 +5,7 @@ import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Entity;
@@ -12,6 +13,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
@@ -20,7 +22,7 @@ import java.util.function.Consumer;
*/
public class FoliaPlatform extends BukkitPlatform {
public FoliaPlatform(JavaPlugin plugin) {
public FoliaPlatform(@NotNull JavaPlugin plugin) {
super(plugin);
}
@@ -29,8 +31,8 @@ public class FoliaPlatform extends BukkitPlatform {
super.loadPlayers();
// Values are never updated in the API, warn users
TAB.getInstance().sendConsoleMessage("&cFolia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20).", true);
logWarn(new IChatBaseComponent("Folia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20)."));
// Folia never calls PlayerChangedWorldEvent, this is a workaround
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100, "Folia compatibility", "Refreshing world", () -> {
@@ -48,7 +50,7 @@ public class FoliaPlatform extends BukkitPlatform {
}
@Override
public void registerSyncPlaceholder(String identifier, int refresh) {
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
@@ -74,7 +76,7 @@ public class FoliaPlatform extends BukkitPlatform {
*/
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(Entity entity, Runnable task) {
private void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
@@ -82,7 +84,7 @@ public class FoliaPlatform extends BukkitPlatform {
}
@Override
public void runEntityTask(Entity entity, Runnable task) {
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
runSync(entity, task);
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.NameTagVisibility;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
@SuppressWarnings({"ConstantConditions", "deprecation"})
public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
/** Server's minor version */
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
@NotNull
protected final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
player.getPlayer().setScoreboard(scoreboard);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(playerName)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(playerName);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
checkPlayerScoreboard();
newObjective(objectiveName, "dummy", title, display);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
setDisplayName(obj, title);
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.valueOf(display.name()));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
players.forEach(team::addEntry);
} else {
players.forEach(player -> team.addPlayer(Bukkit.getOfflinePlayer(player)));
}
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, 32), RenderType.valueOf(display.name()));
} else {
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
}
}
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.setDisplayName(transform(displayName, 128, 32));
}
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.setPrefix(transform(prefix, 64, 16));
}
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.setSuffix(transform(suffix, 64, 16));
}
/**
* Transforms text into format using bukkit RGB syntax if server version is 1.16+
*
* @param text
* Text to transform
* @param maxLengthModern
* Maximum text length defined by bukkit API on 1.13+
* @param maxLengthLegacy
* Maximum text length defined by bukkit API on 1.12-
* @return Converted text
*/
@NotNull
private String transform(@NotNull String text, int maxLengthModern, int maxLengthLegacy) {
String transformed = BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(text), player.getVersion().getMinorVersion() >= 16);
if (serverMinorVersion >= 16 && maxLengthModern < 128) { // Scoreboard title is not stripping colors
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
transformed = transformed.substring(0, transformed.length()-1);
} else if (serverMinorVersion >= 13) {
if (transformed.length() > maxLengthModern)
transformed = transformed.substring(0, maxLengthModern);
} else {
if (transformed.length() > maxLengthLegacy)
transformed = transformed.substring(0, maxLengthLegacy);
}
return transformed;
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
player.getPlayer().setScoreboard(scoreboard);
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
}
}
}
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
@@ -16,37 +16,19 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import static me.neznamy.tab.platforms.bukkit.nms.BukkitReflection.getLegacyClass;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards. While md_5 keeps saying that you don't
* need packets for scoreboards in every single spigot update,
* this is not true. It may seem that way at first, but after
* investigating, fatal problems are found:<p>
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
* to send scoreboards to use the full potential on all versions
* and server software without any artificial limits.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static NMSStorage nms;
@Getter
private static boolean available;
public static Class<?> Scoreboard;
public static Class<?> IScoreboardCriteria;
private static Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
@@ -55,16 +37,15 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
public static Enum[] DisplaySlot_values;
// PacketPlayOutScoreboardScore
public static Class<?> ScorePacketClass;
public static Class<?> ScoreboardScoreClass;
public static Class<Enum> EnumScoreboardAction;
private static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
public static Method ScoreboardScore_setScore;
private static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
public static Class<?> ObjectivePacketClass;
@@ -73,8 +54,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Field Objective_DISPLAY_NAME;
public static Class<Enum> EnumScoreboardHealthDisplay;
public static Class<?> ScoreboardObjective;
private static Class<Enum> EnumScoreboardHealthDisplay;
private static Constructor<?> newScoreboardObjective;
// PacketPlayOutScoreboardTeamStorage
@@ -85,123 +65,230 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
public static Class<Enum> EnumNameTagVisibility;
public static Class<Enum> EnumTeamPush;
public static Class<?> ScoreboardTeam;
private static Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Class<Enum> EnumChatFormatClass;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
public static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
public static Method ScoreboardTeam_setPrefix;
public static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setPrefix;
private static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
public static Method ScoreboardTeam_setAllowFriendlyFire;
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
public static void load(NMSStorage nms) throws ReflectiveOperationException {
PacketScoreboard.nms = nms;
emptyScoreboard = Scoreboard.getConstructor().newInstance();
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam;
Class<?> scoreboardObjective;
Class<?> scoreboardScoreClass;
Class<?> IScoreboardCriteria;
Class<?> scoreboard;
Class<?> scorePacketClass;
Class<?> IChatBaseComponent = null;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
scoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
scoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
} else if (minorVersion >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
scoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
scoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
} else {
EnumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
scorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
scoreboard = getLegacyClass("Scoreboard");
scoreboardObjective = getLegacyClass("ScoreboardObjective");
scoreboardScoreClass = getLegacyClass("ScoreboardScore");
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
scoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
}
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
emptyScoreboard = scoreboard.getConstructor().newInstance();
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
DisplaySlot_values = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
} else {
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
}
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(scoreboardObjective);
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newScoreboardScore = ScoreboardScoreClass.getConstructor(Scoreboard, ScoreboardObjective, String.class);
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
newScoreboardScore = scoreboardScoreClass.getConstructor(scoreboard, scoreboardObjective, String.class);
newScoreboardTeam = scoreboardTeam.getConstructor(scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(ScoreboardTeam, Collection.class);
ScoreboardScore_setScore = ReflectionUtils.getMethod(ScoreboardScoreClass, new String[] {"func_96647_c", "setScore", "b", "c"}, int.class); // {Thermos, 1.5.1 - 1.17.1, 1.18+, 1.5}
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96660_a", "setAllowFriendlyFire", "a"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+}
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+}
if (nms.getMinorVersion() >= 13) {
newScorePacket_1_13 = ScorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, nms.IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(ScoreboardTeam, new String[]{"setPrefix", "b"}, nms.IChatBaseComponent); // {1.17.1-, 1.18+}
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(ScoreboardTeam, new String[]{"setSuffix", "c"}, nms.IChatBaseComponent); // {1.17.1-, 1.18+}
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
if (minorVersion >= 13) {
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumChatFormatClass);
} else {
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
newScorePacket_String = scorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96666_b", "setPrefix", "b"}, String.class); // {Thermos, 1.5.1+, 1.5}
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96662_c", "setSuffix", "c"}, String.class); // {Thermos, 1.5.1+, 1.5}
if (nms.getMinorVersion() >= 8) {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
if (minorVersion >= 8) {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.class);
}
}
if (nms.getMinorVersion() >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, EnumNameTagVisibility); // {1.8.1+, 1.8 & 1.18+}
if (minorVersion >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
}
if (nms.getMinorVersion() >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
}
if (nms.getMinorVersion() >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
if (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeam, int.class);
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
ScoreboardScore_setScore = ReflectionUtils.getMethod(
scoreboardScoreClass,
new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, // {Thermos, 1.5.1 - 1.17.1 & 1.20.2+, 1.18 - 1.20.1, 1.5, Mohist 1.18.2}
int.class
);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 13) {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
IChatBaseComponent
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
IChatBaseComponent
);
} else {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
}
if (minorVersion >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
EnumNameTagVisibility
);
}
available = true;
}
public PacketScoreboard(BukkitTabPlayer player) {
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(newDisplayObjective.newInstance(slot.ordinal(), newScoreboardObjective(objective)));
Object displaySlot;
if (BukkitReflection.is1_20_2Plus()) {
displaySlot = DisplaySlot_values[slot.ordinal()];
} else {
displaySlot = slot.ordinal();
}
player.sendPacket(newDisplayObjective.newInstance(displaySlot, newScoreboardObjective(objective)));
}
@Override
@SneakyThrows
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(0, objectiveName, title, hearts));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(0, objectiveName, title, display));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", false));
player.sendPacket(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(2, objectiveName, title, hearts));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(2, objectiveName, title, display));
}
@SneakyThrows
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
if (nms.getMinorVersion() >= 13) {
private Object buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
asDisplayType(hearts)),
toComponent(IChatBaseComponent.optimizedComponent(title)),
Enum.valueOf(EnumScoreboardHealthDisplay, display.name())),
action
);
}
Object nmsPacket = newObjectivePacket.newInstance();
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
if (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, display.name()));
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
}
private Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
@Override
@SneakyThrows
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@@ -209,7 +296,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
@@ -220,7 +307,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
@@ -232,7 +319,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
@@ -240,45 +327,44 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
}
@SneakyThrows
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, CollisionRule collision, int options) {
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(prefix)));
ScoreboardTeam_setSuffix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(suffix)));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(EnumChatFormatClass, EnumChatFormat.lastColorsOf(prefix).toString()));
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
if (BukkitReflection.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (BukkitReflection.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
return team;
}
@Override
@SneakyThrows
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
Object packet;
if (nms.getMinorVersion() >= 13) {
packet = newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score);
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score));
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
packet = newScorePacket.newInstance(scoreboardScore);
if (BukkitReflection.getMinorVersion() >= 8) {
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
} else {
packet = newScorePacket.newInstance(scoreboardScore, 0);
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
}
}
player.sendPacket(packet);
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (nms.getMinorVersion() >= 13) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
@@ -294,12 +380,12 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (nms.getMinorVersion() >= 13) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
toComponent(new IChatBaseComponent("")),
null
);
}
@@ -309,4 +395,8 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
IScoreboardCriteria_self.get(null)
);
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
}
@@ -1,20 +1,16 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Scoreboard handler using Paper API, which got added
* in early 1.16.5 builds. Unlike the Bukkit API, the new
@@ -23,98 +19,36 @@ import java.util.Collection;
* <p>
* However, it still inherits the issue with compatibility
* with other plugins that create scoreboards.
* <p>
* Currently, this class is never used. In the future, it might
* be used as a fallback solution for unsupported versions if NMS
* changes and fields fail to load. Or as a complete replacement
* if compatibility with other plugins turns out not to be a problem.
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
// Use Bukkit setColor, the adventure one does not support magic codes while they are permitted
@SuppressWarnings({"ConstantConditions", "deprecation"})
public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
public class PaperScoreboard extends BukkitScoreboard {
private final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
ReflectionUtils.methodExists(Team.class, "prefix", Component.class);
public PaperScoreboard(BukkitTabPlayer player) {
public PaperScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
player.getPlayer().setScoreboard(scoreboard);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.valueOf(display.name()));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.displayName(toAdventure(displayName));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
checkPlayerScoreboard();
scoreboard.resetScores(playerName);
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
checkPlayerScoreboard();
scoreboard.registerNewObjective(objectiveName, "dummy", toAdventure(title), hearts ? RenderType.HEARTS : RenderType.INTEGER);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
obj.setRenderType(hearts ? RenderType.HEARTS : RenderType.INTEGER);
obj.displayName(toAdventure(title));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.prefix(toAdventure(prefix));
team.suffix(toAdventure(suffix));
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
players.forEach(team::addEntry);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
team.prefix(toAdventure(prefix));
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.suffix(toAdventure(suffix));
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
/**
@@ -124,24 +58,8 @@ public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
* Text to convert
* @return Converted component
*/
private @NotNull Component toAdventure(@NotNull String text) {
return IChatBaseComponent.optimizedComponent(text).toAdventureComponent(player.getVersion());
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
player.getPlayer().setScoreboard(scoreboard);
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
}
@NotNull
private Component toAdventure(@NotNull String text) {
return AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
@@ -18,11 +18,15 @@ import java.util.UUID;
*/
@RequiredArgsConstructor
public class BungeeBossBar implements BossBar {
private final @NotNull BungeeTabPlayer player;
@NotNull
private final BungeeTabPlayer player;
/** Both are included in the same action, need to remember and write them both */
@NotNull
private final Map<UUID, Integer> colors = new HashMap<>();
@NotNull
private final Map<UUID, Integer> styles = new HashMap<>();
@Override
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -10,6 +11,7 @@ import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import org.jetbrains.annotations.NotNull;
/**
* The core for BungeeCord forwarding events into all enabled features
@@ -27,23 +29,24 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
}
@EventHandler
public void onChat(ChatEvent e) {
public void onCommand(ChatEvent e) {
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
e.setCancelled(true);
}
}
@EventHandler
public void on(PluginMessageEvent event) {
if (!event.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (event.getReceiver() instanceof ProxiedPlayer) {
event.setCancelled(true);
pluginMessage(((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
public void on(PluginMessageEvent e) {
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (e.getReceiver() instanceof ProxiedPlayer) {
e.setCancelled(true);
pluginMessage(((ProxiedPlayer) e.getReceiver()).getUniqueId(), e.getData());
}
}
@Override
public TabPlayer createPlayer(ProxiedPlayer player) {
return new BungeeTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull ProxiedPlayer player) {
return new BungeeTabPlayer((BungeePlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -8,6 +8,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import java.lang.reflect.Field;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
@@ -18,14 +19,17 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisPlayer;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -38,41 +42,60 @@ import java.util.function.Supplier;
@SuppressWarnings("unchecked")
public class BungeePipelineInjector extends NettyPipelineInjector {
private static @Nullable Field wrapperField;
/** Inaccessible bungee internals */
@Nullable
private static Field wrapperField;
@Nullable
private static Object directionData;
@Nullable
private static Method getId;
static {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
}
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
@NotNull
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
@NotNull
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
@Override
@NotNull
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
}
@Override
protected @Nullable Channel getChannel(@NotNull TabPlayer player) {
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
if (wrapperField == null) return null;
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (final IllegalAccessException exception) {
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
((ScoreboardDisplay) packet).getName()
);
}
@Override
@@ -110,10 +133,11 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam != null && col.contains(p.getName()) &&
if (expectedTeam != null && (col.contains(p.getNickname()) || col.contains(p.getName())) &&
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
col.remove(p.getNickname());
col.remove(p.getName());
}
}
@@ -122,9 +146,9 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
RedisTeams teams = redis.getRedisTeams();
if (teams != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getName()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
logTeamOverride(packet.getName(), p.getName(), teams.getTeamNames().get(p));
col.remove(p.getName());
if (col.contains(p.getNickname()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
logTeamOverride(packet.getName(), p.getNickname(), teams.getTeamNames().get(p));
col.remove(p.getNickname());
}
}
}
@@ -141,6 +165,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItem listItem = (PlayerListItem) packet;
@@ -149,6 +179,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
}
}
@@ -173,12 +209,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* @param player
* player to inject
*/
public DeserializableBungeeChannelDuplexHandler(TabPlayer player) {
public DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
super(player);
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
public void write(@NotNull ChannelHandlerContext context, @NotNull Object packet, @NotNull ChannelPromise channelPromise) {
long time = System.nanoTime();
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
TAB.getInstance().getCPUManager().addTime(TabConstants.Feature.PACKET_DESERIALIZING, TabConstants.CpuUsageCategory.BYTE_BUF, System.nanoTime()-time);
@@ -193,12 +229,13 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* byte buf to deserialize
* @return deserialized packet or input byte buf if packet is not tracked
*/
private @NotNull Object deserialize(@NotNull ByteBuf buf) {
@NotNull
private Object deserialize(@NotNull ByteBuf buf) {
int marker = buf.readerIndex();
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
if (packetId == ((BungeeTabPlayer)player).getPacketId(extraPacketClasses[i])) {
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
DefinedPacket packet = extraPacketSuppliers[i].get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
buf.release();
@@ -211,5 +248,25 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
buf.readerIndex(marker);
return buf;
}
/**
* Returns packet ID of specified packet on the protocol version
*
* @param protocolVersion
* Protocol version to get packet id for
* @param clazz
* packet class
* @return packet ID
*/
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, protocolVersion);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + protocolVersion, e);
return -1;
}
}
}
}
@@ -2,53 +2,131 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.stream.Collectors;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
public class BungeePlatform extends ProxyPlatform<BaseComponent> {
@NotNull private final BungeeTAB plugin;
@NotNull
private final BungeeTAB plugin;
/** Component cache for BungeeCord components */
@NotNull
private final ComponentCache<IChatBaseComponent, BaseComponent> bungeeCache =
new ComponentCache<>(1000, this::toComponent0);
@Override
public @Nullable RedisSupport getRedisSupport() {
public void loadPlayers() {
ViaVersionHook.getInstance().printProxyWarn();
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
}
}
@Override
@Nullable
public RedisSupport getRedisSupport() {
if (ReflectionUtils.classExists("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI") &&
RedisBungeeAPI.getRedisBungeeApi() != null) {
return new RedisBungeeSupport(plugin);
return new BungeeRedisSupport(plugin);
}
return null;
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
public void logInfo(@NotNull IChatBaseComponent message) {
plugin.getLogger().info(message.toLegacyText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
plugin.getLogger().warning(EnumChatFormat.RED.getFormat() + message.toLegacyText());
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[BungeeCord] " + plugin.getProxy().getName() + " - " + plugin.getProxy().getVersion();
}
@Override
public void loadPlayers() {
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(p));
}
public void registerListener() {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, new BungeeEventListener());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
public void registerCommand() {
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
}
@Override
public void startMetrics() {
new Metrics(plugin, 10535).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return bungeeCache.get(component, version);
}
private BaseComponent toComponent0(IChatBaseComponent component, ProtocolVersion version) {
ChatModifier modifier = component.getModifier();
TextComponent textComponent = new TextComponent(component.getText());
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (modifier.getFont() != null) textComponent.setFont(modifier.getFont());
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent0(c, version)).collect(Collectors.toList()));
return textComponent;
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return new BungeePipelineInjector();
}
}
@Override
public void registerChannel() {
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
}
}
@@ -1,6 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
@@ -10,6 +10,8 @@ import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
/**
@@ -19,84 +21,105 @@ import java.util.Collection;
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
public BungeeScoreboard(BungeeTabPlayer player) {
/** Constructor to support both <1.20.2 and 1.20.2+ builds */
@SuppressWarnings("unchecked")
private static final Constructor<ScoreboardDisplay> newScoreboardDisplay = (Constructor<ScoreboardDisplay>)
Arrays.stream(ScoreboardDisplay.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findAny().orElse(null);
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
player.getPlayer().unsafe().sendPacket(newScoreboardDisplay.newInstance((byte)slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
objectiveName,
jsonOrRaw(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1)); // Empty string to prevent kick on 1.7
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
objectiveName,
"", // Empty string to prevent kick on 1.7
null,
(byte) 1
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
objectiveName,
jsonOrRaw(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2
));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(new Team(name, (byte) 0, jsonOrRaw(name, player.getVersion()),
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 0, jsonOrRaw(name), jsonOrRaw(prefix), jsonOrRaw(suffix),
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(new Team(name));
player.getPlayer().unsafe().sendPacket(new Team(name));
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(new Team(name, (byte) 2, jsonOrRaw(name, player.getVersion()),
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
visibility.toString(), collision.toString(), color, (byte)options, null));
}
/**
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is returned
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
private String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return text;
}
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 2, jsonOrRaw(name), jsonOrRaw(prefix),
jsonOrRaw(suffix), visibility.toString(), collision.toString(), color, (byte)options, null));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private String jsonOrRaw(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(player.getVersion());
} else {
return text;
}
}
}
@@ -1,33 +1,23 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
/**
* Main class for BungeeCord platform
* Main class for BungeeCord.
*/
public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
"(with 1.19.3 support) and up (or an equivalent fork) to work."));
return;
}
getProxy().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
getProxy().getPluginManager().registerCommand(this, new BungeeTabCommand());
TAB.setInstance(new TAB(new BungeePlatform(this), ProtocolVersion.PROXY, getDataFolder()));
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 10535);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
TAB.create(new BungeePlatform(this));
}
@Override
@@ -9,8 +9,9 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
public class BungeeTabCommand extends Command implements TabExecutor {
@@ -22,7 +23,7 @@ public class BungeeTabCommand extends Command implements TabExecutor {
}
@Override
public void execute(CommandSender sender, String[] args) {
public void execute(@NotNull CommandSender sender, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
@@ -38,11 +39,12 @@ public class BungeeTabCommand extends Command implements TabExecutor {
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
@NotNull
public Iterable<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof ProxiedPlayer) {
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
@@ -6,66 +6,57 @@ import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
/**
* TabPlayer implementation for BungeeCord
*/
@Getter
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Inaccessible bungee internals */
private static @Nullable Object directionData;
private static @Nullable Method getId;
/** Flag tracking plugin presence */
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
static {
try {
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
}
}
/** Player's scoreboard */
@Getter private final @NotNull Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
@NotNull
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
/** Player's tab list based on version */
private final @NotNull TabList tabList1_7 = new BungeeTabList17(this);
private final @NotNull TabList tabList1_8 = new BungeeTabList18(this);
private final @NotNull TabList tabList1_19_3 = new BungeeTabList1193(this);
@NotNull
private final TabList tabList1_7 = new BungeeTabList17(this);
@Getter private final @NotNull BossBar bossBar = new BungeeBossBar(this);
@NotNull
private final TabList tabList1_8 = new BungeeTabList18(this);
@NotNull
private final TabList tabList1_19_3 = new BungeeTabList1193(this);
/** Player's boss bar view */
@NotNull
private final BossBar bossBar = new BungeeBossBar(this);
/**
* Constructs new instance for given player
*
* @param platform
* Server platform
* @param p
* BungeeCord player
*/
public BungeeTabPlayer(@NotNull ProxiedPlayer p) {
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
public BungeeTabPlayer(@NotNull BungeePlatform platform, @NotNull ProxiedPlayer p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
}
@Override
@@ -78,45 +69,44 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
return getPlayer().getPing();
}
public void sendPacket(@NotNull Object nmsPacket) {
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
}
@Override
public @Nullable TabList.Skin getSkin() {
@Nullable
public TabList.Skin getSkin() {
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
if (loginResult == null) return null;
Property[] properties = loginResult.getProperties();
if (properties == null || properties.length == 0) return null;
if (properties == null || properties.length == 0) return null; //Offline mode
return new TabList.Skin(properties[0].getValue(), properties[0].getSignature());
}
@Override
public @NotNull ProxiedPlayer getPlayer() {
@NotNull
public ProxiedPlayer getPlayer() {
return (ProxiedPlayer) player;
}
/**
* Returns packet ID for this player of provided packet class
*
* @param clazz
* packet class
* @return packet ID
*/
public int getPacketId(@NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, getPlayer().getPendingConnection().getVersion());
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + getPlayer().getPendingConnection().getVersion(), e);
return -1;
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public BungeePlatform getPlatform() {
return (BungeePlatform) platform;
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
errorNoServer(message);
return;
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
}
/**
@@ -127,35 +117,28 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* @return Player's current protocol version
*/
@Override
public @NotNull ProtocolVersion getVersion() {
@NotNull
public ProtocolVersion getVersion() {
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
}
@Override
public boolean isVanished() {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
try {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
return super.isVanished();
}
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public @NotNull TabList getTabList() {
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
TAB.getInstance().getErrorManager().printError("Skipped plugin message send to " + getName() + ", because player is not" +
"connected to any server (message=" + new String(message) + ")");
return;
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
@NotNull
public TabList getTabList() {
int version = getPlayer().getPendingConnection().getVersion();
if (version >= ProtocolVersion.V1_19_3.getNetworkId()) return tabList1_19_3;
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
return tabList1_7;
}
}
@@ -1,24 +1,25 @@
package me.neznamy.tab.platforms.bungeecord;
package me.neznamy.tab.platforms.bungeecord.features;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import org.jetbrains.annotations.NotNull;
/**
* Redis implementation for BungeeCord
* RedisBungee implementation for BungeeCord
*/
@AllArgsConstructor
public class RedisBungeeSupport extends RedisSupport implements Listener {
public class BungeeRedisSupport extends RedisSupport implements Listener {
/** Plugin reference for registering listener */
private final @NotNull Plugin plugin;
@NotNull
private final BungeeTAB plugin;
@EventHandler
public void onMessage(@NotNull PubSubMessageEvent e) {
@@ -25,6 +25,7 @@ import java.util.UUID;
public class BungeeTabList1193 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
@Override
@@ -71,7 +72,8 @@ public class BungeeTabList1193 implements TabList {
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
}
public @NotNull Item item(@NotNull UUID id) {
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
@@ -86,6 +88,9 @@ public class BungeeTabList1193 implements TabList {
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
}
}
@@ -28,10 +28,14 @@ import java.util.UUID;
public class BungeeTabList17 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
/** Because entries are identified by names and not uuids on 1.7 */
@NotNull
private final Map<UUID, String> userNames = new HashMap<>();
@NotNull
private final Map<UUID, String> displayNames = new HashMap<>();
@Override
@@ -23,6 +23,7 @@ import java.util.UUID;
public class BungeeTabList18 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
@Override
@@ -66,7 +67,8 @@ public class BungeeTabList18 implements TabList {
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
}
public @NotNull Item item(@NotNull UUID id) {
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
@@ -81,6 +83,9 @@ public class BungeeTabList18 implements TabList {
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
}
}
+3 -4
View File
@@ -1,12 +1,11 @@
// Versions that don't require any code changes:
// 1.19.3, 1.19.4, 1.20, 1.20.1
val minecraftVersion = "1.20.1"
val minecraftVersion = "1.20.2"
val v114 = "0.28.5+1.14"
val v115 = "0.28.5+1.15"
val v116 = "0.42.0+1.16"
val fabricApiVersions = mapOf(
"1.20.2" to "0.88.5+1.20.2",
"1.20.1" to "0.83.0+1.20.1",
"1.20" to "0.83.0+1.20",
"1.19.4" to "0.77.0+1.19.4",
@@ -41,7 +40,7 @@ plugins {
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://repo.kryptonmc.org/releases")
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
@@ -15,13 +15,16 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
@NotNull
private final FabricTabPlayer player;
@NotNull
private final Map<UUID, ServerBossEvent> bars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
BossEvent.BossBarColor.valueOf(color.name()),
BossEvent.BossBarOverlay.valueOf(style.name())
);
@@ -32,7 +35,7 @@ public class FabricBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bars.get(id).setName(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
bars.get(id).setName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
}
@Override
@@ -0,0 +1,94 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@NotNull
private static final Entity dummyEntity = new ArmorStand(null, 0, 0, 0);
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
@NotNull EntityData data) {
player.sendPacket(new ClientboundAddEntityPacket(entityId, id,
location.getX(), location.getY(), location.getZ(), 0, 0,
(EntityType<?>) entityType, 0, Vec3.ZERO, 0));
updateEntityMetadata(entityId, data);
}
@Override
@SuppressWarnings("unchecked")
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build()));
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
dummyEntity.setId(entityId);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
player.sendPacket(new ClientboundTeleportEntityPacket(dummyEntity));
}
@Override
public void destroyEntities(int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return packet instanceof ClientboundRemoveEntitiesPacket;
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return packet instanceof ClientboundTeleportEntityPacket;
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket;
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket.Rot;
}
@Override
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
}
@Override
public int getMoveEntityId(@NotNull Object movePacket) {
return ((ClientboundMoveEntityPacket) movePacket).entityId;
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
}
}
@@ -1,23 +1,28 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricEventListener extends EventListener<ServerPlayer> {
public void register() {
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) ->
worldChange(player.getUUID(), destination.dimension().location().toString()));
//TODO command
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
}
@Override
public TabPlayer createPlayer(ServerPlayer player) {
return new FabricTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
return new FabricTabPlayer((FabricPlatform) TAB.getInstance().getPlatform(), player);
}
//TODO world switch
}
@@ -0,0 +1,129 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().connection.connection.channel;
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket)packet).getSlot().ordinal(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<Action> actions = packet.actions();
List<Entry> updatedList = new ArrayList<>();
for (Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
packet.entries = updatedList;
}
@Override
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = packet.method;
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
packet.players = newList;
}
}
@@ -1,16 +0,0 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricPlaceholderRegistry extends UniversalPlaceholderRegistry {
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((ServerPlayer) p.getPlayer()).getHealth()));
super.registerPlaceholders(manager);
}
}
@@ -1,6 +1,9 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
@@ -9,13 +12,27 @@ 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 net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.ChatFormatting;
import net.minecraft.SharedConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.contents.LiteralContents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class FabricPlatform implements BackendPlatform {
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Platform implementation for Fabric
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform<Component> {
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
@@ -24,43 +41,115 @@ public final class FabricPlatform implements BackendPlatform {
@Override
public void loadPlayers() {
for (ServerPlayer player : PlayerLookup.all(FabricTAB.getInstance().getServer())) {
TAB.getInstance().addPlayer(new FabricTabPlayer(player));
for (ServerPlayer player : PlayerLookup.all(server)) {
TAB.getInstance().addPlayer(new FabricTabPlayer(this, player));
}
}
@Override
public void registerPlaceholders() {
new FabricPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
@NotNull
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new NameTag();
@NotNull
public NameTag getUnlimitedNameTags() {
return new FabricNameTagX();
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return null;
}
@Override
public @NotNull TabExpansion createTabExpansion() {
@NotNull
public TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
@Nullable
public TabFeature getPerWorldPlayerList() {
return null;
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
public void logInfo(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
}
@Override
public void registerListener() {
new FabricEventListener().register();
}
@Override
public void registerCommand() {
// Event listener must be registered in main class
}
@Override
public void startMetrics() {
// Not available
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName());
}
@Override
@NotNull
public File getDataFolder() {
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
}
@Override
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
// Text
MutableComponent comp = MutableComponent.create(new LiteralContents(component.getText()));
// Color and style
List<ChatFormatting> formats = new ArrayList<>();
if (component.getModifier().isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
if (component.getModifier().isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
if (component.getModifier().isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
if (component.getModifier().isItalic()) formats.add(ChatFormatting.ITALIC);
if (component.getModifier().isBold()) formats.add(ChatFormatting.BOLD);
Style style = comp.getStyle().applyFormats(formats.toArray(new ChatFormatting[0]));
if (component.getModifier().getColor() != null) {
if (version.getMinorVersion() >= 16) {
style = style.withColor(component.getModifier().getColor().getRgb());
} else {
style = style.withColor(ChatFormatting.valueOf(component.getModifier().getColor().getLegacyColor().name()));
}
}
if (component.getModifier().getFont() != null)
style = style.withFont(new ResourceLocation(component.getModifier().getFont()));
comp.withStyle(style);
// Extra
comp.getSiblings().addAll(component.getExtra().stream().map(c -> toComponent(c, version)).toList());
return comp;
}
@Override
public double getTPS() {
return -1; // Not available
}
@Override
public double getMSPT() {
return server.getAverageTickTime();
}
}
@@ -1,7 +1,6 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import java.util.Objects;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
@@ -21,8 +20,8 @@ import org.jetbrains.annotations.NotNull;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
private final @NotNull net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
private final @NotNull Component EMPTY_COMPONENT = Objects.requireNonNull(Component.Serializer.fromJson(IChatBaseComponent.EMPTY_COMPONENT));
@NotNull
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
public FabricScoreboard(FabricTabPlayer player) {
super(player);
@@ -32,12 +31,12 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
new ClientboundSetDisplayObjectivePacket(
slot.ordinal(),
net.minecraft.world.scores.DisplaySlot.values()[slot.ordinal()],
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
Component.empty(),
ObjectiveCriteria.RenderType.INTEGER
)
)
@@ -45,15 +44,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
hearts ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
0
)
@@ -68,7 +67,7 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
Component.empty(),
ObjectiveCriteria.RenderType.INTEGER
),
1
@@ -77,15 +76,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
hearts ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
2
)
@@ -93,15 +92,17 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
team.setAllowFriendlyFire((options & 0x01) > 0);
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.getPlayers().addAll(players);
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
}
@@ -112,15 +113,16 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
team.setAllowFriendlyFire((options & 0x01) > 0);
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setSeeFriendlyInvisibles((options & 0x02) != 0);
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
}
@@ -1,53 +1,26 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class FabricTAB implements DedicatedServerModInitializer {
private final ComponentCache<IChatBaseComponent, Component> componentCache = new ComponentCache<>(1000,
(text, version) -> Component.Serializer.fromJson(text.toString(version)));
@Getter private static FabricTAB instance;
private final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Getter private MinecraftServer server;
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Override
public void onInitializeServer() {
instance = this;
ProtocolVersion protocolVersion = ProtocolVersion.fromNetworkId(SharedConstants.getCurrentVersion().getProtocolVersion());
File folder = FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
TAB.setInstance(new TAB(new FabricPlatform(), protocolVersion, folder));
new FabricEventListener().register();
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
this.server = server;
TAB.getInstance().load();
});
ServerLifecycleEvents.SERVER_STOPPING.register(server -> TAB.getInstance().unload());
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
@@ -13,7 +13,6 @@ import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -33,6 +32,7 @@ public class FabricTabCommand {
dispatcher.getRoot().addChild(command);
}
@NotNull
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
String input = context.getInput();
int firstSpace = input.indexOf(' ');
@@ -48,10 +48,10 @@ public class FabricTabCommand {
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = FabricTAB.getInstance().hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.getInstance().hasPermission(source, TabConstants.Permission.COMMAND_ALL);
boolean hasReloadPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
source.sendSystemMessage(Component.Serializer.fromJson(IChatBaseComponent.optimizedComponent(message).toString()));
source.sendSystemMessage(((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
}
} else {
if (source.getEntity() == null) {
@@ -64,20 +64,23 @@ public class FabricTabCommand {
return 0;
}
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args, @NotNull SuggestionsBuilder builder) {
@NotNull
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
@NotNull SuggestionsBuilder builder) {
TabPlayer player = null;
if (source.getEntity() != null) {
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
if (player == null) return Suggestions.empty();
}
int lastSpace = builder.getRemaining().lastIndexOf(' ');
SuggestionsBuilder newBuilder = builder;
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
if (lastSpace != -1) {
builder = builder.createOffset(lastSpace + 1 + builder.getStart());
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
}
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
builder.suggest(suggestion);
newBuilder.suggest(suggestion);
}
return builder.buildFuture();
return newBuilder.buildFuture();
}
}
@@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public record FabricTabList(FabricTabPlayer player) implements TabList {
public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList {
@Override
public void removeEntry(@NotNull UUID entry) {
@@ -27,7 +27,7 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME),
new Builder(entry).setDisplayName(displayName == null ? null : FabricTAB.getInstance().toComponent(displayName, player.getVersion()))));
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))));
}
@Override
@@ -47,18 +47,19 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
.setSkin(entry.getSkin())
.setGameMode(entry.getGameMode())
.setLatency(entry.getLatency())
.setDisplayName(entry.getDisplayName() == null ? null : FabricTAB.getInstance().toComponent(entry.getDisplayName(), player.getVersion()))));
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))));
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().connection.send(new ClientboundTabListPacket(
FabricTAB.getInstance().toComponent(header, player.getVersion()),
FabricTAB.getInstance().toComponent(footer, player.getVersion()))
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion()))
);
}
private Packet<?> build(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, FabricTabList.Builder entry) {
@NotNull
private Packet<?> build(@NotNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, @NotNull FabricTabList.Builder entry) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
@@ -83,32 +84,39 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
private int gameMode;
@Nullable private Component displayName;
public Builder setName(String name) {
@NotNull
public Builder setName(@Nullable String name) {
this.name = name;
return this;
}
public Builder setSkin(Skin skin) {
@NotNull
public Builder setSkin(@Nullable Skin skin) {
this.skin = skin;
return this;
}
@NotNull
public Builder setLatency(int latency) {
this.latency = latency;
return this;
}
@NotNull
public Builder setGameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
public Builder setDisplayName(Component displayName) {
@NotNull
public Builder setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
@Nullable
public GameProfile createProfile() {
if (name == null) return null;
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
@@ -1,46 +1,52 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import java.util.Collection;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Getter
public class FabricTabPlayer extends TabPlayer {
import java.util.Collection;
@Getter
public class FabricTabPlayer extends BackendTabPlayer {
@NotNull
private final Scoreboard<FabricTabPlayer> scoreboard = new FabricScoreboard(this);
@NotNull
private final TabList tabList = new FabricTabList(this);
@NotNull
private final BossBar bossBar = new FabricBossBar(this);
public FabricTabPlayer(ServerPlayer player) {
super(player, player.getUUID(), player.getGameProfile().getName(), TAB.getInstance().getConfiguration().getServerName(),
"N/A", ViaVersionHook.getInstance().getPlayerVersion(player.getUUID(), player.getGameProfile().getName()), true);
@NotNull
private final EntityView entityView = new FabricEntityView(this);
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(), player.level().dimension().location().toString());
}
@Override
public boolean hasPermission(@NotNull String permission) {
return FabricTAB.getInstance().hasPermission(getPlayer().createCommandSourceStack(), permission);
return FabricTAB.hasPermission(getPlayer().createCommandSourceStack(), permission);
}
@Override
public int getPing() {
return getPlayer().latency;
return getPlayer().connection.latency();
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendSystemMessage(FabricTAB.getInstance().toComponent(message, getVersion()));
getPlayer().sendSystemMessage(getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -54,27 +60,30 @@ public class FabricTabPlayer extends TabPlayer {
}
@Override
public @Nullable TabList.Skin getSkin() {
@Nullable
public TabList.Skin getSkin() {
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
if (properties.isEmpty()) return null;
if (properties.isEmpty()) return null; // Offline mode
Property skinProperty = properties.iterator().next();
return new TabList.Skin(skinProperty.getValue(), skinProperty.getSignature());
return new TabList.Skin(skinProperty.value(), skinProperty.signature());
}
@Override
public @NotNull ServerPlayer getPlayer() {
@NotNull
public ServerPlayer getPlayer() {
return (ServerPlayer) player;
}
public void sendPacket(Packet<?> packet) {
getPlayer().connection.send(packet);
}
@Override
public boolean isOnline() {
return true;
}
@Override
public FabricPlatform getPlatform() {
return (FabricPlatform) platform;
}
@Override
public boolean isVanished() {
return false;
@@ -84,4 +93,25 @@ public class FabricTabPlayer extends TabPlayer {
public int getGamemode() {
return getPlayer().gameMode.getGameModeForPlayer().getId();
}
@Override
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName().getString(); // Will make it work properly if someone asks
}
/**
* Sends packet to the player
*
* @param packet
* Packet to send
*/
public void sendPacket(@NotNull Packet<?> packet) {
getPlayer().connection.send(packet);
}
}
@@ -0,0 +1,140 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class FabricNameTagX extends BackendNameTagX {
private boolean enabled = true;
public FabricNameTagX() {
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
// no sneaking for fabric //TODO
}
@Override
public double getDistance(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
Vec3 loc1 = ((FabricTabPlayer)player1).getPlayer().position();
Vec3 loc2 = ((FabricTabPlayer)player2).getPlayer().position();
return Math.sqrt(Math.pow(loc1.x-loc2.x, 2) + Math.pow(loc1.z-loc2.z, 2));
}
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((FabricTabPlayer)player1).getPlayer().level() == ((FabricTabPlayer)player2).getPlayer().level();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return true;
}
@Override
public void unregisterListener() {
enabled = false;
}
@Override
@NotNull
public List<Integer> getPassengers(@NotNull Object vehicle) {
return ((Entity)vehicle).getPassengers().stream().map(Entity::getId).collect(Collectors.toList());
}
@Override
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().getVehicle();
}
@Override
public int getEntityId(@NotNull Object entity) {
return ((Entity)entity).getId();
}
@Override
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity)entity).getType().toString(); // TODO test/fix
}
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isCrouching();
}
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return false; // TODO
}
@Override
public boolean isSleeping(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isSleeping();
}
@Override
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().position().x;
}
@Override
public double getY(@NotNull Object entity) {
return ((Entity)entity).position().y;
}
@Override
public double getZ(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().position().z;
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return () -> Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return !((FabricTabPlayer)player).getPlayer().isAlive();
}
}
+2 -1
View File
@@ -11,7 +11,8 @@
},
"depends": {
"fabricloader": ">=0.9.0",
"fabric-api-base": "*"
"fabric-api-base": "*",
"minecraft": "1.20.2"
},
"recommends": {
"fabric-permissions-api-v0": "*"
@@ -1,5 +1,13 @@
accessWidener v1 named
accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I
accessible field net/minecraft/server/network/ServerGamePacketListenerImpl connection Lnet/minecraft/network/Connection;
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
-2
View File
@@ -7,11 +7,9 @@ plugins {
val platforms = setOf(
rootProject.projects.bukkit,
rootProject.projects.bungeecord,
rootProject.projects.krypton,
rootProject.projects.velocity,
rootProject.projects.sponge7,
rootProject.projects.sponge8,
// rootProject.projects.fabric
).map { it.dependencyProject }
tasks {
-10
View File
@@ -1,10 +0,0 @@
dependencies {
implementation(projects.shared)
compileOnly("me.lucko:spark-api:0.1-SNAPSHOT")
compileOnly("org.kryptonmc:krypton-api:f93a12382f")
annotationProcessor("org.kryptonmc:krypton-annotation-processor:f93a12382f")
}
tasks.compileJava {
options.release.set(17)
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.kryptonmc.api.entity.player.Player;
import org.kryptonmc.api.event.Listener;
import org.kryptonmc.api.event.command.CommandExecuteEvent;
import org.kryptonmc.api.event.player.PlayerJoinEvent;
import org.kryptonmc.api.event.player.PlayerQuitEvent;
public class KryptonEventListener extends EventListener<Player> {
@Listener
public void onJoin(PlayerJoinEvent event) {
join(event.getPlayer());
}
@Listener
public void onQuit(PlayerQuitEvent event) {
quit(event.getPlayer().getUuid());
}
//TODO world switch
@Listener
public void onCommand(CommandExecuteEvent event) {
if (event.getSender() instanceof Player && command(((Player) event.getSender()).getUuid(), event.getCommand())) {
event.deny();
}
}
@Override
public TabPlayer createPlayer(Player player) {
return new KryptonTabPlayer(player);
}
}
@@ -1,84 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import lombok.RequiredArgsConstructor;
import me.lucko.spark.api.Spark;
import me.lucko.spark.api.statistic.StatisticWindow.*;
import me.lucko.spark.api.statistic.misc.DoubleAverageInfo;
import me.lucko.spark.api.statistic.types.DoubleStatistic;
import me.lucko.spark.api.statistic.types.GenericStatistic;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.entity.player.Player;
@RequiredArgsConstructor
public class KryptonPlaceholderRegistry extends UniversalPlaceholderRegistry {
private final @NotNull KryptonTAB plugin;
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
// Built-in stuff
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> Math.ceil(((Player) p.getPlayer()).getHealth()));
// Spark placeholders, registered if Spark is present
if (plugin.getServer().getPluginManager().isLoaded("spark")) SparkPlaceholders.register(this, manager);
super.registerPlaceholders(manager);
}
/*
* This class is a massive hack. The issue is that the lambdas in here create InvokeDynamic instructions, which have
* references to the Spark classes that will be resolved on class load, causing class not found errors when the registry
* is loaded.
*
* This class solves this, as the InvokeDynamic instructions are now part of this class, rather than the registry class,
* which means we can have this class only load if Spark is present, solving the issue.
*/
private static final class SparkPlaceholders {
static void register(KryptonPlaceholderRegistry registry, PlaceholderManager manager) {
Spark spark = registry.plugin.getServer().getServicesManager().provide(Spark.class);
if (spark == null) return;
// TPS
DoubleStatistic<TicksPerSecond> tps = spark.tps();
if (tps != null) {
manager.registerServerPlaceholder("%tps_5s%", 1000, () -> registry.format(tps.poll(TicksPerSecond.SECONDS_5)));
manager.registerServerPlaceholder("%tps_10s%", 1000, () -> registry.format(tps.poll(TicksPerSecond.SECONDS_10)));
manager.registerServerPlaceholder("%tps_1m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_1)));
manager.registerServerPlaceholder("%tps_5m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_5)));
manager.registerServerPlaceholder("%tps_15m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_15)));
}
// MSPT
GenericStatistic<DoubleAverageInfo, MillisPerTick> mspt = spark.mspt();
if (mspt != null) {
manager.registerServerPlaceholder("%mspt_min_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).min()));
manager.registerServerPlaceholder("%mspt_max_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).max()));
manager.registerServerPlaceholder("%mspt_mean_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).mean()));
manager.registerServerPlaceholder("%mspt_median_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).median()));
manager.registerServerPlaceholder("%mspt_percentile95_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
manager.registerServerPlaceholder("%mspt_min_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).min()));
manager.registerServerPlaceholder("%mspt_max_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).max()));
manager.registerServerPlaceholder("%mspt_mean_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).mean()));
manager.registerServerPlaceholder("%mspt_median_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).median()));
manager.registerServerPlaceholder("%mspt_percentile95_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
}
// CPU process
DoubleStatistic<CpuUsage> process = spark.cpuProcess();
manager.registerServerPlaceholder("%cpu_process_10s%", 1000, () -> registry.format(process.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_process_1m%", 1000, () -> registry.format(process.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_process_15m%", 1000, () -> registry.format(process.poll(CpuUsage.MINUTES_15)));
// CPU system
DoubleStatistic<CpuUsage> system = spark.cpuSystem();
manager.registerServerPlaceholder("%cpu_system_10s%", 1000, () -> registry.format(system.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_system_1m%", 1000, () -> registry.format(system.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_system_15m%", 1000, () -> registry.format(system.poll(CpuUsage.MINUTES_15)));
}
}
}
@@ -1,73 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.Server;
import org.kryptonmc.api.entity.player.Player;
public class KryptonPlatform implements BackendPlatform {
@NotNull private final KryptonTAB plugin;
@NotNull private final Server server;
public KryptonPlatform(@NotNull KryptonTAB plugin) {
this.plugin = plugin;
server = plugin.getServer();
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
server.getConsole().sendMessage(Component.text("[TAB] ").append(message.toAdventureComponent(TAB.getInstance().getServerVersion())));
}
@Override
public String getServerVersionInfo() {
return "[Krypton] " + server.getPlatform().version();
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> "");
}
@Override
public void loadPlayers() {
for (Player player : server.getPlayers()) {
TAB.getInstance().addPlayer(new KryptonTabPlayer(player));
}
}
@Override
public void registerPlaceholders() {
new KryptonPlaceholderRegistry(plugin).registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new NameTag();
}
@Override
public @NotNull TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
return null;
}
}
@@ -1,108 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.scoreboard.Objective;
import org.kryptonmc.api.scoreboard.ObjectiveRenderType;
import org.kryptonmc.api.scoreboard.Team;
import org.kryptonmc.api.scoreboard.Visibility;
import org.kryptonmc.api.scoreboard.criteria.Criteria;
import java.util.Collection;
public class KryptonScoreboard extends Scoreboard<KryptonTabPlayer> {
public KryptonScoreboard(@NotNull KryptonTabPlayer player) {
super(player);
// Create a new, blank scoreboard for each player to avoid conflicts
player.getPlayer().showScoreboard(org.kryptonmc.api.scoreboard.Scoreboard.create());
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objectiveName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) getScoreboard().updateSlot(objective, convertDisplaySlot(slot));
}
private org.kryptonmc.api.scoreboard.DisplaySlot convertDisplaySlot(DisplaySlot slot) {
return org.kryptonmc.api.scoreboard.DisplaySlot.values()[slot.ordinal()];
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
Objective ignored = getScoreboard().createObjectiveBuilder()
.name(objectiveName)
.criterion(Criteria.DUMMY.get())
.displayName(IChatBaseComponent.optimizedComponent(title).toAdventureComponent(player.getVersion()))
.renderType(hearts ? ObjectiveRenderType.HEARTS : ObjectiveRenderType.INTEGER)
.buildAndRegister();
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) getScoreboard().removeObjective(objective);
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective == null) return;
objective.setDisplayName(IChatBaseComponent.optimizedComponent(title).toAdventureComponent(player.getVersion()));
objective.setRenderType(hearts ? ObjectiveRenderType.HEARTS : ObjectiveRenderType.INTEGER);
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
Team team = getScoreboard().createTeamBuilder(name)
.displayName(IChatBaseComponent.optimizedComponent(name).toAdventureComponent(player.getVersion()))
.prefix(IChatBaseComponent.optimizedComponent(prefix).toAdventureComponent(player.getVersion()))
.suffix(IChatBaseComponent.optimizedComponent(suffix).toAdventureComponent(player.getVersion()))
.friendlyFire((options & 0x01) != 0)
.canSeeInvisibleMembers((options & 0x02) != 0)
.collisionRule(org.kryptonmc.api.scoreboard.CollisionRule.valueOf(collision.name()))
.nameTagVisibility(Visibility.valueOf(visibility.name()))
.buildAndRegister();
for (String member : players) {
team.addMember(IChatBaseComponent.optimizedComponent(member).toAdventureComponent(player.getVersion()));
}
}
@Override
public void unregisterTeam0(@NotNull String name) {
Team team = getScoreboard().getTeam(name);
if (team != null) getScoreboard().removeTeam(team);
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Team team = getScoreboard().getTeam(name);
if (team == null) return;
team.setDisplayName(IChatBaseComponent.optimizedComponent(name).toAdventureComponent(player.getVersion()));
team.setPrefix(IChatBaseComponent.optimizedComponent(prefix).toAdventureComponent(player.getVersion()));
team.setSuffix(IChatBaseComponent.optimizedComponent(suffix).toAdventureComponent(player.getVersion()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeInvisibleMembers((options & 0x02) != 0);
team.setCollisionRule(org.kryptonmc.api.scoreboard.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Visibility.valueOf(visibility.name()));
}
@Override
public void setScore0(@NotNull String objectiveName, @NotNull String playerName, int score) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null)
objective.getOrCreateScore(IChatBaseComponent.optimizedComponent(playerName)
.toAdventureComponent(player.getVersion())).setScore(score);
}
@Override
public void removeScore0(@NotNull String objectiveName, @NotNull String playerName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) objective.removeScore(IChatBaseComponent.optimizedComponent(playerName).toAdventureComponent(player.getVersion()));
}
private org.kryptonmc.api.scoreboard.Scoreboard getScoreboard() {
return player.getPlayer().getScoreboard();
}
}
@@ -1,57 +0,0 @@
package me.neznamy.tab.platforms.krypton;
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.kryptonmc.api.Server;
import org.kryptonmc.api.command.CommandMeta;
import org.kryptonmc.api.event.Event;
import org.kryptonmc.api.event.EventFilter;
import org.kryptonmc.api.event.EventNode;
import org.kryptonmc.api.event.Listener;
import org.kryptonmc.api.event.server.ServerStartEvent;
import org.kryptonmc.api.event.server.ServerStopEvent;
import org.kryptonmc.api.plugin.annotation.Dependency;
import org.kryptonmc.api.plugin.annotation.Plugin;
import java.nio.file.Path;
/**
* Main class for Krypton platform
*/
@Plugin(
id = TabConstants.PLUGIN_ID,
name = TabConstants.PLUGIN_NAME,
version = TabConstants.PLUGIN_VERSION,
description = TabConstants.PLUGIN_DESCRIPTION,
authors = {TabConstants.PLUGIN_AUTHOR, "BomBardyGamer"},
dependencies = {
@Dependency(id = "luckperms", optional = true),
@Dependency(id = "viaversion", optional = true)
}
)
public class KryptonTAB {
@Inject @Getter private Server server;
@Inject private EventNode<Event> pluginEventNode;
@Inject private Path folder;
private final EventNode<Event> eventNode = EventNode.Companion.filteredForEvent("tab_events", EventFilter.ALL,
(it) -> !TAB.getInstance().isPluginDisabled());
@Listener
public final void onStart(ServerStartEvent event) {
pluginEventNode.addChild(eventNode);
eventNode.registerListeners(new KryptonEventListener());
server.getCommandManager().register(new KryptonTabCommand(), CommandMeta.builder(TabConstants.COMMAND_BACKEND).build());
TAB.setInstance(new TAB(new KryptonPlatform(this), ProtocolVersion.fromNetworkId(server.getPlatform().protocolVersion()),
folder.toFile()));
TAB.getInstance().load();
}
@Listener
public final void onStop(ServerStopEvent event) {
TAB.getInstance().unload();
}
}
@@ -1,44 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.command.Sender;
import org.kryptonmc.api.command.SimpleCommand;
import org.kryptonmc.api.entity.player.Player;
import java.util.Collections;
import java.util.List;
public class KryptonTabCommand implements SimpleCommand {
@Override
public void execute(@NotNull Sender sender, String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean canReload = sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD);
boolean isAdmin = sender.hasPermission(TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, canReload, isAdmin)) {
sender.sendMessage(Component.text(message));
}
return;
}
TabPlayer player = null;
if (sender instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) sender).getUuid());
if (player == null) return;
}
TAB.getInstance().getCommand().execute(player, args);
}
@Override
public @NotNull List<String> suggest(@NotNull Sender sender, String[] args) {
TabPlayer player = null;
if (sender instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) sender).getUuid());
if (player == null) return Collections.emptyList();
}
return TAB.getInstance().getCommand().complete(player, args);
}
}
@@ -1,65 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.auth.GameProfile;
import org.kryptonmc.api.auth.ProfileProperty;
import org.kryptonmc.api.entity.player.TabListEntry;
import org.kryptonmc.api.world.GameMode;
import java.util.Collections;
import java.util.UUID;
public record KryptonTabList(KryptonTabPlayer player) implements TabList {
@Override
public void removeEntry(@NotNull UUID entryId) {
player.getPlayer().getTabList().removeEntry(entryId);
}
@Override
public void updateDisplayName(@NotNull UUID entryId, @Nullable IChatBaseComponent displayName) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setDisplayName(displayName == null ? null : displayName.toAdventureComponent(player.getVersion()));
}
@Override
public void updateLatency(@NotNull UUID entryId, int latency) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setLatency(latency);
}
@Override
public void updateGameMode(@NotNull UUID entryId, int gameMode) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setGameMode(GameMode.values()[gameMode]);
}
@Override
public void addEntry(@NotNull Entry entry) {
GameProfile profile = createGameProfile(entry.getUniqueId(), entry.getName(), entry.getSkin());
player.getPlayer().getTabList().createEntryBuilder(entry.getUniqueId(), profile)
.displayName(entry.getDisplayName() == null ? null : entry.getDisplayName().toAdventureComponent(player.getVersion()))
.gameMode(GameMode.values()[entry.getGameMode()])
.latency(entry.getLatency())
.listed(true)
.buildAndRegister();
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().getTabList().setHeaderAndFooter(
header.toAdventureComponent(player.getVersion()),
footer.toAdventureComponent(player.getVersion())
);
}
private @NotNull
GameProfile createGameProfile(@NotNull UUID uuid, @Nullable String name, @Nullable Skin skin) {
String newName = name == null ? "" : name;
if (skin == null) return GameProfile.of(newName, uuid);
ProfileProperty property = ProfileProperty.of(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature());
return GameProfile.of(newName, uuid, Collections.singletonList(property));
}
}
@@ -1,81 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.bossbar.AdventureBossBar;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.auth.ProfileProperty;
import org.kryptonmc.api.entity.player.Player;
import java.util.List;
@Getter
public class KryptonTabPlayer extends TabPlayer {
private final Scoreboard<KryptonTabPlayer> scoreboard = new KryptonScoreboard(this);
private final TabList tabList = new KryptonTabList(this);
private final BossBar bossBar = new AdventureBossBar(this);
public KryptonTabPlayer(Player player) {
super(player, player.getUuid(), player.getProfile().name(), TAB.getInstance().getConfiguration().getServerName(),
player.getWorld().getName(), TAB.getInstance().getServerVersion().getNetworkId(), true);
}
@Override
public boolean hasPermission(@NotNull String permission) {
return getPlayer().hasPermission(permission);
}
@Override
public int getPing() {
return getPlayer().getPing();
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(message.toAdventureComponent(getVersion()));
}
@Override
public boolean hasInvisibilityPotion() {
return false;
}
@Override
public boolean isDisguised() {
return false;
}
@Override
public @Nullable TabList.Skin getSkin() {
List<ProfileProperty> list = getPlayer().getProfile().properties();
if (list.isEmpty()) return null;
return new TabList.Skin(list.get(0).value(), list.get(0).signature());
}
@Override
public @NotNull Player getPlayer() {
return (Player) player;
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@Override
public boolean isVanished() {
return false;
}
@Override
public int getGamemode() {
return getPlayer().getGameMode().ordinal();
}
}
+5 -10
View File
@@ -2,19 +2,15 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {
repositories {
mavenCentral() // Netty, SnakeYaml, json-simple, slf4j, Guava, Kyori event, bStats, AuthLib
mavenCentral() // Netty, SnakeYaml, json-simple, Guava, Kyori event, bStats, AuthLib, LuckPerms
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee
maven("https://repo.kryptonmc.org/releases") // Krypton, YamlAssist, LuckPerms
maven("https://repo.kryptonmc.org/snapshots") // BungeeCord
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord-proxy
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.purpurmc.org/snapshots") // Purpur
maven("https://repo.papermc.io/repository/maven-public/") // Velocity
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
maven("https://oss.sonatype.org/content/repositories/snapshots/") // Spark
maven("https://maven.fabricmc.net/") // Fabric
maven("https://jitpack.io") // PremiumVanish, Vault
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
}
}
@@ -31,11 +27,10 @@ rootProject.name = "TAB"
include(":api")
include(":shared")
include(":krypton")
include(":velocity")
include(":bukkit")
include(":bungeecord")
include(":sponge7")
include(":sponge8")
include(":fabric")
include(":jar")
include(":jar")
+1 -3
View File
@@ -5,7 +5,7 @@ plugins {
dependencies {
api(projects.api)
api("org.yaml:snakeyaml:1.33")
api("me.neznamy:yamlassist:1.0.5")
api("com.github.NEZNAMY:yamlassist:1.0.5")
api("com.googlecode.json-simple:json-simple:1.1.1") {
exclude("junit", "junit")
}
@@ -13,13 +13,11 @@ dependencies {
exclude("com.google.guava", "guava")
exclude("org.checkerframework", "checker-qual")
}
compileOnlyApi("net.md-5:bungeecord-chat:1.19-R0.1-SNAPSHOT")
compileOnlyApi("com.viaversion:viaversion-api:4.5.1")
compileOnlyApi("io.netty:netty-all:4.1.90.Final")
compileOnlyApi("net.luckperms:api:5.4")
compileOnlyApi("com.google.guava:guava:31.1-jre")
compileOnlyApi("org.geysermc.floodgate:api:2.2.0-SNAPSHOT")
compileOnlyApi("org.slf4j:slf4j-api:2.0.6")
compileOnlyApi("net.kyori:adventure-api:4.13.0")
compileOnlyApi("net.kyori:adventure-text-serializer-legacy:4.13.0")
compileOnlyApi("net.kyori:adventure-text-minimessage:4.13.0")
@@ -1,257 +0,0 @@
package me.neznamy.tab.shared;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.neznamy.tab.shared.features.types.TabFeature;
import org.jetbrains.annotations.NotNull;
/**
* A class which measures CPU usage of all tasks inserted into it and shows usage
*/
public class CpuManager {
private final int UPDATE_RATE_SECONDS = 10;
private final long TIME_PERCENT = TimeUnit.SECONDS.toNanos(1) / UPDATE_RATE_SECONDS;
/** Active time in current time period saved as nanoseconds from features */
private volatile Map<String, Map<String, Long>> featureUsageCurrent = new ConcurrentHashMap<>();
/** Active time in current time period saved as nanoseconds from placeholders */
private volatile Map<String, Long> placeholderUsageCurrent = new ConcurrentHashMap<>();
/** Active time in previous time period saved as nanoseconds from features */
private volatile Map<String, Map<String, Long>> featureUsagePrevious = new HashMap<>();
/** Active time in previous time period saved as nanoseconds from placeholders */
private volatile Map<String, Long> placeholderUsagePrevious = new HashMap<>();
// Scheduler for scheduling delayed and repeating tasks
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("TAB Processing Thread").build());
/** Tasks submitted to main thread before plugin was fully enabled */
private final Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();
/** Enabled flag used to queue incoming tasks if plugin is not enabled yet */
private volatile boolean enabled = false;
/**
* Constructs new instance and starts repeating task that resets values in configured interval
*/
public CpuManager() {
startRepeatingTask((int) TimeUnit.SECONDS.toMillis(UPDATE_RATE_SECONDS), () -> {
featureUsagePrevious = Collections.unmodifiableMap(featureUsageCurrent);
placeholderUsagePrevious = Collections.unmodifiableMap(placeholderUsageCurrent);
featureUsageCurrent = new ConcurrentHashMap<>();
placeholderUsageCurrent = new ConcurrentHashMap<>();
for (Entry<String, Long> entry : placeholderUsagePrevious.entrySet()) {
float usagePercent = nanosToPercent(entry.getValue());
if (usagePercent > 30) {
TAB.getInstance().sendConsoleMessage("&c[WARN] CPU usage of placeholder " + entry.getKey() +
" is " + (int)usagePercent + "%. It will most likely cause problems. Try increasing refresh interval.", true);
}
}
Map<String, Map<String, Float>> features = getFeatureUsage();
double featuresTotal = 0;
for (Map<String, Float> map : features.values()) {
featuresTotal += map.values().stream().mapToDouble(Float::floatValue).sum();
}
if (featuresTotal > 90) {
TAB.getInstance().sendConsoleMessage("&c[WARN] CPU usage of the plugin is "
+ (int)featuresTotal + "%. This will cause problems. Check /tab cpu to find out why.", true);
}
});
}
/**
* Cancels all tasks and shuts down thread pools
*/
public void cancelAllTasks() {
scheduler.shutdownNow();
}
/**
* Marks cpu manager as loaded and submits all queued tasks
*/
public void enable() {
enabled = true;
Runnable r;
while ((r = taskQueue.poll()) != null) {
submit(r);
}
}
/**
* Submits task to TAB's main thread. If plugin is not enabled yet,
* queues the task instead and executes once it's loaded.
*
* @param task task to execute
*/
private void submit(@NotNull Runnable task) {
if (scheduler.isShutdown()) return;
if (!enabled) {
taskQueue.add(task);
return;
}
scheduler.submit(() -> run(task));
}
/**
* Returns cpu usage map of placeholders from previous time period
*
* @return cpu usage map of placeholders
*/
public Map<String, Float> getPlaceholderUsage() {
return getUsage(placeholderUsagePrevious);
}
/**
* Converts nano map to percent and sorts it from highest to lowest usage.
*
* @param map map to convert
* @return converted and sorted map
*/
private @NotNull Map<String, Float> getUsage(@NotNull Map<String, Long> map) {
return map
.entrySet()
.stream()
.sorted(Entry.comparingByValue((o1, o2) -> Long.compare(o2, o1)))
.collect(LinkedHashMap::new,
(m, e) -> m.put(e.getKey(), nanosToPercent(e.getValue())),
Map::putAll
);
}
/**
* Returns map of CPU usage per feature and type in the previous time period
*
* @return map of CPU usage per feature and type
*/
public @NotNull Map<String, Map<String, Float>> getFeatureUsage() {
final Map<String, Map<String, Long>> map = featureUsagePrevious;
TreeMap<Long, Map.Entry<String, Map<String, Float>>> sorted
= new TreeMap<>((o1, o2) -> Long.compare(o2, o1));
map.forEach((key, val) -> {
Set<Map.Entry<String, Long>> entries = val.entrySet();
Map<String, Float> percent = new LinkedHashMap<>(entries.size());
long sum = entries
.stream()
.sorted(Map.Entry.comparingByValue((o1, o2) -> Long.compare(o2, o1)))
.peek(e -> percent.put(e.getKey(), nanosToPercent(e.getValue())))
.mapToLong(Map.Entry::getValue)
.sum();
sorted.put(sum, // Map.entry(key, percent) inline type java9+
new AbstractMap.SimpleImmutableEntry<>(key, percent));
});
// we will also try to get rid of O(log(n)) for random reading
int assumeCapacity = map.size();
return sorted
.values()
.stream()
.collect(() -> new LinkedHashMap<>(assumeCapacity),
(m, e) -> m.put(e.getKey(), e.getValue()),
Map::putAll
);
}
/**
* Converts nanoseconds to percent usage.
*
* @param nanos nanoseconds of cpu time
* @return usage in % (0-100)
*/
private float nanosToPercent(long nanos) {
return (float) nanos / TIME_PERCENT;
}
/**
* Adds cpu time to specified feature and usage type
*
* @param feature feature to add time to
* @param type sub-feature to add time to
* @param nanoseconds time to add
*/
public void addTime(@NotNull TabFeature feature, @NotNull String type, long nanoseconds) {
addTime(feature.getFeatureName(), type, nanoseconds);
}
/**
* Adds cpu time to specified feature and usage type
*
* @param feature feature to add time to
* @param type sub-feature to add time to
* @param nanoseconds time to add
*/
public void addTime(@NotNull String feature, @NotNull String type, long nanoseconds) {
featureUsageCurrent
.computeIfAbsent(feature, f -> new ConcurrentHashMap<>())
.merge(type, nanoseconds, Long::sum);
}
/**
* Adds used time to specified key into specified map
*
* @param map map to add usage to
* @param key usage key
* @param time nanoseconds to add
*/
private void addTime(@NotNull Map<String, Long> map, @NotNull String key, long time) {
map.merge(key, time, Long::sum);
}
/**
* Adds placeholder time to specified placeholder
*
* @param placeholder placeholder to add time to
* @param nanoseconds time to add
*/
public void addPlaceholderTime(@NotNull String placeholder, long nanoseconds) {
addTime(placeholderUsageCurrent, placeholder, nanoseconds);
}
public void runMeasuredTask(@NotNull String feature, @NotNull String type, @NotNull Runnable task) {
submit(() -> {
long time = System.nanoTime();
task.run();
addTime(feature, type, System.nanoTime() - time);
});
}
public void runTask(@NotNull Runnable task) {
submit(task);
}
public void startRepeatingMeasuredTask(int intervalMilliseconds, @NotNull String feature, @NotNull String type, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.scheduleAtFixedRate(() -> runMeasuredTask(feature, type, task), intervalMilliseconds, intervalMilliseconds, TimeUnit.MILLISECONDS);
}
public void startRepeatingTask(int intervalMilliseconds, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.scheduleAtFixedRate(() -> run(task), intervalMilliseconds, intervalMilliseconds, TimeUnit.MILLISECONDS);
}
public void runTaskLater(int delayMilliseconds, @NotNull String feature, @NotNull String type, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.schedule(() -> runMeasuredTask(feature, type, task), delayMilliseconds, TimeUnit.MILLISECONDS);
}
private void run(@NotNull Runnable task) {
try {
task.run();
} catch (Exception | LinkageError | StackOverflowError e) {
TAB.getInstance().getErrorManager().printError("An error was thrown when executing task", e);
}
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared;
import lombok.Getter;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,24 +124,24 @@ public class ErrorManager {
if (file.length() < 1000000)
buf.write(dateFormat.format(new Date()) + "[TAB v" + TabConstants.PLUGIN_VERSION + "] " + EnumChatFormat.decolor(message) + System.getProperty("line.separator"));
if (intoConsoleToo || TAB.getInstance().getConfiguration().isDebugMode())
TAB.getInstance().sendConsoleMessage(EnumChatFormat.color("&c[TAB v" + TabConstants.PLUGIN_VERSION + "] ") + EnumChatFormat.decolor(message), false);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(message));
}
for (String line : error) {
if (file.length() < 1000000)
buf.write(dateFormat.format(new Date()) + line + System.getProperty("line.separator"));
if (intoConsoleToo || TAB.getInstance().getConfiguration().isDebugMode())
TAB.getInstance().sendConsoleMessage(EnumChatFormat.color("&c") + line, false);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(line));
}
}
} catch (IOException ex) {
TAB.getInstance().sendConsoleMessage("&cAn error occurred when printing error message into file", true);
TAB.getInstance().sendConsoleMessage(ex.getClass().getName() + ": " + ex.getMessage(), true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("An error occurred when printing error message into file"));
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(ex.getClass().getName() + ": " + ex.getMessage()));
for (StackTraceElement e : ex.getStackTrace()) {
TAB.getInstance().sendConsoleMessage("\t" + e.toString(), true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("\t" + e.toString()));
}
TAB.getInstance().sendConsoleMessage("&cOriginal error: " + message, true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Original error: " + message));
for (String line : error) {
TAB.getInstance().sendConsoleMessage(line, true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(line));
}
}
}
@@ -211,7 +212,7 @@ public class ErrorManager {
*/
public double parseDouble(@NotNull String string, double defaultValue) {
try {
return Double.parseDouble(string);
return Double.parseDouble(string.replace(",", "."));
} catch (NumberFormatException e) {
return defaultValue;
}
@@ -251,6 +251,26 @@ public class FeatureManager {
}
}
public void onEntryAdd(TabPlayer packetReceiver, UUID id, String name) {
for (TabFeature f : values) {
if (!(f instanceof EntryAddListener)) continue;
long time = System.nanoTime();
((EntryAddListener)f).onEntryAdd(packetReceiver, id, name);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_PLAYER_INFO, System.nanoTime() - time);
}
}
public int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency) {
int newLatency = latency;
for (TabFeature f : values) {
if (!(f instanceof LatencyListener)) continue;
long time = System.nanoTime();
newLatency = ((LatencyListener)f).onLatencyChange(packetReceiver, id, newLatency);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_PLAYER_INFO, System.nanoTime() - time);
}
return newLatency;
}
public void registerFeature(@NotNull String featureName, @NotNull TabFeature featureHandler) {
features.put(featureName, featureHandler);
values = features.values().toArray(new TabFeature[0]);
@@ -284,6 +304,9 @@ public class FeatureManager {
FeatureManager featureManager = TAB.getInstance().getFeatureManager();
int minorVersion = TAB.getInstance().getServerVersion().getMinorVersion();
if (configuration.getConfig().getBoolean("ping-spoof.enabled", false))
featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
if (configuration.getConfig().getBoolean("bossbar.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : TAB.getInstance().getPlatform().getLegacyBossBar());
@@ -340,6 +363,12 @@ public class FeatureManager {
// Must be loaded after: Sorting
if (minorVersion >= 8 && configuration.getConfig().getBoolean("layout.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true)) {
TAB.getInstance().getMisconfigurationHelper().layoutBreaksYellowNumber();
}
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().layoutIncludesPreventSpectatorEffect();
}
}
// Must be loaded after: Layout
@@ -363,5 +392,7 @@ public class FeatureManager {
// Must be loaded after: Global PlayerList, PlayerList, NameTags, YellowNumber, BelowName
RedisSupport redis = TAB.getInstance().getPlatform().getRedisSupport();
if (redis != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, redis);
featureManager.registerFeature(TabConstants.Feature.NICK_COMPATIBILITY, new NickCompatibility());
}
}
@@ -2,9 +2,13 @@ package me.neznamy.tab.shared;
import java.util.*;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.TabConstants.Placeholder;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.features.sorting.types.SortingType;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,15 +34,25 @@ public class MisconfigurationHelper {
* Configured refresh intervals
*/
public void fixRefreshIntervals(@NotNull Map<String, Integer> refreshIntervals) {
int defaultRefresh = refreshIntervals.getOrDefault("default-refresh-interval", 500);
LinkedHashMap<String, Integer> valuesToFix = new LinkedHashMap<>();
for (Map.Entry<String, Integer> entry : refreshIntervals.entrySet()) {
for (Map.Entry<String, ?> entry : refreshIntervals.entrySet()) {
if (entry.getValue() == null) {
startupWarn("Refresh interval of " + entry.getKey() +
" is set to null. Define a valid value or remove it if you don't want to override default value.");
valuesToFix.put(entry.getKey(), Placeholder.MINIMUM_REFRESH_INTERVAL);
continue;
}
int interval = entry.getValue();
if (!(entry.getValue() instanceof Integer)) {
startupWarn("Refresh interval configured for \"" + entry.getKey() +
"\" is not a valid number.");
valuesToFix.put(entry.getKey(), 500);
continue;
}
int interval = (int) entry.getValue();
if (!entry.getKey().equals("default-refresh-interval") && interval == defaultRefresh) {
hint("Refresh interval of " + entry.getKey() + " is same as default interval, therefore there is no need to override it.");
}
if (interval < 0) {
startupWarn("Invalid refresh interval configured for " + entry.getKey() +
" (" + interval + "). Value cannot be negative.");
@@ -74,10 +88,11 @@ public class MisconfigurationHelper {
return 1000;
}
if (interval % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL != 0) {
int newInterval = interval - interval % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
if (newInterval == 0) newInterval = TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s, which is not divisible by " +
TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL + "! &bUsing %s.", name, interval, newInterval));
int min = TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
int newInterval = Math.round((float) interval / min) * min; // rounding
if (newInterval == 0) newInterval = min;
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s, which is not divisible by %s! &bUsing %s.",
name, interval, min, newInterval));
return newInterval;
}
return interval;
@@ -222,6 +237,103 @@ public class MisconfigurationHelper {
}
}
/**
* Checks bossbar section configuration for: <p>
* - Unknown keys, to let people know if they made a typo <p>
* - Missing required properties (text, color, style, progress) and adding them with some default values <p>
* - Evaluating static values of color, style and progress if they can represent the required data type
*
* @param bossbarSection
* Map section of a bossbar in config
* @param name
* Name of the bossbar defined in config
*/
public void checkBossBarProperties(Map<String, Object> bossbarSection, String name) {
// Unknown properties
List<String> validProperties = Arrays.asList("style", "color", "progress", "text", "announcement-bar", "display-condition");
for (String mapKey : bossbarSection.keySet()) {
if (!validProperties.contains(mapKey)) {
startupWarn("Unknown property \"" + mapKey + "\" in bossbar \"" + name + "\". Valid properties: " + validProperties);
}
}
// Text
if (!bossbarSection.containsKey("text")) {
startupWarn("Bossbar \"" + name + "\" is missing \"text\" property.");
bossbarSection.put("text", "Text is not defined!");
}
// Color
if (bossbarSection.containsKey("color")) {
String color = bossbarSection.get("color").toString();
if (!color.contains("%")) {
try {
BarColor.valueOf(color.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
startupWarn("Bossbar \"" + name + " has color set to \"" + color + "\", which is not one of the supported colors " +
Arrays.toString(BarColor.values()) + " or a placeholder evaluating to one.");
bossbarSection.put("color", "PURPLE");
}
}
} else {
startupWarn("Bossbar \"" + name + "\" is missing \"color\" property.");
bossbarSection.put("color", "PURPLE");
}
// Style
if (bossbarSection.containsKey("style")) {
String style = bossbarSection.get("style").toString();
if (!style.contains("%")) {
try {
BarStyle.valueOf(style.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
startupWarn("Bossbar \"" + name + " has style set to \"" + style + "\", which is not one of the supported styles " +
Arrays.toString(BarStyle.values()) + " or a placeholder evaluating to one.");
bossbarSection.put("style", "PROGRESS");
}
}
} else {
startupWarn("Bossbar \"" + name + "\" is missing \"style\" property.");
bossbarSection.put("style", "PROGRESS");
}
// Progress
if (bossbarSection.containsKey("progress")) {
String progress = bossbarSection.get("progress").toString();
if (!progress.contains("%")) {
try {
Float.parseFloat(progress);
} catch (IllegalArgumentException e) {
startupWarn("Bossbar \"" + name + " has progress set to \"" + progress + "\", which is not a valid number between 0 and 100 or a placeholder evaluating to one.");
bossbarSection.put("progress", "100");
}
}
} else {
startupWarn("Bossbar \"" + name + "\" is missing \"progress\" property.");
bossbarSection.put("progress", "100");
}
}
public void teamAntiOverrideDisabled() {
startupWarn("anti-override for scoreboard-teams is disabled in config. This is usually a mistake. If you notice the" +
" feature randomly breaking, enable it back.");
}
public void tablistAntiOverrideDisabled() {
startupWarn("anti-override for tablist-name-formatting is disabled in config. This is usually a mistake. If you notice the" +
" feature randomly breaking, enable it back.");
}
public void nonLastNoConditionScoreboard(@NotNull String noConditionScoreboard, @NotNull String nextScoreboard) {
startupWarn("Scoreboard \"" + noConditionScoreboard + "\" has no display condition set, however, there is" +
" another scoreboard in the chain (" + nextScoreboard + "). Scoreboards are checked from top to bottom" +
" until a scoreboard with meeting condition or no condition is found. Because of this, the scoreboard (" +
nextScoreboard + ") after the no-condition scoreboard (" + noConditionScoreboard + ") will never be displayed. " +
"Unless this is intentional to externally display the scoreboard (commands, API), this is a mistake.");
}
public void layoutBreaksYellowNumber() {
startupWarn("Layout feature breaks yellow-number feature, because it replaces real player with fake slots " +
"with different usernames for more reliable functionality. Disable yellow-number feature, as it will only look bad " +
"and consume resources.");
}
/**
* Sends a startup warn message into console
*
@@ -231,7 +343,7 @@ public class MisconfigurationHelper {
private void startupWarn(@NotNull String... messages) {
warnCount++;
for (String message : messages) {
TAB.getInstance().sendConsoleMessage("&c[WARN] " + message, true);
TAB.getInstance().getPlatform().logWarn(IChatBaseComponent.fromColoredText(message));
}
}
@@ -240,7 +352,7 @@ public class MisconfigurationHelper {
*/
public void printWarnCount() {
if (warnCount == 0) return;
TAB.getInstance().sendConsoleMessage("&eFound a total of " + warnCount + " issues.", true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Found a total of " + warnCount + " issues."));
// Reset after printing to prevent count going up on each reload
warnCount = 0;
}
@@ -251,12 +363,64 @@ public class MisconfigurationHelper {
public void invalidNumberForBossBarProgress(@NotNull String bossBar, @NotNull String input, @NotNull String configuredValue) {
if (configuredValue.contains("%")) {
TAB.getInstance().sendConsoleMessage("&c[WARN] Placeholder \"" + configuredValue +
"\" used in BossBar progress of \"" + bossBar + "\" returned value, which cannot be evaluated to a number between 0 and 100 (\"" + input + "\")", true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Placeholder \"" + configuredValue +
"\" used in BossBar progress of \"" + bossBar + "\" returned value, which cannot be evaluated to a number between 0 and 100 (\"" + input + "\")"));
} else {
TAB.getInstance().sendConsoleMessage("&c[WARN] BossBar \"" + bossBar +
"\" has invalid input configured for progress (\"" + configuredValue + "\"). Expecting a number between 0 and 100 or a placeholder returning one.", true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("BossBar \"" + bossBar +
"\" has invalid input configured for progress (\"" + configuredValue + "\"). Expecting a number between 0 and 100 or a placeholder returning one."));
}
}
public void invalidInputForNumericSorting(SortingType type, String placeholder, String output, TabPlayer player) {
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Placeholder " + placeholder + " used in sorting type " +
type + " returned \"" + output + "\" for player " + player.getName() + ", which is not a valid number."));
}
// ------------------------
// Hints
// ------------------------
@SuppressWarnings("unchecked")
public void checkHeaderFooterForRedundancy(Map<String, Object> configSection) {
String defaultHeader = String.valueOf(configSection.get("header"));
String defaultFooter = String.valueOf(configSection.get("footer"));
if (configSection.get("per-world") instanceof Map) {
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) configSection.get("per-world");
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
String world = entry.getKey();
if (String.valueOf(entry.getValue().getOrDefault("header", "-")).equals(defaultHeader)) {
hint("Per-world header for world \"" + world + "\" is identical to default header. " +
"This is redundant and can be removed for cleaner config.");
}
if (String.valueOf(entry.getValue().getOrDefault("footer", "-")).equals(defaultFooter)) {
hint("Per-world footer for world \"" + world + "\" is identical to default footer. " +
"This is redundant and can be removed for cleaner config.");
}
}
}
if (configSection.get("per-server") instanceof Map) {
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) configSection.get("per-server");
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
String server = entry.getKey();
if (String.valueOf(entry.getValue().getOrDefault("header", "-")).equals(defaultHeader)) {
hint("Per-server header for server \"" + server + "\" is identical to default header. " +
"This is redundant and can be removed for cleaner config.");
}
if (String.valueOf(entry.getValue().getOrDefault("footer", "-")).equals(defaultFooter)) {
hint("Per-server footer for server \"" + server + "\" is identical to default footer. " +
"This is redundant and can be removed for cleaner config.");
}
}
}
}
public void layoutIncludesPreventSpectatorEffect() {
hint("Layout feature automatically includes prevent-spectator-effect, therefore the feature can be disabled " +
"for better performance, as it is not needed at all (assuming it is configured to always display some layout).");
}
public void hint(@NotNull String message) {
TAB.getInstance().getPlatform().logInfo(IChatBaseComponent.fromColoredText("&6[Hint] " + message));
}
}
@@ -14,6 +14,7 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_20_2 (764),
V1_20_1 (763),
V1_20 (763),
V1_19_4 (762),
@@ -12,7 +12,7 @@ import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.tablist.HeaderFooterManager;
import me.neznamy.tab.api.tablist.TabListFormatManager;
import me.neznamy.tab.api.nametag.NameTagManager;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.cpu.CpuManager;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.command.DisabledCommand;
import me.neznamy.tab.shared.command.TabCommand;
@@ -21,6 +21,7 @@ import me.neznamy.tab.shared.event.EventBusImpl;
import me.neznamy.tab.shared.event.impl.TabLoadEventImpl;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.error.YAMLException;
@@ -36,7 +37,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class TAB extends TabAPI {
/** Instance of this class */
@Getter @Setter private static TAB instance;
@Getter private static TAB instance;
/** Player data storage */
private final Map<UUID, TabPlayer> data = new ConcurrentHashMap<>();
@@ -54,7 +55,7 @@ public class TAB extends TabAPI {
@Getter private final DisabledCommand disabledCommand = new DisabledCommand();
/** Implementation of platform the plugin is installed on for platform-specific calls */
@Getter private final Platform platform;
@Getter private final Platform<?> platform;
/**
* CPU manager for thread and task management as well as
@@ -100,19 +101,28 @@ public class TAB extends TabAPI {
/** Helper for detecting misconfiguration in configs and send it to user */
@Getter private final MisconfigurationHelper misconfigurationHelper = new MisconfigurationHelper();
/**
* Creates new instance using given platform and loads it
*
* @param platform
* Platform interface
*/
public static void create(@NotNull Platform<?> platform) {
instance = new TAB(platform);
instance.load();
}
/**
* Constructs new instance with given parameters and sets this
* new instance as {@link me.neznamy.tab.api.TabAPI} instance.
*
* @param platform
* Platform interface
* @param serverVersion
* Version the server is running on
*/
public TAB(@NotNull Platform platform, @NotNull ProtocolVersion serverVersion, @NotNull File dataFolder) {
private TAB(@NotNull Platform<?> platform) {
this.platform = platform;
this.serverVersion = serverVersion;
this.dataFolder = dataFolder;
this.serverVersion = platform.getServerVersion();
this.dataFolder = platform.getDataFolder();
this.errorManager = new ErrorManager(this);
try {
eventBus = new EventBusImpl();
@@ -120,6 +130,12 @@ public class TAB extends TabAPI {
//1.7.10 or lower
}
TabAPI.setInstance(this);
platform.registerListener();
platform.registerCommand();
platform.startMetrics();
if (platform instanceof ProxyPlatform) {
((ProxyPlatform<?>) platform).registerChannel();
}
}
/**
@@ -157,12 +173,11 @@ public class TAB extends TabAPI {
if (eventBus != null) eventBus.fire(TabLoadEventImpl.getInstance());
pluginDisabled = false;
cpu.enable();
ViaVersionHook.getInstance().printProxyWarn();
misconfigurationHelper.printWarnCount();
sendConsoleMessage("&aEnabled in " + (System.currentTimeMillis()-time) + "ms", true);
platform.logInfo(IChatBaseComponent.fromColoredText("&aEnabled in " + (System.currentTimeMillis()-time) + "ms"));
return configuration.getMessages().getReloadSuccess();
} catch (YAMLException e) {
sendConsoleMessage("&cDid not enable due to a broken configuration file.", true);
platform.logWarn(IChatBaseComponent.fromColoredText("&cDid not enable due to a broken configuration file."));
kill();
return (configuration == null ? "&4Failed to reload, file %file% has broken syntax. Check console for more info."
: configuration.getMessages().getReloadFailBrokenFile()).replace("%file%", brokenFile);
@@ -183,7 +198,7 @@ public class TAB extends TabAPI {
long time = System.currentTimeMillis();
if (configuration.getMysql() != null) configuration.getMysql().closeConnection();
featureManager.unload();
sendConsoleMessage("&aDisabled in " + (System.currentTimeMillis()-time) + "ms", true);
platform.logInfo(IChatBaseComponent.fromColoredText("&aDisabled in " + (System.currentTimeMillis()-time) + "ms"));
} catch (Exception | NoClassDefFoundError e) {
errorManager.criticalError("Failed to disable", e);
}
@@ -277,10 +292,6 @@ public class TAB extends TabAPI {
return data.get(uniqueId);
}
public void sendConsoleMessage(@NotNull String message, boolean translateColors) {
platform.sendConsoleMessage(translateColors ? IChatBaseComponent.fromColoredText(message) : new IChatBaseComponent(message));
}
@Override
public @Nullable HeaderFooterManager getHeaderFooterManager() {
return featureManager.getFeature(TabConstants.Feature.HEADER_FOOTER);
@@ -313,6 +324,7 @@ public class TAB extends TabAPI {
* Message to send
*/
public void debug(@NotNull String message) {
if (configuration != null && configuration.isDebugMode()) sendConsoleMessage("&9[DEBUG] " + message, true);
if (configuration != null && configuration.isDebugMode())
platform.logInfo(IChatBaseComponent.fromColoredText("&9[DEBUG] " + message));
}
}
@@ -82,7 +82,7 @@ public class TabConstants {
public static final String COMMAND_BOSSBAR_ANNOUNCE = "tab.announce.bar";
public static final String COMMAND_BOSSBAR_TOGGLE = "tab.bossbar.toggle";
public static final String COMMAND_BOSSBAR_SEND = "tab.send.bar";
public static final String COMMAND_BOSSBAR_TOGGLE_OTHER = "tab.bossbar.toggle.other";
public static final String COMMAND_MYSQL_DOWNLOAD = "tab.mysql.download";
public static final String COMMAND_MYSQL_UPLOAD = "tab.mysql.upload";
@@ -192,6 +192,7 @@ public class TabConstants {
public static final String UNLIMITED_NAME_TAGS = "NameTagX";
public static final String UNLIMITED_NAME_TAGS_PACKET_LISTENER = "nametagx-packet";
public static final String UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER = "nametagx-vehicle";
public static final String PING_SPOOF = "PingSpoof";
//Bukkit only
public static final String PER_WORLD_PLAYER_LIST = "PerWorldPlayerList";
@@ -203,6 +204,7 @@ public class TabConstants {
//additional info displayed in cpu command
public static final String PACKET_DESERIALIZING = "Packet deserializing";
public static final String NICK_COMPATIBILITY = "Nick";
public static String scoreboardLine(String line) {
return "scoreboard-" + line;
@@ -280,6 +282,7 @@ public class TabConstants {
public static final String HEALTH = "%health%";
public static final int MINIMUM_REFRESH_INTERVAL = 50;
public static final int RETURN_TIME_WARN_THRESHOLD = 50;
public static String condition(String name) {
return "%condition:" + name + "%";
@@ -302,7 +305,6 @@ public class TabConstants {
public static final String PERMISSION_SYSTEM = "permission_system";
public static final String GLOBAL_PLAYER_LIST_ENABLED = "global_playerlist_enabled";
public static final String PLACEHOLDER_API = "placeholderapi";
public static final String SERVER_VERSION = "server_version";
public static final String UNLIMITED_NAME_TAG_MODE_ENABLED = "unlimited_nametag_mode_enabled";
@@ -1,13 +1,16 @@
package me.neznamy.tab.shared.backend;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import org.jetbrains.annotations.NotNull;
public interface BackendPlatform extends Platform {
public interface BackendPlatform<T> extends Platform<T> {
@Override
@NotNull default GroupManager detectPermissionPlugin() {
@@ -18,4 +21,33 @@ public interface BackendPlatform extends Platform {
}
default RedisSupport getRedisSupport() { return null; }
@Override
default void registerPlaceholders() {
UniversalPlaceholderRegistry registry = new UniversalPlaceholderRegistry();
PlaceholderManager manager = TAB.getInstance().getPlaceholderManager();
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100,
p -> (int) Math.ceil(((BackendTabPlayer)p).getHealth()));
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500,
p -> ((BackendTabPlayer)p).getDisplayName());
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000,
() -> registry.formatTPS(getTPS()));
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000,
() -> registry.format(getMSPT()));
registry.registerPlaceholders(manager);
}
/**
* Returns server's TPS for {@link TabConstants.Placeholder#TPS} placeholder
*
* @return server's TPS
*/
double getTPS();
/**
* Returns server's MSPT for {@link TabConstants.Placeholder#MSPT} placeholder
*
* @return server's MSPT
*/
double getMSPT();
}
@@ -1,32 +1,55 @@
package me.neznamy.tab.shared.backend;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* TabPlayer extension for backend platforms, which have access to
* more data and can display it.
*/
public abstract class BackendTabPlayer extends TabPlayer {
/**
* Constructs new instance with given parameters
*
* @param player platform-specific player object
* @param uniqueId Player's unique ID
* @param name Player's name
* @param server Player's server
* @param world Player's world
* @param protocolVersion Player's game version
* @param player
* platform-specific player object
* @param uniqueId
* Player's unique ID
* @param name
* Player's name
* @param world
* Player's world
*/
protected BackendTabPlayer(@NotNull Object player, @NotNull UUID uniqueId, @NotNull String name,
@NotNull String server, @NotNull String world, int protocolVersion) {
super(player, uniqueId, name, server, world, protocolVersion, true);
protected BackendTabPlayer(@NotNull BackendPlatform<?> platform, @NotNull Object player, @NotNull UUID uniqueId,
@NotNull String name, @NotNull String world) {
super(platform, player, uniqueId, name, TAB.getInstance().getConfiguration().getServerName(),
world, ViaVersionHook.getInstance().getPlayerVersion(uniqueId, name), true);
}
public abstract void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data);
/**
* Returns player's health for {@link me.neznamy.tab.shared.TabConstants.Placeholder#HEALTH} placeholder.
*
* @return player's health
*/
public abstract double getHealth();
public abstract void updateEntityMetadata(int entityId, @NotNull EntityData data);
/**
* Returns player's display name for {@link me.neznamy.tab.shared.TabConstants.Placeholder#DISPLAY_NAME} placeholder.
*
* @return player's display name
*/
public abstract String getDisplayName();
public abstract void teleportEntity(int entityId, @NotNull Location location);
public abstract void destroyEntities(int... entities);
/**
* Returns player's entity view
*
* @return player's entity view
*/
public abstract EntityView getEntityView();
}
@@ -9,6 +9,4 @@ public class Location {
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
}
@@ -0,0 +1,75 @@
package me.neznamy.tab.shared.backend.entityview;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class DummyEntityView implements EntityView {
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public void destroyEntities(int... entities) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isDestroyPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isTeleportPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isNamedEntitySpawnPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isMovePacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isLookPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int getTeleportEntityId(Object teleportPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int getMoveEntityId(Object movePacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int getSpawnedPlayer(Object playerSpawnPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int[] getDestroyedEntities(Object destroyPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
}
@@ -0,0 +1,36 @@
package me.neznamy.tab.shared.backend.entityview;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public interface EntityView {
void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data);
void updateEntityMetadata(int entityId, @NotNull EntityData data);
void teleportEntity(int entityId, @NotNull Location location);
void destroyEntities(int... entities);
boolean isDestroyPacket(Object packet);
boolean isTeleportPacket(Object packet);
boolean isNamedEntitySpawnPacket(Object packet);
boolean isMovePacket(Object packet);
boolean isLookPacket(Object packet);
int getTeleportEntityId(Object teleportPacket);
int getMoveEntityId(Object movePacket);
int getSpawnedPlayer(Object playerSpawnPacket);
int[] getDestroyedEntities(Object destroyPacket);
}
@@ -110,8 +110,8 @@ public class ArmorStand {
public void spawn(BackendTabPlayer viewer) {
visible = calculateVisibility();
viewer.spawnEntity(entityId, uuid, manager.getArmorStandType(),
new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner), 0, 0),
viewer.getEntityView().spawnEntity(entityId, uuid, manager.getArmorStandType(),
new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner)),
createDataWatcher(property.getFormat(viewer), viewer));
}
@@ -137,10 +137,9 @@ public class ArmorStand {
*/
protected boolean isNameVisiblyEmpty(@NotNull String displayName) {
if (displayName.length() == 0) return true;
if (!displayName.startsWith(EnumChatFormat.COLOR_STRING) && !displayName.startsWith("&") && !displayName.startsWith("#")) return false;
String text = IChatBaseComponent.fromColoredText(displayName).toRawText();
if (text.contains(" ")) text = text.replace(" ", "");
return text.length() == 0;
String rawText = displayName.contains(" ") ? displayName.replace(" ", "") : displayName;
if (!rawText.startsWith(EnumChatFormat.COLOR_STRING) && !rawText.startsWith("&") && !rawText.startsWith("#")) return false;
return IChatBaseComponent.fromColoredText(rawText).toRawText().length() == 0;
}
/**
@@ -179,7 +178,7 @@ public class ArmorStand {
*/
public void updateMetadata() {
for (BackendTabPlayer viewer : asm.getNearbyPlayers()) {
viewer.updateEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer));
viewer.getEntityView().updateEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer));
}
}
@@ -190,7 +189,7 @@ public class ArmorStand {
}
public void sendTeleportPacket(@NotNull BackendTabPlayer viewer) {
viewer.teleportEntity(entityId, new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner), 0, 0));
viewer.getEntityView().teleportEntity(entityId, new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner)));
}
/**
@@ -117,7 +117,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
// creating new delayed task every time someone sneaks can be abused and cause OOM
// RIP 1.8.0
for (ArmorStand as : armorStandArray) {
viewer.destroyEntities(as.getEntityId());
viewer.getEntityView().destroyEntities(as.getEntityId());
}
for (ArmorStand a : armorStandArray) {
a.spawn(viewer);
@@ -173,7 +173,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
*/
public void destroy(@NotNull BackendTabPlayer viewer) {
for (ArmorStand as : armorStandArray) {
viewer.destroyEntities(as.getEntityId());
viewer.getEntityView().destroyEntities(as.getEntityId());
}
unregisterPlayer(viewer);
}
@@ -182,7 +182,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
public void destroy() {
for (BackendTabPlayer viewer : nearbyPlayers) {
for (ArmorStand as : armorStandArray) {
viewer.destroyEntities(as.getEntityId());
viewer.getEntityView().destroyEntities(as.getEntityId());
}
}
nearbyPlayerList.clear();
@@ -203,7 +203,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
public void updateMetadata(@NotNull BackendTabPlayer viewer) {
for (ArmorStand a : armorStandArray) {
viewer.updateEntityMetadata(a.entityId, a.createDataWatcher(a.getProperty().getFormat(viewer), viewer));
viewer.getEntityView().updateEntityMetadata(a.entityId, a.createDataWatcher(a.getProperty().getFormat(viewer), viewer));
}
}
}
@@ -4,6 +4,7 @@ import lombok.Getter;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.types.GameModeListener;
import me.neznamy.tab.shared.features.types.PacketSendListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
@@ -12,8 +13,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public abstract class BackendNameTagX extends NameTagX implements GameModeListener {
public abstract class BackendNameTagX extends NameTagX implements GameModeListener, PacketSendListener {
/** Vehicle manager reference */
@Getter private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
@@ -90,7 +92,7 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
*/
private void spawnArmorStands(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
if (viewer.getVersion().getMinorVersion() < 8) return;
if (target == viewer || isPlayerDisabled(target)) return;
if (target == viewer || isPlayerDisabled(target) || isDead(target)) return;
if (!areInSameWorld(viewer, target)) return;
if (getDistance(viewer, target) <= 48 && canSee(viewer, target) && !target.isVanished())
getArmorStandManager(target).spawn((BackendTabPlayer) viewer);
@@ -156,6 +158,36 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
}
}
@Override
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
BackendTabPlayer player = (BackendTabPlayer) receiver;
if (player.getEntityView().isMovePacket(packet) && !player.getEntityView().isLookPacket(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(player, player.getEntityView().getMoveEntityId(packet));
} else if (player.getEntityView().isTeleportPacket(packet)) {
packetListener.onEntityMove(player, player.getEntityView().getTeleportEntityId(packet));
} else if (player.getEntityView().isNamedEntitySpawnPacket(packet)) {
packetListener.onEntitySpawn(player, player.getEntityView().getSpawnedPlayer(packet));
} else if (player.getEntityView().isDestroyPacket(packet)) {
packetListener.onEntityDestroy(player, player.getEntityView().getDestroyedEntities(packet));
}
}
public void sneak(UUID playerUUID, boolean sneaking) {
TabPlayer p = TAB.getInstance().getPlayer(playerUUID);
if (p == null || isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
() -> getArmorStandManager(p).sneak(sneaking));
}
public void respawn(UUID playerUUID) {
TabPlayer respawned = TAB.getInstance().getPlayer(playerUUID);
if (respawned == null || isPlayerDisabled(respawned)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> getArmorStandManager(respawned).teleport());
}
public int getEntityId(@NotNull TabPlayer player) {
return getEntityId(player.getPlayer());
}
@@ -204,4 +236,6 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
public abstract EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible);
public abstract void runInEntityScheduler(Object entity, Runnable task);
public abstract boolean isDead(TabPlayer player);
}
@@ -13,7 +13,6 @@ import me.neznamy.tab.shared.backend.BackendTabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -99,21 +98,6 @@ public class PacketListener extends TabFeature implements JoinListener, QuitList
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
*
* @param receiver
* packet receiver
* @param entities
* de-spawned entities
*/
public void onEntityDestroy(@NotNull BackendTabPlayer receiver, @NotNull List<Integer> entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
@@ -125,25 +109,12 @@ public class PacketListener extends TabFeature implements JoinListener, QuitList
*/
public void onEntityDestroy(@NotNull BackendTabPlayer receiver, int... entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
*
* @param receiver
* packet receiver
* @param entity
* de-spawned entity
*/
public void onEntityDestroy(@NotNull BackendTabPlayer receiver, int entity) {
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer)) {
BackendArmorStandManager asm = nameTagX.getArmorStandManager(deSpawnedPlayer);
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PACKET_ENTITY_DESTROY,
() -> asm.destroy(receiver));
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer)) {
BackendArmorStandManager asm = nameTagX.getArmorStandManager(deSpawnedPlayer);
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PACKET_ENTITY_DESTROY,
() -> asm.destroy(receiver));
}
}
}
}
@@ -9,12 +9,15 @@ import org.json.simple.JSONObject;
@Data @NoArgsConstructor
public class ChatModifier {
@Nullable private TextColor color;
@Nullable
private TextColor color;
private boolean bold;
private boolean italic;
private boolean underlined;
private boolean strikethrough;
private boolean obfuscated;
@Nullable
private String font;
public ChatModifier(@NotNull ChatModifier modifier) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
@@ -23,10 +26,12 @@ public class ChatModifier {
this.underlined = modifier.underlined;
this.strikethrough = modifier.strikethrough;
this.obfuscated = modifier.obfuscated;
this.font = modifier.font;
}
@SuppressWarnings("unchecked")
public @NotNull JSONObject serialize(boolean rgbSupport) {
@NotNull
public JSONObject serialize(boolean rgbSupport) {
JSONObject json = new JSONObject();
if (color != null) json.put("color", color.toString(rgbSupport));
if (bold) json.put("bold", true);
@@ -34,10 +39,12 @@ public class ChatModifier {
if (underlined) json.put("underlined", true);
if (strikethrough) json.put("strikethrough", true);
if (obfuscated) json.put("obfuscated", true);
if (font != null) json.put("font", font);
return json;
}
public @NotNull String getMagicCodes() {
@NotNull
public String getMagicCodes() {
StringBuilder builder = new StringBuilder();
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
@@ -6,15 +6,13 @@ import lombok.Setter;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.util.ComponentCache;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.TextComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.json.simple.JSONObject;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -28,21 +26,19 @@ public class IChatBaseComponent {
* Component cache maps to avoid large memory allocations as well as
* higher CPU usage when using animations which send the same text on repeat.
*/
private static final ComponentCache<String, IChatBaseComponent> stringCache = new ComponentCache<>(10000, (text, clientVersion) -> {
private static final ComponentCache<String, IChatBaseComponent> stringCache = new ComponentCache<>(1000, (text, clientVersion) -> {
return text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x") || text.contains("<") ?
IChatBaseComponent.fromColoredText(text) : //contains RGB colors
IChatBaseComponent.fromColoredText(text) : //contains RGB colors or font
new IChatBaseComponent(text); //no RGB
});
private static final ComponentCache<IChatBaseComponent, String> serializeCache = new ComponentCache<>(10000,
private static final ComponentCache<IChatBaseComponent, String> serializeCache = new ComponentCache<>(1000,
(component, clientVersion) -> component.toString());
/** Component cache for BungeeCord components */
private static final @NotNull ComponentCache<IChatBaseComponent, Object> bungeeCache =
new ComponentCache<>(10000, IChatBaseComponent::toBungeeComponent0);
public static final String EMPTY_COMPONENT = "{\"text\":\"\"}";
private static final Pattern fontPattern = Pattern.compile("<font:(.*?)>(.*?)</font>");
/** Text of the component */
@Getter @Setter private String text;
@@ -143,11 +139,41 @@ public class IChatBaseComponent {
* text to convert
* @return organized component from colored text
*/
public static @NotNull IChatBaseComponent fromColoredText(@NotNull String originalText) {
@NotNull
public static IChatBaseComponent fromColoredText(@NotNull String originalText) {
if (originalText.isEmpty()) return new IChatBaseComponent("");
String remainingText = originalText;
List<IChatBaseComponent> components = new ArrayList<>();
while (!remainingText.isEmpty()) {
Matcher m = fontPattern.matcher(remainingText);
if (m.find()) {
if (m.start() > 0) {
// Something is before the text with font, process normally
components.addAll(toComponentArray(remainingText.substring(0, m.start()), null));
}
// Process text with font
String match = m.group();
components.addAll(toComponentArray(
match.substring(match.indexOf(">")+1, match.length()-7),
match.substring(6, match.indexOf(">"))
));
// Prepare the rest for next loop
remainingText = remainingText.substring(m.start() + match.length());
} else {
components.addAll(toComponentArray(remainingText, null));
break;
}
}
return new IChatBaseComponent("").setExtra(components);
}
@NotNull
private static List<IChatBaseComponent> toComponentArray(@NotNull String originalText, @Nullable String font) {
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
List<IChatBaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
IChatBaseComponent component = new IChatBaseComponent();
component.modifier.setFont(font);
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == EnumChatFormat.COLOR_CHAR) {
@@ -166,6 +192,7 @@ public class IChatBaseComponent {
components.add(component);
component = new IChatBaseComponent(component);
component.text = null;
component.modifier.setFont(font);
builder = new StringBuilder();
}
switch (format) {
@@ -187,10 +214,12 @@ public class IChatBaseComponent {
case RESET:
component = new IChatBaseComponent();
component.modifier.setColor(new TextColor(EnumChatFormat.WHITE));
component.modifier.setFont(font);
break;
default:
component = new IChatBaseComponent();
component.modifier.setColor(new TextColor(format));
component.modifier.setFont(font);
break;
}
}
@@ -198,8 +227,9 @@ public class IChatBaseComponent {
String hex = text.substring(i+1, i+7);
if (RGBUtils.getInstance().isHexCode(hex)) {
TextColor color;
if (containsLegacyCode(text, i)) {
color = new TextColor(hex, EnumChatFormat.getByChar(text.charAt(i+8)));
EnumChatFormat code = text.length() - i >= 9 ? EnumChatFormat.getByChar(text.charAt(i+8)) : null;
if (code != null && text.charAt(i+7) == '|') {
color = new TextColor(hex, code);
i += 8;
} else {
color = new TextColor(hex);
@@ -212,6 +242,7 @@ public class IChatBaseComponent {
}
component = new IChatBaseComponent();
component.modifier.setColor(color);
component.modifier.setFont(font);
} else {
builder.append(c);
}
@@ -221,21 +252,7 @@ public class IChatBaseComponent {
}
component.setText(builder.toString());
components.add(component);
return new IChatBaseComponent("").setExtra(components);
}
/**
* Returns true if text contains legacy color request at defined RGB index start
*
* @param text
* text to check
* @param i
* current index start
* @return true if legacy color is defined, false if not
*/
private static boolean containsLegacyCode(@NotNull String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
return components;
}
/**
@@ -308,22 +325,6 @@ public class IChatBaseComponent {
return builder.toString();
}
/**
* Converts the component into flat text with used colors (including rgb) and magic codes
*
* @return converted text
*/
public @NotNull String toFlatText() {
StringBuilder builder = new StringBuilder();
if (modifier.getColor() != null) builder.append("#").append(modifier.getColor().getHexCode());
builder.append(modifier.getMagicCodes());
if (text != null) builder.append(text);
for (IChatBaseComponent child : getExtra()) {
builder.append(child.toFlatText());
}
return builder.toString();
}
/**
* Returns the most optimized component based on text. Returns null if text is null,
* organized component if RGB colors are used or simple component with only text field
@@ -336,59 +337,4 @@ public class IChatBaseComponent {
public static @NotNull IChatBaseComponent optimizedComponent(@NotNull String text) {
return stringCache.get(text, null);
}
/**
* Converts this component to adventure component. RGB conversion to
* legacy codes is managed by the platform using adventure components.
*
* @param clientVersion
* Version to create component for
* @return Adventure component from this component.
*/
public @NotNull Component toAdventureComponent(@NotNull ProtocolVersion clientVersion) {
net.kyori.adventure.text.format.TextColor color = null;
if (modifier.getColor() != null) {
if (clientVersion.getMinorVersion() >= 16) {
color = net.kyori.adventure.text.format.TextColor.color(modifier.getColor().getRgb());
} else {
color = net.kyori.adventure.text.format.TextColor.color(modifier.getColor().getLegacyColor().getHexCode());
}
}
Set<TextDecoration> decorations = new HashSet<>();
if (modifier.isBold()) decorations.add(TextDecoration.BOLD);
if (modifier.isItalic()) decorations.add(TextDecoration.ITALIC);
if (modifier.isObfuscated()) decorations.add(TextDecoration.OBFUSCATED);
if (modifier.isStrikethrough()) decorations.add(TextDecoration.STRIKETHROUGH);
if (modifier.isUnderlined()) decorations.add(TextDecoration.UNDERLINED);
return Component.text(text, color, decorations)
.children(getExtra().stream().map(c -> c.toAdventureComponent(clientVersion)).collect(Collectors.toList()));
}
/**
* Converts this component to bungeecord component.
*
* @return BungeeCord component from this component.
*/
public TextComponent toBungeeComponent(@NotNull ProtocolVersion clientVersion) {
return (TextComponent) bungeeCache.get(this, clientVersion);
}
/**
* Converts this component to bungeecord component.
*
* @return BungeeCord component from this component.
*/
private Object toBungeeComponent0(@NotNull ProtocolVersion clientVersion) {
TextComponent textComponent = new TextComponent(text);
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(clientVersion.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (!getExtra().isEmpty()) textComponent.setExtra(
getExtra().stream().map(c -> c.toBungeeComponent(clientVersion)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -1,7 +1,6 @@
package me.neznamy.tab.shared.chat.rgb;
import lombok.Getter;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.rgb.format.BukkitFormat;
import me.neznamy.tab.shared.chat.rgb.format.HtmlFormat;
@@ -16,7 +15,6 @@ import me.neznamy.tab.shared.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.shared.chat.rgb.format.CMIFormat;
import me.neznamy.tab.shared.chat.rgb.format.RGBFormatter;
@@ -36,12 +34,6 @@ public class RGBUtils {
/** Registered gradient patterns */
private final GradientPattern[] gradients;
/** TAB's RGB pattern, used to convert text to bukkit format for boss bar */
private final Pattern tabPattern = Pattern.compile("#[0-9a-fA-F]{6}");
/** RGB pattern for legacy codes */
private final Pattern tabPatternLegacy = Pattern.compile("#[0-9a-fA-F]{6}\\|.");
/**
* Constructs new instance and loads all RGB patterns and gradients
*/
@@ -106,36 +98,6 @@ public class RGBUtils {
return replaced;
}
/**
* Converts TAB's RGB format (#RRGGBB) into bukkit one
* (&amp;x&amp;r&amp;r&amp;g&amp;g&amp;b&amp;b) for modern
* clients (1.16+), for legacy clients it will use the closest color.
*
* @param text
* text to convert
* @param rgbClient
* whether client accepts RGB or not
* @return converted text
*/
public @NotNull String convertToBukkitFormat(@NotNull String text, boolean rgbClient) {
if (!text.contains("#")) return text; //no rgb codes
if (rgbClient) {
//converting random formats to TAB one
String replaced = applyFormats(text);
for (Pattern p : new Pattern[]{tabPatternLegacy, tabPattern}) {
Matcher m = p.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = "&x&" + hexCode.charAt(1) + "&" + hexCode.charAt(2) + "&" + hexCode.charAt(3) + "&" + hexCode.charAt(4) + "&" + hexCode.charAt(5) + "&" + hexCode.charAt(6);
replaced = replaced.replace(hexCode, EnumChatFormat.color(fixed));
}
}
return replaced;
} else {
return convertRGBtoLegacy(text);
}
}
/**
* Converts all hex codes in given string to legacy codes.
* Also removes redundant color codes caused by this operation
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
@@ -11,7 +12,10 @@ public class MiniMessageFormat implements RGBFormatter {
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("<")) return text;
if (
!text.contains("<") // No MiniMessage syntax is used
|| text.contains(EnumChatFormat.COLOR_STRING) // MiniMessage throws errors when using legacy color code
) return text;
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text));
} catch (Throwable ignored) {
@@ -1,41 +0,0 @@
package me.neznamy.tab.shared.command;
import java.util.Arrays;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.command.level2.AnnounceBarCommand;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Handler for "/tab announce" subcommand
*/
public class AnnounceCommand extends SubCommand {
/**
* Constructs new instance and registers subcommands
*/
public AnnounceCommand() {
super("announce", null);
getSubcommands().put("bar", new AnnounceBarCommand());
}
@Override
public void execute(@Nullable TabPlayer sender, @NotNull String[] args) {
if (args.length == 0) {
sendMessage(sender, getMessages().getAnnounceCommandUsage());
return;
}
String arg0 = args[0].toLowerCase();
SubCommand command = getSubcommands().get(arg0);
if (command != null) {
if (command.hasPermission(sender)) {
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
} else {
sendMessage(sender, getMessages().getNoPermission());
}
} else {
sendMessage(sender, getMessages().getAnnounceCommandUsage());
}
}
}
Loaded 100 of 207 files, more files were not shown because too many files have changed in this diff. Show more