Compare commits

..
83 Commits
Author SHA1 Message Date
NEZNAMY 51635a7322 3.3.2 2023-03-31 20:44:09 +02:00
NEZNAMY 481b33ba55 Print error message if plugin message could not be sent 2023-03-31 15:30:30 +02:00
NEZNAMY 22c785fc99 Add error message when plugin message could not be processed 2023-03-31 15:21:53 +02:00
NEZNAMY 42d4159b69 Global PlayerList - Register placeholders in constructor to avoid it being registered as PAPI placeholder 2023-03-31 14:41:21 +02:00
NEZNAMY 6d95a2f06d Fix error on concurrent access 2023-03-31 13:40:50 +02:00
NEZNAMY 7f0b1d8a2e Fix rare error on /btab reload 2023-03-31 13:40:37 +02:00
NEZNAMY a3fecd0fea Unregister tab expansion on unload to fix compatibility with latest PAPI release 2023-03-30 21:17:09 +02:00
NEZNAMY f3d6a54a2a Merge remote-tracking branch 'origin/master' 2023-03-29 17:24:21 +02:00
NEZNAMY 1d434f2557 Add more documentation 2023-03-29 17:24:11 +02:00
NEZNAMY b3500086e4 Merge pull request #856 from zelvalea/master
optimizations in CPUManager
2023-03-28 23:23:48 +02:00
Amoeba bf9202af38 added time constants 2023-03-28 20:31:29 +03:00
Amoeba bd2c683568 fixed happens before and cpu usage statistics 2023-03-28 20:09:51 +03:00
Amoeba c03feaab23 fixed nanosToPercent 2023-03-28 19:44:11 +03:00
Amoeba 9cb9ccbd2a optimize 2023-03-28 19:21:48 +03:00
Callum Seabrook 4606ebd588 Use single thread for tasks and remove ThreadManager from API 2023-03-28 12:32:13 +01:00
Callum Seabrook 679be83c5a Merge pull request #854 from zelvalea/master 2023-03-28 11:51:03 +01:00
NEZNAMY afe868bb95 Fix group update through bridge after recoding group updating 2023-03-28 01:21:29 +02:00
ZelvaLea c9ce3aa23e reduced number of threads in the pool, fixed races 2023-03-28 01:15:04 +03:00
NEZNAMY 11a49c4874 Remove unnecessary full import 2023-03-27 21:21:27 +02:00
NEZNAMY 65d2a0fba9 Remove unused field 2023-03-27 21:19:15 +02:00
NEZNAMY c1219195c7 Remove workarounds for fixing compilation, which are no longer needed 2023-03-26 17:06:57 +02:00
NEZNAMY fca0dfc91b More constant extraction to plugin.yml & bungee.yml 2023-03-26 17:02:32 +02:00
NEZNAMY 7f1cc3063c Move text cutting to 16/32 characters to avoid duplication 2023-03-26 16:53:47 +02:00
NEZNAMY e444c25287 Convert krypton to java 2023-03-26 16:36:51 +02:00
NEZNAMY 476354e407 Change thrown exception 2023-03-25 23:25:07 +01:00
NEZNAMY 9f84d3f22f Remove cpu measurement of chosen methods 2023-03-25 23:22:28 +01:00
NEZNAMY 4608ff360c Simplify 2023-03-25 23:12:48 +01:00
NEZNAMY 68b12e199d Placeholders - Measure per-placeholder cpu usage on join as well 2023-03-25 20:54:53 +01:00
NEZNAMY 4b1151f769 Use defined value instead of duplicating it in build settings 2023-03-25 20:25:27 +01:00
NEZNAMY 6687214f2a Sponge - Add support all the way down to Sponge 5 2023-03-25 19:22:07 +01:00
NEZNAMY a57757258e Sponge - Use API for scoreboards instead of packets (also small bug fix) 2023-03-25 18:41:56 +01:00
NEZNAMY 3a6060c45d Fix display name change on 1.7 2023-03-25 14:29:27 +01:00
NEZNAMY ee375c1b60 Unify tablist management between proxies 2023-03-25 14:22:40 +01:00
NEZNAMY c9d870cca0 Use placeholder refreshing engine for refreshing player groups instead of creating another thread 2023-03-23 14:52:19 +01:00
NEZNAMY 3ad8015cc1 Placeholder manager - Initialize task with the lowest refresh interval immediately instead of re-initializing 2023-03-23 14:19:42 +01:00
NEZNAMY 33609c322f Remove duplicated Field#setAccessible 2023-03-22 00:59:02 +01:00
NEZNAMY 431b89c487 Create constant for "textures" String in skin data 2023-03-22 00:33:16 +01:00
NEZNAMY 35a146992c Remove redundant checks and simplify 2023-03-22 00:27:46 +01:00
NEZNAMY dedee114c4 BungeeCord - Split boss bar handler into 2 classes 2023-03-21 20:07:11 +01:00
NEZNAMY 9d42af0388 Bukkit - Split boss bar handler into 3 classes 2023-03-21 18:39:08 +01:00
NEZNAMY 8f5d81bc33 Remove redundant annotation 2023-03-21 18:38:41 +01:00
NEZNAMY b17f5563dd Remove unused method 2023-03-21 14:09:57 +01:00
NEZNAMY 0ab8bb363a Unlimited NameTags - Force marker tag for 1.8.x and remove config option 2023-03-20 22:33:56 +01:00
NEZNAMY ac5fca86bd Split TabFeature into multiple interfaces based on used methods 2023-03-20 19:36:40 +01:00
NEZNAMY 28c2c2e193 Fix armor stand metadata on spawn 2023-03-20 16:25:37 +01:00
NEZNAMY 904c0b6a17 Fix anti-override for tablist name formatting (and possibly ping spoof & spectator fix) 2023-03-20 16:21:23 +01:00
NEZNAMY f3c31bc92c Fix error 2023-03-20 12:42:42 +01:00
NEZNAMY 6016b84889 BungeeCord - Fix bossbar color & style update 2023-03-19 22:24:13 +01:00
NEZNAMY cfb5cb3a9f Create object for calling bossbar methods 2023-03-19 22:07:45 +01:00
NEZNAMY 1746f6b1d0 Bukkit - Send all tablist packets grouped, ViaVersion splits them properly for 1.7 players 2023-03-19 21:21:33 +01:00
NEZNAMY 9c3979a0d9 More tablist fixes on 1.7 2023-03-19 21:04:52 +01:00
NEZNAMY 810676c69f Bukkit - Fix NPE on <1.8 servers 2023-03-19 16:44:55 +01:00
NEZNAMY d0892a245a BungeeCord - Fix NPE kick on 1.7 2023-03-19 16:40:53 +01:00
NEZNAMY f539b1b93b BungeeCord - Split TabList class into 3 based on version & make 1.7 actually work (potential future use?) 2023-03-19 14:50:02 +01:00
NEZNAMY adfa64a29f Scoreboard - Fix unregistering teams of lines that aren't displayed due to placeholder output being empty 2023-03-19 14:07:44 +01:00
NEZNAMY d4f86ee79d Remove redundant declaration 2023-03-19 13:43:06 +01:00
NEZNAMY 6ebe31b737 Add @NonNull / @Nullable annotations to TeamManager (#843) 2023-03-19 13:38:58 +01:00
NEZNAMY dcbd5e62d4 Fix packet un-cancel breaking PetFix (#842) 2023-03-19 13:26:24 +01:00
NEZNAMY a7e6e49363 Fix #845 (1.7 requires non-null title in scoreboard unregister action) 2023-03-19 12:52:56 +01:00
NEZNAMY d115257ff7 Move DisplaySlot class inside Scoreboard 2023-03-18 23:53:49 +01:00
NEZNAMY d578be276c Remove duplicated caches 2023-03-18 22:35:16 +01:00
NEZNAMY e338f57441 Completely get rid of PacketBuilder class 2023-03-18 22:25:05 +01:00
Callum Seabrook c87321b5b0 Rewrote packet injection for Krypton 2023-03-18 19:37:34 +00:00
NEZNAMY 84bb9b449b Add methods for manipulating entities instead of using sendPacket 2023-03-18 19:22:29 +01:00
NEZNAMY 9d613fc24d Rework tablist packet management to make it more API-style 2023-03-18 17:44:43 +01:00
NEZNAMY 3f2ae3131e Add back a default animation, which is still used in default scoreboard 2023-03-18 00:14:56 +01:00
NEZNAMY 0e9f53bd58 Fix wrong action on objective unregister 2023-03-17 18:20:47 +01:00
NEZNAMY 3981815b63 Create Scoreboard interface for calling scoreboard-related methods 2023-03-17 15:04:55 +01:00
NEZNAMY 3ef99e4e7c Better abstraction of pipeline injection 2023-03-16 15:16:24 +01:00
NEZNAMY 76767a0958 Use API-style managing of scoreboard teams 2023-03-16 13:53:50 +01:00
NEZNAMY 1683943d58 Use API-style managing of scoreboard objectives 2023-03-16 01:07:21 +01:00
NEZNAMY cc0dbe8623 Move sendPluginMessage method to ProxyTabPlayer 2023-03-16 00:03:13 +01:00
NEZNAMY b056251e07 Remove calculating of packets sent by the plugin 2023-03-15 23:50:23 +01:00
NEZNAMY 3b352c66e2 Change way how scoreboard score and display objective packets are sent 2023-03-15 23:31:30 +01:00
NEZNAMY b4f0ea73c1 Supplier with Exception is Callable 2023-03-15 21:43:27 +01:00
NEZNAMY 1c683788b5 BungeeCord - Create cleaner methods for player info action conversion 2023-03-15 21:29:58 +01:00
NEZNAMY 250597df2d Sponge 8 - Fix class cast exception 2023-03-15 21:07:20 +01:00
NEZNAMY 0d2d1d5aaa Sponge 8 - Use direct code instead of reflection for entity teleport packet 2023-03-15 21:00:42 +01:00
NEZNAMY 061375d1e7 3.3.2-SNAPSHOT 2023-03-15 21:00:14 +01:00
NEZNAMY 5b05614305 Remove unused field 2023-03-15 20:13:29 +01:00
NEZNAMY 69db2e0e53 Use API style code for bossbars since packets were not used anymore 2023-03-15 20:03:10 +01:00
NEZNAMY 00ef364324 Use APIs for header/footer instead of packets where possible (rip bukkit) 2023-03-15 16:13:31 +01:00
NEZNAMY de68312b6b Use APIs for chat messages instead of packets 2023-03-15 14:36:25 +01:00
223 changed files with 5913 additions and 6303 deletions

No files matched your search

@@ -34,19 +34,6 @@ public interface ArmorStand {
* @return property for armor stand's name
*/
Property getProperty();
/**
* Teleports armor stand to owner's current location for everyone in range
*/
void teleport();
/**
* Teleports armor stand to owner's current location for specified player
*
* @param viewer
* player to send packet to
*/
void teleport(TabPlayer viewer);
/**
* Updates armor stand's name if needed
@@ -67,12 +54,4 @@ public interface ArmorStand {
* @return entity ID of this armor stand
*/
int getEntityId();
/**
* Spawns armor stand for specified player
*
* @param viewer
* player to spawn for
*/
void spawn(TabPlayer viewer);
}
@@ -0,0 +1,74 @@
package me.neznamy.tab.api;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import java.util.UUID;
public interface BossBarHandler {
/**
* Creates boss bar and sends it to the player
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param title
* BossBar title
* @param progress
* BossBar progress (0-1)
* @param color
* BossBar color
* @param style
* BossBar style
*/
void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
/**
* Updates title
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param title
* New title
*/
void update(@NonNull UUID id, @NonNull String title);
/**
* Updates progress
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param progress
* New progress (0-1)
*/
void update(@NonNull UUID id, float progress);
/**
* Updates style
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param style
* New style
*/
void update(@NonNull UUID id, @NonNull BarStyle style);
/**
* Updates color
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param color
* New color
*/
void update(@NonNull UUID id, @NonNull BarColor color);
/**
* Removes boss bar
*
* @param id
* Unique identifier to match the bossbar with other functions
*/
void remove(@NonNull UUID id);
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.api;
import me.neznamy.tab.api.feature.TabFeature;
import java.util.UUID;
/**
@@ -91,22 +93,6 @@ public interface FeatureManager {
*/
boolean onCommand(TabPlayer sender, String command);
/**
* Internal function that marks objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markObjective();
/**
* Internal function that marks display objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markDisplayObjective();
/**
* Returns array of all currently enabled features
*
@@ -0,0 +1,57 @@
package me.neznamy.tab.api;
import lombok.NonNull;
import java.util.Collection;
public interface Scoreboard {
/**
* Sets objective display slot of specified objective
*
* @param slot
* Display slot
* @param objective
* Objective name
*/
void setDisplaySlot(DisplaySlot slot, @NonNull String objective);
/**
* Sets scoreboard score
* @param objective
* Objective name
* @param player
* Affected player
* @param score
* New score value
*/
void setScore(@NonNull String objective, @NonNull String player, int score);
/**
* Removes scoreboard score
*
* @param objective
* Objective to remove from
* @param player
* Player to remove from sidebar
*/
void removeScore(@NonNull String objective, @NonNull String player);
void registerObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
void unregisterObjective(@NonNull String objectiveName);
void updateObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
void registerTeam(@NonNull String name, String prefix, String suffix, String visibility,
String collision, Collection<String> players, int options);
void unregisterTeam(@NonNull String name);
void updateTeam(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options);
enum DisplaySlot {
PLAYER_LIST, SIDEBAR, BELOW_NAME
}
}
@@ -7,7 +7,6 @@ import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.api.event.EventBus;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.task.ThreadManager;
import me.neznamy.tab.api.team.TeamManager;
/**
@@ -143,13 +142,6 @@ public abstract class TabAPI {
*/
public abstract void sendConsoleMessage(String message, boolean translateColors);
/**
* Returns TAB's Thread manager, which allows task submitting
*
* @return ThreadManager instance
*/
public abstract ThreadManager getThreadManager();
/**
* Returns TAB's cache file used to store player toggle data
*
@@ -63,42 +63,6 @@ public class TabConstants {
public static final String TELEPORTING_WITHER = "Teleporting wither";
}
/**
* Information why a specific feature sent a packet
* and what the packet sent is supposed to do.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class PacketCategory {
public static final String BOSSBAR_COLOR_STYLE = "BossBar (Color and style)";
public static final String BOSSBAR_PROGRESS = "BossBar (Progress)";
public static final String BOSSBAR_TEXT = "BossBar (Text)";
public static final String BOSSBAR_WITHER_TELEPORT = "BossBar (Teleporting Wither)";
public static final String GLOBAL_PLAYERLIST_LATENCY = "Global PlayerList (Updating latency)";
public static final String GLOBAL_PLAYERLIST_VANISH = "Global PlayerList (Vanishing)";
public static final String LAYOUT_FIXED_SLOTS = "Layout (Fixed slots)";
public static final String LAYOUT_PLAYER_SLOTS = "Layout (Player slots)";
public static final String LAYOUT_LATENCY = "Layout (Updating latency)";
public static final String NAMETAGS_TEAM_REGISTER = "NameTags (Team register)";
public static final String NAMETAGS_TEAM_UNREGISTER = "NameTags (Team unregister)";
public static final String NAMETAGS_TEAM_UPDATE = "NameTags (Team update)";
public static final String SORTING_CHANGING_TEAM_NAME = "Sorting (Changing team name)";
public static final String SCOREBOARD_TITLE = "Scoreboard (Title)";
public static final String SCOREBOARD_LINES = "Scoreboard (Lines)";
public static final String UNLIMITED_NAMETAGS_OFFSET_CHANGE = "Unlimited NameTags (Changing offset)";
public static final String UNLIMITED_NAMETAGS_SPAWN = "Unlimited NameTags (Spawning)";
public static final String UNLIMITED_NAMETAGS_DESPAWN = "Unlimited NameTags (Despawning)";
public static final String UNLIMITED_NAMETAGS_TELEPORT = "Unlimited NameTags (Teleporting)";
public static final String UNLIMITED_NAMETAGS_SNEAK = "Unlimited NameTags (Sneaking)";
public static final String UNLIMITED_NAMETAGS_METADATA = "Unlimited NameTags (Metadata)";
}
/**
* Permission nodes used by the plugin
*/
@@ -273,8 +237,12 @@ public class TabConstants {
return "BossBar-progress-" + name;
}
public static String bossBarColorStyle(String name) {
return "BossBar-color-style-" + name;
public static String bossBarColor(String color) {
return "BossBar-color-" + color;
}
public static String bossBarStyle(String style) {
return "BossBar-style-" + style;
}
}
@@ -1,393 +0,0 @@
package me.neznamy.tab.api;
import java.util.*;
import lombok.NonNull;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
/**
* Abstract class representing a core feature of the plugin.
* <p>
* It receives all kinds of events and can react to them.
*/
public abstract class TabFeature {
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
/** Flag tracking whether disabled server list is a whitelist or blacklist */
private boolean serverWhitelistMode = false;
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledWorlds = new String[0];
/** Flag tracking whether disabled world list is a whitelist or blacklist */
private boolean worldWhitelistMode = false;
/** Players located in currently disabled worlds / servers */
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
/**
* List of methods overridden (implemented) by each feature.
* If a method is not overridden, it is not called at all.
* This avoids a massive spam for every single method
* in every single feature in /tab cpu output
*/
private final List<String> methodOverrides = new ArrayList<>();
/**
* Constructs new instance and loads method overrides
*/
protected TabFeature() {
try {
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onCommand");
if (getClass().getMethod("load").getDeclaringClass() != TabFeature.class)
methodOverrides.add("load");
if (getClass().getMethod("unload").getDeclaringClass() != TabFeature.class)
methodOverrides.add("unload");
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onJoin");
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onQuit");
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onWorldChange");
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onServerChange");
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onDisplayObjective");
TabAPI.getInstance().getFeatureManager().markDisplayObjective();
}
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onLoginPacket");
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onObjective");
TabAPI.getInstance().getFeatureManager().markObjective();
}
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPlayerInfo");
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketReceive");
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketSend");
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("refresh");
if (getClass().getMethod("onVanishStatusChange", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onVanishStatusChange");
} catch (NoSuchMethodException e) {
//this will never happen
}
}
/**
* Constructs new instance with given parameters and loads method overrides.
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(@NonNull String configSection) {
this();
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
}
}
/**
* Loads all online players and sends packets
*/
public void load() {
//empty by default
}
/**
* Unloads all players and sends clear packets
*/
public void unload() {
//empty by default
}
/**
* Processes command from player. This is typically a toggle command.
*
* @param sender
* command sender
* @param message
* command line
* @return {@code true} if event should be cancelled, {@code true} if not
*/
public boolean onCommand(TabPlayer sender, String message) {
return false;
}
/**
* Processes join event
*
* @param connectedPlayer
* player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
//empty by default
}
/**
* Processes quit event
*
* @param disconnectedPlayer
* player who disconnected
*/
public void onQuit(TabPlayer disconnectedPlayer) {
//empty by default
}
/**
* Processes world switch
*
* @param changed
* player who switched world
* @param from
* world player changed from
* @param to
* world player changed to
*/
public void onWorldChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes server switch
*
* @param changed
* player who switched server
* @param from
* server player changed from
* @param to
* server player changed to
*/
public void onServerChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes the packet send and returns true if packet should be cancelled
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
//empty by default
}
/**
* Processes login packet, only available on BungeeCord
*
* @param packetReceiver
* player receiving client reset packet
*/
public void onLoginPacket(TabPlayer packetReceiver) {
//empty by default
}
/**
* Processes the packet send
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
//empty by default
}
/**
* Processes the packet send and possibly modifies it
*
* @param receiver
* player receiving packet
* @param info
* received packet
*/
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
//empty by default
}
/**
* Processes raw packet sent by client
*
* @param sender
* packet sender
* @param packet
* packet received
* @return true if false should be cancelled, false if not
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
return false;
}
/**
* Processes raw packet sent to client
*
* @param receiver
* packet receiver
* @param packet
* the packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
//empty by default
}
/**
* Performs refresh of specified player
*
* @param refreshed
* player to refresh
* @param force
* if refresh should be forced despite refresh seemingly not needed
*/
public void refresh(TabPlayer refreshed, boolean force) {
//empty by default
}
/**
* Processes vanish status change of player
*
* @param player
* Player who changed vanish status
*/
public void onVanishStatusChange(TabPlayer player) {
//empty by default
}
/**
* Registers this feature as one using specified placeholders
*
* @param placeholders
* placeholders to add as used in this feature
*/
public void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* Returns {@code true} if world or server is disabled, {@code false} if not
*
* @param server
* server to check
* @param world
* world to check
* @return {@code true} if feature should be disabled, {@code false} if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
/**
* Returns {@code true} if list contains the specified element or element
* ends with {@code "*"} and element meeting that requirement is present,
* {@code false} otherwise.
*
* @param list
* List to check
* @param element
* Element to find
* @return {@code true} if element was found, {@code false} if not
*/
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns {@code true} if method with specified name is overridden
* in the implementation, {@code false} if not
*
* @param method
* Method to check
* @return {@code true} if overridden, {@code false} if not
*/
public boolean overridesMethod(String method) {
return methodOverrides.contains(method);
}
/**
* Returns {@code true} if player is currently located in a server
* or world, which is marked as disabled, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if player is in disabled server / world,
* {@code false} if not
*/
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
/**
* Adds specified player into list of players in disabled
* servers / worlds.
*
* @param p
* Player to add
*/
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
/**
* Removes specified player from list of players in disabled
* servers / worlds
*
* @param p
* Player to check
*/
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
/**
* Returns display name of {@link #refresh(TabPlayer, boolean)}
* called for this feature in /tab cpu
*
* @return Display name of refresh function in this feature
*/
public String getRefreshDisplayName() { return null; }
/**
* Returns name of this feature displayed in /tab cpu
*
* @return name of this feature display in /tab cpu
*/
public abstract String getFeatureName();
}
@@ -2,9 +2,11 @@ package me.neznamy.tab.api;
import java.util.UUID;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.api.feature.Refreshable;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
/**
* An interface representing a player
@@ -75,36 +77,6 @@ public interface TabPlayer {
*/
boolean hasPermission(String permission);
/**
* Sends the player a custom universal packet
*
* @param packet
* packet to send
*/
void sendCustomPacket(TabPacket packet);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, TabFeature feature);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, String feature);
/**
* Sends the player a platform-specific packet
*
@@ -174,7 +146,7 @@ public interface TabPlayer {
* new raw value
* @return {@code true} if value changed / did not exist, {@code false} if value did not change
*/
boolean setProperty(TabFeature feature, String identifier, String rawValue);
boolean setProperty(Refreshable feature, String identifier, String rawValue);
/**
* Loads property from config using standard property loading algorithm
@@ -183,7 +155,7 @@ public interface TabPlayer {
* property name to load
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property);
boolean loadPropertyFromConfig(Refreshable feature, String property);
/**
* Loads property from config using standard property loading algorithm. If the property is
@@ -195,7 +167,7 @@ public interface TabPlayer {
* value to use if property is not defined in config
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
boolean loadPropertyFromConfig(Refreshable feature, String property, String ifNotSet);
/**
* Returns name of player's scoreboard team or {@code null} if NameTag feature is disabled
@@ -295,4 +267,33 @@ public interface TabPlayer {
* @return player's game profile name as seen by other players
*/
String getNickname();
/**
* Sets header and footer to specified values
*
* @param header
* Header to use
* @param footer
* Footer to use
*/
void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer);
/**
* Returns scoreboard interface for calling scoreboard-related methods
*
* @return scoreboard interface for calling scoreboard-related methods
*/
Scoreboard getScoreboard();
/**
* Returns TabList interface for calling tablist-related methods
* @return TabList interface for calling tablist-related methods
*/
TabList getTabList();
/**
* Returns handler for calling bossbar-related methods
* @return handler for calling bossbar-related methods
*/
BossBarHandler getBossBarHandler();
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to command preprocess event
* possibly cancelling it.
*/
public interface CommandListener {
/**
* Called when player is about to run a command
*
* @param sender
* Command sender
* @param message
* Command line including /
* @return Whether the command should be cancelled or not
*/
boolean onCommand(TabPlayer sender, String message);
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.UUID;
/**
* Interface for features listening to display name change
*/
public interface DisplayNameListener {
/**
* Called when player is about to receive a packet for changing display name
*
* @param packetReceiver
* Player who is about to receive the packet
* @param id
* Affected entry
* @param displayName
* Display name in the packet
* @return New display name to write to the packet
*/
IChatBaseComponent onDisplayNameChange(TabPlayer packetReceiver, UUID id, IChatBaseComponent displayName);
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Listener for features listening to display objective packet.
*/
public interface DisplayObjectiveListener {
/**
* Called when player is about to receive display objective packet
*
* @param receiver
* Packet receiver
* @param slot
* Objective slot
* @param objective
* Objective name
*/
void onDisplayObjective(TabPlayer receiver, int slot, String objective);
}
@@ -0,0 +1,23 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Interface for features listening to tablist entry add
*/
public interface EntryAddListener {
/**
* Called when player is about to receive packet for adding a tablist entry
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of added player
* @param name
* Username of added player
*/
void onEntryAdd(TabPlayer packetReceiver, UUID id, String name);
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Interface for features listening to gamemode change packet
*/
public interface GameModeListener {
/**
* Called when player receives packet to change gamemode
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of affected player
* @param gameMode
* gamemode in the packet
* @return New gamemode to write to the packet
*/
int onGameModeChange(TabPlayer packetReceiver, UUID id, int gameMode);
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to player join event
*/
public interface JoinListener {
/**
* Called when player connects to the server. The player is
* already added to online player list.
*
* @param connectedPlayer
* Player who connected
*/
void onJoin(TabPlayer connectedPlayer);
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Called when a packet to change player's latency is sent.
*/
public interface LatencyListener {
/**
* Called when a player receives a packet for latency change.
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of affected player
* @param latency
* Latency in the packet
* @return New latency
*/
int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency);
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.feature;
/**
* Interface for features looking to load players on plugin enable
*/
public interface Loadable {
/**
* Called on plugin (re)load
*/
void load();
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to Login packet
* usually sent by proxy on server switch
*/
public interface LoginPacketListener {
/**
* Called when player receives Login packet
*
* @param packetReceiver
* Player who received the packet
*/
void onLoginPacket(TabPlayer packetReceiver);
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Listener for features listening to objective packet.
*/
public interface ObjectiveListener {
/**
* Called when player is about to receive display objective packet
*
* @param receiver
* Packet receiver
* @param action
* Packet action (0 = register, 1 = unregister, 2 = update)
* @param objective
* Objective name
*/
void onObjective(TabPlayer receiver, int action, String objective);
}
@@ -0,0 +1,22 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to packets sent by players
*/
public interface PacketReceiveListener {
/**
* Called when a player is about to send a packet to the server
*
* @param sender
* Packet sender
* @param packet
* The packet
* @return Whether the packet should be cancelled or not
* @throws ReflectiveOperationException
* If thrown by reflective operation
*/
boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException;
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to packets sent to players
*/
public interface PacketSendListener {
/**
* Called when a player is about to receive a packet from the server
*
* @param receiver
* Player who is about to receive a packet
* @param packet
* The packet
* @throws ReflectiveOperationException
* If thrown by reflective operation
*/
void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException;
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to player quit event
*/
public interface QuitListener {
/**
* Called when player disconnected from the server. The player is
* still present in online player list.
*
* @param disconnectedPlayer
* Player who disconnected
*/
void onQuit(TabPlayer disconnectedPlayer);
}
@@ -0,0 +1,48 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import java.util.Collection;
/**
* Interface for features periodically refreshing visuals
*/
public interface Refreshable {
/**
* Called when a placeholder used by this feature changes value
*
* @param refreshed
* Player which a placeholder changed value for
* @param force
* Whether refresh should be forced
*/
void refresh(TabPlayer refreshed, boolean force);
/**
* Returns display name of {@link #refresh(TabPlayer, boolean)}
* called for this feature in /tab cpu
*
* @return Display name of refresh function in this feature
*/
String getRefreshDisplayName();
/**
* Registers this feature as one using specified placeholders
*
* @param placeholders
* placeholders to add as used in this feature
*/
default void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* Returns name of this feature displayed in /tab cpu
*
* @return name of this feature display in /tab cpu
*/
String getFeatureName();
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to players switching servers
*/
public interface ServerSwitchListener {
/**
* Called when player switched server
*
* @param changed
* Player who changed server
* @param from
* Name of previous server
* @param to
* Name of new server
*/
void onServerChange(TabPlayer changed, String from, String to);
}
@@ -0,0 +1,136 @@
package me.neznamy.tab.api.feature;
import java.util.*;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
/**
* Abstract class representing a core feature of the plugin.
*/
public abstract class TabFeature {
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
/** Flag tracking whether disabled server list is a whitelist or blacklist */
private boolean serverWhitelistMode = false;
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledWorlds = new String[0];
/** Flag tracking whether disabled world list is a whitelist or blacklist */
private boolean worldWhitelistMode = false;
/** Players located in currently disabled worlds / servers */
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
protected TabFeature() {}
/**
* Constructs new instance with given parameters and loads method overrides.
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(@NonNull String configSection) {
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
}
}
/**
* Returns {@code true} if world or server is disabled, {@code false} if not
*
* @param server
* server to check
* @param world
* world to check
* @return {@code true} if feature should be disabled, {@code false} if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
/**
* Returns {@code true} if list contains the specified element or element
* ends with {@code "*"} and element meeting that requirement is present,
* {@code false} otherwise.
*
* @param list
* List to check
* @param element
* Element to find
* @return {@code true} if element was found, {@code false} if not
*/
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns {@code true} if player is currently located in a server
* or world, which is marked as disabled, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if player is in disabled server / world,
* {@code false} if not
*/
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
/**
* Adds specified player into list of players in disabled
* servers / worlds.
*
* @param p
* Player to add
*/
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
/**
* Removes specified player from list of players in disabled
* servers / worlds
*
* @param p
* Player to check
*/
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
/**
* Returns name of this feature displayed in /tab cpu
*
* @return name of this feature display in /tab cpu
*/
public abstract String getFeatureName();
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.feature;
/**
* Interface for features looking to unload players on plugin disable
*/
public interface UnLoadable {
/**
* Called on plugin unload
*/
void unload();
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to vanish status change
*/
public interface VanishListener {
/**
* Called when player's vanish status changes. New status
* is available in {@link TabPlayer#isVanished()}.
*
* @param player
* Player who changed vanish status
*/
void onVanishStatusChange(TabPlayer player);
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to players switching worlds
*/
public interface WorldSwitchListener {
/**
* Called when player switched world
*
* @param changed
* Player who changed world
* @param from
* Name of previous world
* @param to
* Name of new world
*/
void onWorldChange(TabPlayer changed, String from, String to);
}
@@ -5,10 +5,10 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.Refreshable;
import me.neznamy.tab.api.TabPlayer;
public interface PlaceholderManager {
public interface PlaceholderManager extends Refreshable {
/**
* Registers a server placeholder (placeholder with same output for all players)
@@ -43,7 +43,7 @@ public interface PlaceholderManager {
* @param feature
* feature using the placeholder
*/
void addUsedPlaceholder(String identifier, TabFeature feature);
void addUsedPlaceholder(String identifier, Refreshable feature);
String findReplacement(String placeholder, String output);
@@ -1,255 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.BiFunctionWithException;
import java.util.HashMap;
import java.util.Map;
/**
* A class for packet building, methods are overridden in
* platform-specific builders which extend this class to build actual packets.
* Packets which don't have a method override are forwarded into
* sendPacket method, where they are handled using platform's API instead.
*/
public class PacketBuilder {
/** Function map turning custom packet class into platform-specific packets */
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
/**
* Constructs new instance and fills {@link #buildMap} with abstract build methods
* of packets present on all platforms.
*/
public PacketBuilder() {
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
}
/**
* Converts custom packet into platform-specific packet by calling a function from
* {@link #buildMap}.
*
* @param packet
* Packet to build
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if reflection fails
*/
public Object build(@NonNull TabPacket packet, @NonNull ProtocolVersion clientVersion) throws ReflectiveOperationException {
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
}
/**
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Cuts given string to specified character length (or length-1 if last character is a color character)
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
* RGB codes are converted into legacy, since cutting is only needed for &lt;1.13.
* If {@code string} is {@code null}, empty string is returned.
*
* @param string
* String to cut
* @param length
* Length to cut to
* @return string cut to {@code length} characters
*/
public String cutTo(String string, int length) {
if (string == null) return "";
String legacyText = string;
if (string.contains("#")) {
//converting RGB to legacy colors
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
}
if (legacyText.length() <= length) return legacyText;
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
} else {
return legacyText.substring(0, length);
}
}
/**
* 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 cut to {@code length} characters if
* needed and 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-
*/
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return cutTo(text, length);
}
}
/**
* Converts platform-specific instance of player info packet into
* {@link PacketPlayOutPlayerInfo} object.
*
* @param packet
* platform-specific info packet
* @param clientVersion
* Version of client receiving the packet
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return null;
}
/**
* Converts platform-specific instance of objective packet into
* {@link PacketPlayOutScoreboardObjective} object.
*
* @param packet
* platform-specific objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
return null;
}
/**
* Converts platform-specific instance of display objective packet into
* {@link PacketPlayOutScoreboardDisplayObjective} object.
*
* @param packet
* platform-specific display objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
return null;
}
}
@@ -1,175 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private Action action;
/** BossBar title */
private String name;
/** BossBar progress (0-1)*/
private float pct;
/** BossBar color */
private BarColor color;
/** BossBar style */
private BarStyle overlay;
/** Darken screen flag */
private boolean darkenScreen;
/** Play music flag */
private boolean playMusic;
/** Create fog flag */
private boolean createWorldFog;
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param pct
* BossBar progress
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name, float pct, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.ADD;
this.id = id;
this.name = name;
this.pct = pct;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#REMOVE} action
*
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(@NonNull UUID id) {
this.action = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PCT} action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(@NonNull UUID id, float pct) {
this.action = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_NAME} action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name) {
this.action = Action.UPDATE_NAME;
this.id = id;
this.name = name;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_STYLE} action
*
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PROPERTIES} action
*
* @param darkenScreen
* Darken screen flag
* @param playMusic
* Play music flag
* @param createWorldFog
* Create fog flag
*/
public PacketPlayOutBoss(@NonNull UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.action = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
this.playMusic = playMusic;
this.createWorldFog = createWorldFog;
}
/**
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
* <p>
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
* to the final value.
*
* @return the bitmask
*/
public byte getFlags() {
byte value = 0;
if (darkenScreen) value += 1;
if (playMusic) value += 2;
if (createWorldFog) value += 4;
return value;
}
/**
* An enum representing all valid boss packet actions.
* Calling ordinal() will return action's network ID.
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}
@@ -1,29 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
@NonNull private final IChatBaseComponent message;
/** Message position */
@NonNull private final ChatMessageType type;
/**
* An enum representing positions of a chat message
* Calling ordinal() will return type's network ID.
*/
public enum ChatMessageType {
CHAT,
SYSTEM,
GAME_INFO
}
}
@@ -1,192 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.*;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Map for faster EnumSet creation */
private static final EnumMap<EnumPlayerInfoAction, EnumSet<EnumPlayerInfoAction>> actionToEnumSetMap =
new EnumMap<EnumPlayerInfoAction, EnumSet<EnumPlayerInfoAction>>(EnumPlayerInfoAction.class) {{
for (EnumPlayerInfoAction action : EnumPlayerInfoAction.values()) {
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
EnumSet<EnumPlayerInfoAction> set = EnumSet.allOf(EnumPlayerInfoAction.class);
set.remove(EnumPlayerInfoAction.REMOVE_PLAYER);
put(action, set);
} else {
put(action, EnumSet.of(action));
}
}
}};
/** Packet actions */
private final EnumSet<EnumPlayerInfoAction> actions;
/** List of affected entries */
private final List<PlayerInfoData> entries;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
this(action, Arrays.asList(entries));
}
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
this.actions = actionToEnumSetMap.get(action);
this.entries = entries;
}
/**
* Constructs new instance with given parameters.
*
* @param actions
* Packet actions
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumSet<EnumPlayerInfoAction> actions, @NonNull PlayerInfoData... entries) {
this.actions = actions;
this.entries = Arrays.asList(entries);
}
/**
* A subclass representing player list entry
*/
@Data @AllArgsConstructor
public static class PlayerInfoData {
/** Real name of affected player */
private String name;
/** Player UUID */
@NonNull private UUID uniqueId;
/** Player's skin, null for empty skin */
private Skin skin;
/** I have no idea what this is */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
private IChatBaseComponent displayName;
/** Chat session ID */
private UUID chatSessionId;
/** Player's chat signing key */
private Object profilePublicKey;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_GAME_MODE} action
*
* @param uniqueId
* Player's uuid
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(@NonNull UUID uniqueId, EnumGamemode gameMode) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
*
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
*/
public PlayerInfoData(@NonNull UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
*
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
*/
public PlayerInfoData(@NonNull UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
*
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(@NonNull UUID uniqueId) {
this.uniqueId = uniqueId;
}
}
/**
* En enum representing packet action
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
}
/**
* An enum representing GameMode
*/
public enum EnumGamemode {
NOT_SET,
SURVIVAL,
CREATIVE,
ADVENTURE,
SPECTATOR;
/** Value array to iterate over to avoid array creations on each call */
public static final EnumGamemode[] VALUES = values();
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
@NonNull private final IChatBaseComponent header;
/** TabList footer */
@NonNull private final IChatBaseComponent footer;
/**
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(@NonNull String header, @NonNull String footer) {
this.header = IChatBaseComponent.optimizedComponent(header);
this.footer = IChatBaseComponent.optimizedComponent(footer);
}
}
@@ -1,22 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
* Display slot.
* 0 = PlayerList,
* 1 = SideBar,
* 2 = BelowName.
*/
private final int slot;
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
}
@@ -1,55 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutScoreboardObjective implements TabPacket {
/**
* Packet action.
* 0 = register,
* 1 = unregister,
* 2 = update title
*/
private final int action;
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Constructs new packet with given objective name and 1 (unregister) action.
*
* @param objectiveName
* objective name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(@NonNull String objectiveName) {
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
this.action = 1;
this.renderType = null;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
*/
public enum EnumScoreboardHealthDisplay {
INTEGER, HEARTS
}
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
@NonNull private final Action action;
/** Objective name */
@NonNull private final String objectiveName;
/** Affected player */
@NonNull private final String player;
/** Player's score */
private final int score;
/**
* An enum representing action types
*/
public enum Action {
CHANGE,
REMOVE
}
}
@@ -1,180 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Up to 16 characters long team name.
* It is used to sort players alphabetically in TabList by team names.
* Team-less players appear on top, sorted by usernames.
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
@NonNull private final String name;
/** Team prefix, limited to 16 characters, on &lt;1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on &lt;1.13 */
private String playerSuffix;
/**
* NameTag visibility rule since 1.8. Possible options are:
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
*/
private String nameTagVisibility;
/**
* Collision rule added in 1.9. Possible options are:
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
*/
private String collisionRule;
/**
* Team color field added in 1.13. It determines name color,
* prefix and suffix color start.
*/
private EnumChatFormat color;
/**
* Affected entities. For players, it is their name, for other entities their UUID
* Player name length is limited to 16 characters for &lt;1.8, 40 characters since 1.8.
* Entity UUID option was added in 1.8.
*/
@NonNull private Collection<String> players = Collections.emptyList();
/**
* Packet action.
* 0 = create team,
* 1 = remove team,
* 2 = update team info,
* 3 = add entries,
* 4 = remove entries
*/
private final int action;
/**
* Bit mask.
* 0x01: Allow friendly fire,
* 0x02: Can see invisible players on the same team.
*/
private int options;
/**
* Constructs new instance with given parameters. Private constructor used
* internally to validate team name and set action.
*
* @param action
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
* 3 = add entries, 4 = remove entries)
* @param name
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
private PacketPlayOutScoreboardTeam(int action, @NonNull String name) {
Preconditions.checkMaxLength(name, 16, "team name");
this.action = action;
this.name = name;
}
/**
* Constructs new instance with given parameters and 0 (CREATE) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param players
* Affected entities
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix, String visibility,
String collision, @NonNull Collection<String> players, int options) {
this(0, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.players = players;
this.options = options;
}
/**
* Constructs new instance with given parameters and 1 (REMOVE) action
*
* @param team
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team) {
this(1, team);
}
/**
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix,
String visibility, String collision, int options) {
this(2, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.options = options;
}
/**
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
*
* @param team
* Team name, up to 16 characters long
* @param players
* Affected entities
* @param add
* {@code true} if players should be added, {@code false} if removed from team
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
this.players = players;
}
}
@@ -1,17 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* Interface representing a packet that can be sent to client independently of platform.
* It is technically empty, existence is only present to restrict type access
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
* be called with an actual packet.
*/
public interface TabPacket {
/**
* Returns user-friendly string representation of the packet
*
* @return user-friendly string representation of the packet
*/
String toString();
}
@@ -1,8 +1,9 @@
package me.neznamy.tab.api.protocol;
package me.neznamy.tab.api.tablist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
/**
* Class representing a minecraft skin as a value - signature pair.
@@ -13,6 +14,6 @@ public class Skin {
/** Skin value */
@NonNull private final String value;
/** Skin signature, can be null */
private final String signature;
/** Skin signature */
@Nullable private final String signature;
}
@@ -0,0 +1,34 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
public interface TabList {
String TEXTURES_PROPERTY = "textures";
void removeEntry(@NonNull UUID entry);
void removeEntries(@NonNull Collection<UUID> entries);
void updateDisplayName(@NonNull UUID entry, @Nullable IChatBaseComponent displayName);
void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries);
void updateLatency(@NonNull UUID entry, int latency);
void updateLatencies(@NonNull Map<UUID, Integer> entries);
void updateGameMode(@NonNull UUID entry, int gameMode);
void updateGameModes(@NonNull Map<UUID, Integer> entries);
void addEntry(@NonNull TabListEntry entry);
void addEntries(@NonNull Collection<TabListEntry> entries);
}
@@ -0,0 +1,70 @@
package me.neznamy.tab.api.tablist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* A subclass representing player list entry
*/
@Data
@AllArgsConstructor
public class TabListEntry {
/** Player UUID */
@NonNull private UUID uniqueId;
/** Real name of affected player */
@Nullable private String name;
/** Player's skin, null for empty skin */
@Nullable private Skin skin;
/** If player should appear in tablist or not */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private int gameMode;
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
@Nullable private IChatBaseComponent displayName;
/** Player's chat signing key (1.19+), remote chat session (1.19.3+) */
@Nullable private Object chatSession;
@RequiredArgsConstructor
public static class Builder {
@NonNull private UUID uniqueId;
@Nullable private String name;
@Nullable private Skin skin;
private boolean listed;
private int latency;
private int gameMode;
@Nullable private IChatBaseComponent displayName;
@Nullable private Object chatSession;
public Builder name(String name) { this.name = name; return this; }
public Builder skin(Skin skin) { this.skin = skin; return this; }
public Builder listed(boolean listed) { this.listed = listed; return this; }
public Builder latency(int latency) { this.latency = latency; return this; }
public Builder gameMode(int gameMode) { this.gameMode = gameMode; return this; }
public Builder displayName(IChatBaseComponent displayName) { this.displayName = displayName; return this; }
public Builder chatSession(Object chatSession) { this.chatSession = chatSession; return this; }
public TabListEntry build() {
return new TabListEntry(uniqueId, name, skin, listed, latency, gameMode, displayName, chatSession);
}
}
}
@@ -1,96 +0,0 @@
package me.neznamy.tab.api.task;
import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
/**
* Manager for scheduling and executing tasks in TAB's
* main thread to avoid concurrency issues.
*/
public interface ThreadManager {
/**
* Starts a task in new thread and measures how long it took to process
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
/**
* Starts a task in new thread and measures how long it took to process
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
/**
* Runs task in a new thread
*
* @param task
* the task
*/
Future<Void> runTask(Runnable task);
/**
* Starts a new task with defined repeat interval that measures cpu usage
*
* @param intervalMilliseconds
* task interval
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Starts a new task with defined repeat interval
*
* @param intervalMilliseconds
* task interval
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingTask(int intervalMilliseconds, Runnable task);
/**
* Runs task with a delay and measures how long it took to process
*
* @param delayMilliseconds
* how long to run the task after
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Runs task with a delay
*
* @param delayMilliseconds
* how long to run the task after
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
}
@@ -1,6 +1,8 @@
package me.neznamy.tab.api.team;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
public interface TeamManager {
@@ -12,7 +14,7 @@ public interface TeamManager {
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void hideNametag(TabPlayer player);
void hideNametag(@NonNull TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
@@ -22,7 +24,7 @@ public interface TeamManager {
* @param viewer
* player to hide NameTag for
*/
void hideNametag(TabPlayer player, TabPlayer viewer);
void hideNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Makes player's NameTag visible again
@@ -32,7 +34,7 @@ public interface TeamManager {
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void showNametag(TabPlayer player);
void showNametag(@NonNull TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
@@ -42,7 +44,7 @@ public interface TeamManager {
* @param viewer
* player to show NameTag back for
*/
void showNametag(TabPlayer player, TabPlayer viewer);
void showNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
@@ -53,7 +55,7 @@ public interface TeamManager {
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
*/
boolean hasHiddenNametag(TabPlayer player);
boolean hasHiddenNametag(@NonNull TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
@@ -64,7 +66,7 @@ public interface TeamManager {
* player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
boolean hasHiddenNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
@@ -73,7 +75,7 @@ public interface TeamManager {
* @param player
* player to pause team handling of
*/
void pauseTeamHandling(TabPlayer player);
void pauseTeamHandling(@NonNull TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
@@ -81,7 +83,7 @@ public interface TeamManager {
* @param player
* player to resume team handling of
*/
void resumeTeamHandling(TabPlayer player);
void resumeTeamHandling(@NonNull TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
@@ -91,7 +93,7 @@ public interface TeamManager {
* player to check handling status of
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(TabPlayer player);
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
/**
* Forces new team name for the player until this method is called again with null argument and
@@ -102,7 +104,7 @@ public interface TeamManager {
* @param teamName
* forced team name
*/
void forceTeamName(TabPlayer player, String teamName);
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
/**
* Returns forced team name of player or null if not forced
@@ -111,7 +113,7 @@ public interface TeamManager {
* player to check forced team name of
* @return forced team name of player or null if not forced
*/
String getForcedTeamName(TabPlayer player);
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
/**
@@ -122,7 +124,7 @@ public interface TeamManager {
* @param collision
* forced collision rule
*/
void setCollisionRule(TabPlayer player, Boolean collision);
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
@@ -131,7 +133,7 @@ public interface TeamManager {
* player to get forced collision of
* @return forced value or null if not forced
*/
Boolean getCollisionRule(TabPlayer player);
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
@@ -139,23 +141,23 @@ public interface TeamManager {
* @param player
* player to update team data of
*/
void updateTeamData(TabPlayer player);
void updateTeamData(@NonNull TabPlayer player);
void setPrefix(TabPlayer player, String prefix);
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
void setSuffix(TabPlayer player, String suffix);
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
void resetPrefix(TabPlayer player);
void resetPrefix(@NonNull TabPlayer player);
void resetSuffix(TabPlayer player);
void resetSuffix(@NonNull TabPlayer player);
String getCustomPrefix(TabPlayer player);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
String getCustomSuffix(TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
String getOriginalPrefix(TabPlayer player);
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
String getOriginalSuffix(TabPlayer player);
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Toggles nametag visibility view on all players for specified player.
@@ -167,7 +169,7 @@ public interface TeamManager {
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Returns {@code true} if player has hidden nametags by either calling
@@ -178,5 +180,5 @@ public interface TeamManager {
* player to check
* @return {@code true} if hidden, {@code false} if not
*/
boolean hasHiddenNameTagVisibilityView(TabPlayer player);
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
}
@@ -2,9 +2,6 @@ package me.neznamy.tab.api.util;
/**
* An interface for BiFunction which can throw an exception.
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* call build method for packet classes, which may throw an
* exception due to reflection.
* @param <A>
* First function argument
* @param <B>
@@ -1,12 +0,0 @@
package me.neznamy.tab.api.util;
/**
* Supplier that can throw an exception
*
* @param <T>
* Return class type
*/
public interface SupplierWithException<T> {
T get() throws Exception;
}
@@ -11,7 +11,8 @@ tasks {
"name" to rootProject.name,
"version" to project.version,
"description" to project.description,
"website" to rootProject.ext.get("website")!!.toString()
"website" to rootProject.ext.get("website")!!.toString(),
"author" to rootProject.ext.get("author")!!.toString()
))
}
javadoc {
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "3.3.1"
version = "3.3.2"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.shared.TAB;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -27,8 +28,8 @@ public class BukkitEventListener implements Listener {
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId())));
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
@@ -39,9 +40,9 @@ public class BukkitEventListener implements Listener {
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() ->
TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
}
/**
@@ -52,9 +53,9 @@ public class BukkitEventListener implements Listener {
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() ->
TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
}
/**
@@ -1,98 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
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.shared.backend.protocol.PacketPlayOutEntityDestroy;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityMetadata;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityTeleport;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutSpawnEntityLiving;
import org.bukkit.entity.EntityType;
import java.util.UUID;
public class BukkitPacketBuilder extends PacketBuilder {
{
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> PacketPlayOutEntityMetadataStorage.build((PacketPlayOutEntityMetadata) packet));
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> PacketPlayOutEntityTeleportStorage.build((PacketPlayOutEntityTeleport) packet));
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> PacketPlayOutEntityDestroyStorage.build((PacketPlayOutEntityDestroy) packet));
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> PacketPlayOutSpawnEntityLivingStorage.build((PacketPlayOutSpawnEntityLiving) packet));
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> PacketPlayOutPlayerListHeaderFooterStorage.build((PacketPlayOutPlayerListHeaderFooter) packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> PacketPlayOutChatStorage.build((PacketPlayOutChat) packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> PacketPlayOutScoreboardObjectiveStorage.build((PacketPlayOutScoreboardObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> PacketPlayOutScoreboardDisplayObjectiveStorage.build((PacketPlayOutScoreboardDisplayObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> PacketPlayOutScoreboardTeamStorage.build((PacketPlayOutScoreboardTeam) packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> PacketPlayOutScoreboardScoreStorage.build((PacketPlayOutScoreboardScore) packet, version));
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (NMSStorage.getInstance().getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
//1.9+ server or client, handled by bukkit api or ViaVersion
return packet;
}
//<1.9 client and server
return buildBossPacketEntity(packet, clientVersion);
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return PacketPlayOutPlayerInfoStorage.build(packet, clientVersion);
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return PacketPlayOutPlayerInfoStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
return PacketPlayOutScoreboardObjectiveStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
return PacketPlayOutScoreboardDisplayObjectiveStorage.read(nmsPacket);
}
/**
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
*
* @param packet
* packet to build
* @param clientVersion
* client version
* @return entity BossBar packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getAction() == Action.REMOVE) {
return build(new PacketPlayOutEntityDestroy(entityId), clientVersion);
}
DataWatcher w = new DataWatcher();
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.getHelper().setHealth(health);
}
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
w.getHelper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getAction() == Action.ADD) {
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
return build(new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, 0, 0, 0, 0, 0, w), clientVersion);
} else {
return build(new PacketPlayOutEntityMetadata(entityId, w), clientVersion);
}
}
}
@@ -1,38 +1,30 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
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 java.util.*;
import java.util.stream.Collectors;
/**
* Pipeline injection for bukkit
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
/** NMS data storage */
private final NMSStorage nms = NMSStorage.getInstance();
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BukkitChannelDuplexHandler::new;
/**
* Constructs new instance
*/
@@ -42,7 +34,8 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
protected Channel getChannel(TabPlayer player) {
final BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (final IllegalAccessException exception) {
@@ -51,106 +44,124 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
return null;
}
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
@Override
public void onDisplayObjective(TabPlayer player, Object packet) throws IllegalAccessException {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketPlayOutScoreboardDisplayObjectiveStorage.POSITION.getInt(packet),
(String) PacketPlayOutScoreboardDisplayObjectiveStorage.OBJECTIVE_NAME.get(packet));
}
/** Injected player */
private final TabPlayer player;
@Override
public void onObjective(TabPlayer player, Object packet) throws IllegalAccessException {
TAB.getInstance().getFeatureManager().onObjective(player,
PacketPlayOutScoreboardObjectiveStorage.METHOD.getInt(packet),
(String) PacketPlayOutScoreboardObjectiveStorage.OBJECTIVE_NAME.get(packet));
}
@Override
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
try {
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
super.channelRead(context, packet);
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
@Override
public boolean isDisplayObjective(Object packet) {
return PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet);
}
@Override
public boolean isObjective(Object packet) {
return PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet);
}
@Override
public boolean isTeam(Object packet) {
return PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet);
}
@Override
public boolean isPlayerInfo(Object packet) {
return PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet);
}
@Override
public boolean isLogin(Object packet) {
return false;
}
@Override
public void onPlayerInfo(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)PacketPlayOutPlayerInfoStorage.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
} else {
//1.19.2-
actions = Collections.singletonList(PacketPlayOutPlayerInfoStorage.ACTION.get(packet).toString());
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) PacketPlayOutPlayerInfoStorage.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoDataStorage.PlayerInfoData_getProfile.invoke(nmsData);
int gameMode = 0;
int latency = 0;
IChatBaseComponent displayName = null;
if (actions.contains("UPDATE_GAME_MODE") || actions.contains("ADD_PLAYER")) {
gameMode = PacketPlayOutPlayerInfoStorage.gameMode2Int(PlayerInfoDataStorage.PlayerInfoData_GameMode.get(nmsData));
gameMode = TAB.getInstance().getFeatureManager().onGameModeChange(receiver, profile.getId(), gameMode);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_GameMode.set(nmsData, PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode));
}
if (actions.contains("UPDATE_LATENCY") || actions.contains("ADD_PLAYER")) {
latency = PlayerInfoDataStorage.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latency);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains("UPDATE_DISPLAY_NAME") || actions.contains("ADD_PLAYER")) {
Object nmsComponent = PlayerInfoDataStorage.PlayerInfoData_DisplayName.get(nmsData);
displayName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId(), displayName);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_DisplayName.set(nmsData, nms.toNMSComponent(displayName, receiver.getVersion()));
}
if (actions.contains("ADD_PLAYER")) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
if (nms.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
boolean listed = PlayerInfoDataStorage.PlayerInfoData_Listed.getBoolean(nmsData);
Object chatSession = PlayerInfoDataStorage.PlayerInfoData_RemoteChatSession.get(nmsData);
updatedList.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
profile.getId(),
profile,
listed,
latency,
PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode),
nms.toNMSComponent(displayName, receiver.getVersion()),
chatSession));
}
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet) ||
(PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null &&
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet)) {
long time = System.nanoTime();
modifyPlayers(packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
super.write(context, packet, channelPromise);
return;
}
if (PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
}
if (PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
/**
* Removes all real players from team if packet does not come from TAB and reports this to override log
*
* @param packetPlayOutScoreboardTeam
* team packet
* @throws ReflectiveOperationException
* nmsGameMode
*/
@SuppressWarnings("unchecked")
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
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 = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
} else {
newList.add(entry);
}
}
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
/**
* Returns player with matching game profile name. This is different from
* real name when a nick plugin changing names of players is used. If no
* player was found, returns {@code null}.
*
* @param name
* Game profile name
* @return Player with matching game profile name
*/
private TabPlayer getPlayer(String name) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getNickname().equals(name)) return p;
}
return null;
if (nms.is1_19_3Plus()) {
PacketPlayOutPlayerInfoStorage.PLAYERS.set(packet, updatedList);
}
}
@SuppressWarnings("unchecked")
@Override
public void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
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 = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
} else {
newList.add(entry);
}
}
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -6,7 +6,7 @@ import lombok.Setter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
@@ -46,7 +46,6 @@ import java.util.Collection;
@RequiredArgsConstructor
public class BukkitPlatform extends BackendPlatform {
@Getter private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
@Getter private final BukkitPipelineInjector pipelineInjector = NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
/** Plugin instance for registering tasks and events */
@@ -0,0 +1,96 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardScoreStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
import me.neznamy.tab.shared.TabScoreboard;
import java.util.Collection;
/**
* 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>
* #4 - API made scoreboards get saved into the world folder and then
* resent on join with everything that was ever registered to it,
* which is very poor performance-wise, especially since it does
* that in the main server thread. It might also send conflicting
* information, such as putting a player into team they were on
* previously, but are not supposed to be anymore. Removing the plugin
* would cause all visuals to still be present on the server,
* requiring owners to properly clear scoreboards, which is not something
* everyone knows how to do.
*/
public class BukkitScoreboard extends TabScoreboard {
public BukkitScoreboard(TabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(DisplaySlot slot, @NonNull String objective) {
player.sendPacket(PacketPlayOutScoreboardDisplayObjectiveStorage.buildSilent(slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(0, objectiveName, title, hearts, player.getVersion()));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(1, objectiveName, "", false, player.getVersion()));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(2, objectiveName, title, hearts, player.getVersion()));
}
@Override
public void registerTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.register(name, prefix, suffix, visibility, collision, players, options, player.getVersion()));
}
@Override
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.unregister(name));
}
@Override
public void updateTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.update(name, prefix, suffix, visibility, collision, options, player.getVersion()));
}
@Override
public void setScore0(@NonNull String objective, @NonNull String playerName, int score) {
player.sendPacket(PacketPlayOutScoreboardScoreStorage.change(objective, playerName, score));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String playerName) {
player.sendPacket(PacketPlayOutScoreboardScoreStorage.remove(objective, playerName));
}
}
@@ -5,7 +5,6 @@ import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.SupplierWithException;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import org.bstats.bukkit.Metrics;
@@ -19,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Main class for Bukkit platform
@@ -95,14 +95,14 @@ public class BukkitTAB extends JavaPlugin {
}
private NMSStorage getNMSLoader() {
List<SupplierWithException<NMSStorage>> loaders = new ArrayList<>();
List<Callable<NMSStorage>> loaders = new ArrayList<>();
loaders.add(BukkitLegacyNMSStorage::new);
loaders.add(BukkitModernNMSStorage::new);
loaders.add(MojangModernNMSStorage::new);
loaders.add(ThermosNMSStorage::new);
for (SupplierWithException<NMSStorage> loader : loaders) {
for (Callable<NMSStorage> loader : loaders) {
try {
return loader.get();
return loader.call();
} catch (Exception ignored) {}
}
throw new IllegalStateException("Unsupported server version");
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.shared.tablist.BulkUpdateTabList;
import java.util.*;
import java.util.stream.Collectors;
/**
* TabList which support modifying many entries at once
* for significantly better performance. For 1.7 players,
* ViaVersion properly splits the packet into multiple, so
* we don't need to worry about that here.
* <p>
* This class does not support server versions of 1.7 and
* below, because of the massive differences in tablist
* and packet fields.
*/
@RequiredArgsConstructor
public class BukkitTabList extends BulkUpdateTabList {
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
@Override
public void removeEntries(@NonNull Collection<UUID> entries) {
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
try {
player.sendPacket(PacketPlayOutPlayerInfoStorage.newClientboundPlayerInfoRemovePacket.newInstance(new ArrayList<>(entries)));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
} else {
//1.19.2-
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("REMOVE_PLAYER",
entries.stream().map(id ->
new TabListEntry.Builder(id).build()).collect(Collectors.toList()),
player.getVersion())
);
}
}
@Override
public void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_DISPLAY_NAME",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).displayName(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
);
}
@Override
public void updateLatencies(@NonNull Map<UUID, Integer> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_LATENCY",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).latency(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
);
}
@Override
public void updateGameModes(@NonNull Map<UUID, Integer> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_GAME_MODE",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).gameMode(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
);
}
@Override
public void addEntries(@NonNull Collection<TabListEntry> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("ADD_PLAYER",
entries.stream().map(entry ->
new TabListEntry(entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getChatSession())).collect(Collectors.toList()),
player.getVersion())
);
}
}
@@ -2,50 +2,58 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.Scoreboard;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_8;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_9;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBarVia;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarFlag;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
*/
public class BukkitTabPlayer extends ITabPlayer {
@SuppressWarnings("deprecation")
public class BukkitTabPlayer extends BackendTabPlayer {
/** Spigot check */
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
/** Component cache to save CPU when creating components */
private static final ComponentCache<IChatBaseComponent, BaseComponent[]> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ComponentSerializer.parse(component.toString(clientVersion)));
/** Player's NMS handle (EntityPlayer), preloading for speed */
private Object handle;
/** Player's connection for sending packets, preloading for speed */
@Getter private Object playerConnection;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Getter private final Scoreboard scoreboard = new BukkitScoreboard(this);
@Getter private final TabList tabList = new BukkitTabList(this);
@Getter private final BossBarHandler bossBarHandler = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar1_9(this) : getVersion().getMinorVersion() >= 9 ? new BukkitBossBarVia(this) : new BukkitBossBar1_8(this);
/**
* Constructs new instance with given bukkit player and protocol version
@@ -56,7 +64,7 @@ public class BukkitTabPlayer extends ITabPlayer {
* Player's protocol network id
*/
public BukkitTabPlayer(Player p, int protocolVersion) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion, true);
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion);
try {
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
@@ -67,10 +75,7 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public boolean hasPermission(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
return getPlayer().hasPermission(permission);
}
@Override
@@ -87,150 +92,20 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public void sendPacket(Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
long time = System.nanoTime();
try {
if (nmsPacket instanceof PacketPlayOutBoss) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
handle((PacketPlayOutBoss) nmsPacket);
} else {
handleVia((PacketPlayOutBoss) nmsPacket);
}
} else if (nmsPacket instanceof PacketPlayOutChat) {
getPlayer().sendMessage(((PacketPlayOutChat) nmsPacket).getMessage().toLegacyText());
} else {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
}
/**
* Handles PacketPlayOutBoss packet send request using Bukkit API,
* since the API offers everything we need and makes us not need to
* deal with NMS code at all.
*
* @param packet
* packet request to handle using Bukkit API
*/
private void handle(PacketPlayOutBoss packet) {
BossBar bar = bossBars.get(packet.getId());
if (packet.getAction() == PacketPlayOutBoss.Action.ADD) {
if (bossBars.containsKey(packet.getId())) return;
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BarColor.valueOf(packet.getColor().name()),
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
bar.setProgress(packet.getPct());
bossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer());
return;
@Override
public void sendMessage(IChatBaseComponent message) {
if (spigot) {
getPlayer().spigot().sendMessage(componentCache.get(message, version));
} else {
getPlayer().sendMessage(message.toLegacyText());
}
if (bar == null) return; //no idea how
switch (packet.getAction()) {
case REMOVE:
bar.removePlayer(getPlayer());
bossBars.remove(packet.getId());
break;
case UPDATE_PCT:
bar.setProgress(packet.getPct());
break;
case UPDATE_NAME:
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
bar.setColor(BarColor.valueOf(packet.getColor().name()));
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = bossBars.get(packet.getId());
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
/**
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
* 1.8 servers using ViaVersion API instead of using Wither.
*
* @param packet
* packet request to handle using ViaVersion API
*/
private void handleVia(PacketPlayOutBoss packet) {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
switch (packet.getAction()) {
case ADD:
if (viaBossBars.containsKey(packet.getId())) return;
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
packet.getPct(),
BossColor.valueOf(packet.getColor().name()),
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
//fog missing from via API
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
viaBossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer().getUniqueId());
break;
case REMOVE:
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
viaBossBars.remove(packet.getId());
break;
case UPDATE_PCT:
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
break;
case UPDATE_NAME:
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = viaBossBars.get(packet.getId());
//fog missing from via API
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
/**
* Sets BossBar flag to requested target value.
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
}
/**
* Sets BossBar flag to requested target value.
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
}
@Override
@@ -254,7 +129,7 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public Skin getSkin() {
try {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get("textures");
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 Skin(property.getValue(), property.getSignature());
@@ -279,9 +154,81 @@ public class BukkitTabPlayer extends ITabPlayer {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
}
@SuppressWarnings("deprecation")
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer) {
// Method was added to Bukkit API in 1.13.1, however despite that it's just a String one
// Using it would cause high CPU usage and massive memory allocations on RGB & animations
// Send packet instead for performance & older server version support
/*if (TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_13_1.getNetworkId()) {
String bukkitHeader = RGBUtils.getInstance().convertToBukkitFormat(header.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
String bukkitFooter = RGBUtils.getInstance().convertToBukkitFormat(footer.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
getPlayer().setPlayerListHeaderFooter(bukkitHeader, bukkitFooter);
return;
}*/
try {
sendPacket(PacketPlayOutPlayerListHeaderFooterStorage.build(header, footer, version));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void spawnEntity(int entityId, UUID id, Object entityType, Location location, EntityData data) {
try {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void updateEntityMetadata(int entityId, EntityData data) {
try {
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));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void teleportEntity(int entityId, Location location) {
try {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void destroyEntities(int... entities) {
try {
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));
}
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,64 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
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.shared.backend.Location;
import org.bukkit.entity.EntityType;
import java.util.UUID;
/**
* BossBar using wither entity for <1.9 players on <1.9 servers.
* Additional logic, such as teleporting the entity must be done
* separately, as this class does not handle it.
*/
@RequiredArgsConstructor
public class BukkitBossBar1_8 implements BossBarHandler {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
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);
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NonNull UUID id, float progress) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {} // Added in 1.9
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {} // Added in 1.9
@Override
public void remove(@NonNull UUID id) {
player.destroyEntities(id.hashCode());
}
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BossBar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Bossbar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
* to allow 1.8 players, it will handle the entity and teleporting by itself.
*/
@RequiredArgsConstructor
public class BukkitBossBar1_9 implements BossBarHandler {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, me.neznamy.tab.api.bossbar.@NonNull BarColor color, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
if (bossBars.containsKey(id)) return;
BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NonNull UUID id, float progress) {
bossBars.get(id).setProgress(progress);
}
@Override
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
}
@Override
public void remove(@NonNull UUID id) {
bossBars.remove(id).removePlayer(player.getPlayer());
}
}
@@ -0,0 +1,68 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossBar;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
*/
@RequiredArgsConstructor
public class BukkitBossBarVia implements BossBarHandler {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
private final Map<UUID, BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NonNull UUID id, float progress) {
viaBossBars.get(id).setHealth(progress);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
}
@Override
public void remove(@NonNull UUID id) {
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
}
}
@@ -5,7 +5,6 @@ import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
@@ -48,7 +47,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
return text;
}
if (identifier.startsWith("placeholder_")) {
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TabAPI.getInstance().getPlaceholderManager());
}
return values.get(player).get(identifier);
}
@@ -8,6 +8,8 @@ import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.feature.Loadable;
import me.neznamy.tab.api.feature.UnLoadable;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -17,7 +19,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
@@ -26,7 +28,7 @@ import me.neznamy.tab.shared.TAB;
*/
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener {
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
@@ -1,7 +1,9 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.PacketReceiveListener;
import me.neznamy.tab.api.feature.PacketSendListener;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
@@ -23,7 +25,7 @@ import java.util.WeakHashMap;
* Since 1.16 this results in client sending entity use packet twice,
* so we must cancel the 2nd one to prevent double toggle.
*/
public class PetFix extends TabFeature {
public class PetFix extends TabFeature implements PacketReceiveListener, PacketSendListener {
/** NMS Storage reference for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
@@ -5,7 +5,7 @@ import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityTeleport;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -49,7 +49,7 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
if (!line.containsPlayer(p)) continue;
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc.getX(), loc.getY(), loc.getZ(), 0, 0), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
}
}
}
@@ -4,10 +4,13 @@ import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.feature.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 org.bukkit.Bukkit;
import org.bukkit.Location;
@@ -26,7 +29,7 @@ import java.util.stream.Collectors;
* The core class for unlimited NameTag mode on Bukkit
*/
@RequiredArgsConstructor
public class BukkitNameTagX extends BackendNameTagX {
public class BukkitNameTagX extends BackendNameTagX implements PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
@@ -43,46 +46,28 @@ public class BukkitNameTagX extends BackendNameTagX {
super.load();
}
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.PacketPlayInUseEntity_ENTITY.set(packet, getEntityId(attacked));
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(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(receiver, (List<Integer>) entities);
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy(receiver, (int) entities);
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
}
@@ -191,12 +176,12 @@ public class BukkitNameTagX extends BackendNameTagX {
}
@Override
public Object createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible, boolean markerFlag) {
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(nameVisible);
if (markerFlag) datawatcher.getHelper().setArmorStandFlags((byte)16);
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
}
@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -30,7 +31,7 @@ public class EventListener implements Listener {
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || feature.isPlayerDisabled(p)) return;
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
}
/**
@@ -41,7 +42,7 @@ public class EventListener implements Listener {
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isPlayerDisabled(respawned)) return;
feature.getArmorStandManager(respawned).teleport();
@@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -16,7 +17,7 @@ import java.util.Map;
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher {
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> CLASS;
@@ -87,23 +88,25 @@ public class DataWatcher {
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build() throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
public Object build() {
try {
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;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
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,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
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;
@@ -98,7 +94,6 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
PacketPlayOutChatStorage.CLASS = getLegacyClass("PacketPlayOutChat", "Packet3Chat");
NetworkManager = getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
@@ -108,9 +103,6 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
}
if (minorVersion >= 12) {
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) getLegacyClass("ChatMessageType");
}
// DataWatcher
DataWatcher.CLASS = getLegacyClass("DataWatcher");
@@ -1,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
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;
@@ -66,12 +62,6 @@ public class BukkitModernNMSStorage extends NMSStorage {
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
if (minorVersion >= 19) {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSystemChatPacket");
} else {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutChat");
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatMessageType");
}
// DataWatcher
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
@@ -104,7 +94,6 @@ public class BukkitModernNMSStorage extends NMSStorage {
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
PacketPlayOutPlayerInfoStorage.ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$a");
}
if (is1_19_3Plus()) {
@@ -112,7 +101,6 @@ public class BukkitModernNMSStorage extends NMSStorage {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
PacketPlayOutPlayerInfoStorage.RemoteChatSession = Class.forName("net.minecraft.network.chat.RemoteChatSession");
PacketPlayOutPlayerInfoStorage.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
@@ -1,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
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;
@@ -56,12 +52,6 @@ public class MojangModernNMSStorage extends NMSStorage {
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
if (minorVersion >= 19) {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSystemChatPacket");
} else {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundChatPacket");
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatType");
}
// DataWatcher
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
@@ -94,7 +84,6 @@ public class MojangModernNMSStorage extends NMSStorage {
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
PacketPlayOutPlayerInfoStorage.ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$Data");
}
if (is1_19_3Plus()) {
@@ -102,7 +91,6 @@ public class MojangModernNMSStorage extends NMSStorage {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
PacketPlayOutPlayerInfoStorage.RemoteChatSession = Class.forName("net.minecraft.network.chat.RemoteChatSession");
PacketPlayOutPlayerInfoStorage.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
@@ -10,10 +10,6 @@ import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
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;
@@ -75,7 +71,6 @@ public abstract class NMSStorage {
public Class<?> PacketPlayInUseEntity;
public Class<?> PacketPlayInUseEntity$d;
protected Class<Enum> EnumEntityUseAction;
public Field PacketPlayInUseEntity_ENTITY;
public Field PacketPlayInUseEntity_ACTION;
public Class<?> PacketPlayOutEntity;
@@ -129,7 +124,6 @@ public abstract class NMSStorage {
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketPlayOutChatStorage.load(this);
PacketPlayOutPlayerListHeaderFooterStorage.load(this);
PacketPlayOutPlayerInfoStorage.load(this);
PacketPlayOutScoreboardObjectiveStorage.load(this);
@@ -149,7 +143,6 @@ public abstract class NMSStorage {
PacketPlayOutEntity_ENTITYID = getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion >= 7) {
PacketPlayInUseEntity_ENTITY = getFields(PacketPlayInUseEntity, int.class).get(0);
PacketPlayInUseEntity_ACTION = getFields(PacketPlayInUseEntity, EnumEntityUseAction).get(0);
}
}
@@ -1,56 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.util.UUID;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutChatStorage {
public static Class<?> CLASS;
public static Class<Enum> ChatMessageTypeClass;
public static Constructor<?> CONSTRUCTOR;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
try {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, boolean.class);
} catch (NoSuchMethodException e) {
//1.19.0
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, int.class);
}
} else if (nms.getMinorVersion() >= 16) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, ChatMessageTypeClass, UUID.class);
} else if (nms.getMinorVersion() >= 12) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, ChatMessageTypeClass);
} else if (nms.getMinorVersion() >= 8) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, byte.class);
} else if (nms.getMinorVersion() >= 7) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent);
}
}
public static Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object component = nms.toNMSComponent(packet.getMessage(), clientVersion);
if (nms.getMinorVersion() >= 19)
try {
return CONSTRUCTOR.newInstance(component, packet.getType() == PacketPlayOutChat.ChatMessageType.GAME_INFO);
} catch (Exception e) {
//1.19.0
return CONSTRUCTOR.newInstance(component, packet.getType().ordinal());
}
if (nms.getMinorVersion() >= 16)
return CONSTRUCTOR.newInstance(component, Enum.valueOf(ChatMessageTypeClass, packet.getType().toString()), UUID.randomUUID());
if (nms.getMinorVersion() >= 12)
return CONSTRUCTOR.newInstance(component, Enum.valueOf(ChatMessageTypeClass, packet.getType().toString()));
if (nms.getMinorVersion() >= 8)
return CONSTRUCTOR.newInstance(component, (byte) packet.getType().ordinal());
if (nms.getMinorVersion() == 7)
return CONSTRUCTOR.newInstance(component);
return packet;
}
}
@@ -1,8 +1,6 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityDestroy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -10,7 +8,7 @@ import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
public class PacketPlayOutEntityDestroyStorage implements TabPacket {
public class PacketPlayOutEntityDestroyStorage {
/** NMS Fields */
public static Class<?> CLASS;
@@ -34,20 +32,4 @@ public class PacketPlayOutEntityDestroyStorage implements TabPacket {
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public static Object build(PacketPlayOutEntityDestroy packet) throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterTypes()[0] != int.class) {
return CONSTRUCTOR.newInstance(new Object[]{packet.getEntities()});
} else {
//1.17.0
return CONSTRUCTOR.newInstance(packet.getEntities()[0]);
}
}
}
@@ -1,9 +1,7 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.protocol.TabPacket;
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.protocol.PacketPlayOutEntityMetadata;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -12,7 +10,7 @@ import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
public class PacketPlayOutEntityMetadataStorage implements TabPacket {
public class PacketPlayOutEntityMetadataStorage {
/** NMS Fields */
public static Class<?> CLASS;
@@ -35,20 +33,4 @@ public class PacketPlayOutEntityMetadataStorage implements TabPacket {
}
LIST = nms.getFields(CLASS, List.class).get(0);
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public static Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
return CONSTRUCTOR.newInstance(packet.getEntityId(), DataWatcher.packDirty.invoke(((DataWatcher)packet.getDataWatcher()).build()));
} else {
return CONSTRUCTOR.newInstance(packet.getEntityId(), ((DataWatcher)packet.getDataWatcher()).build(), true);
}
}
}
@@ -1,8 +1,7 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityTeleport;
import me.neznamy.tab.shared.backend.Location;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -10,7 +9,7 @@ import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
public class PacketPlayOutEntityTeleportStorage implements TabPacket {
public class PacketPlayOutEntityTeleportStorage {
/** NMS Fields */
public static Class<?> CLASS;
@@ -57,7 +56,7 @@ public class PacketPlayOutEntityTeleportStorage implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public static Object build(PacketPlayOutEntityTeleport packet) throws ReflectiveOperationException {
public static Object build(int entityId, Location location) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -65,18 +64,18 @@ public class PacketPlayOutEntityTeleportStorage implements TabPacket {
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, packet.getEntityId());
ENTITY_ID.set(nmsPacket, entityId);
if (nms.getMinorVersion() >= 9) {
X.set(nmsPacket, packet.getX());
Y.set(nmsPacket, packet.getY());
Z.set(nmsPacket, packet.getZ());
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(packet.getX()*32));
Y.set(nmsPacket, floor(packet.getY()*32));
Z.set(nmsPacket, floor(packet.getZ()*32));
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) (packet.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (packet.getPitch()/360*256));
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
return nmsPacket;
}
@@ -1,17 +1,14 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import com.google.common.collect.Iterables;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutPlayerInfoStorage {
@@ -22,29 +19,21 @@ public class PacketPlayOutPlayerInfoStorage {
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
public static Class<?> ProfilePublicKey;
public static Class<?> ProfilePublicKey$a;
//1.19.3+
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
public static Constructor<?> newRemoteChatSession$Data;
public static Method ClientboundPlayerInfoRemovePacket_getEntries;
public static Method RemoteChatSession$Data_getSessionId;
public static Method RemoteChatSession$Data_getProfilePublicKey;
public static void load(NMSStorage nms) throws NoSuchMethodException {
public static void load(NMSStorage nms) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return;
if (nms.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
CONSTRUCTOR = CLASS.getConstructor(EnumSet.class, Collection.class);
ClientboundPlayerInfoRemovePacket_getEntries = nms.getMethods(ClientboundPlayerInfoRemovePacket, List.class).get(0);
ACTION = nms.getFields(CLASS, EnumSet.class).get(0);
newRemoteChatSession$Data = RemoteChatSession$Data.getConstructor(UUID.class, ProfilePublicKey$a);
RemoteChatSession$Data_getSessionId = nms.getMethods(RemoteChatSession$Data, UUID.class).get(0);
RemoteChatSession$Data_getProfilePublicKey = nms.getMethods(RemoteChatSession$Data, ProfilePublicKey$a).get(0);
} else {
CONSTRUCTOR = CLASS.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = nms.getFields(CLASS, EnumPlayerInfoActionClass).get(0);
@@ -53,56 +42,75 @@ public class PacketPlayOutPlayerInfoStorage {
PlayerInfoDataStorage.load(nms);
}
public static Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public static Object createPacket(String action, Collection<TabListEntry> entries, ProtocolVersion clientVersion) {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
Object nmsPacket;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
if (packet.getActions().contains(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER)) {
return newClientboundPlayerInfoRemovePacket.newInstance(
packet.getEntries().stream().map(PacketPlayOutPlayerInfo.PlayerInfoData::getUniqueId).collect(Collectors.toList())
);
try {
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
EnumSet<?> actions;
if (action.equals("ADD_PLAYER")) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action));
}
packet = CONSTRUCTOR.newInstance(actions, Collections.emptyList());
for (TabListEntry entry : entries) {
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()));
players.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
entry.isListed(),
entry.getLatency(),
int2GameMode(entry.getGameMode()),
nms.toNMSComponent(entry.getDisplayName(), clientVersion),
entry.getChatSession()
));
}
} else {
packet = CONSTRUCTOR.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
for (TabListEntry entry : entries) {
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()));
List<Object> parameters = new ArrayList<>();
if (PlayerInfoDataStorage.newPlayerInfoData.getParameterTypes()[0] == CLASS) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(entry.getChatSession());
players.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(parameters.toArray()));
}
}
Enum[] array = packet.getActions().stream().map(action -> Enum.valueOf(EnumPlayerInfoActionClass, action.toString())).toArray(Enum[]::new);
nmsPacket = CONSTRUCTOR.newInstance(EnumSet.of(array[0], array), Collections.emptyList());
} else {
//1.19.2-
PacketPlayOutPlayerInfo.EnumPlayerInfoAction action = packet.getActions().contains(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER) ?
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
nmsPacket = CONSTRUCTOR.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.toString()), Array.newInstance(nms.EntityPlayer, 0));
PLAYERS.set(packet, players);
return packet;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
List<Object> items = new ArrayList<>();
for (PacketPlayOutPlayerInfo.PlayerInfoData data : packet.getEntries()) {
items.add(PlayerInfoDataStorage.build(nmsPacket, data, clientVersion));
}
PLAYERS.set(nmsPacket, items);
return nmsPacket;
}
public static PacketPlayOutPlayerInfo read(Object nmsPacket) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
if (ClientboundPlayerInfoRemovePacket != null && ClientboundPlayerInfoRemovePacket.isInstance(nmsPacket)) {
List<UUID> entries = (List<UUID>) ClientboundPlayerInfoRemovePacket_getEntries.invoke(nmsPacket);
return new PacketPlayOutPlayerInfo(
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER,
entries.stream().map(PacketPlayOutPlayerInfo.PlayerInfoData::new).collect(Collectors.toList())
);
public static 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");
}
List<PacketPlayOutPlayerInfo.PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) PLAYERS.get(nmsPacket)) {
listData.add(PlayerInfoDataStorage.read(nmsData));
}
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
PacketPlayOutPlayerInfo.EnumPlayerInfoAction[] array = ((EnumSet<?>) ACTION.get(nmsPacket)).stream().map(action ->
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.valueOf(action.toString())).toArray(
PacketPlayOutPlayerInfo.EnumPlayerInfoAction[]::new);
return new PacketPlayOutPlayerInfo(EnumSet.of(array[0], array), listData);
} else {
//1.19.2-
return new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.valueOf(ACTION.get(nmsPacket).toString()), listData);
}
public static int gameMode2Int(Object gameMode) {
switch (String.valueOf(gameMode)) {
case "CREATIVE": return 1;
case "ADVENTURE": return 2;
case "SPECTATOR": return 3;
default: return 0;
}
}
@@ -111,99 +119,25 @@ public class PacketPlayOutPlayerInfoStorage {
public static Class<?> CLASS;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
public static Method PlayerInfoData_isListed;
public static Method PlayerInfoData_getLatency;
public static Method PlayerInfoData_getGamemode;
public static Method PlayerInfoData_getDisplayName;
public static Method PlayerInfoData_getProfilePublicKeyRecord;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
//1.19.3+
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
public static void load(NMSStorage nms) {
if (nms.getMinorVersion() < 8) return;
newPlayerInfoData = CLASS.getConstructors()[0];
PlayerInfoData_getProfile = nms.getMethods(CLASS, GameProfile.class).get(0);
PlayerInfoData_getGamemode = nms.getMethods(CLASS, EnumGamemodeClass).get(0);
PlayerInfoData_getDisplayName = nms.getMethods(CLASS, nms.IChatBaseComponent).get(0);
for (Method m: nms.getMethods(CLASS, int.class)) {
// do not take .hashCode(), which is final
if (!Modifier.isFinal(m.getModifiers())) PlayerInfoData_getLatency = m;
}
PlayerInfoData_Latency = nms.getFields(CLASS, int.class).get(0);
PlayerInfoData_GameMode = nms.getFields(CLASS, EnumGamemodeClass).get(0);
PlayerInfoData_DisplayName = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
if (nms.is1_19_3Plus()) {
PlayerInfoData_getProfilePublicKeyRecord = nms.getMethods(CLASS, RemoteChatSession$Data).get(0);
PlayerInfoData_isListed = nms.getMethods(CLASS, boolean.class).get(0);
} else {
if (nms.getMinorVersion() >= 19) {
PlayerInfoData_getProfilePublicKeyRecord = nms.getMethods(CLASS, ProfilePublicKey$a).get(0);
}
PlayerInfoData_Listed = nms.getFields(CLASS, boolean.class).get(0);
PlayerInfoData_RemoteChatSession = nms.getFields(CLASS, RemoteChatSession$Data).get(0);
}
}
public static Object build(Object nmsPacket, PacketPlayOutPlayerInfo.PlayerInfoData data, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures",
new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
return newPlayerInfoData.newInstance(
data.getUniqueId(),
profile,
data.isListed(),
data.getLatency(),
data.getGameMode() == null ? null : Enum.valueOf(EnumGamemodeClass, data.getGameMode().toString()),
data.getDisplayName() == null ? null : nms.toNMSComponent(data.getDisplayName(), clientVersion),
data.getProfilePublicKey() == null ? null : newRemoteChatSession$Data.newInstance(data.getChatSessionId(), data.getProfilePublicKey()));
} else {
//1.19.2-
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoStorage.CLASS) {
parameters.add(nmsPacket);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : Enum.valueOf(EnumGamemodeClass, data.getGameMode().toString()));
parameters.add(data.getDisplayName() == null ? null : nms.toNMSComponent(data.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(data.getProfilePublicKey());
return newPlayerInfoData.newInstance(parameters.toArray());
}
}
public static PacketPlayOutPlayerInfo.PlayerInfoData read(Object nmsData) throws ReflectiveOperationException {
Object nmsGameMode = PlayerInfoData_getGamemode.invoke(nmsData);
PacketPlayOutPlayerInfo.EnumGamemode gameMode = (nmsGameMode == null) ? null : PacketPlayOutPlayerInfo.EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = PlayerInfoData_getDisplayName.invoke(nmsData);
Skin skin = null;
Collection<Property> textures = profile.getProperties().get("textures");
Property pr = Iterables.getFirst(textures, null);
if (pr != null) {
skin = new Skin(pr.getValue(), pr.getSignature());
}
boolean listed = true;
UUID chatSessionId = null;
Object profilePublicKey;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
listed = (boolean) PlayerInfoData_isListed.invoke(nmsData);
Object remoteChatSession = PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData);
chatSessionId = remoteChatSession == null ? null : (UUID) RemoteChatSession$Data_getSessionId.invoke(remoteChatSession);
profilePublicKey = remoteChatSession == null ? null : RemoteChatSession$Data_getProfilePublicKey.invoke(remoteChatSession);
} else {
//1.19.2-
profilePublicKey = NMSStorage.getInstance().getMinorVersion() >= 19 ? PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData) : null;
}
return new PacketPlayOutPlayerInfo.PlayerInfoData(
profile.getName(),
profile.getId(),
skin,
listed,
(int) PlayerInfoData_getLatency.invoke(nmsData),
gameMode,
nmsComponent == null ? null : new WrappedChatComponent(nmsComponent),
chatSessionId,
profilePublicKey);
}
}
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
@@ -16,25 +16,24 @@ public class PacketPlayOutPlayerListHeaderFooterStorage {
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() < 8) return;
HEADER = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
FOOTER = nms.getFields(CLASS, nms.IChatBaseComponent).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
} else {
CONSTRUCTOR = CLASS.getConstructor();
HEADER = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
FOOTER = nms.getFields(CLASS, nms.IChatBaseComponent).get(1);
}
}
public static Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public static Object build(IChatBaseComponent header, IChatBaseComponent footer, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
if (CONSTRUCTOR.getParameterCount() == 2) {
return CONSTRUCTOR.newInstance(nms.toNMSComponent(packet.getHeader(), clientVersion),
nms.toNMSComponent(packet.getFooter(), clientVersion));
return CONSTRUCTOR.newInstance(nms.toNMSComponent(header, clientVersion), nms.toNMSComponent(footer, clientVersion));
}
Object nmsPacket = CONSTRUCTOR.newInstance();
HEADER.set(nmsPacket, nms.toNMSComponent(packet.getHeader(), clientVersion));
FOOTER.set(nmsPacket, nms.toNMSComponent(packet.getFooter(), clientVersion));
HEADER.set(nmsPacket, nms.toNMSComponent(header, clientVersion));
FOOTER.set(nmsPacket, nms.toNMSComponent(footer, clientVersion));
return nmsPacket;
}
}
@@ -1,7 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
@@ -20,14 +18,11 @@ public class PacketPlayOutScoreboardDisplayObjectiveStorage {
OBJECTIVE_NAME = nms.getFields(CLASS, String.class).get(0);
}
public static Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return CONSTRUCTOR.newInstance(packet.getSlot(), NMSStorage.getInstance().newScoreboardObjective(packet.getObjectiveName()));
}
public static PacketPlayOutScoreboardDisplayObjective read(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardDisplayObjective(
POSITION.getInt(nmsPacket),
(String) OBJECTIVE_NAME.get(nmsPacket)
);
public static Object buildSilent(int slot, String objective) {
try {
return CONSTRUCTOR.newInstance(slot, NMSStorage.getInstance().newScoreboardObjective(objective));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -2,9 +2,7 @@ package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -41,30 +39,28 @@ public class PacketPlayOutScoreboardObjectiveStorage {
}
}
public static Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
String displayName = clientVersion.getMinorVersion() < 13 ? TAB.getInstance().getPlatform().getPacketBuilder().cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR.newInstance(newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
packet.getRenderType() == null ? null : Enum.valueOf(EnumScoreboardHealthDisplay, packet.getRenderType().toString())),
packet.getAction());
public static Object buildSilent(int action, String objectiveName, String title, boolean hearts, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR.newInstance(newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), clientVersion),
asDisplayType(hearts)), action);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
OBJECTIVE_NAME.set(nmsPacket, objectiveName);
DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
}
METHOD.set(nmsPacket, action);
return nmsPacket;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
OBJECTIVE_NAME.set(nmsPacket, packet.getObjectiveName());
DISPLAY_NAME.set(nmsPacket, displayName);
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
}
METHOD.set(nmsPacket, packet.getAction());
return nmsPacket;
}
public static PacketPlayOutScoreboardObjective read(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardObjective(METHOD.getInt(nmsPacket),
(String) OBJECTIVE_NAME.get(nmsPacket), null,
PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER
);
private static Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
}
@@ -1,7 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
@@ -33,19 +31,31 @@ public class PacketPlayOutScoreboardScoreStorage {
}
}
public static Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, packet.getAction().toString()), packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
public static Object change(String objective, String player, int score) {
try {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, player, score);
}
Object scoreboardScore = newScoreboardScore.newInstance(nms.emptyScoreboard, nms.newScoreboardObjective(objective), player);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
return CONSTRUCTOR.newInstance(scoreboardScore);
}
return CONSTRUCTOR.newInstance(scoreboardScore, 0);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
return CONSTRUCTOR_String.newInstance(packet.getPlayer());
}
public static Object remove(String objective, String player) {
try {
if (NMSStorage.getInstance().getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, player, 0);
}
return CONSTRUCTOR_String.newInstance(player);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
Object score = newScoreboardScore.newInstance(nms.emptyScoreboard, nms.newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
ScoreboardScore_setScore.invoke(score, packet.getScore());
if (nms.getMinorVersion() >= 8) {
return CONSTRUCTOR.newInstance(score);
}
return CONSTRUCTOR.newInstance(score, 0);
}
}
@@ -3,9 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -63,56 +61,21 @@ public class PacketPlayOutScoreboardTeamStorage {
}
}
public static Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
private static void createTeamModern(Object team, ProtocolVersion clientVersion, String prefix, String suffix, String visibility, String collision) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, packet.getName());
String prefix = packet.getPlayerPrefix();
String suffix = packet.getPlayerSuffix();
if (clientVersion.getMinorVersion() < 13) {
prefix = TAB.getInstance().getPlatform().getPacketBuilder().cutTo(prefix, 16);
suffix = TAB.getInstance().getPlatform().getPacketBuilder().cutTo(suffix, 16);
}
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(packet.getPlayers());
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (packet.getOptions() & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (packet.getOptions() & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(packet, clientVersion, team, prefix, suffix, nms);
} else {
createTeamLegacy(packet, team, prefix, suffix, nms);
}
if (nms.getMinorVersion() >= 17) {
switch (packet.getAction()) {
case 0:
return Constructor_ofBoolean.invoke(null, team, true);
case 1:
return Constructor_of.invoke(null, team);
case 2:
return Constructor_ofBoolean.invoke(null, team, false);
case 3:
return Constructor_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(PlayerAction, "ADD"));
case 4:
return Constructor_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(PlayerAction, "REMOVE"));
default:
throw new IllegalArgumentException("Invalid action: " + packet.getAction());
}
}
return CONSTRUCTOR.newInstance(team, packet.getAction());
}
private static void createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion, Object team, String prefix, String suffix, NMSStorage nms) throws ReflectiveOperationException {
if (prefix != null) ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null) ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, format.toString()));
ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(visibility).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(collision).equals("always") ? "ALWAYS" : "NEVER"));
}
private static void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix, NMSStorage nms) throws ReflectiveOperationException {
private static void createTeamLegacy(Object team, String prefix, String suffix, String visibility, String collision) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (prefix != null) ScoreboardTeam_setPrefix.invoke(team, prefix);
if (suffix != null) ScoreboardTeam_setSuffix.invoke(team, suffix);
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(visibility).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(collision).equals("always") ? "ALWAYS" : "NEVER"));
}
private static List<Field> getInstanceFields(Class<?> clazz, Class<?> type) {
@@ -125,4 +88,59 @@ public class PacketPlayOutScoreboardTeamStorage {
}
return list;
}
public static Object register(String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(team, clientVersion, prefix, suffix, visibility, collision);
} else {
createTeamLegacy(team, prefix, suffix, visibility, collision);
}
if (nms.getMinorVersion() >= 17) {
return Constructor_ofBoolean.invoke(null, team, true);
}
return CONSTRUCTOR.newInstance(team, 0);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object unregister(String name) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
return Constructor_of.invoke(null, team);
} else {
return CONSTRUCTOR.newInstance(team, 1);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object update(String name, String prefix, String suffix, String visibility, String collision, int options, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(team, clientVersion, prefix, suffix, visibility, collision);
} else {
createTeamLegacy(team, prefix, suffix, visibility, collision);
}
if (nms.getMinorVersion() >= 17) {
return Constructor_ofBoolean.invoke(null, team, false);
}
return CONSTRUCTOR.newInstance(team, 2);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.protocol.TabPacket;
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.protocol.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
@@ -14,7 +14,7 @@ import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
public class PacketPlayOutSpawnEntityLivingStorage implements TabPacket {
public class PacketPlayOutSpawnEntityLivingStorage {
/** NMS Fields */
public static Class<?> CLASS;
@@ -97,7 +97,7 @@ public class PacketPlayOutSpawnEntityLivingStorage implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public static Object build(PacketPlayOutSpawnEntityLiving packet) throws ReflectiveOperationException {
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -105,26 +105,26 @@ public class PacketPlayOutSpawnEntityLivingStorage implements TabPacket {
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, packet.getEntityId());
YAW.set(nmsPacket, (byte)(packet.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(packet.getPitch() * 256.0f / 360.0f));
ENTITY_ID.set(nmsPacket, entityId);
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, ((DataWatcher)packet.getDataWatcher()).build());
DATA_WATCHER.set(nmsPacket, data.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, packet.getUniqueId());
X.set(nmsPacket, packet.getX());
Y.set(nmsPacket, packet.getY());
Z.set(nmsPacket, packet.getZ());
UUID.set(nmsPacket, id);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(packet.getX()*32));
Y.set(nmsPacket, floor(packet.getY()*32));
Z.set(nmsPacket, floor(packet.getZ()*32));
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) packet.getEntityType()));
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
}
return nmsPacket;
}
+1 -1
View File
@@ -1,6 +1,6 @@
name: @name@
version: @version@
author: NEZNAMY
author: @author@
main: me.neznamy.tab.platforms.bukkit.BukkitTAB
api-version: 1.13
softdepend: [PlaceholderAPI, Vault, LuckPerms, Essentials, LibsDisguises, ProtocolSupport, ViaVersion, floodgate]
-1
View File
@@ -3,5 +3,4 @@ dependencies {
implementation(libs.bstats.bungeecord)
compileOnly(libs.bungeecord)
compileOnly(libs.redisBungee)
compileOnly("io.netty:netty-transport-native-epoll:4.1.84.Final") // Fixing compilation with 1.19.4 update
}
@@ -26,7 +26,7 @@ public class BungeeEventListener implements Listener {
@EventHandler
public void onQuit(PlayerDisconnectEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TAB.getInstance().getCPUManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
@@ -39,7 +39,7 @@ public class BungeeEventListener implements Listener {
@EventHandler
public void onSwitch(ServerSwitchEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> {
TAB.getInstance().getCPUManager().runTask(() -> {
if (TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()) == null) {
TabAPI.getInstance().getFeatureManager().onJoin(new BungeeTabPlayer(e.getPlayer()));
} else {
@@ -73,7 +73,7 @@ public class BungeeEventListener implements Listener {
if (event.getReceiver() instanceof ProxiedPlayer) {
event.setCancelled(true);
((ProxyPlatform)TAB.getInstance().getPlatform()).getPluginMessageHandler().onPluginMessage(
((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
((ProxiedPlayer) event.getReceiver()).getUniqueId(), ((ProxiedPlayer) event.getReceiver()).getName(), event.getData());
}
}
}
@@ -1,171 +0,0 @@
package me.neznamy.tab.platforms.bungeecord;
import java.util.*;
import java.util.stream.Collectors;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import net.md_5.bungee.protocol.PlayerPublicKey;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.ScoreboardObjective.HealthDisplay;
/**
* Packet builder for BungeeCord platform
*/
public class BungeePacketBuilder extends PacketBuilder {
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() < 9) return null;
BossBar bungeePacket = new BossBar(packet.getId(), packet.getAction().ordinal());
bungeePacket.setHealth(packet.getPct());
bungeePacket.setTitle(packet.getName() == null ? null : IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion));
bungeePacket.setColor(packet.getColor() == null ? 0 : packet.getColor().ordinal());
bungeePacket.setDivision(packet.getOverlay() == null ? 0: packet.getOverlay().ordinal());
bungeePacket.setFlags(packet.getFlags());
return bungeePacket;
}
@Override
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() >= 19) {
return new SystemChat(packet.getMessage().toString(clientVersion), (byte) packet.getType().ordinal());
} else {
return new Chat(packet.getMessage().toString(clientVersion), (byte) packet.getType().ordinal());
}
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) {
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() &&
packet.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(packet.getEntries().stream().map(PlayerInfoData::getUniqueId).toArray(UUID[]::new));
return remove;
}
List<Item> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
Item item = new Item();
if (data.getDisplayName() != null) {
if (clientVersion.getMinorVersion() >= 8) {
item.setDisplayName(data.getDisplayName().toString(clientVersion));
} else {
item.setDisplayName(data.getDisplayName().toLegacyText());
}
} else if (clientVersion.getMinorVersion() < 8) {
item.setDisplayName(String.valueOf(data.getName())); //avoiding NPE, 1.7 client requires this, 1.8 added a leading boolean
}
if (data.getGameMode() != null) item.setGamemode(data.getGameMode().ordinal() - 1);
item.setListed(data.isListed());
item.setPing(data.getLatency());
if (data.getSkin() != null) {
item.setProperties(new Property[]{new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
item.setChatSessionId(data.getChatSessionId());
item.setPublicKey((PlayerPublicKey) data.getProfilePublicKey());
items.add(item);
}
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
PlayerListItemUpdate bungeePacket = new PlayerListItemUpdate();
PlayerListItemUpdate.Action[] array = packet.getActions().stream().map(action -> PlayerListItemUpdate.Action.valueOf(
action.toString().replace("GAME_MODE", "GAMEMODE"))).toArray(PlayerListItemUpdate.Action[]::new);
bungeePacket.setActions(EnumSet.of(array[0], array));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
} else {
PlayerListItem bungeePacket = new PlayerListItem();
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
bungeePacket.setAction(PlayerListItem.Action.valueOf(action.toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
}
}
@Override
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return new PlayerListHeaderFooter(packet.getHeader().toString(clientVersion, true), packet.getFooter().toString(clientVersion, true));
}
@Override
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return new ScoreboardDisplay((byte)packet.getSlot(), packet.getObjectiveName());
}
@Override
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return new ScoreboardObjective(packet.getObjectiveName(), jsonOrCut(packet.getDisplayName(), clientVersion, 32), packet.getRenderType() == null ? null : HealthDisplay.valueOf(packet.getRenderType().toString()), (byte)packet.getAction());
}
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return new ScoreboardScore(packet.getPlayer(), (byte) packet.getAction().ordinal(), packet.getObjectiveName(), packet.getScore());
}
@Override
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
int color = 0;
if (clientVersion.getMinorVersion() >= 13) {
color = (packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(packet.getPlayerPrefix())).ordinal();
}
return new Team(packet.getName(), (byte)packet.getAction(), jsonOrCut(packet.getName(), clientVersion, 16), jsonOrCut(packet.getPlayerPrefix(), clientVersion, 16), jsonOrCut(packet.getPlayerSuffix(), clientVersion, 16),
packet.getNameTagVisibility(), packet.getCollisionRule(), color, (byte)packet.getOptions(), packet.getPlayers().toArray(new String[0]));
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object bungeePacket, ProtocolVersion clientVersion) {
if (bungeePacket instanceof PlayerListItemRemove) {
return new PacketPlayOutPlayerInfo(
EnumPlayerInfoAction.REMOVE_PLAYER,
Arrays.stream(((PlayerListItemRemove) bungeePacket).getUuids()).map(PlayerInfoData::new).collect(Collectors.toList())
);
}
Item[] items = bungeePacket instanceof PlayerListItemUpdate ? ((PlayerListItemUpdate) bungeePacket).getItems() : ((PlayerListItem) bungeePacket).getItems();
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : items) {
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
listData.add(new PlayerInfoData(
i.getUsername(),
i.getUuid(),
skin,
Boolean.TRUE.equals(i.getListed()),
i.getPing() == null ? 0 : i.getPing(),
i.getGamemode() == null ? null : EnumGamemode.VALUES[i.getGamemode()+1],
IChatBaseComponent.deserialize(i.getDisplayName()),
i.getChatSessionId(),
i.getPublicKey()));
}
if (bungeePacket instanceof PlayerListItemUpdate) {
EnumPlayerInfoAction[] array = ((PlayerListItemUpdate) bungeePacket).getActions().stream().map(this::toCustomAction).toArray(EnumPlayerInfoAction[]::new);
return new PacketPlayOutPlayerInfo(EnumSet.of(array[0], array), listData);
} else {
return new PacketPlayOutPlayerInfo(toCustomAction(((PlayerListItem) bungeePacket).getAction()), listData);
}
}
private EnumPlayerInfoAction toCustomAction(Object action) {
return EnumPlayerInfoAction.valueOf(action.toString().replace("GAMEMODE", "GAME_MODE"));
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object bungeePacket) {
return new PacketPlayOutScoreboardObjective(((ScoreboardObjective) bungeePacket).getAction(), ((ScoreboardObjective) bungeePacket).getName(),
null, PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object bungeePacket) {
return new PacketPlayOutScoreboardDisplayObjective(((ScoreboardDisplay) bungeePacket).getPosition(), ((ScoreboardDisplay) bungeePacket).getName());
}
}
@@ -7,9 +7,9 @@ import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import java.lang.reflect.Field;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
@@ -19,9 +19,7 @@ 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.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.Team;
import net.md_5.bungee.protocol.packet.*;
import java.util.Collection;
import java.util.function.Function;
@@ -51,7 +49,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@Override
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : BungeeChannelDuplexHandler::new;
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
}
@Override
@@ -65,6 +63,107 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
return null;
}
@Override
public void onDisplayObjective(TabPlayer player, Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
}
@Override
public void onObjective(TabPlayer player, Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
}
@Override
public boolean isDisplayObjective(Object packet) {
return packet instanceof ScoreboardDisplay;
}
@Override
public boolean isObjective(Object packet) {
return packet instanceof ScoreboardObjective;
}
@Override
public boolean isTeam(Object packet) {
return packet instanceof Team;
}
@Override
public boolean isPlayerInfo(Object packet) {
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
}
@Override
public void modifyPlayers(Object team) {
Team packet = (Team) team;
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (col.contains(p.getNickname()) && !((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !packet.getName().equals(sorting.getShortTeamName(p))) {
logTeamOverride(packet.getName(), p.getName(), sorting.getShortTeamName(p));
col.remove(p.getNickname());
}
}
RedisBungeeSupport redis = (RedisBungeeSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getNickname()) && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName(), p.getTeamName());
col.remove(p.getNickname());
}
}
}
packet.setPlayers(col.toArray(new String[0]));
}
@Override
public boolean isLogin(Object packet) {
return packet instanceof Login;
}
@Override
public void onPlayerInfo(TabPlayer receiver, Object packet) {
PlayerListItem.Item[] items;
if (packet instanceof PlayerListItemUpdate) {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_GAMEMODE)) {
item.setGamemode(TAB.getInstance().getFeatureManager().onGameModeChange(receiver, item.getUuid(), item.getGamemode()));
}
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.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent displayName = IChatBaseComponent.deserialize(item.getDisplayName());
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid(), displayName);
item.setDisplayName(displayName == null ? null : displayName.toString(receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_GAMEMODE || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setGamemode(TAB.getInstance().getFeatureManager().onGameModeChange(receiver, item.getUuid(), item.getGamemode()));
}
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.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent displayName = IChatBaseComponent.deserialize(item.getDisplayName());
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid(), displayName);
item.setDisplayName(displayName == null ? null : displayName.toString(receiver.getVersion()));
}
}
}
}
/**
* Constructs new instance of the feature
*/
@@ -72,91 +171,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
super("inbound-boss");
}
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BungeeChannelDuplexHandler extends ChannelDuplexHandler {
/** Injected player */
protected final TabPlayer player;
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
switch(packet.getClass().getSimpleName()) {
case "PlayerListItem":
case "PlayerListItemUpdate":
case "PlayerListItemRemove":
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
case "Team":
if (antiOverrideTeams) {
long time = System.nanoTime();
modifyPlayers((Team) packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
}
break;
case "ScoreboardDisplay":
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
break;
case "ScoreboardObjective":
TAB.getInstance().getFeatureManager().onObjective(player, packet);
break;
case "Login":
//making sure to not send own packets before login packet is actually sent
super.write(context, packet, channelPromise);
TAB.getInstance().getFeatureManager().onLoginPacket(player);
return;
default:
break;
}
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("An error occurred when analyzing packets for player " + player.getName() + " with client version " + player.getVersion().getFriendlyName(), e);
}
try {
super.write(context, packet, channelPromise);
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to forward packet %s to %s", packet.getClass().getSimpleName(), player.getName()), e);
}
}
/**
* Removes all real players from packet if the packet doesn't come from TAB
*
* @param packet
* packet to modify
*/
private void modifyPlayers(Team packet) {
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (col.contains(p.getNickname()) && !((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !packet.getName().equals(sorting.getShortTeamName(p))) {
logTeamOverride(packet.getName(), p.getName(), sorting.getShortTeamName(p));
col.remove(p.getNickname());
}
}
RedisBungeeSupport redis = (RedisBungeeSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getNickname()) && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName(), p.getTeamName());
col.remove(p.getNickname());
}
}
}
packet.setPlayers(col.toArray(new String[0]));
}
}
/**
* Channel duplex handler override if features using packets that must be
* deserialized manually are used. If they are disabled, deserialization is
* disabled for better performance.
*/
public class DeserializableBungeeChannelDuplexHandler extends BungeeChannelDuplexHandler {
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
/**
* Constructs new instance with given player
@@ -19,7 +19,6 @@ import org.jetbrains.annotations.Nullable;
public class BungeePlatform extends ProxyPlatform {
private final Plugin plugin;
@Getter private final BungeePacketBuilder packetBuilder = new BungeePacketBuilder();
@Getter private final BungeePipelineInjector pipelineInjector = new BungeePipelineInjector();
@Override
@@ -0,0 +1,104 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.shared.TabScoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import java.util.Collection;
/**
* Scoreboard handler for BungeeCord. Because it does not offer
* any Scoreboard API and the scoreboard class it has is just a
* downstream tracker, we need to use packets.
*/
public class BungeeScoreboard extends TabScoreboard {
public BungeeScoreboard(TabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(DisplaySlot slot, @NonNull String objective) {
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
}
@Override
public void registerTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, 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()),
visibility, collision, color, (byte)options, players.toArray(new String[0])));
}
@Override
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(new Team(name));
}
@Override
public void updateTeam0(@NonNull String name, String prefix, String suffix, String visibility, String 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, collision, 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(String text, ProtocolVersion clientVersion) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return text;
}
}
@Override
public void setScore0(@NonNull String objective, @NonNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
}
@@ -1,29 +1,45 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.Scoreboard;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.platforms.bungeecord.bossbar.BungeeBossBarHandler;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1_19_3;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1_7;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1_8;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
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 net.md_5.bungee.protocol.packet.LoginRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* TabPlayer implementation for BungeeCord
*/
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Component cache to save CPU when creating components */
private static final ComponentCache<IChatBaseComponent, BaseComponent[]> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ComponentSerializer.parse(component.toString(clientVersion)));
/** Inaccessible bungee internals */
private static Object directionData;
private static Method getId;
@@ -39,6 +55,16 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
}
/** Player's scoreboard */
@Getter private final Scoreboard scoreboard = new BungeeScoreboard(this);
/** Player's tablist based on version */
private final TabList tabList1_7 = new BungeeTabList1_7(this);
private final TabList tabList1_8 = new BungeeTabList1_8(this);
private final TabList tabList1_19_3 = new BungeeTabList1_19_3(this);
@Getter private final BossBarHandler bossBarHandler = new BungeeBossBarHandler(this);
/**
* Constructs new instance for given player
*
@@ -51,10 +77,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public boolean hasPermission0(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
return getPlayer().hasPermission(permission);
}
@Override
@@ -64,9 +87,12 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendPacket(Object nmsPacket) {
long time = System.nanoTime();
if (nmsPacket != null && getPlayer().isConnected()) getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
}
@Override
public void sendMessage(IChatBaseComponent message) {
getPlayer().sendMessage(componentCache.get(message, getVersion()));
}
@Override
@@ -99,6 +125,13 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
}
/**
* If ViaVersion is installed on BungeeCord, it changes protocol to match version
* of server to which player is connected to. For that reason, we need to retrieve
* the field more often than just on join.
*
* @return Player's current protocol version
*/
@Override
public ProtocolVersion getVersion() {
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
@@ -127,18 +160,29 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
@Override
public Object getProfilePublicKey() {
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getPublicKey();
public void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer) {
getPlayer().setTabHeader(componentCache.get(header, getVersion()), componentCache.get(footer, getVersion()));
}
@Override
public UUID getChatSessionId() {
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getUuid();
public TabList getTabList() {
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
}
@Override
public Object getChatSession() {
LoginRequest login = ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest();
return new Object[]{login.getUuid(), login.getPublicKey()}; // BungeeCord has no single object for this
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) return;
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);
}
}
@@ -0,0 +1,96 @@
package me.neznamy.tab.platforms.bungeecord.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import net.md_5.bungee.protocol.packet.BossBar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* BossBar handler for BungeeCord. It uses packets, since
* BungeeCord does not have a BossBar API. Only supports
* 1.9+ players, as dealing with entities would be simply impossible.
*/
@RequiredArgsConstructor
public class BungeeBossBarHandler implements BossBarHandler {
private final BungeeTabPlayer player;
/** Both are included in the same action, need to remember and write them both */
private final Map<UUID, Integer> colors = new HashMap<>();
private final Map<UUID, Integer> styles = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 0);
bossbar.setHealth(progress);
bossbar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
bossbar.setColor(color.ordinal());
bossbar.setDivision(style.ordinal());
player.getPlayer().unsafe().sendPacket(bossbar);
colors.put(id, color.ordinal());
styles.put(id, style.ordinal());
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 3);
bossbar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
player.getPlayer().unsafe().sendPacket(bossbar);
}
@Override
public void update(@NonNull UUID id, float progress) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 2);
bossbar.setHealth(progress);
player.getPlayer().unsafe().sendPacket(bossbar);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 4);
bossbar.setDivision(style.ordinal());
bossbar.setColor(colors.get(id));
player.getPlayer().unsafe().sendPacket(bossbar);
styles.put(id, style.ordinal());
}
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 4);
bossbar.setDivision(styles.get(id));
bossbar.setColor(color.ordinal());
player.getPlayer().unsafe().sendPacket(bossbar);
colors.put(id, color.ordinal());
}
@Override
public void remove(@NonNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
player.getPlayer().unsafe().sendPacket(new BossBar(id, 1));
colors.remove(id);
styles.remove(id);
}
}
@@ -0,0 +1,109 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.tablist.BulkUpdateTabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.PlayerPublicKey;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
import java.util.*;
/**
* TabList handler for 1.19.3+ players using the new tablist packets.
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList1_19_3 extends BulkUpdateTabList {
/** Player this TabList belongs to */
private final BungeeTabPlayer player;
@Override
public void removeEntries(@NonNull Collection<UUID> entries) {
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(entries.toArray(new UUID[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
}
@Override
public void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries) {
List<Item> items = new ArrayList<>();
for (Map.Entry<UUID, IChatBaseComponent> entry : entries.entrySet()) {
Item item = new Item();
item.setUuid(entry.getKey());
item.setDisplayName(entry.getValue() == null ? null : entry.getValue().toString(player.getVersion()));
items.add(item);
}
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME));
packet.setItems(items.toArray(new Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void updateLatencies(@NonNull Map<UUID, Integer> entries) {
List<Item> items = new ArrayList<>();
for (Map.Entry<UUID, Integer> entry : entries.entrySet()) {
Item item = new Item();
item.setUuid(entry.getKey());
item.setPing(entry.getValue());
items.add(item);
}
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY));
packet.setItems(items.toArray(new Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void updateGameModes(@NonNull Map<UUID, Integer> entries) {
List<Item> items = new ArrayList<>();
for (Map.Entry<UUID, Integer> entry : entries.entrySet()) {
Item item = new Item();
item.setUuid(entry.getKey());
item.setGamemode(entry.getValue());
items.add(item);
}
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE));
packet.setItems(items.toArray(new Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void addEntries(@NonNull Collection<TabListEntry> entries) {
List<Item> items = new ArrayList<>();
for (TabListEntry data : entries) {
Item item = new Item();
if (data.getDisplayName() != null) item.setDisplayName(data.getDisplayName().toString(player.getVersion()));
item.setGamemode(data.getGameMode());
item.setListed(data.isListed());
item.setPing(data.getLatency());
if (data.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, data.getSkin().getValue(), data.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
if (data.getChatSession() != null) {
item.setChatSessionId((UUID) ((Object[])data.getChatSession())[0]);
item.setPublicKey((PlayerPublicKey) ((Object[])data.getChatSession())[1]);
}
items.add(item);
}
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(EnumSet.allOf(PlayerListItemUpdate.Action.class));
packet.setItems(items.toArray(new Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
}
@@ -0,0 +1,89 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.tablist.SingleUpdateTabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabList handler for 1.7 players, which has a completely different
* system. Entries are identified by display name, not uuids. Display
* names are changed by removing old entry and adding a new one, which
* requires further tracking than just the UUID.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
* While BungeeCord itself does not support 1.7, some of its forks do.
* This was tested on FlameCord fork.
*/
@RequiredArgsConstructor
public class BungeeTabList1_7 extends SingleUpdateTabList {
/** Player this TabList belongs to */
private final BungeeTabPlayer player;
/** Because entries are identified by names and not uuids on 1.7 */
private final Map<UUID, String> userNames = new HashMap<>();
private final Map<UUID, String> displayNames = new HashMap<>();
@Override
public void removeEntry(@NonNull UUID entry) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
// Remove from map
userNames.remove(entry);
displayNames.remove(entry);
}
@Override
public void updateDisplayName(@NonNull UUID entry, @Nullable IChatBaseComponent displayName) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
addEntry(new TabListEntry.Builder(entry).displayName(displayName).name(userNames.get(entry)).build());
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
update(PlayerListItem.Action.UPDATE_LATENCY, createItem(null, displayNames.get(entry), latency));
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {} // Added in 1.8
@Override
public void addEntry(@NonNull TabListEntry entry) {
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
// Add to map
userNames.put(entry.getUniqueId(), entry.getName());
displayNames.put(entry.getUniqueId(), displayNameString);
}
private void update(PlayerListItem.Action action, PlayerListItem.Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new PlayerListItem.Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
private PlayerListItem.Item createItem(String username, String displayName, int latency) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUsername(username);
item.setDisplayName(displayName);
item.setPing(latency);
return item;
}
}
@@ -0,0 +1,113 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.tablist.BulkUpdateTabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.PlayerPublicKey;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import java.util.*;
/**
* TabList handler for players between 1.8 and 1.19.2.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList1_8 extends BulkUpdateTabList {
/** Player this TabList belongs to */
private final BungeeTabPlayer player;
@Override
public void removeEntries(@NonNull Collection<UUID> entries) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(PlayerListItem.Action.REMOVE_PLAYER);
List<PlayerListItem.Item> items = new ArrayList<>();
for (UUID uuid : entries) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid(uuid);
items.add(item);
}
packet.setItems(items.toArray(new PlayerListItem.Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries) {
List<PlayerListItem.Item> items = new ArrayList<>();
for (Map.Entry<UUID, IChatBaseComponent> entry : entries.entrySet()) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid(entry.getKey());
item.setDisplayName(entry.getValue() == null ? null : entry.getValue().toString(player.getVersion()));
items.add(item);
}
PlayerListItem packet = new PlayerListItem();
packet.setAction(PlayerListItem.Action.UPDATE_DISPLAY_NAME);
packet.setItems(items.toArray(new PlayerListItem.Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void updateLatencies(@NonNull Map<UUID, Integer> entries) {
List<PlayerListItem.Item> items = new ArrayList<>();
for (Map.Entry<UUID, Integer> entry : entries.entrySet()) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid(entry.getKey());
item.setPing(entry.getValue());
items.add(item);
}
PlayerListItem packet = new PlayerListItem();
packet.setAction(PlayerListItem.Action.UPDATE_LATENCY);
packet.setItems(items.toArray(new PlayerListItem.Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void updateGameModes(@NonNull Map<UUID, Integer> entries) {
List<PlayerListItem.Item> items = new ArrayList<>();
for (Map.Entry<UUID, Integer> entry : entries.entrySet()) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid(entry.getKey());
item.setGamemode(entry.getValue());
items.add(item);
}
PlayerListItem packet = new PlayerListItem();
packet.setAction(PlayerListItem.Action.UPDATE_GAMEMODE);
packet.setItems(items.toArray(new PlayerListItem.Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void addEntries(@NonNull Collection<TabListEntry> entries) {
List<PlayerListItem.Item> items = new ArrayList<>();
for (TabListEntry data : entries) {
PlayerListItem.Item item = new PlayerListItem.Item();
if (data.getDisplayName() != null) item.setDisplayName(data.getDisplayName().toString(player.getVersion()));
item.setGamemode(data.getGameMode());
item.setPing(data.getLatency());
if (data.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, data.getSkin().getValue(), data.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
if (data.getChatSession() != null) {
item.setChatSessionId((UUID) ((Object[])data.getChatSession())[0]);
item.setPublicKey((PlayerPublicKey) ((Object[])data.getChatSession())[1]);
}
items.add(item);
}
PlayerListItem packet = new PlayerListItem();
packet.setAction(PlayerListItem.Action.ADD_PLAYER);
packet.setItems(items.toArray(new PlayerListItem.Item[0]));
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
name: @name@
main: me.neznamy.tab.platforms.bungeecord.BungeeTAB
version: @version@
author: NEZNAMY
author: @author@
description: @description@
depends: []
softDepends: [RedisBungee, LuckPerms, PremiumVanish]
+2 -2
View File
@@ -20,9 +20,9 @@ event = "3.0.0"
# Platforms
bukkit = "1.16.5-R0.1-SNAPSHOT"
bungeecord = "1.19-R0.1-SNAPSHOT"
krypton = "a81b4baaed" # Krypton uses commit-based versioning
krypton = "d45865ccf1" # Krypton uses commit-based versioning
velocity = "3.1.2-SNAPSHOT"
sponge7 = "7.0.0"
sponge7 = "5.0.0"
sponge8 = "8.0.0"
# Platform extras
+4 -15
View File
@@ -1,10 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "1.8.0"
kotlin("kapt") version "1.8.0"
}
dependencies {
implementation(projects.shared)
compileOnly(libs.krypton.api)
@@ -16,13 +9,9 @@ dependencies {
exclude("org.kryptonmc", "serialization-gson")
exclude("org.kryptonmc", "serialization-nbt")
}
compileOnly(libs.kotlin.stdlib)
compileOnly(libs.spark)
kapt(libs.krypton.annotationProcessor)
}
tasks {
compileKotlin {
compilerOptions.jvmTarget.set(JvmTarget.JVM_1_8)
}
annotationProcessor(libs.krypton.annotationProcessor)
}
tasks.compileJava {
options.release.set(17)
}
@@ -0,0 +1,49 @@
package me.neznamy.tab.platforms.krypton;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import net.kyori.adventure.bossbar.BossBar;
import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class KryptonBossBarHandler implements BossBarHandler {
private final KryptonTabPlayer player;
private final Map<UUID, BossBar> bossBars = new LinkedHashMap<>();
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
BossBar bar = BossBar.bossBar(Main.toComponent(title, player.getVersion()),
progress,
BossBar.Color.valueOf(color.toString()),
BossBar.Overlay.valueOf(style.toString()));
bossBars.put(id, bar);
player.getPlayer().showBossBar(bar);
}
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).name(Main.toComponent(title, player.getVersion()));
}
public void update(@NotNull UUID id, float progress) {
bossBars.get(id).progress(progress);
}
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).overlay(BossBar.Overlay.valueOf(style.toString()));
}
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).color(BossBar.Color.valueOf(color.toString()));
}
public void remove(@NotNull UUID id) {
player.getPlayer().hideBossBar(bossBars.remove(id));
}
}
@@ -0,0 +1,36 @@
package me.neznamy.tab.platforms.krypton;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.TAB;
import org.kryptonmc.api.command.Sender;
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;
@RequiredArgsConstructor
public class KryptonEventListener {
private final Main plugin;
@Listener
public void onJoin(PlayerJoinEvent event) {
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onJoin(
new KryptonTabPlayer(event.getPlayer(), plugin.getProtocolVersion(event.getPlayer()))));
}
@Listener
public void onQuit(PlayerQuitEvent event) {
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(
TAB.getInstance().getPlayer(event.getPlayer().getUuid())));
}
@Listener
public void onCommand(CommandExecuteEvent event) {
Sender player = event.getSender();
if (!(player instanceof Player)) return;
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(((Player)player).getUuid()), event.getCommand()))
event.deny();
}
}
@@ -0,0 +1,126 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.auth.GameProfile;
import org.kryptonmc.api.world.GameMode;
import org.kryptonmc.krypton.network.NioConnection;
import org.kryptonmc.krypton.network.handlers.PacketHandler;
import org.kryptonmc.krypton.network.interceptor.PacketInterceptor;
import org.kryptonmc.krypton.network.interceptor.PacketInterceptorRegistry;
import org.kryptonmc.krypton.packet.GenericPacket;
import org.kryptonmc.krypton.packet.InboundPacket;
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoUpdate;
import org.kryptonmc.krypton.util.enumhelper.GameModes;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class KryptonPipelineInjector extends PipelineInjector {
private final TabPacketInterceptor interceptor = new TabPacketInterceptor();
private final Map<NioConnection, TabPlayer> players = new ConcurrentHashMap<>();
@Override
public void inject(TabPlayer player) {
if (players.isEmpty()) {
// If it's empty, no players have been registered yet, and we should add the interceptor
PacketInterceptorRegistry.INSTANCE.register(interceptor);
}
players.put(((KryptonTabPlayer)player).getPlayer().getConnection(), player);
}
@Override
public void uninject(TabPlayer player) {
players.remove(((KryptonTabPlayer)player).getPlayer().getConnection());
if (players.isEmpty()) {
// If it's empty, we unregistered the last player, and we should remove the interceptor
PacketInterceptorRegistry.INSTANCE.unregister(interceptor);
}
}
public class TabPacketInterceptor implements PacketInterceptor {
@Override
public GenericPacket onSend(@NotNull NioConnection connection, @NotNull GenericPacket packet) {
TabPlayer player = players.get(connection);
if (player == null) return packet; // Packet needs to be returned unmodified to be sent
if (packet instanceof PacketOutPlayerInfoUpdate) return rewritePlayerInfo(player, (PacketOutPlayerInfoUpdate) packet);
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
return packet;
}
private PacketOutPlayerInfoUpdate rewritePlayerInfo(TabPlayer receiver, PacketOutPlayerInfoUpdate packet) {
EnumSet<PacketOutPlayerInfoUpdate.Action> actions = packet.actions();
List<PacketOutPlayerInfoUpdate.Entry> newEntries = null;
for (PacketOutPlayerInfoUpdate.Entry entry : packet.entries()) {
GameProfile profile = entry.profile();
GameMode gameMode = entry.gameMode();
int latency = entry.latency();
Component displayName = entry.displayName();
boolean updated = false;
if (actions.contains(PacketOutPlayerInfoUpdate.Action.UPDATE_GAME_MODE)) {
int newGameMode = TAB.getInstance().getFeatureManager().onGameModeChange(receiver, profile.uuid(), gameMode.ordinal());
if (newGameMode != gameMode.ordinal()) {
updated = true;
gameMode = GameModes.fromId(newGameMode);
}
}
if (actions.contains(PacketOutPlayerInfoUpdate.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.uuid(), latency);
if (newLatency != latency) {
updated = true;
latency = newLatency;
}
}
if (actions.contains(PacketOutPlayerInfoUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent input = displayName == null ? null : new WrappedChatComponent(displayName);
IChatBaseComponent output = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.uuid(), input);
if (output != input) {
updated = true;
displayName = convertComponent(output, receiver.getVersion());
}
}
if (actions.contains(PacketOutPlayerInfoUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.uuid(), profile.name());
}
if (!updated) {
// Don't bother rewriting the entry if nothing was changed
continue;
}
if (gameMode == null) gameMode = GameMode.SURVIVAL;
PacketOutPlayerInfoUpdate.Entry result = new PacketOutPlayerInfoUpdate.Entry(profile.uuid(), profile, entry.listed(), latency, gameMode, displayName, entry.chatSession());
if (newEntries == null) newEntries = new ArrayList<>();
newEntries.add(result);
}
if (newEntries == null) {
// Don't bother rewriting the packet if there were no updated entries
return packet;
}
return new PacketOutPlayerInfoUpdate(packet.actions(), newEntries);
}
private Component convertComponent(IChatBaseComponent component, ProtocolVersion clientVersion) {
if (component instanceof WrappedChatComponent) return (Component) ((WrappedChatComponent) component).getOriginalComponent();
return GsonComponentSerializer.gson().deserialize(component.toString(clientVersion));
}
@Override
public <H extends PacketHandler> InboundPacket<H> onReceive(@NotNull NioConnection connection, @NotNull InboundPacket<H> packet) {
return packet;
}
}
}
@@ -0,0 +1,72 @@
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.api.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import org.kryptonmc.api.entity.player.Player;
@RequiredArgsConstructor
public class KryptonPlaceholderRegistry extends UniversalPlaceholderRegistry {
private final Main plugin;
@Override
public void registerPlaceholders(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")) registerSparkPlaceholders(manager);
super.registerPlaceholders(manager);
}
private void registerSparkPlaceholders(PlaceholderManager manager){
Spark spark = plugin.getServer().getServicesManager().provide(Spark.class);
if (spark == null) return;
// TPS
DoubleStatistic<TicksPerSecond> tps = spark.tps();
if (tps != null) {
manager.registerServerPlaceholder("%tps_5s%", 1000, () -> format(tps.poll(TicksPerSecond.SECONDS_5)));
manager.registerServerPlaceholder("%tps_10s%", 1000, () -> format(tps.poll(TicksPerSecond.SECONDS_10)));
manager.registerServerPlaceholder("%tps_1m%", 1000, () -> format(tps.poll(TicksPerSecond.MINUTES_1)));
manager.registerServerPlaceholder("%tps_5m%", 1000, () -> format(tps.poll(TicksPerSecond.MINUTES_5)));
manager.registerServerPlaceholder("%tps_15m%", 1000, () -> format(tps.poll(TicksPerSecond.MINUTES_15)));
}
// MSPT
GenericStatistic<DoubleAverageInfo, MillisPerTick> mspt = spark.mspt();
if (mspt != null) {
manager.registerServerPlaceholder("%mspt_min_10s%", 1000, () -> format(mspt.poll(MillisPerTick.SECONDS_10).min()));
manager.registerServerPlaceholder("%mspt_max_10s%", 1000, () -> format(mspt.poll(MillisPerTick.SECONDS_10).max()));
manager.registerServerPlaceholder("%mspt_mean_10s%", 1000, () -> format(mspt.poll(MillisPerTick.SECONDS_10).mean()));
manager.registerServerPlaceholder("%mspt_median_10s%", 1000, () -> format(mspt.poll(MillisPerTick.SECONDS_10).median()));
manager.registerServerPlaceholder("%mspt_percentile95_10s%", 1000, () -> format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
manager.registerServerPlaceholder("%mspt_min_1m%", 1000, () -> format(mspt.poll(MillisPerTick.MINUTES_1).min()));
manager.registerServerPlaceholder("%mspt_max_1m%", 1000, () -> format(mspt.poll(MillisPerTick.MINUTES_1).max()));
manager.registerServerPlaceholder("%mspt_mean_1m%", 1000, () -> format(mspt.poll(MillisPerTick.MINUTES_1).mean()));
manager.registerServerPlaceholder("%mspt_median_1m%", 1000, () -> format(mspt.poll(MillisPerTick.MINUTES_1).median()));
manager.registerServerPlaceholder("%mspt_percentile95_1m%", 1000, () -> format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
}
// CPU process
DoubleStatistic<CpuUsage> process= spark.cpuProcess();
manager.registerServerPlaceholder("%cpu_process_10s%", 1000, () -> format(process.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_process_1m%", 1000, () -> format(process.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_process_15m%", 1000, () -> format(process.poll(CpuUsage.MINUTES_15)));
// CPU system
DoubleStatistic<CpuUsage> system = spark.cpuSystem();
manager.registerServerPlaceholder("%cpu_system_10s%", 1000, () -> format(system.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_system_1m%", 1000, () -> format(system.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_system_15m%", 1000, () -> format(system.poll(CpuUsage.MINUTES_15)));
}
}
@@ -0,0 +1,75 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.platforms.krypton.features.unlimitedtags.KryptonNameTagX;
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 net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.Server;
import org.kryptonmc.api.entity.player.Player;
import org.kryptonmc.api.plugin.PluginContainer;
import java.util.Locale;
public class KryptonPlatform extends BackendPlatform {
private final Main plugin;
private final Server server;
public KryptonPlatform(Main plugin) {
this.plugin = plugin;
server = plugin.getServer();
}
public void sendConsoleMessage(String message, boolean translateColors) {
Component object = translateColors ? LegacyComponentSerializer.legacyAmpersand().deserialize(message) : Component.text(message);
TextComponent actualMessage = Component.text().append(Component.text("[TAB] ")).append(object).build();
server.getConsole().sendMessage(actualMessage);
}
public void registerUnknownPlaceholder(String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> "");
}
public String getPluginVersion(String plugin) {
PluginContainer container = server.getPluginManager().getPlugin(plugin.toLowerCase(Locale.ROOT));
return container == null ? null : container.getDescription().version();
}
public void loadPlayers() {
for (Player player : server.getPlayers()) {
TAB.getInstance().addPlayer(new KryptonTabPlayer(player, plugin.getProtocolVersion(player)));
}
}
public void registerPlaceholders() {
new KryptonPlaceholderRegistry(plugin).registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
public PipelineInjector getPipelineInjector() {
return new KryptonPipelineInjector();
}
public NameTag getUnlimitedNametags() {
return new KryptonNameTagX(plugin);
}
public @NotNull TabExpansion getTabExpansion() {
return new EmptyTabExpansion();
}
public TabFeature getPetFix() {
return null;
}
public TabFeature getPerWorldPlayerlist() {
return null;
}
}
Loaded 100 of 223 files, more files were not shown because too many files have changed in this diff. Show more