Compare commits

...
72 Commits
Author SHA1 Message Date
NEZNAMY ddac08bcbc Fix bungee compilation (did md5 break something?) 2023-03-15 14:31:59 +01:00
NEZNAMY 517e285bb3 Fix bossbar error on <1.9 from recode 2023-03-15 14:29:13 +01:00
NEZNAMY 81a08a6b51 3.3.1 2023-03-14 19:18:01 +01:00
NEZNAMY 4dfef1d032 1.19.4 support 2023-03-14 19:15:15 +01:00
NEZNAMY 5d38e28cae Extract more constants and add config option 2023-03-14 18:35:25 +01:00
NEZNAMY 66a12d10c4 Layout - Resend on server switch on Velocity 2023-03-14 17:27:27 +01:00
NEZNAMY 1a4027c8d5 Remove unused fields and methods 2023-03-14 16:15:55 +01:00
NEZNAMY 2a0243c51a RedisSupport - Add missing packet source 2023-03-14 15:24:13 +01:00
NEZNAMY 4851d09e70 More abstraction of unlimited nametag mode & packets it uses 2023-03-14 15:20:49 +01:00
NEZNAMY 3cdf3accfd Remove redundant checks 2023-03-14 10:46:27 +01:00
NEZNAMY fd1f094216 Layout - Small cleanup in code 2023-03-14 00:22:02 +01:00
NEZNAMY 25ece1ecb8 Move ViaVersion hook (and remove safety checks, hopefully it works correctly now) 2023-03-13 23:31:10 +01:00
NEZNAMY 802e65c280 Change constructor fields to getters in features 2023-03-13 23:04:22 +01:00
NEZNAMY 882688f056 Layout - Cache remove packet 2023-03-13 21:52:29 +01:00
NEZNAMY 97d60d0ab3 Bungee - Remove unused field (and simplify) 2023-03-13 21:33:48 +01:00
NEZNAMY 9ef99daed1 Revert feature loading as references were not null when they were supposed to be 2023-03-13 21:31:48 +01:00
NEZNAMY 71c85d9d39 NameTags - Speed up unloading 2023-03-13 19:36:05 +01:00
NEZNAMY b981ab46be Add debug messages about feature unloading times 2023-03-13 19:35:45 +01:00
NEZNAMY 016633e494 BelowName - Speed up reloads by caching values for players (tested with 100 bots, reloads were 2x faster) 2023-03-13 19:22:00 +01:00
NEZNAMY 1db862ab56 NameTags - Speed up reloading (tested with 100 bots, reload was 3x faster) 2023-03-13 19:15:45 +01:00
NEZNAMY 3563786310 PlayerList - Drastically speed up reloads (up to 10+ times with high player counts) 2023-03-13 19:01:26 +01:00
NEZNAMY ac8851dd6a Cache EnumSets for info packet for better performance 2023-03-13 18:22:16 +01:00
NEZNAMY 53ab2d9793 Commands - Do not load widths for widthcheck command unless command is used 2023-03-13 18:03:55 +01:00
NEZNAMY 2a58c86ffc Move code from initializer to load 2023-03-13 16:35:30 +01:00
NEZNAMY fa46d527ae Spectator Fix - Don't send useless packets on load/unload 2023-03-13 15:17:13 +01:00
NEZNAMY 667bd38305 Velocity - Fix Velocity being Velocity exception 2023-03-13 14:51:52 +01:00
NEZNAMY faba439466 Global PlayerList - Send all affected entries in a single packet, reducing amount of packets sent and improving performance 2023-03-13 14:47:29 +01:00
NEZNAMY c301157758 Global PlayerList - No longer delay task on quit as I was not able to reproduce the bug; Update placeholders periodically as they didn't update properly on vanish change anyway 2023-03-13 14:14:06 +01:00
NEZNAMY 8d09d425a4 Global PlayerList - No longer prevent other sources from removing players, better organize adding on server switch as well 2023-03-13 13:26:12 +01:00
NEZNAMY 11f03d5a10 Simplify 2023-03-13 12:58:19 +01:00
NEZNAMY 83625202a1 Merge pull request #841 from NEZNAMY/better-pipeline-injection
Better pipeline injection
2023-03-12 14:39:19 +01:00
Callum Seabrook eb4b715a88 Move pipeline injection to its own package 2023-03-12 13:35:47 +00:00
Callum Seabrook 89dce941cf Add RequiredArgsConstructor to NettyPipelineInjector 2023-03-12 13:31:58 +00:00
Callum Seabrook 6371b67e9e Removed player Channel and moved Netty injection to NettyPipelineInjector 2023-03-12 13:23:16 +00:00
NEZNAMY 966e27418a Bukkit - Attempt to fix error on ArrayList#remove 2023-03-11 15:35:31 +01:00
NEZNAMY 4adf7d1734 Bukkit - Fix NoSuchElementException that doesn't make sense 2023-03-11 15:25:06 +01:00
NEZNAMY 1a4c4a6fe8 Add more debug messages about feature loading times 2023-03-11 15:14:12 +01:00
NEZNAMY 7ff36592ae Do not re-initialize ErrorManager 2023-03-11 14:27:23 +01:00
NEZNAMY 1b513412d2 Remove redundant import 2023-03-10 13:20:14 +01:00
NEZNAMY f2bb43fbcd Make tabExpansion variable not null and add default empty implementation 2023-03-10 13:16:22 +01:00
NEZNAMY df5d07d885 Include forgotten code from previous commit 2023-03-10 13:13:21 +01:00
NEZNAMY 0ad2f33c4c Further clean up feature loading and mutual hooks 2023-03-10 12:45:28 +01:00
NEZNAMY dee48fccb3 Partially de-spaghetti feature loading and mutual hooking 2023-03-09 00:13:15 +01:00
NEZNAMY 9ca372ab13 Velocity is XD 2023-03-07 23:07:26 +01:00
NEZNAMY 58a849a902 Update README.md 2023-03-06 18:15:04 +01:00
NEZNAMY 895072ec35 Update README.md 2023-03-06 18:07:14 +01:00
NEZNAMY 2b2c4d2c39 Update README.md 2023-03-06 12:03:45 +01:00
NEZNAMY d534b6e2e8 Layout - Make fixed slots take priority over player groups if a slot is used in both 2023-03-05 15:31:14 +01:00
NEZNAMY 783e9d9931 Fix wrong values in widths.txt 2023-03-02 21:06:15 +01:00
NEZNAMY a109d9b55b Enable widthcheck command and fix error in it 2023-03-02 20:54:28 +01:00
NEZNAMY 5ce59ce44f Fix compilation 2023-03-02 20:33:37 +01:00
NEZNAMY 1e9195a9bc Velocity - Add try/catch for calling new methods on old versions 2023-03-01 18:49:41 +01:00
NEZNAMY 3099d16468 Create SECURITY.md 2023-03-01 12:23:39 +01:00
NEZNAMY c9c6af346c Add new issue templates 2023-03-01 12:18:23 +01:00
NEZNAMY af80a43628 Catch Throwable in pipeline instead of Exception to reduce chance of plugin causing player disconnect in future unsupported versions 2023-02-28 23:33:31 +01:00
NEZNAMY dc3c98ec91 Bukkit - Change how ViaVersion is detected (to avoid wrong detection due to a plugin order load bug in 1.17.1) 2023-02-25 18:41:25 +01:00
NEZNAMY dffa2c83f6 Typo 2023-02-24 23:45:40 +01:00
NEZNAMY dc66f253a7 Config converter - Remove placeholder that got deleted 2023-02-24 23:42:35 +01:00
NEZNAMY 17498cbfc2 BungeeCord - Fix bytebuf deserialization from previous commits 2023-02-24 23:15:01 +01:00
NEZNAMY 96e4d30168 Include 1.5 in supported version range 2023-02-24 16:48:56 +01:00
NEZNAMY c9d1934bb9 Use lombok for empty private constructor 2023-02-24 16:33:39 +01:00
NEZNAMY 8bec083194 Remove redundant duplicated checks 2023-02-24 16:30:34 +01:00
NEZNAMY abf047be23 Fix global playerlist class being instantiated twice 2023-02-24 16:22:02 +01:00
NEZNAMY 4a95399d56 3.3.1-SNAPSHOT 2023-02-24 16:19:01 +01:00
NEZNAMY 885bf5154e NameTags - Fix updating nametag visibility of players in disabled world/server 2023-02-24 15:43:58 +01:00
NEZNAMY 3e146fc496 Simplify 2023-02-24 15:41:30 +01:00
NEZNAMY 95710bcaf5 Sorting - Fix team name change registering teams for players in disabled worlds/servers 2023-02-24 15:37:40 +01:00
NEZNAMY edd620f17d BelowName - Fix text refreshing packets being sent to players in disabled worlds/servers 2023-02-24 15:25:09 +01:00
NEZNAMY 9c466d8b1e Update comment 2023-02-24 14:57:56 +01:00
NEZNAMY b385ab70fb Simplify 2023-02-24 14:52:22 +01:00
NEZNAMY 140224840b Remove double registration of sorting feature 2023-02-23 21:24:10 +01:00
NEZNAMY 2078cd5a5f Downgrade sponge API dependencies to confirm support 2023-02-21 18:46:43 +01:00
125 changed files with 1728 additions and 1729 deletions

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
name: Memory leak
description: Memory leak caused by TAB.
labels: ["Type: Memory leak"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Heap dump
description: Link to uploaded memory dump which I can look into.
validations:
required: true
- type: textarea
attributes:
label: Uncleared object(s)
description: Objects you believe should have been removed from the memory but are not
placeholder: |
There is only 1 player online, but heap dump has 100 instances of TabPlayer
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included download link of my heap dump
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have explained what I believe is wrong with the heap dump
required: true
@@ -0,0 +1,55 @@
name: Performance issue
description: Performance issue in TAB or in server caused by TAB
labels: ["Type: Performance"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Evidence
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included sufficient evidence of the issue
required: true
- label: I ticked all of the boxes without actually reading them
required: false
+59 -7
View File
@@ -7,18 +7,70 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
version anymore. Only one version of the plugin exists now, which contains all implemented features
and is available to everyone for free. However, customer support is only provided to
customers who have purchased the plugin.
customers who have purchased the plugin (see [Customer support](#customer-support)).
# Compiling
Compilation requires JDK 8 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Links
Download the plugin for free: [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[BuiltByBit](https://builtbybit.com/resources/20631/), [Modrinth](https://modrinth.com/plugin/tab-was-taken)
Support my work & receive customer support: [BuiltByBit](https://builtbybit.com/resources/14009/), [Polymart](https://polymart.org/resource/484)
Other links: [Discord support for customers](https://discord.gg/EaSvdk6), [Wiki](https://github.com/NEZNAMY/TAB/wiki)
# Customer support
Unlike with other projects where requirements and level of support provided are unclear,
we provide a clear answer to what kind of service you will or will not receive. Some don't
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
a free download link in description, which can be confusing.
Free plugins typically do have a support discord, however you will rarely receive
official support from the developers, especially for large projects with tons of users, where
helping everyone in the world (theoretically) would consume too much time, leaving authors
no time left to actually work on the project.
In the end, they are free, made by people in their free time, and they are allowed to choose
what they do or don't want to do in their free time. They often leave support to other people,
possibly staff members, who are, however, in the end just more volunteers who may not even know
the answer.
With paid plugins you are more likely to receive support, but unless explicitly said, nothing is
guaranteed. There are lots of paid plugins where authors don't provide any support or redirect
it to helpers, where again they are unpaid volunteers who might not even know the answer.
In TAB, everything is clearly defined. The plugin is free to download for everyone.
Customer support, however, is only provided to customers who have formally purchased a copy
of the plugin on any of the websites the plugin is being sold on:
* [BuiltByBit](https://builtbybit.com/resources/14009/) (accepts any payment method)
* [Polymart](https://polymart.org/resource/484) (only PayPal)
After purchasing, you can request assistance via either private messages on the website where you
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
preferred, since the communication will be much faster thanks to notifications and more).
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
after user confirmed all of their questions were answered and then receive a transcript of the ticket
from the bot after ticket is closed into private messages (unless user is blocking them).
* We do not rely on the community helping, which often makes things even worse. We know people have
better things to do than helping random people with a random plugin, especially when most people only
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
most mods on most servers who only check it when they are really bored, give a wiki link and
disappear again.
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
whenever I am available without any intentional delays.
The wiki contains everything about the plugin in one way or another, so support should not
even be needed. It also contains a few examples, but obviously cannot contain a pre-made config
for every possible idea anyone can come up with.
Support is provided to people who decided paying for support is more time efficient than reading the wiki,
especially for serious servers, where price of the plugin is nothing compared to other server costs.
# Other links
Download the plugin for free:
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
* [BuiltByBit](https://builtbybit.com/resources/20631/)
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
[Donate](https://paypal.me/neznamy1) if you are feeling extra generous
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
+2
View File
@@ -0,0 +1,2 @@
# Security Policy
If you found a security issue, send me a private message anywhere.
+2
View File
@@ -12,6 +12,7 @@ dependencies {
compileOnlyApi(libs.adventureMiniMessage)
compileOnlyApi(libs.adventureApi)
compileOnlyApi(libs.adventureLegacy)
compileOnlyApi(libs.via)
}
blossom {
@@ -20,6 +21,7 @@ blossom {
replaceToken("@version@", project.version)
replaceToken("@description@", project.description)
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
}
@@ -47,28 +47,7 @@ public interface ArmorStand {
* player to send packet to
*/
void teleport(TabPlayer viewer);
/**
* Changes sneaking flag and sends packets to everyone in range
*
* @param sneaking
* new sneaking status
*/
void sneak(boolean sneaking);
/**
* DeSpawns armor stand for everyone
*/
void destroy();
/**
* DeSpawns armor stand for specified player
*
* @param viewer
* player to send packet to
*/
void destroy(TabPlayer viewer);
/**
* Updates armor stand's name if needed
*/
@@ -96,13 +75,4 @@ public interface ArmorStand {
* player to spawn for
*/
void spawn(TabPlayer viewer);
/**
* Sends respawn packets to specified player to make position
* change instant instead of teleport animation
*
* @param viewer
* Player to send armor stand respawn packets to
*/
void respawn(TabPlayer viewer);
}
@@ -1,8 +1,11 @@
package me.neznamy.tab.api;
import com.viaversion.viaversion.api.Via;
import lombok.Getter;
import lombok.Setter;
import java.util.UUID;
/**
* Enum class representing all possibly used protocol versions
*/
@@ -11,6 +14,7 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_19_4 (762),
V1_19_3 (761),
V1_19_2 (760),
V1_19_1 (760),
@@ -137,4 +141,19 @@ public enum ProtocolVersion {
}
return UNKNOWN_CLIENT_VERSION;
}
/**
* Gets player's network version using ViaVersion API
*
* @param player
* Player's UUID
* @param playerName
* Player's name for debug messages
* @return Player's network version
*/
public static int getPlayerVersionVia(UUID player, String playerName) {
int version = Via.getAPI().getPlayerVersion(player);
TabAPI.getInstance().debug("ViaVersion returned protocol version " + version + " for " + playerName);
return version;
}
}
@@ -15,6 +15,7 @@ public class TabConstants {
public static final String PLUGIN_VERSION = "@version@";
public static final String PLUGIN_DESCRIPTION = "@description@";
public static final String PLUGIN_WEBSITE = "@website@";
public static final String PLUGIN_AUTHOR = "@author@";
public static final String NO_GROUP = "NONE";
public static final String DEFAULT_GROUP = "_DEFAULT_";
@@ -347,6 +348,7 @@ public class TabConstants {
public static final String VAULT = "Vault";
public static final String PLACEHOLDER_API = "PlaceholderAPI";
public static final String LIBS_DISGUISES = "LibsDisguises";
public static final String VIAREWIND = "ViaRewind";
public static final String VIAVERSION = "ViaVersion";
public static final String PROTOCOL_SUPPORT = "ProtocolSupport";
public static final String ESSENTIALS = "Essentials";
@@ -2,7 +2,7 @@ package me.neznamy.tab.api;
import java.util.*;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
@@ -14,12 +14,6 @@ import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
*/
public abstract class TabFeature {
/** Feature's name displayed in /tab cpu */
@Getter private final String featureName;
/** Feature's function name displayed in place of refreshing in /tab cpu */
@Getter private final String refreshDisplayName;
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
@@ -44,19 +38,16 @@ public abstract class TabFeature {
private final List<String> methodOverrides = new ArrayList<>();
/**
* Constructs new instance with given parameters and loads method overrides
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
* Constructs new instance and loads method overrides
*/
protected TabFeature(String featureName, String refreshDisplayName) {
this.featureName = featureName;
this.refreshDisplayName = refreshDisplayName;
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)
@@ -95,16 +86,12 @@ public abstract class TabFeature {
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(String featureName, String refreshDisplayName, String configSection) {
this(featureName, refreshDisplayName);
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) {
@@ -388,4 +375,19 @@ public abstract class TabFeature {
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,7 +2,6 @@ package me.neznamy.tab.api;
import java.util.UUID;
import io.netty.channel.Channel;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.protocol.TabPacket;
@@ -141,13 +140,6 @@ public interface TabPlayer {
*/
void sendMessage(IChatBaseComponent message);
/**
* Returns player's netty channel. On servers running version 1.7 or older returns {@code null}.
*
* @return player's channel
*/
Channel getChannel();
/**
* Returns player's ping calculated by server
*
@@ -161,18 +161,4 @@ public class RGBUtils {
}
return true;
}
/**
* Returns true if text contains legacy color request at defined RGB index start
*
* @param text
* text to check
* @param i
* current index start
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
}
}
@@ -5,10 +5,7 @@ import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import java.util.*;
/**
* A class representing platform specific packet class
@@ -16,6 +13,20 @@ import java.util.UUID;
@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;
@@ -43,16 +54,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
actions = EnumSet.of(EnumPlayerInfoAction.ADD_PLAYER,
EnumPlayerInfoAction.INITIALIZE_CHAT,
EnumPlayerInfoAction.UPDATE_GAME_MODE,
EnumPlayerInfoAction.UPDATE_LISTED,
EnumPlayerInfoAction.UPDATE_LATENCY,
EnumPlayerInfoAction.UPDATE_DISPLAY_NAME);
} else {
this.actions = EnumSet.of(action);
}
this.actions = actionToEnumSetMap.get(action);
this.entries = entries;
}
@@ -1,18 +1,16 @@
package me.neznamy.tab.api.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Class with utility methods used in the plugin
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Preconditions {
/**
* Private constructor preventing the class from being instantiated
*/
private Preconditions() {}
/**
* Checks length of String and throws {@code IllegalArgumentException} if it is
* longer than limit.
+2 -1
View File
@@ -4,11 +4,12 @@ plugins {
allprojects {
group = "me.neznamy"
version = "3.3.0"
version = "3.3.1"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
ext.set("author", "NEZNAMY")
}
val platforms = setOf(
@@ -3,14 +3,14 @@ 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.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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 org.bukkit.Location;
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;
@@ -18,10 +18,10 @@ import java.util.UUID;
public class BukkitPacketBuilder extends PacketBuilder {
{
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> ((PacketPlayOutEntityMetadata)packet).build());
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> ((PacketPlayOutEntityTeleport)packet).build());
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> ((PacketPlayOutEntityDestroy)packet).build());
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> ((PacketPlayOutSpawnEntityLiving)packet).build());
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));
@@ -76,7 +76,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
int entityId = packet.getId().hashCode();
if (packet.getAction() == Action.REMOVE) {
return new PacketPlayOutEntityDestroy(entityId).build();
return build(new PacketPlayOutEntityDestroy(entityId), clientVersion);
}
DataWatcher w = new DataWatcher();
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
@@ -90,9 +90,9 @@ public class BukkitPacketBuilder extends PacketBuilder {
if (packet.getAction() == Action.ADD) {
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
return new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w).build();
return build(new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, 0, 0, 0, 0, 0, w), clientVersion);
} else {
return new PacketPlayOutEntityMetadata(entityId, w).build();
return build(new PacketPlayOutEntityMetadata(entityId, w), clientVersion);
}
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bukkit;
import io.netty.channel.Channel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
@@ -18,14 +19,14 @@ import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboar
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.PipelineInjector;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
/**
* Pipeline injection for bukkit
*/
public class BukkitPipelineInjector extends PipelineInjector {
public class BukkitPipelineInjector extends NettyPipelineInjector {
/** NMS data storage */
private final NMSStorage nms = NMSStorage.getInstance();
@@ -39,6 +40,17 @@ public class BukkitPipelineInjector extends PipelineInjector {
super("packet_handler");
}
@Override
protected Channel getChannel(TabPlayer player) {
final BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (final IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
}
/**
* Custom channel duplex handler override
*/
@@ -53,7 +65,7 @@ public class BukkitPipelineInjector extends PipelineInjector {
try {
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
super.channelRead(context, packet);
} catch (Exception e) {
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
}
@@ -81,12 +93,12 @@ public class BukkitPipelineInjector extends PipelineInjector {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Exception e) {
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
@@ -1,8 +1,10 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
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.TabPlayer;
@@ -19,7 +21,8 @@ import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.permission.LuckPerms;
@@ -32,6 +35,7 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
@@ -39,6 +43,7 @@ import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
public class BukkitPlatform extends BackendPlatform {
@Getter private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
@@ -50,28 +55,9 @@ public class BukkitPlatform extends BackendPlatform {
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API);
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
private Plugin viaVersion;
private final boolean viaVersion = ReflectionUtils.classExists("com.viaversion.viaversion.api.Via");
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
/**
* Constructs new instance with given plugin parameter
*
* @param plugin
* plugin instance
*/
public BukkitPlatform(JavaPlugin plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VIAVERSION)) {
if (ReflectionUtils.classExists("com.viaversion.viaversion.api.Via")) {
viaVersion = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.VIAVERSION);
} else {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion(TabConstants.Plugin.VIAVERSION) + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LUCKPERMS)) {
@@ -113,13 +99,13 @@ public class BukkitPlatform extends BackendPlatform {
}
@Override
public TabExpansion getTabExpansion() {
public @NotNull TabExpansion getTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return null;
return new EmptyTabExpansion();
}
@Override
@@ -205,8 +191,8 @@ public class BukkitPlatform extends BackendPlatform {
//some PS versions return -1 on unsupported server versions instead of throwing exception
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
}
if (viaVersion != null) {
return getProtocolVersionVia(player.getUniqueId(), player.getName(), 0);
if (viaVersion) {
return ProtocolVersion.getPlayerVersionVia(player.getUniqueId(), player.getName());
}
return TAB.getInstance().getServerVersion().getNetworkId();
}
@@ -72,7 +72,8 @@ public class BukkitTAB extends JavaPlugin {
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1",
"v1_18_R2", "v1_19_R1", "v1_19_R2");
"v1_18_R2", "v1_19_R1", "v1_19_R2", "v1_19_R3");
String supportedVersionRange = "1.5 - 1.19.4";
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
long time = System.currentTimeMillis();
@@ -81,13 +82,13 @@ public class BukkitTAB extends JavaPlugin {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
return true;
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.2 - 1.19.3. Disabling just to stay safe."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on " + supportedVersionRange + ". Disabling just to stay safe."));
}
} catch (IllegalStateException ex) {
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report this issue (include your server version & fork too)"));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.2 - 1.19.3. Disabling."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports " + supportedVersionRange + ". Disabling."));
}
}
return false;
@@ -6,7 +6,7 @@ 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 io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
@@ -39,7 +39,7 @@ public class BukkitTabPlayer extends ITabPlayer {
private Object handle;
/** Player's connection for sending packets, preloading for speed */
private Object playerConnection;
@Getter private Object playerConnection;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
@@ -284,15 +284,4 @@ public class BukkitTabPlayer extends ITabPlayer {
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public Channel getChannel() {
try {
if (NMSStorage.getInstance().CHANNEL != null)
return (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + getName(), e);
}
return null;
}
}
@@ -8,7 +8,7 @@ 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.features.TabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -22,9 +22,9 @@ import java.util.WeakHashMap;
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
private final Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@Getter private final String author = "NEZNAMY";
@Getter private final String author = TabConstants.PLUGIN_AUTHOR;
@Getter private final String identifier = TabConstants.PLUGIN_ID;
@Getter private final String version = TabConstants.PLUGIN_VERSION;
@@ -50,14 +50,11 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
if (identifier.startsWith("placeholder_")) {
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
}
if (player == null) return "<Player cannot be null>";
TabPlayer p = TabAPI.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
return values.get(player).get(identifier);
}
@Override
public void setValue(TabPlayer player, String key, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
}
}
@@ -5,6 +5,8 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -23,6 +25,7 @@ import me.neznamy.tab.shared.TAB;
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener {
/** Config options */
@@ -30,21 +33,15 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/**
* Constructs new instance with given parameters and loads config options
*
* @param plugin
* plugin instance
*/
public PerWorldPlayerList(JavaPlugin plugin) {
super("Per world PlayerList", null);
Bukkit.getPluginManager().registerEvents(this, plugin);
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
}
/** Plugin reference*/
private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@Override
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
@@ -1,14 +1,14 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.api.TabAPI;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
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;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.util.ConcurrentModificationException;
import java.util.List;
@@ -46,13 +46,7 @@ public class PetFix extends TabFeature {
*/
private static final int INTERACT_COOLDOWN = 160;
/**
* Constructs new instance with given parameter
*/
public PetFix() {
super("Pet name fix", null);
TabAPI.getInstance().debug("Loaded PetFix feature");
}
@Getter private final String featureName = "Pet name fix";
/**
* Returns position of pet owner field based on server version
@@ -121,9 +115,9 @@ public class PetFix extends TabFeature {
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (PacketPlayOutEntityMetadata.CLASS.isInstance(packet)) {
if (PacketPlayOutEntityMetadataStorage.CLASS.isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) PacketPlayOutEntityMetadata.LIST.get(packet);
List<Object> items = (List<Object>) PacketPlayOutEntityMetadataStorage.LIST.get(packet);
if (items == null) return;
try {
for (Object item : items) {
@@ -143,18 +137,18 @@ public class PetFix extends TabFeature {
}
}
}
if (removedEntry != null) items.remove(removedEntry);
} catch (ConcurrentModificationException e) {
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
onPacketSend(receiver, packet);
}
if (removedEntry != null) items.remove(removedEntry);
} else if (PacketPlayOutSpawnEntityLiving.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLiving.DATA_WATCHER != null) {
} else if (PacketPlayOutSpawnEntityLivingStorage.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER != null) {
//<1.15
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLiving.DATA_WATCHER.get(packet));
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.get(packet));
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
if (petOwner != null && (petOwner.getValue() instanceof Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
watcher.removeValue(petOwnerPosition);
PacketPlayOutSpawnEntityLiving.DATA_WATCHER.set(packet, watcher.build());
PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.set(packet, watcher.build());
}
}
}
@@ -1,5 +1,12 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
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.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -9,37 +16,25 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
/**
* An additional class with additional code for &lt;1.9 servers due to an entity being required
*/
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/**
* Constructs a new instance of the class and registers events and task
*
* @param plugin
* plugin instance
*/
public WitherBossBar(JavaPlugin plugin) {
/** Reference to plugin for registering listener */
private final JavaPlugin plugin;
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
}
@Override
public void load() {
super.load();
teleport();
}
@@ -54,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), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc.getX(), loc.getY(), loc.getZ(), 0, 0), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
}
}
}
@@ -1,155 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
/**
* A class representing an armor stand attached to a player
*/
public class BukkitArmorStand extends BackendArmorStand {
/** Owner as a Bukkit player */
private final Player player;
/** Entity destroy packet */
protected final TabPacket destroyPacket = new PacketPlayOutEntityDestroy(entityId);
/**
* Constructs new instance with given parameters.
*
* @param asm
* Armor stand manager which this armor stand belongs to
* @param owner
* Owner of the armor stand
* @param propertyName
* Name of refresh property to use
* @param yOffset
* Offset in blocks
* @param staticOffset
* {@code true} if offset is static, {@code false} if not
*/
public BukkitArmorStand(BackendNameTagX feature, BackendArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
super(feature, asm, owner, propertyName, yOffset, staticOffset);
player = (Player) owner.getPlayer();
sneaking = player.isSneaking();
}
@Override
public void spawn(TabPlayer viewer) {
for (TabPacket packet : getSpawnPackets(viewer)) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
}
}
@Override
public void destroy(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
/**
* Returns location where armor stand should be at time of calling.
* This takes into account everything that affects height, including
* viewer's game version.
*
* @param viewer
* Player looking at the armor stand
* @return Location where armor stand should be for specified viewer
*/
public Location getLocation(TabPlayer viewer) {
Location loc = player.getLocation();
double y = loc.getY();
//1.14+ server sided bug
Entity vehicle = player.getVehicle();
if (vehicle != null) {
if (vehicle.getType().toString().contains("horse")) { //covering all 3 horse types
y = vehicle.getLocation().getY() + 0.85;
}
if (vehicle.getType().toString().equals("donkey")) { //1.11+
y = vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
y = vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("strider")) { //1.16+
y = vehicle.getLocation().getY() + 1.15;
}
} else {
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
y = loc.getY()-1.22;
}
}
y += getYAdd(player.isSleeping(), sneaking, viewer);
return new Location(null, loc.getX(), y, loc.getZ());
}
@Override
public void updateMetadata(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
@Override
public void sendTeleportPacket(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityTeleport(entityId, getLocation(viewer)),
TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
/**
* Returns {@code true} if owner is swimming, {@code false} if not
* @return {@code true} if owner is swimming, {@code false} if not
*/
private boolean isSwimming() {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
}
private TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = calculateVisibility();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), dataWatcher),
};
}
}
/**
* Creates data watcher with specified display name for viewer
*
* @param displayName
* armor stand name
* @param viewer
* player to apply checks against
* @return DataWatcher for viewer
*/
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags((byte) (sneaking ? 34 : 32));
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(!shouldBeInvisibleFor(viewer, displayName) && visible);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x())
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
}
@@ -1,18 +1,20 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
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.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
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.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
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.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
@@ -23,6 +25,7 @@ import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
@RequiredArgsConstructor
public class BukkitNameTagX extends BackendNameTagX {
/** Reference to NMS storage for quick access */
@@ -31,15 +34,13 @@ public class BukkitNameTagX extends BackendNameTagX {
/** Bukkit event listener */
private final EventListener eventListener = new EventListener(this);
/**
* Constructs new instance with given parameter, loads config options, registers events
* and registers sub-features.
*
* @param plugin
* plugin instance
*/
public BukkitNameTagX(JavaPlugin plugin) {
/** Plugin reference */
private final JavaPlugin plugin;
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
super.load();
}
@Override
@@ -67,13 +68,13 @@ public class BukkitNameTagX extends BackendNameTagX {
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));
} else if (PacketPlayOutEntityTeleport.CLASS.isInstance(packet)) {
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleport.ENTITY_ID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroy.CLASS.isInstance(packet)) {
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroy.ENTITIES.get(packet);
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy(receiver, (List<Integer>) entities);
} else {
@@ -81,7 +82,7 @@ public class BukkitNameTagX extends BackendNameTagX {
packetListener.onEntityDestroy(receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroy.ENTITIES.get(packet));
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
}
@@ -148,7 +149,54 @@ public class BukkitNameTagX extends BackendNameTagX {
}
@Override
public BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset) {
return new BukkitArmorStand(this, feature, owner, lineName, yOffset, staticOffset);
public boolean isSneaking(TabPlayer player) {
return ((Player)player.getPlayer()).isSneaking();
}
@Override
public boolean isSwimming(TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(TabPlayer player) {
return TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
public boolean isSleeping(TabPlayer player) {
return ((Player)player.getPlayer()).isSleeping();
}
@Override
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getX();
}
@Override
public double getY(Object entity) {
return ((Entity)entity).getLocation().getY();
}
@Override
public double getZ(TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getZ();
}
@Override
public Object createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible, boolean markerFlag) {
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);
return datawatcher;
}
}
@@ -46,7 +46,6 @@ public class DataWatcherItem {
* if thrown by reflective operation
*/
public static DataWatcherItem fromNMS(Object nmsItem) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object value = VALUE.get(nmsItem);
DataWatcherObject object;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9) {
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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;
@@ -122,13 +122,13 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
}
// Entities
PacketPlayOutSpawnEntityLiving.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleport.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayInUseEntity = getLegacyClass("PacketPlayInUseEntity", "Packet7UseEntity");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroy.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadata.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
if (minorVersion >= 7) {
EnumEntityUseAction = (Class<Enum>) getLegacyClass("PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction");
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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;
@@ -34,14 +34,18 @@ public class BukkitModernNMSStorage extends NMSStorage {
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (is1_19_3Plus()) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
} else {
if (is1_19_4Plus()) {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
}
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLiving.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLiving.ENTITY_TYPE = PacketPlayOutSpawnEntityLiving.CLASS.getDeclaredField("e")).setAccessible(true);
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("e")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("b");
}
}
@@ -81,21 +85,21 @@ public class BukkitModernNMSStorage extends NMSStorage {
// Entities
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
} else {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
}
PacketPlayOutEntityTeleport.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayInUseEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity");
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$d");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroy.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadata.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
}
// Player Info
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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;
@@ -35,8 +35,8 @@ public class MojangModernNMSStorage extends NMSStorage {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLiving.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLiving.ENTITY_TYPE = PacketPlayOutSpawnEntityLiving.CLASS.getDeclaredField("type")).setAccessible(true);
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("type")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("packDirty");
}
}
@@ -75,21 +75,21 @@ public class MojangModernNMSStorage extends NMSStorage {
// Entities
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
} else {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
}
PacketPlayOutEntityTeleport.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayInUseEntity = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket");
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$InteractionAction");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
PacketPlayOutEntityDestroy.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadata.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
}
// Player Info
@@ -10,10 +10,10 @@ 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.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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;
@@ -45,6 +45,7 @@ public abstract class NMSStorage {
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Basic universal values */
protected Class<?> Packet;
@@ -124,10 +125,10 @@ public abstract class NMSStorage {
DataWatcher.load(this);
DataWatcherItem.load(this);
DataWatcherObject.load(this);
PacketPlayOutEntityDestroy.load(this);
PacketPlayOutEntityMetadata.load(this);
PacketPlayOutEntityTeleport.load(this);
PacketPlayOutSpawnEntityLiving.load(this);
PacketPlayOutEntityDestroyStorage.load(this);
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketPlayOutChatStorage.load(this);
PacketPlayOutPlayerListHeaderFooterStorage.load(this);
PacketPlayOutPlayerInfoStorage.load(this);
@@ -1,8 +1,8 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.Data;
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,17 +10,13 @@ import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
@Data
public class PacketPlayOutEntityDestroy implements TabPacket {
public class PacketPlayOutEntityDestroyStorage implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/** Packet's instance fields */
private final int[] entities;
/**
* Loads all required Fields and throws Exception if something went wrong
*
@@ -39,16 +35,6 @@ public class PacketPlayOutEntityDestroy implements TabPacket {
}
}
/**
* Constructs new instance with given parameter
*
* @param entities
* Destroyed entities
*/
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
/**
* Converts this class into NMS packet
*
@@ -56,12 +42,12 @@ public class PacketPlayOutEntityDestroy implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
public static Object build(PacketPlayOutEntityDestroy packet) throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterTypes()[0] != int.class) {
return CONSTRUCTOR.newInstance(new Object[]{entities});
return CONSTRUCTOR.newInstance(new Object[]{packet.getEntities()});
} else {
//1.17.0
return CONSTRUCTOR.newInstance(entities[0]);
return CONSTRUCTOR.newInstance(packet.getEntities()[0]);
}
}
}
@@ -1,11 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
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;
@@ -14,18 +12,13 @@ import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityMetadata implements TabPacket {
public class PacketPlayOutEntityMetadataStorage implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field LIST;
/** Packet's instance fields */
private final int entityId;
@NonNull private final DataWatcher dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
*
@@ -50,12 +43,12 @@ public class PacketPlayOutEntityMetadata implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
public static Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
return CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(dataWatcher.build()));
return CONSTRUCTOR.newInstance(packet.getEntityId(), DataWatcher.packDirty.invoke(((DataWatcher)packet.getDataWatcher()).build()));
} else {
return CONSTRUCTOR.newInstance(entityId, dataWatcher.build(), true);
return CONSTRUCTOR.newInstance(packet.getEntityId(), ((DataWatcher)packet.getDataWatcher()).build(), true);
}
}
}
@@ -1,11 +1,8 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import org.bukkit.Location;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityTeleport;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -13,8 +10,7 @@ import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityTeleport implements TabPacket {
public class PacketPlayOutEntityTeleportStorage implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
@@ -26,10 +22,6 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
public static Field YAW;
public static Field PITCH;
/** Packet's instance fields */
private final int entityId;
@NonNull private final Location location;
/**
* Loads all required Fields and throws Exception if something went wrong
*
@@ -65,7 +57,7 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
public static Object build(PacketPlayOutEntityTeleport packet) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -73,22 +65,22 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
ENTITY_ID.set(nmsPacket, packet.getEntityId());
if (nms.getMinorVersion() >= 9) {
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
X.set(nmsPacket, packet.getX());
Y.set(nmsPacket, packet.getY());
Z.set(nmsPacket, packet.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
X.set(nmsPacket, floor(packet.getX()*32));
Y.set(nmsPacket, floor(packet.getY()*32));
Z.set(nmsPacket, floor(packet.getZ()*32));
}
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
YAW.set(nmsPacket, (byte) (packet.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (packet.getPitch()/360*256));
return nmsPacket;
}
private int floor(double paramDouble) {
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
@@ -1,5 +1,6 @@
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;
@@ -175,8 +176,9 @@ public class PacketPlayOutPlayerInfoStorage {
GameProfile profile = (GameProfile) PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = PlayerInfoData_getDisplayName.invoke(nmsData);
Skin skin = null;
if (!profile.getProperties().get("textures").isEmpty()) {
Property pr = profile.getProperties().get("textures").iterator().next();
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;
@@ -1,12 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
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 org.bukkit.Location;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutSpawnEntityLiving;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
@@ -17,8 +14,7 @@ import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
public class PacketPlayOutSpawnEntityLivingStorage implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
@@ -37,13 +33,6 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
/** Entity types */
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Packet's instance fields */
private final int entityId;
@NonNull private final UUID uniqueId;
@NonNull private final EntityType entityType;
@NonNull private final Location location;
private final DataWatcher dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
@@ -108,7 +97,7 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
public static Object build(PacketPlayOutSpawnEntityLiving packet) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -116,32 +105,31 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
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));
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));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, dataWatcher.build());
DATA_WATCHER.set(nmsPacket, ((DataWatcher)packet.getDataWatcher()).build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, uniqueId);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
UUID.set(nmsPacket, packet.getUniqueId());
X.set(nmsPacket, packet.getX());
Y.set(nmsPacket, packet.getY());
Z.set(nmsPacket, packet.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
X.set(nmsPacket, floor(packet.getX()*32));
Y.set(nmsPacket, floor(packet.getY()*32));
Z.set(nmsPacket, floor(packet.getZ()*32));
}
int id = entityIds.get(entityType);
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, id);
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) packet.getEntityType()));
}
return nmsPacket;
}
private int floor(double paramDouble) {
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
+1
View File
@@ -3,4 +3,5 @@ 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
}
@@ -2,19 +2,22 @@ package me.neznamy.tab.platforms.bungeecord;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import java.lang.reflect.Field;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisPlayer;
import me.neznamy.tab.shared.features.sorting.Sorting;
import 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;
@@ -30,13 +33,37 @@ import java.util.function.Supplier;
* from overriding it.
*/
@SuppressWarnings("unchecked")
public class BungeePipelineInjector extends PipelineInjector {
public class BungeePipelineInjector extends NettyPipelineInjector {
private static Field wrapperField;
static {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
}
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BungeeChannelDuplexHandler::new;
@Override
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : BungeeChannelDuplexHandler::new;
}
@Override
protected Channel getChannel(TabPlayer player) {
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (final IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
}
/**
* Constructs new instance of the feature
@@ -84,12 +111,12 @@ public class BungeePipelineInjector extends PipelineInjector {
default:
break;
}
} catch (Exception | NoSuchMethodError e) {
} 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 (Exception e) {
} catch (Throwable e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to forward packet %s to %s", packet.getClass().getSimpleName(), player.getName()), e);
}
}
@@ -11,7 +11,6 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
@@ -26,7 +25,6 @@ import java.util.UUID;
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Inaccessible bungee internals */
private static Field wrapperField;
private static Object directionData;
private static Method getId;
@@ -35,10 +33,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
Field f = Protocol.class.getDeclaredField("TO_CLIENT");
f.setAccessible(true);
directionData = f.get(Protocol.GAME);
getId = directionData.getClass().getDeclaredMethod("getId", Class.class, int.class);
getId.setAccessible(true);
wrapperField = InitialHandler.class.getDeclaredField("ch");
wrapperField.setAccessible(true);
(getId = directionData.getClass().getDeclaredMethod("getId", Class.class, int.class)).setAccessible(true);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
}
@@ -51,12 +46,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* BungeeCord player
*/
public BungeeTabPlayer(ProxiedPlayer p) {
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1, true);
try {
channel = ((ChannelWrapper)wrapperField.get(getPlayer().getPendingConnection())).getHandle();
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + getPlayer().getName(), e);
}
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
}
@Override
@@ -150,6 +140,5 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) return;
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
TAB.getInstance().getCPUManager().packetSent("Plugin Message");
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import net.md_5.bungee.api.ProxyServer;
@@ -12,14 +13,17 @@ import net.md_5.bungee.event.EventHandler;
/**
* Redis implementation for BungeeCord
*/
@AllArgsConstructor
public class RedisBungeeSupport extends RedisSupport implements Listener {
/**
* Constructs new instance, registers listeners and overrides placeholders
*/
public RedisBungeeSupport(Plugin plugin) {
/** Plugin reference for registering listener */
private final Plugin plugin;
@Override
public void load() {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
RedisBungeeAPI.getRedisBungeeApi().registerPubSubChannels(TabConstants.REDIS_CHANNEL_NAME);
super.load();
}
@EventHandler
+2 -2
View File
@@ -22,8 +22,8 @@ bukkit = "1.16.5-R0.1-SNAPSHOT"
bungeecord = "1.19-R0.1-SNAPSHOT"
krypton = "a81b4baaed" # Krypton uses commit-based versioning
velocity = "3.1.2-SNAPSHOT"
sponge7 = "7.4.0"
sponge8 = "8.1.0"
sponge7 = "7.0.0"
sponge8 = "8.0.0"
# Platform extras
placeholderapi = "2.11.2"
@@ -6,27 +6,38 @@ import me.neznamy.tab.api.chat.IChatBaseComponent
import me.neznamy.tab.api.protocol.*
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData
import me.neznamy.tab.api.util.BiFunctionWithException
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityMetadata
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutSpawnEntityLiving
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import org.kryptonmc.api.auth.GameProfile
import org.kryptonmc.api.auth.ProfileProperty
import org.kryptonmc.api.world.GameMode
import org.kryptonmc.krypton.adventure.KryptonAdventure
import org.kryptonmc.krypton.entity.KryptonEntityType
import org.kryptonmc.krypton.entity.metadata.MetadataHolder
import org.kryptonmc.krypton.entity.player.PlayerPublicKey
import org.kryptonmc.krypton.network.chat.RemoteChatSession
import org.kryptonmc.krypton.packet.out.play.PacketOutDisplayObjective
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoRemove
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoUpdate
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateObjectives
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateScore
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateTeams
import java.util.EnumSet
import org.kryptonmc.krypton.packet.out.play.*
import java.util.*
// All build functions that just return the packet parameter will be passed through to be handled in KryptonTabPlayer.
object KryptonPacketBuilder : PacketBuilder() {
private val GAME_MODES = GameMode.values()
init {
buildMap[PacketPlayOutEntityMetadata::class.java] =
BiFunctionWithException { packet: TabPacket, _: ProtocolVersion? ->
build(packet as PacketPlayOutEntityMetadata)
}
buildMap[PacketPlayOutSpawnEntityLiving::class.java] =
BiFunctionWithException { packet: TabPacket, _: ProtocolVersion? ->
build(packet as PacketPlayOutSpawnEntityLiving)
}
}
@JvmStatic
fun toComponent(text: String?, clientVersion: ProtocolVersion): Component {
if (text.isNullOrEmpty()) return Component.empty()
@@ -93,6 +104,15 @@ object KryptonPacketBuilder : PacketBuilder() {
return PacketOutUpdateTeams(packet.name, action, createParameters(packet, clientVersion), players)
}
private fun build(packet: PacketPlayOutEntityMetadata): Any {
return PacketOutSetEntityMetadata(packet.entityId, (packet.dataWatcher as MetadataHolder).collectAll())
}
private fun build(packet: PacketPlayOutSpawnEntityLiving): Any {
return PacketOutSpawnEntity(packet.entityId, packet.uniqueId, packet.entityType as KryptonEntityType<*>,
packet.x, packet.y, packet.z, 0, 0, 0, 0, 0, 0, 0)
}
private fun createParameters(packet: PacketPlayOutScoreboardTeam, clientVersion: ProtocolVersion): PacketOutUpdateTeams.Parameters? {
if (packet.action != 0 && packet.action != 2) return null
var prefix = packet.playerPrefix
@@ -1,26 +1,33 @@
package me.neznamy.tab.platforms.krypton
import io.netty.channel.Channel
import io.netty.channel.ChannelDuplexHandler
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelPromise
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.features.PipelineInjector
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector
import org.kryptonmc.krypton.network.NettyConnection
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoRemove
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoUpdate
import java.util.function.Function
class KryptonPipelineInjector : PipelineInjector("handler") {
class KryptonPipelineInjector : NettyPipelineInjector("handler") {
override fun getChannelFunction(): Function<TabPlayer, ChannelDuplexHandler> = Function(::KryptonChannelDuplexHandler)
override fun getChannel(player: TabPlayer?): Channel {
val connection = (player as KryptonTabPlayer).connection()
return NettyConnection::class.java.getDeclaredField("channel").apply { isAccessible = true }.get(connection) as Channel
}
inner class KryptonChannelDuplexHandler(private val player: TabPlayer) : ChannelDuplexHandler() {
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
try {
if (TAB.getInstance().featureManager.onPacketReceive(player, msg)) return
super.channelRead(ctx, msg)
} catch (exception: Exception) {
} catch (exception: Throwable) {
TAB.getInstance().errorManager.printError("An error occurred when reading packets", exception)
}
}
@@ -37,7 +44,7 @@ class KryptonPipelineInjector : PipelineInjector("handler") {
}
try {
super.write(ctx, msg, promise)
} catch (exception: Exception) {
} catch (exception: Throwable) {
TAB.getInstance().errorManager.printError("Failed to forward packet ${msg.javaClass.simpleName} to ${player.name}", exception)
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.krypton
import me.lucko.spark.api.Spark
import me.lucko.spark.api.statistic.StatisticWindow.*
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
@@ -11,8 +12,8 @@ class KryptonPlaceholderRegistry(private val plugin: Main) : UniversalPlaceholde
override fun registerPlaceholders(manager: PlaceholderManager) {
// Built-in stuff
manager.registerPlayerPlaceholder("%displayname%", 500) { (it.player as Player).displayName }
manager.registerPlayerPlaceholder("%health%", 100) { ceil((it.player as Player).health) }
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500) { (it.player as Player).displayName }
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100) { ceil((it.player as Player).health) }
// Spark placeholders, registered if Spark is present
registerSparkPlaceholders(manager)
@@ -5,9 +5,10 @@ import me.neznamy.tab.api.protocol.PacketBuilder
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.PipelineInjector
import me.neznamy.tab.shared.features.TabExpansion
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.serializer.legacy.LegacyComponentSerializer
@@ -44,8 +45,8 @@ class KryptonPlatform(private val plugin: Main) : BackendPlatform() {
return KryptonNameTagX(plugin)
}
override fun getTabExpansion(): TabExpansion? {
return null
override fun getTabExpansion(): TabExpansion {
return EmptyTabExpansion()
}
override fun getPetFix(): TabFeature? {
@@ -1,6 +1,5 @@
package me.neznamy.tab.platforms.krypton
import io.netty.channel.Channel
import me.neznamy.tab.api.chat.IChatBaseComponent
import me.neznamy.tab.api.protocol.PacketPlayOutBoss
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action
@@ -31,10 +30,7 @@ class KryptonTabPlayer(
private val delegate = delegate as KryptonPlayer
private val bossBars = mutableMapOf<UUID, BossBar>()
init {
val connection = this.delegate.connection
channel = NettyConnection::class.java.getDeclaredField("channel").apply { isAccessible = true }.get(connection) as Channel
}
fun connection(): NettyConnection = delegate.connection
override fun hasPermission(permission: String): Boolean = delegate.hasPermission(permission)
@@ -31,7 +31,7 @@ import java.nio.file.Path
TabConstants.PLUGIN_NAME,
TabConstants.PLUGIN_VERSION,
TabConstants.PLUGIN_DESCRIPTION,
["NEZNAMY", "BomBardyGamer"],
[TabConstants.PLUGIN_AUTHOR, "BomBardyGamer"],
[
Dependency("luckperms", true),
Dependency("viaversion", true)
@@ -77,7 +77,7 @@ class Main @Inject constructor(
fun getProtocolVersion(player: Player): Int {
if (server.pluginManager.isLoaded(TabConstants.Plugin.VIAVERSION.lowercase())) {
return TAB.getInstance().platform.getProtocolVersionVia(player.uuid, player.profile.name, 0)
return ProtocolVersion.getPlayerVersionVia(player.uuid, player.profile.name)
}
return TAB.getInstance().serverVersion.networkId
}
@@ -1,77 +0,0 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.platforms.krypton.KryptonPacketBuilder
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager
import org.kryptonmc.krypton.entity.KryptonEntityTypes
import org.kryptonmc.krypton.entity.metadata.MetadataHolder
import org.kryptonmc.krypton.entity.metadata.MetadataKeys
import org.kryptonmc.krypton.entity.player.KryptonPlayer
import org.kryptonmc.krypton.packet.Packet
import org.kryptonmc.krypton.packet.out.play.PacketOutRemoveEntities
import org.kryptonmc.krypton.packet.out.play.PacketOutSetEntityMetadata
import org.kryptonmc.krypton.packet.out.play.PacketOutSpawnEntity
import org.kryptonmc.krypton.packet.out.play.PacketOutTeleportEntity
class KryptonArmorStand(
manager: BackendArmorStandManager,
nameTagX: KryptonNameTagX,
owner: TabPlayer,
propertyName: String,
yOffset: Double,
staticOffset: Boolean
) : BackendArmorStand(nameTagX, manager, owner, propertyName, yOffset, staticOffset) {
private val player = owner.player as KryptonPlayer
private val destroyPacket = PacketOutRemoveEntities(intArrayOf(entityId))
override fun spawn(viewer: TabPlayer) {
getSpawnPackets(viewer).forEach { viewer.sendPacket(it) }
}
override fun destroy(viewer: TabPlayer) {
viewer.sendPacket(destroyPacket)
}
private fun getArmorStandYFor(viewer: TabPlayer): Double {
var y = player.position.y
if (player.isSwimming || player.isGliding) y -= 1.22
y += getYAdd(false, sneaking, viewer)
return y
}
override fun updateMetadata(viewer: TabPlayer) {
viewer.sendPacket(PacketOutSetEntityMetadata(entityId, createMetadata(property.getFormat(viewer), viewer).collectAll()))
}
override fun sendTeleportPacket(viewer: TabPlayer) {
viewer.sendPacket(PacketOutTeleportEntity(entityId, player.position.x, getArmorStandYFor(viewer), player.position.z, 0, 0, false))
}
private fun getSpawnPackets(viewer: TabPlayer): Array<Packet> {
visible = calculateVisibility()
val data = createMetadata(property.getFormat(viewer), viewer)
return arrayOf(
PacketOutSpawnEntity(entityId, uuid, KryptonEntityTypes.ARMOR_STAND, player.position.x, getArmorStandYFor(viewer),
player.position.z, 0, 0, 0, 0, 0, 0, 0),
PacketOutSetEntityMetadata(entityId, data.collectAll())
)
}
private fun createMetadata(displayName: String, viewer: TabPlayer): MetadataHolder {
val viewerPlayer = viewer.player as KryptonPlayer
val holder = MetadataHolder(viewerPlayer).apply {
define(MetadataKeys.Entity.FLAGS, 0)
define(MetadataKeys.Entity.CUSTOM_NAME, null)
define(MetadataKeys.Entity.CUSTOM_NAME_VISIBILITY, false)
}
holder.set(MetadataKeys.Entity.FLAGS, (if (sneaking) 34 else 32).toByte())
holder.set(MetadataKeys.Entity.CUSTOM_NAME, KryptonPacketBuilder.toComponent(displayName, viewer.version))
holder.set(MetadataKeys.Entity.CUSTOM_NAME_VISIBILITY, !shouldBeInvisibleFor(viewer, displayName) && visible)
if (viewer.version.minorVersion > 8 || manager.isMarkerFor18x) {
holder.define(MetadataKeys.ArmorStand.FLAGS, 16.toByte())
}
return holder
}
}
@@ -2,14 +2,17 @@ package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabConstants
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.platforms.krypton.KryptonPacketBuilder
import me.neznamy.tab.platforms.krypton.Main
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX
import org.kryptonmc.api.entity.Entity
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.EventNode
import org.kryptonmc.krypton.entity.KryptonEntityTypes
import org.kryptonmc.krypton.entity.metadata.MetadataHolder
import org.kryptonmc.krypton.entity.metadata.MetadataKeys
import org.kryptonmc.krypton.entity.player.KryptonPlayer
import org.kryptonmc.krypton.packet.EntityPacket
import org.kryptonmc.krypton.packet.MovementPacket
import org.kryptonmc.krypton.packet.`in`.play.PacketInInteract
@@ -21,10 +24,11 @@ class KryptonNameTagX(private val plugin: Main) : BackendNameTagX() {
private val eventListener = EventListener(this)
init {
override fun load() {
val eventNode = EventNode.all("tab_nametagx")
plugin.eventNode.addChild(eventNode)
eventNode.registerListeners(eventListener)
super.load()
}
override fun onPacketReceive(sender: TabPlayer, packet: Any): Boolean {
@@ -97,8 +101,50 @@ class KryptonNameTagX(private val plugin: Main) : BackendNameTagX() {
return (entity as Entity).type.key().value()
}
override fun createArmorStand(asm: BackendArmorStandManager, owner: TabPlayer, lineName: String, yOffset: Double, staticOffset: Boolean): BackendArmorStand {
return KryptonArmorStand(asm, this, owner, lineName, yOffset, staticOffset)
override fun isSneaking(player: TabPlayer): Boolean {
return (player as Player).isSneaking
}
override fun isSwimming(player: TabPlayer?): Boolean {
return (player as Player).isSwimming
}
override fun isGliding(player: TabPlayer?): Boolean {
return (player as Player).isGliding
}
override fun isSleeping(player: TabPlayer?): Boolean {
return false
}
override fun getArmorStandType(): Any {
return KryptonEntityTypes.ARMOR_STAND
}
override fun getX(player: TabPlayer): Double {
return (player as Player).position.x
}
override fun getY(entity: Any): Double {
return (entity as Entity).position.y
}
override fun getZ(player: TabPlayer): Double {
return (player as Player).position.z
}
override fun createDataWatcher(viewer: TabPlayer, flags: Byte, displayName: String, nameVisible: Boolean, markerFlag: Boolean): Any {
val viewerPlayer = viewer.player as KryptonPlayer
val holder = MetadataHolder(viewerPlayer).apply {
define(MetadataKeys.Entity.FLAGS, 0)
define(MetadataKeys.Entity.CUSTOM_NAME, null)
define(MetadataKeys.Entity.CUSTOM_NAME_VISIBILITY, false)
}
holder.set(MetadataKeys.Entity.FLAGS, flags)
holder.set(MetadataKeys.Entity.CUSTOM_NAME, KryptonPacketBuilder.toComponent(displayName, viewer.version))
holder.set(MetadataKeys.Entity.CUSTOM_NAME_VISIBILITY, nameVisible)
if (markerFlag) holder.define(MetadataKeys.ArmorStand.FLAGS, 16.toByte())
return holder
}
companion object {
+1 -1
View File
@@ -4,9 +4,9 @@ dependencies {
exclude("com.google.guava", "guava")
exclude("org.checkerframework", "checker-qual")
}
compileOnlyApi(libs.netty)
compileOnlyApi(libs.luckperms)
compileOnlyApi(libs.guava)
compileOnlyApi(libs.floodgate)
compileOnlyApi(libs.slf4j)
compileOnlyApi(libs.via)
}
@@ -9,7 +9,7 @@ import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.placeholders.RelationalPlaceholderImpl;
/**
@@ -120,8 +120,8 @@ public class DynamicText implements Property {
}
lastReplacedValue = rawFormattedValue;
update();
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null && name != null) {
if (name != null) {
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
expansion.setPropertyValue(owner, name, lastReplacedValue);
expansion.setRawPropertyValue(owner, name, getCurrentRawValue());
}
@@ -193,9 +193,8 @@ public class DynamicText implements Property {
string = EnumChatFormat.color(string);
if (!lastReplacedValue.equals(string)) {
lastReplacedValue = string;
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null && name != null) {
expansion.setPropertyValue(owner, name, lastReplacedValue);
if (name != null) {
TAB.getInstance().getPlaceholderManager().getTabExpansion().setPropertyValue(owner, name, lastReplacedValue);
}
TAB.getInstance().getCPUManager().addMethodTime("Property#update", System.nanoTime()-time);
return true;
@@ -1,5 +1,10 @@
package me.neznamy.tab.shared;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -7,30 +12,35 @@ import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
/**
* An error assistant to print internal errors into error file
* and warn user about misconfiguration
*/
public class ErrorManager {
/** Date format used in error messages */
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy - HH:mm:ss - ");
/** errors.log file for internal plugin errors */
private final File errorLog = new File(TAB.getInstance().getDataFolder(), "errors.log");
private final File errorLog;
/** anti-override.log file when some plugin or server itself attempts to override the plugin */
@Getter private final File antiOverrideLog = new File(TAB.getInstance().getDataFolder(), "anti-override.log");
@Getter private final File antiOverrideLog;
/** placeholder-errors.log file for errors thrown by placeholders */
private final File placeholderErrorLog = new File(TAB.getInstance().getDataFolder(), "placeholder-errors.log");
private final File placeholderErrorLog;
public ErrorManager(TAB tab) {
errorLog = new File(tab.getDataFolder(), "errors.log");
antiOverrideLog = new File(tab.getDataFolder(), "anti-override.log");
placeholderErrorLog = new File(tab.getDataFolder(), "placeholder-errors.log");
}
/**
* Prints error message into errors.log file
@@ -39,7 +39,12 @@ public class FeatureManagerImpl implements FeatureManager {
* This function is called on plugin startup.
*/
public void load() {
for (TabFeature f : values) f.load();
for (TabFeature f : values) {
if (!f.overridesMethod("load")) continue;
long time = System.currentTimeMillis();
f.load();
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed load in " + (System.currentTimeMillis()-time) + "ms");
}
if (TAB.getInstance().getConfiguration().getUsers() instanceof MySQLUserConfiguration) {
MySQLUserConfiguration users = (MySQLUserConfiguration) TAB.getInstance().getConfiguration().getUsers();
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) users.load(p);
@@ -51,7 +56,13 @@ public class FeatureManagerImpl implements FeatureManager {
* This function is called on plugin disable.
*/
public void unload() {
for (TabFeature f : values) f.unload();
for (TabFeature f : values) {
if (!f.overridesMethod("unload")) continue;
long time = System.currentTimeMillis();
f.unload();
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed unload in " + (System.currentTimeMillis()-time) + "ms");
}
if (TAB.getInstance().getPlatform() instanceof ProxyPlatform) {
PluginMessageHandler plm = ((ProxyPlatform) TAB.getInstance().getPlatform()).getPluginMessageHandler();
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
@@ -310,6 +321,7 @@ public class FeatureManagerImpl implements FeatureManager {
@Override
public void registerFeature(String featureName, TabFeature featureHandler) {
if (featureName == null || featureHandler == null) return;
features.put(featureName, featureHandler);
values = features.values().toArray(new TabFeature[0]);
}
@@ -23,6 +23,8 @@ public class GroupManager extends TabFeature {
/** List of group permissions to iterate through if {@link #groupsByPermissions} is {@code true} */
private final List<String> primaryGroupFindingList = TAB.getInstance().getConfiguration().getConfig().getStringList("primary-group-finding-list", Arrays.asList("Owner", "Admin", "Helper", "default"));
@Getter private final String featureName = "Permission group refreshing";
/**
* Constructs new instance with given permission plugin, loads
* event listeners and registers group placeholder.
@@ -31,7 +33,6 @@ public class GroupManager extends TabFeature {
* Detected permission plugin
*/
public GroupManager(PermissionPlugin plugin) {
super("Permission group refreshing", null);
this.plugin = plugin;
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(1000, this, TabConstants.CpuUsageCategory.GROUP_REFRESHING, () -> {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
@@ -1,7 +1,8 @@
package me.neznamy.tab.shared;
import com.viaversion.viaversion.api.Via;
import lombok.NonNull;
import me.neznamy.tab.api.FeatureManager;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.chat.EnumChatFormat;
@@ -10,17 +11,18 @@ import me.neznamy.tab.shared.config.Configs;
import me.neznamy.tab.shared.features.*;
import me.neznamy.tab.shared.features.alignedplayerlist.AlignedPlayerList;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.globalplayerlist.GlobalPlayerList;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.layout.LayoutManager;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import java.util.UUID;
/**
* An interface with methods that are called in universal code,
* but require platform-specific API calls
@@ -49,34 +51,15 @@ public abstract class Platform {
public void loadFeatures() {
Configs configuration = TAB.getInstance().getConfiguration();
FeatureManager featureManager = TAB.getInstance().getFeatureManager();
if (configuration.isPipelineInjection() && TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
PipelineInjector inj = getPipelineInjector();
if (inj != null) featureManager.registerFeature(TabConstants.Feature.PIPELINE_INJECTION, inj);
}
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true)) {
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
if (configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false)
&& TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, getUnlimitedNametags());
} else {
featureManager.registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
int minorVersion = TAB.getInstance().getServerVersion().getMinorVersion();
if (configuration.getConfig().getBoolean("bossbar.enabled", false)) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR, new BossBarManagerImpl());
} else {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR, getLegacyBossBar());
}
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : getLegacyBossBar());
}
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9 &&
configuration.getConfig().getBoolean("fix-pet-names.enabled", false)) {
TabFeature petFix = getPetFix();
if (petFix != null) featureManager.registerFeature(TabConstants.Feature.PET_FIX, getPetFix());
if (minorVersion >= 9 && configuration.getConfig().getBoolean("fix-pet-names.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.PET_FIX, getPetFix());
}
if (configuration.getConfig().getBoolean("header-footer.enabled", true))
@@ -85,7 +68,42 @@ public abstract class Platform {
if (configuration.isRemoveGhostPlayers())
featureManager.registerFeature(TabConstants.Feature.GHOST_PLAYER_FIX, new GhostPlayerFix());
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8 && configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true)) {
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (configuration.isPipelineInjection()) {
featureManager.registerFeature(TabConstants.Feature.PIPELINE_INJECTION, getPipelineInjector());
}
if (configuration.getConfig().getBoolean("scoreboard.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (configuration.getConfig().getBoolean("per-world-playerlist.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, getPerWorldPlayerlist());
}
// No requirements, but due to chicken vs egg, the feature uses NameTags, Layout and RedisBungee
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true) ||
configuration.getLayout().getBoolean("enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
}
// Must be loaded after: Sorting
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true)) {
if (configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && minorVersion >= 8) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, getUnlimitedNametags());
} else {
featureManager.registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
// Must be loaded after: Sorting
if (minorVersion >= 8 && configuration.getLayout().getBoolean("enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManager());
}
// Must be loaded after: Layout
if (minorVersion >= 8 && configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true)) {
if (configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false)) {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new AlignedPlayerList());
} else {
@@ -93,46 +111,28 @@ public abstract class Platform {
}
}
// Must be loaded after: PlayerList
if (configuration.getConfig().getBoolean("global-playerlist.enabled", false) &&
TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY) {
featureManager.registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST, new GlobalPlayerList());
}
// Must be loaded after: Layout
if (configuration.getConfig().getBoolean("ping-spoof.enabled", false))
featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
// Must be loaded after: Global PlayerList, PlayerList, NameTags
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, getRedisSupport());
// Must be loaded after: RedisBungee
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true))
featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
// Must be loaded after: RedisBungee
if (configuration.getConfig().getBoolean("belowname-objective.enabled", true))
featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
if (configuration.getConfig().getBoolean("scoreboard.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8 && configuration.getLayout().getBoolean("enabled", false)) {
if (TAB.getInstance().getTeamManager() == null) {
//sorting is disabled, but layout needs team names
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
}
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManager());
}
if (configuration.getConfig().getBoolean("placeholders.register-tab-expansion", false)) {
TAB.getInstance().getPlaceholderManager().setTabExpansion(getTabExpansion());
}
if (configuration.getConfig().getBoolean("global-playerlist.enabled", false)) {
TabFeature global = getGlobalPlayerlist();
if (global != null) featureManager.registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST, getGlobalPlayerlist());
}
if (configuration.getConfig().getBoolean("per-world-playerlist.enabled", false)) {
TabFeature pwp = getPerWorldPlayerlist();
if (pwp != null) featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, pwp);
}
RedisSupport redis = getRedisSupport();
if (redis != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, redis);
// Must be loaded after: NameTags, BelowName, YellowNumber, RedisBungee
featureManager.registerFeature(TabConstants.Feature.NICK_COMPATIBILITY, new NickCompatibility());
}
@@ -140,38 +140,6 @@ public abstract class Platform {
return new BossBarManagerImpl();
}
/**
* Returns protocol version of requested player using ViaVersion
*
* @param player
* UUID of player to get protocol version of
* @param playerName
* Name of the player
* @return protocol version of the player using ViaVersion
*/
public int getProtocolVersionVia(UUID player, String playerName, int retryLevel) {
try {
if (retryLevel == 10) {
TAB.getInstance().debug("Failed to get protocol version of " + playerName + " after 10 retries");
return TAB.getInstance().getServerVersion().getNetworkId();
}
int version = Via.getAPI().getPlayerVersion(player);
if (version == -1) {
Thread.sleep(5);
return getProtocolVersionVia(player, playerName, retryLevel + 1);
}
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + playerName);
return version;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return -1;
} catch (Exception | LinkageError e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ViaVersion v%s",
playerName, getPluginVersion(TabConstants.Plugin.VIAVERSION)), e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
}
/**
* Creates an instance of {@link me.neznamy.tab.api.placeholder.Placeholder}
* to handle this unknown placeholder (typically a PAPI placeholder)
@@ -205,12 +173,10 @@ public abstract class Platform {
public abstract NameTag getUnlimitedNametags();
public abstract TabExpansion getTabExpansion();
public abstract @NonNull TabExpansion getTabExpansion();
public abstract @Nullable TabFeature getPetFix();
public abstract @Nullable TabFeature getGlobalPlayerlist();
public abstract @Nullable RedisSupport getRedisSupport();
public abstract @Nullable TabFeature getPerWorldPlayerlist();
@@ -66,7 +66,7 @@ public class TAB extends TabAPI {
* occur in any part of the code including hooks into other plugins
* into files instead of flooding the already flooded console.
*/
@Getter private ErrorManager errorManager;
@Getter private final ErrorManager errorManager;
/** Feature manager forwarding events into all loaded features */
@Getter private FeatureManagerImpl featureManager;
@@ -113,6 +113,7 @@ public class TAB extends TabAPI {
this.serverVersionString = serverVersionString;
this.dataFolder = dataFolder;
this.logger = logger;
this.errorManager = new ErrorManager(this);
try {
eventBus = new EventBusImpl();
} catch (NoSuchMethodError e) {
@@ -141,7 +142,6 @@ public class TAB extends TabAPI {
public String load() {
try {
long time = System.currentTimeMillis();
errorManager = new ErrorManager();
cpu = new CpuManager();
configuration = new Configs();
featureManager = new FeatureManagerImpl();
@@ -2,7 +2,6 @@ package me.neznamy.tab.shared.backend;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.shared.Platform;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.permission.LuckPerms;
@@ -11,7 +10,6 @@ import me.neznamy.tab.shared.permission.PermissionPlugin;
public abstract class BackendPlatform extends Platform {
@Getter private final TabFeature globalPlayerlist = null;
@Getter private final RedisSupport redisSupport = null;
@Override
@@ -1,15 +1,17 @@
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.*;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.TabPacket;
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 java.util.UUID;
public abstract class BackendArmorStand implements ArmorStand {
public class BackendArmorStand implements ArmorStand {
/** Entity id counter to pick unique entity ID for each armor stand */
private static int idCounter = 2000000000;
@@ -35,9 +37,6 @@ public abstract class BackendArmorStand implements ArmorStand {
/** Unique ID of this armor stand */
protected final UUID uuid = UUID.randomUUID();
/** Sneaking flag of armor stands */
protected boolean sneaking;
/** Armor stand visibility to use in metadata */
protected boolean visible;
@@ -96,31 +95,6 @@ public abstract class BackendArmorStand implements ArmorStand {
}
}
@Override
public void sneak(boolean sneaking) {
if (this.sneaking == sneaking) return; //idk
this.sneaking = sneaking;
for (TabPlayer viewer : asm.getNearbyPlayers()) {
if (viewer.getVersion().getMinorVersion() == 14 && !manager.isArmorStandsAlwaysVisible()) {
//1.14.x client sided bug, de-spawning completely
if (sneaking) {
destroy(viewer);
} else {
spawn(viewer);
}
} else {
//respawning so there's no animation and it's instant
respawn(viewer);
}
}
}
@Override
public void destroy() {
for (TabPlayer all : asm.getNearbyPlayers())
destroy(all);
}
@Override
public void teleport() {
for (TabPlayer all : asm.getNearbyPlayers()) {
@@ -137,6 +111,13 @@ public abstract class BackendArmorStand implements ArmorStand {
}
}
@Override
public void spawn(TabPlayer viewer) {
for (TabPacket packet : getSpawnPackets(viewer)) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
}
}
/**
* Returns general visibility rule for everyone with limited info
*
@@ -196,19 +177,12 @@ public abstract class BackendArmorStand implements ArmorStand {
return y;
}
public void respawn(TabPlayer viewer) {
destroy(viewer);
// 1.8.0 will not see entity that respawned in the same tick
// creating new delayed task every time someone sneaks can be abused and cause OOM
spawn(viewer);
}
/**
* Updates armor stand's metadata for everyone
*/
public void updateMetadata() {
for (TabPlayer viewer : asm.getNearbyPlayers()) {
updateMetadata(viewer);
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
}
@@ -218,10 +192,79 @@ public abstract class BackendArmorStand implements ArmorStand {
(owner.hasInvisibilityPotion() && viewer.getGamemode() != 3);
}
/**
* Updates armor stand's metadata for specified viewer
*/
public abstract void updateMetadata(TabPlayer viewer);
public TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = calculateVisibility();
Object dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, manager.getArmorStandType(),
manager.getX(owner), getYLocation(viewer), manager.getZ(owner), 0, 0, null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, manager.getArmorStandType(),
manager.getX(owner), getYLocation(viewer), manager.getZ(owner), 0, 0, dataWatcher),
};
}
}
public abstract void sendTeleportPacket(TabPlayer viewer);
public void sendTeleportPacket(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityTeleport(entityId, manager.getX(owner), getYLocation(viewer),
manager.getZ(owner), 0, 0), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
/**
* Creates data watcher with specified display name for viewer
*
* @param displayName
* armor stand name
* @param viewer
* player to apply checks against
* @return DataWatcher for viewer
*/
public Object createDataWatcher(String displayName, TabPlayer viewer) {
byte flags = (byte) (asm.isSneaking() ? 34 : 32);
boolean nameVisible = !shouldBeInvisibleFor(viewer, displayName) && visible;
boolean marker = viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x();
return manager.createDataWatcher(viewer, flags, displayName, nameVisible, marker);
}
/**
* Returns Y location where armor stand should be at time of calling.
* This takes into account everything that affects height, including
* viewer's game version.
*
* @param viewer
* Player looking at the armor stand
* @return Location where armor stand should be for specified viewer
*/
public double getYLocation(TabPlayer viewer) {
double y = manager.getY(owner.getPlayer());
//1.14+ server sided bug
Object vehicle = manager.getVehicle(owner);
if (vehicle != null) {
String type = manager.getEntityType(vehicle);
double vehicleY = manager.getY(vehicle);
if (type.contains("horse")) { //covering all 3 horse types
y = vehicleY + 0.85;
}
if (type.equals("donkey")) { //1.11+
y = vehicleY + 0.525;
}
if (type.equals("pig")) {
y = vehicleY + 0.325;
}
if (type.equals("strider")) { //1.16+
y = vehicleY + 1.15;
}
} else {
//1.13+ swimming or 1.9+ flying with elytra
if (manager.isSwimming(owner) || manager.isGliding(owner)) {
y = manager.getY(owner.getPlayer())-1.22;
}
}
y += getYAdd(manager.isSleeping(owner), asm.isSneaking(), viewer);
return y;
}
}
@@ -1,31 +1,37 @@
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.ArmorStandManager;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.*;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityDestroy;
import me.neznamy.tab.shared.backend.protocol.PacketPlayOutEntityMetadata;
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
public class BackendArmorStandManager implements ArmorStandManager {
private final BackendNameTagX nameTagX;
@Getter private boolean sneaking;
/** Map of registered armor stands by name */
private final Map<String, BackendArmorStand> armorStands = new LinkedHashMap<>();
/** Armor stands in an array for speed while iterating */
private BackendArmorStand[] armorStandArray = new BackendArmorStand[0];
private BackendArmorStand[] armorStandArray;
/** Players in entity tracking range of owner */
private final List<TabPlayer> nearbyPlayerList = new ArrayList<>();
/** Nearby players in an array for speed while iterating */
@Getter
private TabPlayer[] nearbyPlayers = new TabPlayer[0];
@Getter private TabPlayer[] nearbyPlayers = new TabPlayer[0];
/**
* Packets to destroy armor stands. On 1.17 it's a list of individual armor stands
* due to packet only containing one entity, on all other server versions
* it's a single packet with all armor stand ids.
*/
private final PacketPlayOutEntityDestroy[] destroyPackets;
/**
* Constructs new instance with given parameters and loads armor stands.
@@ -36,18 +42,29 @@ public class BackendArmorStandManager implements ArmorStandManager {
* Owner of this armor stand manager
*/
public BackendArmorStandManager(NameTagX nameTagX, TabPlayer owner) {
this.nameTagX = (BackendNameTagX) nameTagX;
sneaking = this.nameTagX.isSneaking(owner);
owner.setProperty(nameTagX, TabConstants.Property.NAMETAG, owner.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue()
+ owner.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue()
+ owner.getProperty(TabConstants.Property.TAGSUFFIX).getCurrentRawValue());
double height = 0;
for (String line : nameTagX.getDynamicLines()) {
addArmorStand(line, ((BackendNameTagX)nameTagX).createArmorStand(this, owner, line, height, false));
armorStands.put(line, new BackendArmorStand((BackendNameTagX) nameTagX, this, owner, line, height, false));
height += 0.26;
}
for (Map.Entry<String, Object> line : nameTagX.getStaticLines().entrySet()) {
addArmorStand(line.getKey(), ((BackendNameTagX)nameTagX).createArmorStand(this, owner, line.getKey(), Double.parseDouble(line.getValue().toString()), true));
armorStands.put(line.getKey(), new BackendArmorStand((BackendNameTagX) nameTagX, this, owner, line.getKey(), Double.parseDouble(line.getValue().toString()), true));
}
armorStandArray = armorStands.values().toArray(new BackendArmorStand[0]);
fixArmorStandHeights();
List<PacketPlayOutEntityDestroy> destroyPackets = new ArrayList<>();
if (TabAPI.getInstance().getServerVersion() == ProtocolVersion.V1_17) { // Mojank
armorStands.values().forEach(as -> destroyPackets.add(new PacketPlayOutEntityDestroy(as.getEntityId())));
} else {
destroyPackets.add(new PacketPlayOutEntityDestroy(armorStands.values().stream().mapToInt(BackendArmorStand::getEntityId).toArray()));
}
this.destroyPackets = destroyPackets.toArray(new PacketPlayOutEntityDestroy[0]);
}
/**
@@ -101,17 +118,41 @@ public class BackendArmorStandManager implements ArmorStandManager {
* new sneaking status
*/
public void sneak(boolean sneaking) {
for (ArmorStand a : armorStandArray) a.sneak(sneaking);
if (this.sneaking == sneaking) return;
this.sneaking = sneaking;
for (TabPlayer viewer : nearbyPlayers) {
if (viewer.getVersion().getMinorVersion() == 14 && !nameTagX.isArmorStandsAlwaysVisible()) {
//1.14.x client sided bug, de-spawning completely
if (sneaking) {
destroy(viewer);
} else {
spawn(viewer);
}
} else {
//respawning so there's no animation and it's instant
respawn(viewer);
}
}
}
/**
* Performs respawn operation on all armor stands to skip teleport animation
*/
public void respawn() {
for (TabPlayer viewer : nearbyPlayers) {
respawn(viewer);
}
}
public void respawn(TabPlayer viewer) {
// 1.8.0 will not see entity that respawned in the same tick
// creating new delayed task every time someone sneaks can be abused and cause OOM
// RIP 1.8.0
for (PacketPlayOutEntityDestroy packet : destroyPackets) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
for (ArmorStand a : armorStandArray) {
for (TabPlayer viewer : nearbyPlayers) {
a.respawn(viewer);
}
a.spawn(viewer);
}
}
@@ -177,13 +218,19 @@ public class BackendArmorStandManager implements ArmorStandManager {
* player to destroy armor stands for
*/
public void destroy(TabPlayer viewer) {
for (ArmorStand a : armorStandArray) a.destroy(viewer);
for (PacketPlayOutEntityDestroy packet : destroyPackets) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
unregisterPlayer(viewer);
}
@Override
public void destroy() {
for (ArmorStand a : armorStandArray) a.destroy();
for (TabPlayer viewer : nearbyPlayers) {
for (PacketPlayOutEntityDestroy packet : destroyPackets) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
}
nearbyPlayerList.clear();
nearbyPlayers = new TabPlayer[0];
}
@@ -201,6 +248,8 @@ public class BackendArmorStandManager implements ArmorStandManager {
}
public void updateMetadata(TabPlayer viewer) {
for (BackendArmorStand a : armorStandArray) a.updateMetadata(viewer);
for (BackendArmorStand a : armorStandArray) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(a.getEntityId(), a.createDataWatcher(a.getProperty().getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
}
}
@@ -19,8 +19,11 @@ public abstract class BackendNameTagX extends NameTagX {
super(BackendArmorStandManager::new);
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER, vehicleManager);
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_PACKET_LISTENER, packetListener);
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder("%gamemode%", 500, TabPlayer::getGamemode);
TabFeature gamemode = new TabFeature("Unlimited NameTags", "Gamemode listener") {
TabFeature gamemode = new TabFeature() {
@Getter private final String featureName = "Unlimited NameTags";
@Getter private final String refreshDisplayName = "Gamemode listener";
{
addUsedPlaceholders(Collections.singletonList("%gamemode%"));
}
@@ -55,6 +58,7 @@ public abstract class BackendNameTagX extends NameTagX {
@Override
public void load() {
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder("%gamemode%", 500, TabPlayer::getGamemode);
super.load();
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(all)) continue;
@@ -194,5 +198,21 @@ public abstract class BackendNameTagX extends NameTagX {
public abstract String getEntityType(Object entity);
public abstract BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset);
public abstract boolean isSneaking(TabPlayer player);
public abstract boolean isSwimming(TabPlayer player);
public abstract boolean isGliding(TabPlayer player);
public abstract boolean isSleeping(TabPlayer player);
public abstract Object getArmorStandType();
public abstract double getX(TabPlayer player);
public abstract double getY(Object entity);
public abstract double getZ(TabPlayer player);
public abstract Object createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible, boolean markerFlag);
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
@@ -17,25 +19,17 @@ import java.util.concurrent.ConcurrentHashMap;
* For entering/leaving tracking range there are no events and
* periodic / move-triggered distance checks would cause high CPU usage.
*/
@RequiredArgsConstructor
public class PacketListener extends TabFeature {
@Getter private final String featureName = "Unlimited NameTags";
/** Reference to the main feature */
protected final BackendNameTagX nameTagX;
/** A player map by entity id, used for better performance */
private final Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
/**
* Constructs new instance with given parameter
*
* @param nameTagX
* main feature
*/
public PacketListener(BackendNameTagX nameTagX) {
super(nameTagX.getFeatureName(), null);
this.nameTagX = nameTagX;
}
@Override
public void load() {
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
@@ -1,6 +1,7 @@
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
@@ -18,8 +19,12 @@ import java.util.concurrent.ConcurrentHashMap;
* Additionally, when entering a vehicle, no move packet is sent
* and therefore manual teleporting of armor stands is required.
*/
@RequiredArgsConstructor
public class VehicleRefresher extends TabFeature {
@Getter private final String featureName = "NameTags";
@Getter private final String refreshDisplayName = "Refreshing vehicles";
/** Map of players currently in a vehicle */
private final WeakHashMap<TabPlayer, Object> playersInVehicle = new WeakHashMap<>();
@@ -33,15 +38,8 @@ public class VehicleRefresher extends TabFeature {
/** Reference to the main feature */
private final BackendNameTagX feature;
/**
* Constructs new instance with given parameter and starts tasks.
*
* @param feature
* Main feature
*/
public VehicleRefresher(BackendNameTagX feature) {
super(feature.getFeatureName(), "Refreshing vehicles");
this.feature = feature;
@Override
public void load() {
TabAPI.getInstance().getThreadManager().startRepeatingMeasuredTask(50,
this, TabConstants.CpuUsageCategory.PROCESSING_PLAYER_MOVEMENT, () -> {
for (TabPlayer inVehicle : playersInVehicle.keySet()) {
@@ -56,10 +54,6 @@ public class VehicleRefresher extends TabFeature {
});
addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.VEHICLE));
feature.registerVehiclePlaceholder();
}
@Override
public void load() {
for (TabPlayer p : TabAPI.getInstance().getOnlinePlayers()) {
Object vehicle = feature.getVehicle(p);
if (vehicle != null) {
@@ -0,0 +1,24 @@
package me.neznamy.tab.shared.backend.protocol;
import lombok.Data;
import me.neznamy.tab.api.protocol.TabPacket;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
@Data
public class PacketPlayOutEntityDestroy implements TabPacket {
/** Packet's instance fields */
private final int[] entities;
/**
* Constructs new instance with given parameter
*
* @param entities
* Destroyed entities
*/
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
}
@@ -1,9 +1,8 @@
package me.neznamy.tab.platforms.sponge8.nms;
package me.neznamy.tab.shared.backend.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.protocol.TabPacket;
import net.minecraft.network.syncher.SynchedEntityData;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
@@ -14,6 +13,6 @@ public class PacketPlayOutEntityMetadata implements TabPacket {
/** Entity's id */
private final int entityId;
/** Entity's data that changed */
@NonNull private final SynchedEntityData dataWatcher;
/** Entity's platform specific data watcher object */
@NonNull private final Object dataWatcher;
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8.nms;
package me.neznamy.tab.shared.backend.protocol;
import lombok.Data;
import me.neznamy.tab.api.protocol.TabPacket;
@@ -11,6 +11,8 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
/** Entity's id */
private final int entityId;
/** Entity location */
private final double x;
private final double y;
private final double z;
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.sponge8.nms;
package me.neznamy.tab.shared.backend.protocol;
import lombok.Data;
import lombok.NonNull;
@@ -19,11 +19,14 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
@NonNull private final UUID uniqueId;
/** Type of entity */
private final int entityType;
private final Object entityType;
private final double x;
private final double y;
private final double z;
private final float yaw;
private final float pitch;
/** Data Watcher for 1.14- */
private final Object dataWatcher;
}
@@ -39,7 +39,7 @@ public class TabCommand extends SubCommand {
registerSubCommand(new SetCollisionCommand());
registerSubCommand(new ScoreboardCommand());
registerSubCommand(new WidthCommand());
//registerSubCommand(new WidthCheckCommand());
registerSubCommand(new WidthCheckCommand());
List<String> properties = Lists.newArrayList(TabConstants.Property.TABPREFIX, TabConstants.Property.TABSUFFIX, TabConstants.Property.TAGPREFIX, TabConstants.Property.TAGSUFFIX, TabConstants.Property.CUSTOMTABNAME, TabConstants.Property.ABOVENAME, TabConstants.Property.BELOWNAME, TabConstants.Property.CUSTOMTAGNAME);
properties.addAll(((DebugCommand) getSubcommands().get("debug")).getExtraLines());
SubCommand.setAllProperties(properties.toArray(new String[0]));
@@ -17,7 +17,7 @@ import java.util.stream.Collectors;
*/
public class WidthCheckCommand extends SubCommand {
private final byte[] widths = loadWidths();
private byte[] widths;
/**
* Constructs new instance
@@ -46,14 +46,15 @@ public class WidthCheckCommand extends SubCommand {
@Override
public void execute(TabPlayer sender, String[] args) {
if (widths == null) widths = loadWidths();
if (sender == null) {
sendMessage(null, getMessages().getCommandOnlyFromGame());
return;
}
if (args.length == 1) {
int i = Integer.parseInt(args[0]);
if (i > Character.MAX_VALUE) {
sendMessage(sender, "&cCharacter ID out of range: 0-" + (int) Character.MAX_VALUE);
if (i < 0 || i > Character.MAX_VALUE) {
sendMessage(sender, "&cCharacter ID out of range: 0-" + (int) Character.MAX_VALUE + " (was " + i + ")");
return;
}
int ROWS = 15;
@@ -27,8 +27,9 @@ public class Configs {
@Getter private final boolean bukkitPermissions = TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY && config.getBoolean("use-bukkit-permissions-manager", false);
@Getter private final boolean debugMode = config.getBoolean("debug", false);
@Getter private final boolean onlineUuidInTabList = config.getBoolean("use-online-uuid-in-tablist", true);
@Getter private final boolean removeGhostPlayers = getSecretOption("remove-ghost-players", false);
@Getter private final boolean pipelineInjection = getSecretOption("pipeline-injection", true) && TAB.getInstance().getServerVersion().getMinorVersion() >= 8;
@Getter private final boolean pipelineInjection = getSecretOption("pipeline-injection", true);
@Getter private final String serverName = getSecretOption("server-name", "N/A");
//animations.yml file
@@ -268,7 +268,7 @@ public class Converter {
}
newConfig.set("scoreboard.scoreboards.admin.display-condition", "permission:tab.scoreboard.admin");
newConfig.set("scoreboard.scoreboards.admin.title", "Admin scoreboard");
newConfig.set("scoreboard.scoreboards.admin.lines", Arrays.asList("%animation:MyAnimation1%", "&6Online:", "* &eOnline&7: &f%online%&7/&4%maxplayers%",
newConfig.set("scoreboard.scoreboards.admin.lines", Arrays.asList("%animation:MyAnimation1%", "&6Online:", "* &eOnline&7: &f%online%&7",
"* &eCurrent World&7: &f%worldonline%", "* &eStaff&7: &f%staffonline%", " ", "&6Server Info:", "* &bTPS&7: %tps%",
"* &bUptime&7: &f%server_uptime%", "* &bMemory&7: &f%memory-used%&7/&4%memory-max%", "%animation:MyAnimation1%"));
newConfig.set("scoreboard.scoreboards.scoreboard1.title", "Default");
@@ -1,8 +1,8 @@
package me.neznamy.tab.shared.features;
import java.util.Arrays;
import java.util.Objects;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
@@ -11,9 +11,12 @@ import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective.EnumScoreboa
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore.Action;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* Feature handler for BelowName feature
*/
@@ -22,14 +25,17 @@ public class BelowName extends TabFeature {
public static final String OBJECTIVE_NAME = "TAB-BelowName";
public static final int DISPLAY_SLOT = 2;
@Getter private final String refreshDisplayName = "Updating BelowName number";
@Getter private final String featureName = "BelowName";
private final String rawNumber = TAB.getInstance().getConfiguration().getConfig().getString("belowname-objective.number", TabConstants.Placeholder.HEALTH);
private final String rawText = TAB.getInstance().getConfiguration().getConfig().getString("belowname-objective.text", "Health");
private final TabFeature textRefresher = new TextRefresher();
private final TabFeature textRefresher = new TextRefresher(this);
private final RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
public BelowName() {
super("BelowName", "Updating BelowName number", "belowname-objective");
super("belowname-objective");
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.BELOW_NAME_TEXT, textRefresher);
TAB.getInstance().debug(String.format("Loaded BelowName feature with parameters number=%s, text=%s, disabledWorlds=%s, disabledServers=%s", rawNumber, rawText, Arrays.toString(disabledWorlds), Arrays.toString(disabledServers)));
}
@Override
@@ -44,11 +50,16 @@ public class BelowName extends TabFeature {
loaded.sendCustomPacket(new PacketPlayOutScoreboardObjective(0, OBJECTIVE_NAME, loaded.getProperty(TabConstants.Property.BELOWNAME_TEXT).updateAndGet(), EnumScoreboardHealthDisplay.INTEGER), textRefresher);
loaded.sendCustomPacket(new PacketPlayOutScoreboardDisplayObjective(DISPLAY_SLOT, OBJECTIVE_NAME), textRefresher);
}
Map<TabPlayer, Integer> values = new HashMap<>();
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (isDisabledPlayer(target)) continue;
values.put(target, getValue(target));
}
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (isDisabledPlayer(viewer)) continue;
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (sameServerAndWorld(target, viewer)) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, target.getNickname(), getValue(target)), this);
for (Map.Entry<TabPlayer, Integer> entry : values.entrySet()) {
if (sameServerAndWorld(entry.getKey(), viewer)) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, entry.getKey().getNickname(), entry.getValue()), this);
}
}
}
@@ -79,7 +90,6 @@ public class BelowName extends TabFeature {
connectedPlayer.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, all.getNickname(), getValue(all)), this);
}
}
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateBelowName(connectedPlayer, connectedPlayer.getProperty(TabConstants.Property.BELOWNAME_NUMBER).get());
}
@@ -114,7 +124,6 @@ public class BelowName extends TabFeature {
p.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, all.getNickname(), getValue(all)), this);
}
}
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateBelowName(p, p.getProperty(TabConstants.Property.BELOWNAME_NUMBER).get());
}
@@ -130,7 +139,6 @@ public class BelowName extends TabFeature {
if (sameServerAndWorld(all, refreshed))
all.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, refreshed.getNickname(), number), this);
}
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateBelowName(refreshed, refreshed.getProperty(TabConstants.Property.BELOWNAME_NUMBER).get());
}
@@ -150,15 +158,16 @@ public class BelowName extends TabFeature {
return player2.getWorld().equals(player1.getWorld()) && Objects.equals(player2.getServer(), player1.getServer());
}
@RequiredArgsConstructor
public class TextRefresher extends TabFeature {
public TextRefresher() {
super("BelowName", "Updating BelowName text");
}
@Getter private final String refreshDisplayName = "Updating BelowName text";
@Getter private final String featureName = "BelowName";
private final BelowName feature;
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (isDisabledPlayer(refreshed)) return;
if (feature.isDisabledPlayer(refreshed)) return;
refreshed.sendCustomPacket(new PacketPlayOutScoreboardObjective(2, OBJECTIVE_NAME, refreshed.getProperty(TabConstants.Property.BELOWNAME_TEXT).updateAndGet(), EnumScoreboardHealthDisplay.INTEGER), textRefresher);
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared.features;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
@@ -13,11 +14,8 @@ import me.neznamy.tab.shared.TAB;
*/
public class GhostPlayerFix extends TabFeature {
public GhostPlayerFix() {
super("Ghost player fix", null);
TAB.getInstance().debug("Loaded GhostPlayerFix feature");
}
@Getter private final String featureName = "Ghost player fix";
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
TAB.getInstance().getCPUManager().runTaskLater(500, this, TabConstants.CpuUsageCategory.PLAYER_QUIT, () -> {
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared.features;
import lombok.Getter;
import me.neznamy.tab.api.HeaderFooterManager;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
@@ -8,7 +9,6 @@ import me.neznamy.tab.api.protocol.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.shared.TAB;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -16,12 +16,13 @@ import java.util.List;
*/
public class HeaderFooter extends TabFeature implements HeaderFooterManager {
@Getter private final String featureName = "Header/Footer";
@Getter private final String refreshDisplayName = "Updating header/footer";
private final List<Object> worldGroups = new ArrayList<>(TAB.getInstance().getConfig().getConfigurationSection("header-footer.per-world").keySet());
private final List<Object> serverGroups = new ArrayList<>(TAB.getInstance().getConfig().getConfigurationSection("header-footer.per-server").keySet());
public HeaderFooter() {
super("Header/Footer", "Updating header/footer", "header-footer");
TAB.getInstance().debug(String.format("Loaded HeaderFooter feature with parameters disabledWorlds=%s, disabledServers=%s", Arrays.toString(disabledWorlds), Arrays.toString(disabledServers)));
super("header-footer");
}
@Override
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared.features;
import java.util.Collections;
import java.util.Objects;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
@@ -20,21 +21,11 @@ import me.neznamy.tab.shared.features.redis.RedisSupport;
public class NickCompatibility extends TabFeature {
@Getter private final String featureName = "Nick compatibility";
private final NameTag nameTags = (NameTag) TAB.getInstance().getTeamManager();
private final BelowName belowname = (BelowName) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.BELOW_NAME);
private final YellowNumber yellownumber = (YellowNumber) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.YELLOW_NUMBER);
private RedisSupport redis;
public NickCompatibility() {
super("Nick compatibility", null);
TAB.getInstance().debug("Loaded NickCompatibility feature");
}
@Override
public void load() {
// redis feature is instantiated after nick compatibility
redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
}
private final RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo packet) {
@@ -1,9 +1,6 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
@@ -16,6 +13,10 @@ import me.neznamy.tab.shared.features.layout.LayoutManager;
import me.neznamy.tab.shared.features.layout.ParentGroup;
import me.neznamy.tab.shared.features.layout.PlayerSlot;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Sets ping of all players in the packet to configured value to prevent hacked clients from seeing exact ping value of each player
*/
@@ -24,15 +25,9 @@ public class PingSpoof extends TabFeature {
//fake ping value
private final int value = TAB.getInstance().getConfiguration().getConfig().getInt("ping-spoof.value", 0);
private LayoutManager layoutManager;
private final LayoutManager layoutManager = (LayoutManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.LAYOUT);
/**
* Constructs new instance and loads config options
*/
public PingSpoof() {
super("Ping spoof", null);
TAB.getInstance().debug(String.format("Loaded PingSpoof feature with parameters value=%s", value));
}
@Getter private final String featureName = "Ping spoof";
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
@@ -56,7 +51,6 @@ public class PingSpoof extends TabFeature {
@Override
public void load() {
layoutManager = (LayoutManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.LAYOUT);
updateAll(false);
}
@@ -1,109 +0,0 @@
package me.neznamy.tab.shared.features;
import java.util.NoSuchElementException;
import java.util.function.Function;
import io.netty.channel.ChannelDuplexHandler;
import lombok.Setter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
/**
* Packet intercepting to secure proper functionality of some features:
* TabList names - anti-override
* NameTags - anti-override
* Scoreboard - disabling tab's scoreboard to prevent conflict
* SpectatorFix - to change game mode to something else than spectator
* PetFix - to remove owner field from entity data
* PingSpoof - full feature functionality
* Unlimited name tags - replacement for bukkit events with much better accuracy and reliability
*/
public abstract class PipelineInjector extends TabFeature {
//name of the pipeline decoder injected in netty
public final String DECODER_NAME = TabConstants.PIPELINE_HANDLER_NAME;
//handler to inject before
private final String injectPosition;
//preventing spam when packet is sent to everyone
private String lastTeamOverrideMessage;
//anti-override rules
@Setter protected boolean antiOverrideTeams;
@Setter protected boolean byteBufDeserialization;
/**
* Constructs new instance with given parameter
*
* @param injectPosition
* position to inject handler before
*/
protected PipelineInjector(String injectPosition) {
super("Pipeline injection", null);
this.injectPosition = injectPosition;
}
/**
* Injects custom channel duplex handler to prevent other plugins from overriding this one
*
* @param player
* player to inject
*/
public void inject(TabPlayer player) {
if (player.getVersion().getMinorVersion() < 8 || player.getChannel() == null) return; //hello A248
if (!player.getChannel().pipeline().names().contains(injectPosition)) {
//fake player or waterfall bug
return;
}
uninject(player);
try {
player.getChannel().pipeline().addBefore(injectPosition, DECODER_NAME, getChannelFunction().apply(player));
} catch (NoSuchElementException | IllegalArgumentException e) {
//I don't really know how does this keep happening but whatever
}
}
public void uninject(TabPlayer player) {
if (player.getVersion().getMinorVersion() < 8 || player.getChannel() == null) return; //hello A248
try {
if (player.getChannel().pipeline().names().contains(DECODER_NAME)) player.getChannel().pipeline().remove(DECODER_NAME);
} catch (NoSuchElementException e) {
//for whatever reason this rarely throws
//java.util.NoSuchElementException: TABReader
}
}
@Override
public void load() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
inject(p);
}
}
@Override
public void unload() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
uninject(p);
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
inject(connectedPlayer);
}
protected void logTeamOverride(String team, String player, String expectedTeam) {
String message = "Something just tried to add player " + player + " into team " + team + " (expected team: " + expectedTeam + ")";
//not logging the same message for every online player who received the packet
if (!message.equals(lastTeamOverrideMessage)) {
lastTeamOverrideMessage = message;
TAB.getInstance().getErrorManager().printError(message, null, false, TAB.getInstance().getErrorManager().getAntiOverrideLog());
}
}
public abstract Function<TabPlayer, ChannelDuplexHandler> getChannelFunction();
}
@@ -13,7 +13,7 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import lombok.NonNull;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.Placeholder;
@@ -28,6 +28,8 @@ import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.RelationalPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.ServerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.TabPlaceholder;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
/**
* Messy class for placeholder management
@@ -36,6 +38,8 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
private final Pattern placeholderPattern = Pattern.compile("%([^%]*)%");
@Getter private final String refreshDisplayName = "Updating placeholders";
@Getter private final String featureName = "Refreshing placeholders";
@Getter private final int defaultRefresh = TAB.getInstance().getConfiguration().getConfig().getInt("placeholderapi-refresh-intervals.default-refresh-interval", 100);
@Getter private final Map<String, Integer> serverPlaceholderRefreshIntervals = TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("placeholderapi-refresh-intervals.server");
@Getter private final Map<String, Integer> playerPlaceholderRefreshIntervals = TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("placeholderapi-refresh-intervals.player");
@@ -52,12 +56,9 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
private int refreshInterval = 10000;
private Future<?> refreshTask = TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(refreshInterval, this, "Refreshing placeholders", this::refresh);
@Getter @Setter private TabExpansion tabExpansion;
@Getter @NonNull private final TabExpansion tabExpansion = TAB.getInstance().getConfig().getBoolean("placeholders.register-tab-expansion", false) ?
TAB.getInstance().getPlatform().getTabExpansion() : new EmptyTabExpansion();
public PlaceholderManagerImpl() {
super("Refreshing placeholders", "Updating placeholders");
}
private void refresh() {
int loopTime = this.loopTime.addAndGet(refreshInterval);
int size = TAB.getInstance().getOnlinePlayers().length;
@@ -232,10 +233,8 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
recalculateUsedPlaceholders();
TabPlaceholder p = getPlaceholder(identifier);
p.markAsUsed();
if (tabExpansion != null) {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
tabExpansion.setPlaceholderValue(all, p.getIdentifier(), p.getLastValue(all));
}
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
tabExpansion.setPlaceholderValue(all, p.getIdentifier(), p.getLastValue(all));
}
if (p.getRefresh() % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL == 0 && p.getRefresh() > 0) {
int refresh = gcd(p.getRefresh(), refreshInterval);
@@ -280,7 +279,7 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
if (p instanceof PlayerPlaceholderImpl) {
((PlayerPlaceholderImpl)p).update(connectedPlayer);
}
if (tabExpansion != null && p instanceof ServerPlaceholder) { // server placeholders don't update on join
if (p instanceof ServerPlaceholder) { // server placeholders don't update on join
tabExpansion.setPlaceholderValue(connectedPlayer, p.getIdentifier(), ((ServerPlaceholder) p).getLastValue());
}
}
@@ -1,10 +1,6 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import lombok.Getter;
import me.neznamy.tab.api.*;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
@@ -17,14 +13,24 @@ import me.neznamy.tab.shared.features.layout.LayoutManager;
import me.neznamy.tab.shared.features.layout.PlayerSlot;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Feature handler for TabList display names
*/
public class PlayerList extends TabFeature implements TablistFormatManager {
@Getter private final String featureName = "Tablist name formatting";
@Getter private final String refreshDisplayName = "Updating TabList format";
/** Config option toggling anti-override which prevents other plugins from overriding TAB */
protected final boolean antiOverrideTabList = TAB.getInstance().getConfiguration().getConfig().getBoolean("tablist-name-formatting.anti-override", true);
private final LayoutManager layoutManager = (LayoutManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.LAYOUT);
private RedisSupport redis;
/**
* Flag tracking when the plugin is disabling to properly clear
* display name by setting it to null value and not force the value back
@@ -36,8 +42,7 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
* Constructs new instance and sends debug message that feature loaded.
*/
public PlayerList() {
super("TabList prefix/suffix", "Updating TabList format", "tablist-name-formatting");
TAB.getInstance().debug(String.format("Loaded PlayerList feature with parameters disabledWorlds=%s, disabledServers=%s, antiOverrideTabList=%s", Arrays.toString(disabledWorlds), Arrays.toString(disabledServers), antiOverrideTabList));
super("tablist-name-formatting");
}
/**
@@ -53,9 +58,8 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
* @return UUID of TabList entry representing requested player
*/
public UUID getTablistUUID(TabPlayer p, TabPlayer viewer) {
LayoutManager manager = (LayoutManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.LAYOUT);
if (manager != null) {
Layout layout = manager.getPlayerViews().get(viewer);
if (layoutManager != null) {
Layout layout = layoutManager.getPlayerViews().get(viewer);
if (layout != null) {
PlayerSlot slot = layout.getSlot(p);
if (slot != null) {
@@ -95,7 +99,6 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
viewer.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME,
new PlayerInfoData(getTablistUUID(p, viewer), format ? getTabFormat(p, viewer) : null)), this);
}
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateTabFormat(p, p.getProperty(TabConstants.Property.TABPREFIX).get() + p.getProperty(TabConstants.Property.CUSTOMTABNAME).get() + p.getProperty(TabConstants.Property.TABSUFFIX).get());
}
@@ -120,13 +123,23 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
@Override
public void load() {
redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
updateProperties(all);
if (isDisabled(all.getServer(), all.getWorld())) {
addDisabledPlayer(all);
updateProperties(all);
continue;
} else {
if (redis != null) redis.updateTabFormat(all, all.getProperty(TabConstants.Property.TABPREFIX).get() + all.getProperty(TabConstants.Property.CUSTOMTABNAME).get() + all.getProperty(TabConstants.Property.TABSUFFIX).get());
}
refresh(all, true);
}
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer.getVersion().getMinorVersion() < 8) continue;
List<PlayerInfoData> list = new ArrayList<>();
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (isDisabledPlayer(target)) continue;
list.add(new PlayerInfoData(getTablistUUID(target, viewer), getTabFormat(target, viewer)));
}
viewer.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, list), this);
}
}
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
@@ -19,31 +20,25 @@ import me.neznamy.tab.api.TabConstants;
*/
public class SpectatorFix extends TabFeature {
/**
* Constructs new instance and sends debug message that feature loaded.
*/
public SpectatorFix() {
super("Spectator fix", null);
TAB.getInstance().debug("Loaded SpectatorFix feature");
}
@Getter private final String featureName = "Spectator fix";
/**
* Sends GameMode update of all players to either their real GameMode if
* {@code realGameMode} is {@code true} or fake value if it's {@code false}.
*
* @param viewer
* Player to send gamemode updates to
* @param realGameMode
* Whether real GameMode should be shown or fake one
*/
private void updateAll(boolean realGameMode) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.hasPermission(TabConstants.Permission.SPECTATOR_BYPASS)) continue;
List<PlayerInfoData> list = new ArrayList<>();
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (p == target) continue;
list.add(new PlayerInfoData(target.getUniqueId(), realGameMode ? EnumGamemode.VALUES[target.getGamemode()+1] : EnumGamemode.CREATIVE));
}
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_GAME_MODE, list), this);
private void updatePlayer(TabPlayer viewer, boolean realGameMode) {
if (viewer.hasPermission(TabConstants.Permission.SPECTATOR_BYPASS)) return;
List<PlayerInfoData> list = new ArrayList<>();
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (viewer == target || target.getGamemode() != 3) continue;
list.add(new PlayerInfoData(target.getUniqueId(), realGameMode ? EnumGamemode.VALUES[target.getGamemode()+1] : EnumGamemode.CREATIVE));
}
if (!list.isEmpty()) viewer.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_GAME_MODE, list), this);
}
@Override
@@ -59,22 +54,20 @@ public class SpectatorFix extends TabFeature {
@Override
public void onJoin(TabPlayer p) {
List<PlayerInfoData> list = new ArrayList<>();
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (p == target || target.getGamemode() != 3) continue;
list.add(new PlayerInfoData(target.getUniqueId(), EnumGamemode.CREATIVE));
}
if (list.isEmpty() || p.hasPermission(TabConstants.Permission.SPECTATOR_BYPASS)) return;
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_GAME_MODE, list), this);
updatePlayer(p, false);
}
@Override
public void load() {
updateAll(false);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
updatePlayer(viewer, false);
}
}
@Override
public void unload() {
updateAll(true);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
updatePlayer(viewer, true);
}
}
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.shared.features;
import java.util.Arrays;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
@@ -10,7 +10,6 @@ import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective.EnumScoreboa
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore.Action;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
/**
@@ -19,6 +18,9 @@ import me.neznamy.tab.shared.features.redis.RedisSupport;
*/
public class YellowNumber extends TabFeature {
@Getter private final String featureName = "Yellow Number";
@Getter private final String refreshDisplayName = "Updating value";
/** Objective name used by this feature */
public static final String OBJECTIVE_NAME = "TAB-YellowNumber";
@@ -35,12 +37,13 @@ public class YellowNumber extends TabFeature {
private final EnumScoreboardHealthDisplay displayType = TabConstants.Placeholder.HEALTH.equals(rawValue) || "%player_health%".equals(rawValue) ||
"%player_health_rounded%".equals(rawValue) ? EnumScoreboardHealthDisplay.HEARTS : EnumScoreboardHealthDisplay.INTEGER;
private final RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
/**
* Constructs new instance and sends debug message that feature loaded.
*/
public YellowNumber() {
super("Yellow number", "Updating value", "yellow-number-in-tablist");
TAB.getInstance().debug(String.format("Loaded YellowNumber feature with parameters value=%s, disabledWorlds=%s, disabledServers=%s, displayType=%s", rawValue, Arrays.toString(disabledWorlds), Arrays.toString(disabledServers), displayType));
super("yellow-number-in-tablist");
}
/**
@@ -122,7 +125,6 @@ public class YellowNumber extends TabFeature {
}
if (!disabledNow && disabledBefore) {
onJoin(p);
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateYellowNumber(p, p.getProperty(TabConstants.Property.YELLOW_NUMBER).get());
}
}
@@ -134,7 +136,6 @@ public class YellowNumber extends TabFeature {
if (isDisabledPlayer(all) || all.isBedrockPlayer()) continue;
all.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, refreshed.getNickname(), value), this);
}
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateYellowNumber(refreshed, refreshed.getProperty(TabConstants.Property.YELLOW_NUMBER).get());
}
@@ -22,10 +22,6 @@ public class AlignedPlayerList extends PlayerList {
private final Map<TabPlayer, PlayerView> playerViews = new HashMap<>();
@Getter private final byte[] widths = loadWidths();
public AlignedPlayerList() {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.VANISHED));
}
/**
* Loads widths from included widths.txt file as well as width overrides from config
*/
@@ -56,6 +52,7 @@ public class AlignedPlayerList extends PlayerList {
@Override
public void load() {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.VANISHED));
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
updateProperties(all);
playerViews.put(all, new PlayerView(this, all));
@@ -93,7 +93,11 @@ public class BossBarLine implements BossBar {
propertyColor = TabConstants.Property.bossbarColor(name);
propertyStyle = TabConstants.Property.bossbarStyle(name);
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.bossBarTitle(name),
textRefresher = new TabFeature(manager.getFeatureName(), "Updating text") {
textRefresher = new TabFeature() {
@Getter private final String featureName = "BossBar";
@Getter private final String refreshDisplayName = "Updating text";
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (!players.contains(refreshed)) return;
@@ -101,7 +105,11 @@ public class BossBarLine implements BossBar {
}
});
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.bossBarProgress(name),
progressRefresher = new TabFeature(manager.getFeatureName(), "Updating progress") {
progressRefresher = new TabFeature() {
@Getter private final String featureName = "BossBar";
@Getter private final String refreshDisplayName = "Updating progress";
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (!players.contains(refreshed)) return;
@@ -109,7 +117,11 @@ public class BossBarLine implements BossBar {
}
});
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.bossBarColorStyle(name),
colorAndStyleRefresher = new TabFeature(manager.getFeatureName(), "Updating color and style") {
colorAndStyleRefresher = new TabFeature() {
@Getter private final String featureName = "BossBar";
@Getter private final String refreshDisplayName = "Updating color and style";
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (!players.contains(refreshed)) return;
@@ -1,9 +1,7 @@
package me.neznamy.tab.shared.features.bossbar;
import java.util.*;
import java.util.stream.Collectors;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BarColor;
@@ -11,8 +9,9 @@ import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.TabExpansion;
import java.util.*;
import java.util.stream.Collectors;
/**
* Class for handling BossBar feature
@@ -27,7 +26,7 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
private BossBar[] lineValues;
//config options
private final String toggleCommand = TAB.getInstance().getConfiguration().getConfig().getString("bossbar.toggle-command", "/bossbar");
@Getter private final String toggleCommand = TAB.getInstance().getConfiguration().getConfig().getString("bossbar.toggle-command", "/bossbar");
private final boolean hiddenByDefault = TAB.getInstance().getConfiguration().getConfig().getBoolean("bossbar.hidden-by-default", false);
private final boolean rememberToggleChoice = TAB.getInstance().getConfiguration().getConfig().getBoolean("bossbar.remember-toggle-choice", false);
private final String toggleOnMessage = TAB.getInstance().getConfiguration().getMessages().getBossBarOn();
@@ -37,27 +36,28 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
@Getter private final List<BossBar> announcedBossBars = new ArrayList<>();
//players with toggled BossBar
private final List<String> bossBarOffPlayers = rememberToggleChoice ? TAB.getInstance().getConfiguration().getPlayerDataFile().getStringList("bossbar-off", new ArrayList<>()) : Collections.emptyList();
private final List<String> bossBarOffPlayers = rememberToggleChoice ? TAB.getInstance().getConfiguration().getPlayerDataFile()
.getStringList("bossbar-off", new ArrayList<>()) : Collections.emptyList();
//time when BossBar announce ends, used for placeholder
private long announceEndTime;
private final Set<TabPlayer> visiblePlayers = Collections.newSetFromMap(new WeakHashMap<>());
@Getter private final String featureName = "BossBar";
@Getter private final String refreshDisplayName = "Updating display conditions";
/**
* Constructs new instance and loads configuration
*/
public BossBarManagerImpl() {
super("BossBar", "Processing display conditions", "bossbar");
super("bossbar");
for (Object bar : TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("bossbar.bars").keySet()) {
BossBarLine line = loadFromConfig(bar.toString());
registeredBossBars.put(bar.toString(), line);
if (!line.isAnnouncementBar()) defaultBars.add(bar.toString());
}
lineValues = registeredBossBars.values().toArray(new BossBar[0]);
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.COUNTDOWN, 100, () -> (announceEndTime - System.currentTimeMillis()) / 1000);
TAB.getInstance().debug(String.format("Loaded BossBar feature with parameters disabledWorlds=%s, disabledServers=%s, toggleCommand=%s, defaultBars=%s, hiddenByDefault=%s, remember_toggle_choice=%s",
Arrays.toString(disabledWorlds), Arrays.toString(disabledServers), toggleCommand, defaultBars, hiddenByDefault, rememberToggleChoice));
}
/**
@@ -95,6 +95,7 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
@Override
public void load() {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.COUNTDOWN, 100, () -> (announceEndTime - System.currentTimeMillis()) / 1000);
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
onJoin(p);
}
@@ -250,8 +251,7 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
TAB.getInstance().getConfiguration().getPlayerDataFile().set("bossbar-off", new ArrayList<>(bossBarOffPlayers));
}
}
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setBossBarVisible(player, visible);
TAB.getInstance().getPlaceholderManager().getTabExpansion().setBossBarVisible(player, visible);
}
@Override
@@ -3,10 +3,10 @@ package me.neznamy.tab.shared.features.globalplayerlist;
import java.util.*;
import java.util.Map.Entry;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.placeholder.ServerPlaceholder;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
@@ -28,33 +28,27 @@ public class GlobalPlayerList extends TabFeature {
private final boolean vanishedAsSpectators = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.display-vanished-players-as-spectators", true);
private final boolean isolateUnlistedServers = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.isolate-unlisted-servers", false);
private final boolean fillProfileKey = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.fill-profile-key", false);
private final boolean updateLatency = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.update-latency", false);
private final List<ServerPlaceholder> placeholders = new ArrayList<>();
private final Map<TabPlayer, Long> lastServerSwitch = new WeakHashMap<>();
private final UUID EMPTY_ID = new UUID(0, 0);
public GlobalPlayerList() {
super("Global PlayerList", null);
boolean updateLatency = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.update-latency", false);
if (updateLatency) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST_LATENCY, new LatencyRefresher());
TAB.getInstance().getPlaceholderManager().addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.VANISHED));
for (Entry<String, List<String>> entry : sharedServers.entrySet()) {
placeholders.add(TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.globalPlayerListGroup(entry.getKey()), -1,
() -> Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(p -> entry.getValue().contains(p.getServer()) && !p.isVanished()).count()));
}
TAB.getInstance().debug(String.format("Loaded GlobalPlayerList feature with parameters spyServers=%s, sharedServers=%s, displayAsSpectators=%s, vanishedAsSpectators=%s, isolateUnlistedServers=%s, updateLatency=%s",
spyServers, sharedServers, displayAsSpectators, vanishedAsSpectators, isolateUnlistedServers, updateLatency));
}
private final PlayerList playerlist = (PlayerList) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PLAYER_LIST);
@Getter private final String featureName = "Global PlayerList";
@Override
public void load() {
for (TabPlayer displayed : TAB.getInstance().getOnlinePlayers()) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer.getServer().equals(displayed.getServer())) continue;
if (shouldSee(viewer, displayed)) viewer.sendCustomPacket(getAddPacket(displayed, viewer), this);
}
if (updateLatency) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST_LATENCY, new LatencyRefresher());
TAB.getInstance().getPlaceholderManager().addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.VANISHED));
for (Entry<String, List<String>> entry : sharedServers.entrySet()) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.globalPlayerListGroup(entry.getKey()), 1000,
() -> Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(p -> entry.getValue().contains(p.getServer()) && !p.isVanished()).count());
}
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
List<PlayerInfoData> entries = new ArrayList<>();
for (TabPlayer displayed : TAB.getInstance().getOnlinePlayers()) {
if (viewer.getServer().equals(displayed.getServer())) continue;
if (shouldSee(viewer, displayed)) entries.add(getAddInfoData(displayed, viewer));
}
if (!entries.isEmpty()) viewer.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, entries), this);
}
placeholders.forEach(pl -> pl.updateValue(pl.request()));
}
public boolean shouldSee(TabPlayer viewer, TabPlayer displayed) {
@@ -83,103 +77,78 @@ public class GlobalPlayerList extends TabFeature {
@Override
public void onJoin(TabPlayer connectedPlayer) {
List<PlayerInfoData> entries = new ArrayList<>();
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == connectedPlayer) continue;
if (connectedPlayer.getServer().equals(all.getServer())) continue;
if (shouldSee(all, connectedPlayer)) {
all.sendCustomPacket(getAddPacket(connectedPlayer, all), this);
}
if (shouldSee(connectedPlayer, all)) {
connectedPlayer.sendCustomPacket(getAddPacket(all, connectedPlayer), this);
all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, getAddInfoData(connectedPlayer, all)), this);
}
if (shouldSee(connectedPlayer, all)) entries.add(getAddInfoData(all, connectedPlayer));
}
placeholders.forEach(pl -> pl.updateValue(pl.request()));
if (!entries.isEmpty()) connectedPlayer.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, entries), this);
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
//delay due to waterfall bug calling server switch when players leave
TAB.getInstance().getCPUManager().runTaskLater(50, this, TabConstants.CpuUsageCategory.PLAYER_QUIT, () -> {
if (TAB.getInstance().getPlayer(disconnectedPlayer.getName()) != null) return;
PacketPlayOutPlayerInfo remove = getRemovePacket(disconnectedPlayer);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == disconnectedPlayer) continue;
all.sendCustomPacket(remove, this);
}
//update after player is removed
placeholders.forEach(pl -> pl.updateValue(pl.request()));
});
PacketPlayOutPlayerInfo remove = getRemovePacket(disconnectedPlayer);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == disconnectedPlayer) continue;
all.sendCustomPacket(remove, this);
}
}
@Override
public void onServerChange(TabPlayer p, String from, String to) {
lastServerSwitch.put(p, System.currentTimeMillis());
Runnable r = () -> {
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(p);
public void onServerChange(TabPlayer changed, String from, String to) {
// Event is fired after all entries are removed from switched player's tablist, ready to re-add immediately
List<PlayerInfoData> entries = new ArrayList<>();
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == changed) continue;
if (shouldSee(changed, all)) {
entries.add(getAddInfoData(all, changed));
} else {
changed.sendCustomPacket(getRemovePacket(all), this);
}
}
if (!entries.isEmpty()) changed.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, entries), this);
// Player who switched server is removed from tablist of other players in ~70-110ms (depending on online count), re-add with a delay
TAB.getInstance().getCPUManager().runTaskLater(200, this, TabConstants.CpuUsageCategory.SERVER_SWITCH, () -> {
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(changed);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == p) continue;
if (shouldSee(all, p)) {
all.sendCustomPacket(getAddPacket(p, all), this);
if (all == changed) continue;
if (shouldSee(all, changed)) {
all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, getAddInfoData(changed, all)), this);
} else {
all.sendCustomPacket(removeChanged, this);
}
if (shouldSee(p, all)) {
p.sendCustomPacket(getAddPacket(all, p), this);
} else {
p.sendCustomPacket(getRemovePacket(all), this);
}
}
};
if (!TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION)) {
TAB.getInstance().getCPUManager().runTaskLater(200, this, TabConstants.CpuUsageCategory.SERVER_SWITCH, r);
} else {
r.run();
}
placeholders.forEach(pl -> pl.updateValue(pl.request()));
});
}
public PacketPlayOutPlayerInfo getRemovePacket(TabPlayer p) {
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER,
new PlayerInfoData(p.getTablistId()), new PlayerInfoData(EMPTY_ID));
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, new PlayerInfoData(p.getTablistId()));
}
public PacketPlayOutPlayerInfo getAddPacket(TabPlayer p, TabPlayer viewer) {
public PlayerInfoData getAddInfoData(TabPlayer p, TabPlayer viewer) {
IChatBaseComponent format = null;
PlayerList playerlist = (PlayerList) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PLAYER_LIST);
if (playerlist != null) {
format = playerlist.getTabFormat(p, viewer);
}
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER,
new PlayerInfoData(
p.getName(),
p.getTablistId(),
p.getSkin(),
true,
p.getPing(),
vanishedAsSpectators && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE,
viewer.getVersion().getMinorVersion() >= 8 ? format : null,
fillProfileKey ? ((ProxyTabPlayer)p).getChatSessionId() : null,
fillProfileKey ? ((ProxyTabPlayer)p).getProfilePublicKey() : null
)
return new PlayerInfoData(
p.getName(),
p.getTablistId(),
p.getSkin(),
true,
p.getPing(),
vanishedAsSpectators && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE,
viewer.getVersion().getMinorVersion() >= 8 ? format : null,
fillProfileKey ? ((ProxyTabPlayer)p).getChatSessionId() : null,
fillProfileKey ? ((ProxyTabPlayer)p).getProfilePublicKey() : null
);
}
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (info.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
boolean packetFromTAB = info.getEntries().stream().anyMatch(data -> data.getUniqueId().equals(EMPTY_ID));
for (PlayerInfoData playerInfoData : info.getEntries()) {
TabPlayer packetPlayer = TAB.getInstance().getPlayerByTabListUUID(playerInfoData.getUniqueId());
//not preventing NPC removals
if (packetPlayer != null && !packetFromTAB && !packetPlayer.isVanished()
&& (System.currentTimeMillis()-lastServerSwitch.getOrDefault(packetPlayer, 0L) < 2000)
) {
//remove packet not coming from tab
//changing to random non-existing player, the easiest way to cancel the removal
playerInfoData.setUniqueId(UUID.randomUUID());
}
}
}
if (!displayAsSpectators) return;
if (info.getActions().contains(EnumPlayerInfoAction.UPDATE_GAME_MODE)) {
for (PlayerInfoData playerInfoData : info.getEntries()) {
@@ -204,7 +173,7 @@ public class GlobalPlayerList extends TabFeature {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer == p) continue;
if (shouldSee(viewer, p)) {
viewer.sendCustomPacket(getAddPacket(p, viewer), TabConstants.PacketCategory.GLOBAL_PLAYERLIST_VANISH);
viewer.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, getAddInfoData(p, viewer)), TabConstants.PacketCategory.GLOBAL_PLAYERLIST_VANISH);
}
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared.features.globalplayerlist;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
@@ -10,8 +11,10 @@ import me.neznamy.tab.shared.TAB;
public class LatencyRefresher extends TabFeature {
public LatencyRefresher() {
super("Global PlayerList", "Updating latency");
@Getter private final String featureName = "Global PlayerList";
@Getter private final String refreshDisplayName = "Updating latency";
{
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder(TabConstants.Placeholder.PING, this);
}
@@ -0,0 +1,57 @@
package me.neznamy.tab.shared.features.injection;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import java.util.NoSuchElementException;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
/**
* A pipeline injector for Netty connections. As most servers use Netty, this avoids code duplication.
*/
@RequiredArgsConstructor
public abstract class NettyPipelineInjector extends PipelineInjector {
//handler to inject before
private final String injectPosition;
protected abstract Channel getChannel(TabPlayer player);
/**
* Injects custom channel duplex handler to prevent other plugins from overriding this one
*
* @param player
* player to inject
*/
@Override
public void inject(TabPlayer player) {
final Channel channel = getChannel(player);
if (player.getVersion().getMinorVersion() < 8 || channel == null) return; //hello A248
if (!channel.pipeline().names().contains(injectPosition)) {
//fake player or waterfall bug
return;
}
uninject(player);
try {
channel.pipeline().addBefore(injectPosition, TabConstants.PIPELINE_HANDLER_NAME, getChannelFunction().apply(player));
} catch (NoSuchElementException | IllegalArgumentException e) {
//I don't really know how does this keep happening but whatever
}
}
@Override
public void uninject(TabPlayer player) {
final Channel channel = getChannel(player);
if (player.getVersion().getMinorVersion() < 8 || channel == null) return; //hello A248
try {
if (channel.pipeline().names().contains(TabConstants.PIPELINE_HANDLER_NAME)) channel.pipeline().remove(TabConstants.PIPELINE_HANDLER_NAME);
} catch (NoSuchElementException e) {
//for whatever reason this rarely throws
//java.util.NoSuchElementException: TABReader
}
}
public abstract Function<TabPlayer, ChannelDuplexHandler> getChannelFunction();
}
@@ -0,0 +1,65 @@
package me.neznamy.tab.shared.features.injection;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
/**
* Packet intercepting to secure proper functionality of some features:
* TabList names - anti-override
* NameTags - anti-override
* Scoreboard - disabling tab's scoreboard to prevent conflict
* SpectatorFix - to change game mode to something else than spectator
* PetFix - to remove owner field from entity data
* PingSpoof - full feature functionality
* Unlimited name tags - replacement for bukkit events with much better accuracy and reliability
*/
public abstract class PipelineInjector extends TabFeature {
@Getter private final String featureName = "Pipeline injection";
//preventing spam when packet is sent to everyone
private String lastTeamOverrideMessage;
//anti-override rules
protected boolean antiOverrideTeams;
protected boolean byteBufDeserialization;
public abstract void inject(TabPlayer player);
public abstract void uninject(TabPlayer player);
@Override
public void load() {
antiOverrideTeams = TAB.getInstance().getConfig().getBoolean("scoreboard-teams.enabled", true) &&
TAB.getInstance().getConfig().getBoolean("scoreboard-teams.anti-override", true);
boolean respectOtherScoreboardPlugins = TAB.getInstance().getConfig().getBoolean("scoreboard.enabled", false) &&
TAB.getInstance().getConfig().getBoolean("scoreboard.respect-other-plugins", true);
byteBufDeserialization = antiOverrideTeams || respectOtherScoreboardPlugins;
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
inject(p);
}
}
@Override
public void unload() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
uninject(p);
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
inject(connectedPlayer);
}
protected void logTeamOverride(String team, String player, String expectedTeam) {
String message = "Something just tried to add player " + player + " into team " + team + " (expected team: " + expectedTeam + ")";
//not logging the same message for every online player who received the packet
if (!message.equals(lastTeamOverrideMessage)) {
lastTeamOverrideMessage = message;
TAB.getInstance().getErrorManager().printError(message, null, false, TAB.getInstance().getErrorManager().getAntiOverrideLog());
}
}
}
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared.features.layout;
import java.util.UUID;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
@@ -12,28 +13,38 @@ import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.TabConstants;
@RequiredArgsConstructor
public class FixedSlot extends TabFeature {
private final Layout layout;
@Getter private final UUID id;
@Getter private final String text;
@Getter private final String propertyName;
@Getter private final Skin skin;
@Getter private final int ping;
@Getter private final String featureName = "Layout";
@Getter private final String refreshDisplayName = "Updating fixed slots";
public FixedSlot(Layout layout, int slot, String text, String skin, int ping) {
super(layout.getFeatureName(), "Updating fixed slots");
this.layout = layout;
this.id = layout.getManager().getUUID(slot);
this.text = text;
propertyName = "Layout-" + layout.getName() + "SLOT-" + slot;
this.skin = layout.getManager().getSkinManager().getSkin(skin.length() == 0 ? layout.getManager().getDefaultSkin() : skin);
this.ping = ping;
}
private final Layout layout;
private final UUID id;
private final String text;
private final String propertyName;
private final Skin skin;
private final int ping;
@Override
public void refresh(TabPlayer p, boolean force) {
if (!layout.containsViewer(p) || p.getVersion().getMinorVersion() < 8 || p.isBedrockPlayer()) return;
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, new PlayerInfoData(id, IChatBaseComponent.optimizedComponent(p.getProperty(propertyName).updateAndGet()))), TabConstants.PacketCategory.LAYOUT_FIXED_SLOTS);
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME,
new PlayerInfoData(id, IChatBaseComponent.optimizedComponent(p.getProperty(propertyName).updateAndGet()))), TabConstants.PacketCategory.LAYOUT_FIXED_SLOTS);
}
public PlayerInfoData createEntry(TabPlayer viewer) {
viewer.setProperty(this, propertyName, text);
return new PlayerInfoData(
layout.getEntryName(viewer, id.getLeastSignificantBits()),
id,
skin,
true,
ping,
PacketPlayOutPlayerInfo.EnumGamemode.CREATIVE,
IChatBaseComponent.optimizedComponent(viewer.getProperty(propertyName).updateAndGet()), // maybe just get is fine?
null,
null
);
}
}
@@ -1,10 +1,7 @@
package me.neznamy.tab.shared.features.layout;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
@@ -13,12 +10,17 @@ import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
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 me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.placeholders.conditions.Condition;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RequiredArgsConstructor
public class Layout extends TabFeature {
@Getter private final String featureName = "Layout";
@Getter private final String refreshDisplayName = "Updating player groups";
@Getter private final String name;
@Getter private final LayoutManager manager;
private final Condition displayCondition;
@@ -27,24 +29,13 @@ public class Layout extends TabFeature {
@Getter private final List<ParentGroup> groups;
@Getter private final Set<TabPlayer> viewers = Collections.newSetFromMap(new WeakHashMap<>());
public Layout(String name, LayoutManager manager, Condition displayCondition, Map<Integer, FixedSlot> fixedSlots, List<Integer> emptySlots, List<ParentGroup> groups) {
super(manager.getFeatureName(), "Updating player groups");
this.name = name;
this.manager = manager;
this.displayCondition = displayCondition;
this.fixedSlots = fixedSlots;
this.emptySlots = emptySlots;
this.groups = groups;
}
public void sendTo(TabPlayer p) {
if (viewers.contains(p)) return;
viewers.add(p);
List<PlayerInfoData> list = new ArrayList<>();
groups.forEach(g -> list.addAll(g.getSlots(p)));
for (FixedSlot slot : fixedSlots.values()) {
p.setProperty(slot, slot.getPropertyName(), slot.getText());
list.add(new PlayerInfoData(getEntryName(p, slot.getId().getLeastSignificantBits()), slot.getId(), slot.getSkin(), true, slot.getPing(), EnumGamemode.CREATIVE, IChatBaseComponent.optimizedComponent(p.getProperty(slot.getPropertyName()).updateAndGet()), null, null));
list.add(slot.createEntry(p));
}
for (int slot : emptySlots) {
list.add(new PlayerInfoData(getEntryName(p, slot), manager.getUUID(slot), manager.getSkinManager().getDefaultSkin(), true, manager.getEmptySlotPing(), EnumGamemode.CREATIVE, new IChatBaseComponent(""), null, null));
@@ -60,12 +51,8 @@ public class Layout extends TabFeature {
public void removeFrom(TabPlayer p) {
if (!viewers.contains(p)) return;
viewers.remove(p);
List<PlayerInfoData> list = new ArrayList<>();
for (UUID id : manager.getUuids().values()) {
list.add(new PlayerInfoData(id));
}
if (p.getVersion().getMinorVersion() < 8 || p.isBedrockPlayer()) return;
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, list), this);
p.sendCustomPacket(manager.getRemovePacket(), this);
}
public boolean isConditionMet(TabPlayer p) {
@@ -100,13 +87,4 @@ public class Layout extends TabFeature {
}
return null;
}
@Override
public void onServerChange(TabPlayer player, String from, String to) {
if (TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION)) return;
//velocity clearing TabList on server switch
if (viewers.remove(player)) {
sendTo(player);
}
}
}
@@ -1,18 +1,21 @@
package me.neznamy.tab.shared.features.layout;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
@RequiredArgsConstructor
public class LayoutLatencyRefresher extends TabFeature {
@Getter private final String featureName = "Layout";
@Getter private final String refreshDisplayName = "Updating latency";
private final LayoutManager manager;
public LayoutLatencyRefresher(LayoutManager manager) {
super(manager.getFeatureName(), "Updating latency");
this.manager = manager;
{
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder(TabConstants.Placeholder.PING, this);
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.layout;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
@@ -12,6 +13,7 @@ import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.PlayerList;
import me.neznamy.tab.shared.features.layout.skin.SkinManager;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.placeholders.conditions.Condition;
@@ -30,17 +32,25 @@ public class LayoutManager extends TabFeature {
put(slot, new UUID(0, translateSlot(slot)));
}
}};
@Getter private final PacketPlayOutPlayerInfo removePacket = new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER,
uuids.values().stream().map(PlayerInfoData::new).collect(Collectors.toList()));
private final Map<String, Layout> layouts = loadLayouts();
@Getter private final WeakHashMap<TabPlayer, Layout> playerViews = new WeakHashMap<>();
private final WeakHashMap<TabPlayer, String> teamNames = new WeakHashMap<>();
@Getter private final Map<TabPlayer, String> sortedPlayers = Collections.synchronizedMap(new TreeMap<>(Comparator.comparing(teamNames::get)));
private final Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
@Getter private PlayerList playerList;
@Getter private final String featureName = "Layout";
@Getter private final String refreshDisplayName = "Switching layouts";
public LayoutManager() {
super("Layout", "Switching layouts");
@Override
public void load() {
playerList = (PlayerList) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PLAYER_LIST);
TAB.getInstance().getPlaceholderManager().addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.VANISHED));
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.LAYOUT_LATENCY, new LayoutLatencyRefresher(this));
TAB.getInstance().debug("Loaded Layout feature");
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
onJoin(p);
}
}
private Direction parseDirection(String value) {
@@ -73,7 +83,9 @@ public class LayoutManager extends TabFeature {
String text = array[1];
String skin = array.length > 2 ? array[2] : "";
int ping = array.length > 3 ? TAB.getInstance().getErrorManager().parseInteger(array[3], emptySlotPing) : emptySlotPing;
FixedSlot f = new FixedSlot(l, slot, text, skin, ping);
FixedSlot f = new FixedSlot(l, uuids.get(slot), text,
"Layout-" + l.getName() + "SLOT-" + slot,
skinManager.getSkin(skin.length() == 0 ? defaultSkin : skin), ping);
fixedSlots.put(slot, f);
emptySlots.remove((Integer)slot);
if (text.length() > 0) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.layoutSlot(layout.getKey().toString(), slot), f);
@@ -94,8 +106,12 @@ public class LayoutManager extends TabFeature {
positions.add(i);
}
}
positions.removeAll(fixedSlots.keySet());
parentGroups.add(new ParentGroup(l, condition, positions.stream().mapToInt(i->i).toArray()));
emptySlots.removeAll(positions);
if (condition != null) {
l.addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.condition(condition.getName())));
}
}
}
layoutMap.put(layout.getKey().toString(), l);
@@ -137,13 +153,6 @@ public class LayoutManager extends TabFeature {
}
}
@Override
public void load() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
onJoin(p);
}
}
@Override
public void refresh(TabPlayer p, boolean force) {
Layout highest = getHighestLayout(p);
@@ -157,13 +166,9 @@ public class LayoutManager extends TabFeature {
@Override
public void unload() {
List<PlayerInfoData> list = new ArrayList<>();
for (UUID id : uuids.values()) {
list.add(new PlayerInfoData(id));
}
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() < 8 || p.isBedrockPlayer()) continue;
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, list), this);
p.sendCustomPacket(removePacket, this);
}
}
@@ -5,7 +5,6 @@ import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.placeholders.conditions.Condition;
public class ParentGroup {
@@ -19,9 +18,6 @@ public class ParentGroup {
public ParentGroup(Layout layout, Condition condition, int[] slots) {
this.layout = layout;
this.condition = condition;
if (condition != null) {
layout.addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.condition(condition.getName())));
}
this.slots = slots;
for (int slot : slots) {
playerSlots.put(slot, new PlayerSlot(layout, layout.getManager().getUUID(slot)));
@@ -1,23 +1,21 @@
package me.neznamy.tab.shared.features.layout;
import java.util.UUID;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
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 me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PlayerList;
import java.util.UUID;
@RequiredArgsConstructor
public class PlayerSlot {
private final PlayerList playerlist = (PlayerList) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PLAYER_LIST);
private final Layout layout;
@Getter private final UUID uniqueId;
@Getter private TabPlayer player;
@@ -39,9 +37,28 @@ public class PlayerSlot {
PlayerInfoData data;
TabPlayer player = this.player; //avoiding NPE from concurrent access
if (player != null) {
data = new PlayerInfoData(layout.getEntryName(p, uniqueId.getLeastSignificantBits()), uniqueId, player.getSkin(), true, player.getPing(), EnumGamemode.SURVIVAL, playerlist == null ? new IChatBaseComponent(player.getName()) : playerlist.getTabFormat(player, p), null, null);
PlayerList playerList = layout.getManager().getPlayerList();
data = new PlayerInfoData(
layout.getEntryName(p, uniqueId.getLeastSignificantBits()),
uniqueId,
player.getSkin(),
true,
player.getPing(),
EnumGamemode.SURVIVAL,
playerList == null ? new IChatBaseComponent(player.getName()) : playerList.getTabFormat(player, p),
null,
null);
} else {
data = new PlayerInfoData(layout.getEntryName(p, uniqueId.getLeastSignificantBits()), uniqueId, layout.getManager().getSkinManager().getDefaultSkin(), true, layout.getManager().getEmptySlotPing(), EnumGamemode.SURVIVAL, new IChatBaseComponent(text), null, null);
data = new PlayerInfoData(
layout.getEntryName(p, uniqueId.getLeastSignificantBits()),
uniqueId,
layout.getManager().getSkinManager().getDefaultSkin(),
true,
layout.getManager().getEmptySlotPing(),
EnumGamemode.SURVIVAL,
new IChatBaseComponent(text),
null,
null);
}
return data;
}
@@ -3,23 +3,30 @@ package me.neznamy.tab.shared.features.nametags;
import java.util.Collections;
import java.util.WeakHashMap;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
@RequiredArgsConstructor
public class CollisionManager extends TabFeature {
@Getter private final String featureName = "NameTags";
@Getter private final String refreshDisplayName = "Updating collision";
private final NameTag nameTags;
private final boolean collisionRule;
private final WeakHashMap<TabPlayer, Boolean> collision = new WeakHashMap<>();
private final WeakHashMap<TabPlayer, Boolean> forcedCollision = new WeakHashMap<>();
public CollisionManager(NameTag nameTags, boolean collisionRule) {
super(nameTags.getFeatureName(), "Updating collision");
this.nameTags = nameTags;
this.collisionRule = collisionRule;
public boolean getCollision(TabPlayer p) {
return forcedCollision.getOrDefault(p, collision.getOrDefault(p, collisionRule));
}
@Override
public void load() {
if (TAB.getInstance().getServerVersion().getMinorVersion() < 9) return; //cannot control collision anyway
if (!collisionRule) return; //no need to refresh disguise status since collision is disabled
if (TAB.getInstance().getPlatform().getPluginVersion(TabConstants.Plugin.LIBS_DISGUISES) == null && TAB.getInstance().getServerVersion() != ProtocolVersion.PROXY) return; //no disguise plugin available
@@ -31,16 +38,8 @@ public class CollisionManager extends TabFeature {
return newCollision;
});
addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.COLLISION));
}
public boolean getCollision(TabPlayer p) {
return forcedCollision.getOrDefault(p, collision.getOrDefault(p, collisionRule));
}
@Override
public void load() {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
collision.put(all, collisionRule);
collision.put(all, true);
}
}
@@ -1,25 +1,23 @@
package me.neznamy.tab.shared.features.nametags;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.api.team.TeamManager;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.features.layout.LayoutManager;
import me.neznamy.tab.shared.features.sorting.Sorting;
import java.util.*;
public class NameTag extends TabFeature implements TeamManager {
@Getter private final String featureName = "NameTags";
@Getter private final String refreshDisplayName = "Updating prefix/suffix";
protected final boolean invisibleNameTags = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard-teams.invisible-nametags", false);
private final boolean collisionRule = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard-teams.enable-collision", true);
private final boolean canSeeFriendlyInvisibles = TAB.getInstance().getConfig().getBoolean("scoreboard-teams.can-see-friendly-invisibles", false);
@@ -33,29 +31,23 @@ public class NameTag extends TabFeature implements TeamManager {
private final WeakHashMap<TabPlayer, String> forcedTeamName = new WeakHashMap<>();
protected final Set<TabPlayer> playersWithInvisibleNameTagView = Collections.newSetFromMap(new WeakHashMap<>());
private RedisSupport redis;
private final boolean accepting18x = TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY ||
TAB.getInstance().getPlatform().getPluginVersion("ViaRewind") != null ||
TAB.getInstance().getPlatform().getPluginVersion(TabConstants.Plugin.VIAREWIND) != null ||
TAB.getInstance().getPlatform().getPluginVersion(TabConstants.Plugin.PROTOCOL_SUPPORT) != null ||
TAB.getInstance().getServerVersion().getMinorVersion() == 8;
public NameTag() {
super("NameTags", "Updating prefix/suffix", "scoreboard-teams");
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.SORTING, sorting);
if (accepting18x) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS_VISIBILITY, new VisibilityRefresher(this));
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS_COLLISION, collisionManager);
PipelineInjector pipeline = (PipelineInjector) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PIPELINE_INJECTION);
boolean antiOverride = TAB.getInstance().getConfig().getBoolean("scoreboard-teams.anti-override", true);
if (pipeline != null && antiOverride) {
pipeline.setAntiOverrideTeams(true);
pipeline.setByteBufDeserialization(true);
}
TAB.getInstance().debug(String.format("Loaded NameTag feature with parameters collisionRule=%s, disabledWorlds=%s, disabledServers=%s, invisibleNameTags=%s",
collisionRule, Arrays.toString(disabledWorlds), Arrays.toString(disabledServers), invisibleNameTags));
super("scoreboard-teams");
}
@Override
public void load() {
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
// RedisSupport is instantiated after NameTags, so must be loaded after
redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (accepting18x) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS_VISIBILITY, new VisibilityRefresher(this));
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS_COLLISION, collisionManager);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
updateProperties(all);
hiddenNameTagFor.put(all, new ArrayList<>());
@@ -63,15 +55,26 @@ public class NameTag extends TabFeature implements TeamManager {
addDisabledPlayer(all);
continue;
}
registerTeam(all);
if (expansion != null) expansion.setNameTagVisibility(all, true);
TAB.getInstance().getPlaceholderManager().getTabExpansion().setNameTagVisibility(all, true);
}
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (!isDisabledPlayer(target)) registerTeam(target, viewer);
}
}
}
@Override
public void unload() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (!isDisabledPlayer(p)) unregisterTeam(p, sorting.getShortTeamName(p));
List<PacketPlayOutScoreboardTeam> packets = new ArrayList<>();
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
if (hasTeamHandlingPaused(target)) return;
packets.add(new PacketPlayOutScoreboardTeam(sorting.getShortTeamName(target)));
}
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
for (PacketPlayOutScoreboardTeam packet : packets) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.NAMETAGS_TEAM_UNREGISTER);
}
}
}
@@ -95,7 +98,7 @@ public class NameTag extends TabFeature implements TeamManager {
boolean suffix = refreshed.getProperty(TabConstants.Property.TAGSUFFIX).update();
refresh = prefix || suffix;
}
if (refresh) updateTeam(refreshed);
if (refresh) updateTeamData(refreshed);
}
@Override
@@ -109,10 +112,7 @@ public class NameTag extends TabFeature implements TeamManager {
registerTeam(all, connectedPlayer);
}
}
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) {
expansion.setNameTagVisibility(connectedPlayer, true);
}
TAB.getInstance().getPlaceholderManager().getTabExpansion().setNameTagVisibility(connectedPlayer, true);
if (isDisabled(connectedPlayer.getServer(), connectedPlayer.getWorld())) {
addDisabledPlayer(connectedPlayer);
return;
@@ -157,7 +157,7 @@ public class NameTag extends TabFeature implements TeamManager {
} else if (!disabledNow && disabledBefore) {
registerTeam(p);
} else if (changed) {
updateTeam(p);
updateTeamData(p);
}
}
@@ -226,7 +226,6 @@ public class NameTag extends TabFeature implements TeamManager {
forcedTeamName.put(player, name);
registerTeam(player);
if (name != null) sorting.setTeamNameNote(player, "Set using API");
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateTeamName(player, sorting.getShortTeamName(player));
}
@@ -247,15 +246,9 @@ public class NameTag extends TabFeature implements TeamManager {
@Override
public void updateTeamData(TabPlayer p) {
Property tagPrefix = p.getProperty(TabConstants.Property.TAGPREFIX);
Property tagSuffix = p.getProperty(TabConstants.Property.TAGSUFFIX);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
String currentPrefix = tagPrefix.getFormat(viewer);
String currentSuffix = tagSuffix.getFormat(viewer);
boolean visible = getTeamVisibility(p, viewer);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(sorting.getShortTeamName(p), currentPrefix, currentSuffix, translate(visible), translate(collisionManager.getCollision(p)), getTeamOptions()), TabConstants.PacketCategory.NAMETAGS_TEAM_UPDATE);
updateTeamData(p, viewer);
}
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateNameTag(p, p.getProperty(TabConstants.Property.TAGPREFIX).get(), p.getProperty(TabConstants.Property.TAGSUFFIX).get());
}
@@ -267,7 +260,7 @@ public class NameTag extends TabFeature implements TeamManager {
}
public void unregisterTeam(TabPlayer p, String teamName) {
if (hasTeamHandlingPaused(p) || sorting.getShortTeamName(p) == null) return;
if (hasTeamHandlingPaused(p)) return;
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName), TabConstants.PacketCategory.NAMETAGS_TEAM_UNREGISTER);
}
@@ -287,21 +280,6 @@ public class NameTag extends TabFeature implements TeamManager {
translate(collisionManager.getCollision(p)), Collections.singletonList(p.getNickname()), getTeamOptions()), TabConstants.PacketCategory.NAMETAGS_TEAM_REGISTER);
}
private void updateTeam(TabPlayer p) {
String oldName = getSorting().getShortTeamName(p);
getSorting().constructTeamNames(p);
if (oldName.equals(sorting.getShortTeamName(p))) {
updateTeamData(p);
} else {
unregisterTeam(p, oldName);
LayoutManager layout = (LayoutManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.LAYOUT);
if (layout != null) layout.updateTeamName(p, sorting.getFullTeamName(p));
registerTeam(p);
RedisSupport redis = (RedisSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) redis.updateTeamName(p, sorting.getShortTeamName(p));
}
}
public String translate(boolean b) {
return b ? "always" : "never";
}
@@ -378,10 +356,7 @@ public class NameTag extends TabFeature implements TeamManager {
playersWithInvisibleNameTagView.add(player);
if (sendToggleMessage) player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNameTagsHidden(), true);
}
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) {
expansion.setNameTagVisibility(player, !playersWithInvisibleNameTagView.contains(player));
}
TAB.getInstance().getPlaceholderManager().getTabExpansion().setNameTagVisibility(player, !playersWithInvisibleNameTagView.contains(player));
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
updateTeamData(all, player);
}
@@ -2,24 +2,28 @@ package me.neznamy.tab.shared.features.nametags;
import java.util.Collections;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
@RequiredArgsConstructor
public class VisibilityRefresher extends TabFeature {
@Getter private final String featureName = "NameTags";
@Getter private final String refreshDisplayName = "Updating NameTag visibility";
private final NameTag nameTags;
public VisibilityRefresher(NameTag nameTags) {
super(nameTags.getFeatureName(), "Updating NameTag visibility");
this.nameTags = nameTags;
{
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(TabConstants.Placeholder.INVISIBLE, 500, TabPlayer::hasInvisibilityPotion);
addUsedPlaceholders(Collections.singletonList(TabConstants.Placeholder.INVISIBLE));
}
@Override
public void refresh(TabPlayer p, boolean force) {
if (nameTags.isDisabledPlayer(p)) return;
nameTags.updateTeamData(p);
}
}
@@ -2,12 +2,11 @@ package me.neznamy.tab.shared.features.nametags.unlimited;
import lombok.Getter;
import me.neznamy.tab.api.ArmorStandManager;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.team.UnlimitedNametagManager;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.features.nametags.NameTag;
import java.util.*;
@@ -37,14 +36,9 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
public NameTagX(BiFunction<NameTagX, TabPlayer, ArmorStandManager> armorStandFunction) {
this.armorStandFunction = armorStandFunction;
}
{
Collections.reverse(dynamicLines);
if (invisibleNameTags) {
TAB.getInstance().getErrorManager().startupWarn("Unlimited nametag mode is enabled as well as invisible nametags. These 2 options are mutually exclusive.");
TAB.getInstance().getErrorManager().startupWarn("If you want nametags to be invisible, you don't need unlimited nametag mode at all.");
TAB.getInstance().getErrorManager().startupWarn("If you want enhanced nametags without limits, making them invisible would defeat the purpose.");
}
TAB.getInstance().debug(String.format("Loaded Unlimited NameTag feature with parameters markerFor18x=%s, disableOnBoats=%s, disabledUnlimitedServers=%s, disabledUnlimitedWorlds=%s",
markerFor18x, disableOnBoats, disabledUnlimitedServers, disabledUnlimitedWorlds));
}
public boolean isUnlimitedDisabled(String server, String world) {
@@ -66,14 +60,18 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
@Override
public void load() {
if (invisibleNameTags) {
TAB.getInstance().getErrorManager().startupWarn("Unlimited nametag mode is enabled as well as invisible nametags. These 2 options are mutually exclusive.");
TAB.getInstance().getErrorManager().startupWarn("If you want nametags to be invisible, you don't need unlimited nametag mode at all.");
TAB.getInstance().getErrorManager().startupWarn("If you want enhanced nametags without limits, making them invisible would defeat the purpose.");
}
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
updateProperties(all);
armorStandManagerMap.put(all, armorStandFunction.apply(this, all));
if (isUnlimitedDisabled(all.getServer(), all.getWorld())) {
disabledUnlimitedPlayers.add(all);
}
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setNameTagPreview(all, false);
TAB.getInstance().getPlaceholderManager().getTabExpansion().setNameTagPreview(all, false);
}
super.load();
}
@@ -84,8 +82,7 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
disabledUnlimitedPlayers.add(connectedPlayer);
super.onJoin(connectedPlayer);
armorStandManagerMap.put(connectedPlayer, armorStandFunction.apply(this, connectedPlayer));
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setNameTagPreview(connectedPlayer, false);
TAB.getInstance().getPlaceholderManager().getTabExpansion().setNameTagPreview(connectedPlayer, false);
}
@Override
@@ -113,8 +110,7 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
if (sendToggleMessage) player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getNametagPreviewOn(), true);
playersPreviewingNametag.add(player);
}
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setNameTagPreview(player, isPreviewingNametag(player));
TAB.getInstance().getPlaceholderManager().getTabExpansion().setNameTagPreview(player, isPreviewingNametag(player));
}
public boolean isPreviewingNametag(TabPlayer player) {
@@ -286,7 +282,6 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
@Override
public void setPrefix(TabPlayer player, String prefix) {
Preconditions.checkLoaded(player);
super.setPrefix(player, prefix);
rebuildNameTagLine(player);
getArmorStandManager(player).refresh(true);
@@ -294,7 +289,6 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
@Override
public void setSuffix(TabPlayer player, String suffix) {
Preconditions.checkLoaded(player);
super.setSuffix(player, suffix);
rebuildNameTagLine(player);
getArmorStandManager(player).refresh(true);
@@ -302,7 +296,6 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
@Override
public void resetPrefix(TabPlayer player) {
Preconditions.checkLoaded(player);
super.resetPrefix(player);
rebuildNameTagLine(player);
getArmorStandManager(player).refresh(true);
@@ -310,7 +303,6 @@ public abstract class NameTagX extends NameTag implements UnlimitedNametagManage
@Override
public void resetSuffix(TabPlayer player) {
Preconditions.checkLoaded(player);
super.resetSuffix(player);
rebuildNameTagLine(player);
getArmorStandManager(player).refresh(true);
@@ -1,17 +1,21 @@
package me.neznamy.tab.shared.features.redis;
import lombok.Getter;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.event.EventHandler;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.event.impl.TabPlaceholderRegisterEvent;
import me.neznamy.tab.shared.features.PlayerList;
import me.neznamy.tab.shared.features.globalplayerlist.GlobalPlayerList;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.placeholders.ServerPlaceholderImpl;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
@@ -26,40 +30,25 @@ import java.util.concurrent.ConcurrentHashMap;
@SuppressWarnings("unchecked")
public abstract class RedisSupport extends TabFeature {
@Getter private final String featureName = "RedisSupport";
/** Redis players on other proxies by their UUID */
@Getter protected final Map<String, RedisPlayer> redisPlayers = new ConcurrentHashMap<>();
/** UUID of this proxy to ignore messages coming from the same proxy */
protected final UUID proxy = UUID.randomUUID();
/** Global PlayerList feature */
/** Features this one hooks into */
protected final GlobalPlayerList global = (GlobalPlayerList) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST);
/** PlayerList feature */
@Getter private final PlayerList playerList = (PlayerList) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PLAYER_LIST);
/** NameTag feature */
@Getter private final NameTag nameTags = (NameTag) TAB.getInstance().getTeamManager();
/** Sorting feature */
@Getter private final Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
@Getter private final Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
private final UUID EMPTY_ID = new UUID(0, 0);
private final Map<RedisPlayer, Long> lastServerSwitch = new WeakHashMap<>();
/**
* Constructs new instance
*/
protected RedisSupport() {
super("RedisBungee", null);
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.ONLINE, 1000, () ->
Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(all -> !all.isVanished()).count() +
redisPlayers.values().stream().filter(all -> !all.isVanished()).count());
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.STAFF_ONLINE, 1000, () ->
Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(all -> !all.isVanished() && all.hasPermission(TabConstants.Permission.STAFF)).count() +
redisPlayers.values().stream().filter(all -> !all.isVanished() && all.isStaff()).count());
}
private EventHandler<TabPlaceholderRegisterEvent> eventHandler;
/**
* Sends a message to all other proxies to update
@@ -333,6 +322,23 @@ public abstract class RedisSupport extends TabFeature {
@Override
public void load() {
eventHandler = event -> {
String identifier = event.getIdentifier();
if (identifier.startsWith("%online_")) {
String server = identifier.substring(8, identifier.length()-1);
event.setPlaceholder(new ServerPlaceholderImpl(identifier, 1000, () ->
Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(p -> p.getServer().equals(server) && !p.isVanished()).count() +
redisPlayers.values().stream().filter(all -> all.getServer().equals(server) && !all.isVanished()).count()));
}
};
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.ONLINE, 1000, () ->
Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(all -> !all.isVanished()).count() +
redisPlayers.values().stream().filter(all -> !all.isVanished()).count());
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(TabConstants.Placeholder.STAFF_ONLINE, 1000, () ->
Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(all -> !all.isVanished() && all.hasPermission(TabConstants.Permission.STAFF)).count() +
redisPlayers.values().stream().filter(all -> !all.isVanished() && all.isStaff()).count());
TabAPI.getInstance().getEventBus().register(TabPlaceholderRegisterEvent.class, eventHandler);
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) onJoin(p);
sendMessage("{\"action\":\"loadrequest\",\"proxy\":\"" + proxy.toString() + "\"}");
}
@@ -341,6 +347,7 @@ public abstract class RedisSupport extends TabFeature {
public void unload() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) onQuit(p);
unregister();
TabAPI.getInstance().getEventBus().unregister(eventHandler);
}
@Override
@@ -421,7 +428,7 @@ public abstract class RedisSupport extends TabFeature {
public void onLoginPacket(TabPlayer packetReceiver) {
for (RedisPlayer p : redisPlayers.values()) {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
all.sendCustomPacket(p.getRegisterTeamPacket());
all.sendCustomPacket(p.getRegisterTeamPacket(), TabConstants.PacketCategory.NAMETAGS_TEAM_REGISTER);
}
}
}
@@ -1,29 +1,31 @@
package me.neznamy.tab.shared.features.scoreboard;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore.Action;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.api.scoreboard.Line;
import me.neznamy.tab.api.scoreboard.Scoreboard;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.features.scoreboard.lines.*;
import me.neznamy.tab.shared.placeholders.conditions.Condition;
import java.util.*;
/**
* A class representing a scoreboard configured in config
*/
public class ScoreboardImpl extends TabFeature implements Scoreboard {
@Getter private final String featureName = "Scoreboard";
@Getter private final String refreshDisplayName = "Updating Scoreboard title";
//scoreboard manager
@Getter private final ScoreboardManagerImpl manager;
@@ -79,7 +81,6 @@ public class ScoreboardImpl extends TabFeature implements Scoreboard {
* lines of scoreboard
*/
public ScoreboardImpl(ScoreboardManagerImpl manager, String name, String title, List<String> lines, boolean dynamicLinesOnly) {
super(manager.getFeatureName(), "Updating scoreboard title");
this.manager = manager;
this.name = name;
this.title = title;
@@ -143,8 +144,7 @@ public class ScoreboardImpl extends TabFeature implements Scoreboard {
}
manager.getActiveScoreboards().put(p, this);
recalculateScores(p);
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setScoreboardName(p, name);
TAB.getInstance().getPlaceholderManager().getTabExpansion().setScoreboardName(p, name);
}
@Override
@@ -163,8 +163,7 @@ public class ScoreboardImpl extends TabFeature implements Scoreboard {
}
players.remove(p);
manager.getActiveScoreboards().remove(p);
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setScoreboardName(p, "");
TAB.getInstance().getPlaceholderManager().getTabExpansion().setScoreboardName(p, "");
}
@Override
@@ -1,19 +1,17 @@
package me.neznamy.tab.shared.features.scoreboard;
import java.util.*;
import java.util.Map.Entry;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.api.scoreboard.Scoreboard;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.TabExpansion;
import java.util.*;
import java.util.Map.Entry;
/**
* Feature handler for scoreboard feature
@@ -24,7 +22,7 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
public static final int DISPLAY_SLOT = 1;
//config options
private final String toggleCommand = TAB.getInstance().getConfiguration().getConfig().getString("scoreboard.toggle-command", "/sb");
@Getter private final String toggleCommand = TAB.getInstance().getConfiguration().getConfig().getString("scoreboard.toggle-command", "/sb");
@Getter private final boolean usingNumbers = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard.use-numbers", false);
private final boolean rememberToggleChoice = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard.remember-toggle-choice", false);
private final boolean hiddenByDefault = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard.hidden-by-default", false);
@@ -36,12 +34,9 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
@Getter private final Map<String, Scoreboard> registeredScoreboards = new LinkedHashMap<>();
private Scoreboard[] definedScoreboards;
//toggle messages
private final String scoreboardOn = TAB.getInstance().getConfiguration().getMessages().getScoreboardOn();
private final String scoreboardOff = TAB.getInstance().getConfiguration().getMessages().getScoreboardOff();
//list of players with disabled scoreboard
private final List<String> sbOffPlayers;
private final List<String> sbOffPlayers = rememberToggleChoice ? TAB.getInstance().getConfiguration().getPlayerDataFile()
.getStringList("scoreboard-off", new ArrayList<>()) : Collections.emptyList();
//active scoreboard announcement
private Scoreboard announcement;
@@ -51,18 +46,19 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
@Getter private final WeakHashMap<TabPlayer, ScoreboardImpl> activeScoreboards = new WeakHashMap<>();
private final Set<TabPlayer> visiblePlayers = Collections.newSetFromMap(new WeakHashMap<>());
@Getter private final WeakHashMap<TabPlayer, String> otherPluginScoreboards = new WeakHashMap<>();
@Getter private final String featureName = "Scoreboard";
@Getter private final String refreshDisplayName = "Switching scoreboards";
/**
* Constructs new instance and loads configuration
*/
@SuppressWarnings("unchecked")
public ScoreboardManagerImpl() {
super("Scoreboard", "Switching displayed scoreboard", "scoreboard");
if (rememberToggleChoice) {
sbOffPlayers = Collections.synchronizedList(new ArrayList<>(TAB.getInstance().getConfiguration().getPlayerDataFile().getStringList("scoreboard-off", new ArrayList<>())));
} else {
sbOffPlayers = Collections.emptyList();
}
super("scoreboard");
}
@Override
@SuppressWarnings("unchecked")
public void load() {
Map<String, Map<String, Object>> map = TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("scoreboard.scoreboards");
for (Entry<String, Map<String, Object>> entry : map.entrySet()) {
String condition = (String) entry.getValue().get("display-condition");
@@ -81,18 +77,6 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.scoreboardLine(entry.getKey()), sb);
}
definedScoreboards = registeredScoreboards.values().toArray(new Scoreboard[0]);
if (respectOtherPlugins) {
PipelineInjector inj = (PipelineInjector) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PIPELINE_INJECTION);
//null check if injection was disabled in config or velocity
if (inj != null) inj.setByteBufDeserialization(true);
}
TAB.getInstance().debug(String.format("Loaded Scoreboard feature with parameters toggleCommand=%s, useNumbers=%s, disabledWorlds=%s"
+ ", disabledServers=%s, rememberToggleChoice=%s, hiddenByDefault=%s, scoreboard_on=%s, scoreboard_off=%s, staticNumber=%s, joinDelay=%s",
toggleCommand, usingNumbers, Arrays.toString(disabledWorlds), Arrays.toString(disabledServers), rememberToggleChoice, hiddenByDefault, scoreboardOn, scoreboardOff, staticNumber, joinDelay));
}
@Override
public void load() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
onJoin(p);
}
@@ -286,7 +270,7 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
visiblePlayers.add(player);
sendHighestScoreboard(player);
if (sendToggleMessage) {
player.sendMessage(scoreboardOn, true);
player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getScoreboardOn(), true);
}
if (rememberToggleChoice) {
if (hiddenByDefault) {
@@ -302,7 +286,7 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
visiblePlayers.remove(player);
unregisterScoreboard(player);
if (sendToggleMessage) {
player.sendMessage(scoreboardOff, true);
player.sendMessage(TAB.getInstance().getConfiguration().getMessages().getScoreboardOff(), true);
}
if (rememberToggleChoice) {
if (hiddenByDefault) {
@@ -315,8 +299,7 @@ public class ScoreboardManagerImpl extends TabFeature implements ScoreboardManag
}
}
}
TabExpansion expansion = TAB.getInstance().getPlaceholderManager().getTabExpansion();
if (expansion != null) expansion.setScoreboardVisible(player, visible);
TAB.getInstance().getPlaceholderManager().getTabExpansion().setScoreboardVisible(player, visible);
}
@Override
@@ -19,6 +19,9 @@ import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
*/
public abstract class ScoreboardLine extends TabFeature implements Line {
@Getter private final String featureName = "Scoreboard";
@Getter private final String refreshDisplayName = "Updating Scoreboard lines";
//ID of this line
protected final int lineNumber;
@@ -43,7 +46,6 @@ public abstract class ScoreboardLine extends TabFeature implements Line {
* ID of this line
*/
protected ScoreboardLine(ScoreboardImpl parent, int lineNumber) {
super(parent.getFeatureName(), "Updating scoreboard lines");
this.parent = parent;
this.lineNumber = lineNumber;
teamName = "TAB-SB-TM-" + lineNumber;
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff. Show more