Compare commits

..
102 Commits
Author SHA1 Message Date
NEZNAMY 4510491177 3.2.0 2022-12-12 09:46:21 +01:00
NEZNAMY 01b3a12377 1.19.3 support 2022-12-12 09:46:12 +01:00
NEZNAMY 64ae9863fb Bukkit - Drop old event API system 2022-12-10 12:27:51 +01:00
NEZNAMY ecd315a407 Sorting - Fix error from previous commit (.toLowerCase made string longer) 2022-12-08 11:20:58 +01:00
NEZNAMY 678eed288e Conditions - Add OR type for anonymous conditions using "|" symbol 2022-12-06 10:32:41 +01:00
NEZNAMY 9510455cb1 Conditions - Fix anonymous conditions not updating from previous commit 2022-12-06 10:31:29 +01:00
NEZNAMY e925ce23d0 Conditions - Fix nested conditions not working if they are defined below the condition using them 2022-12-06 09:51:16 +01:00
NEZNAMY c68cad6a1e Global PlayerList - Fix feature breaking nick/skin changes (minecraft mechanic changed in 1.19) (#752) 2022-12-06 09:50:00 +01:00
NEZNAMY 01d6eda4fb Configuration - Use luckperms placeholders instead of vault in default configuration (and make them return empty value if LP is not found) 2022-12-04 14:19:21 +01:00
NEZNAMY 73e7119bbc Configuration - add hidden server-name option for %server% value (useful with MySQL) 2022-12-04 11:49:59 +01:00
NEZNAMY cea781382a Sorting - Fix wrong team name being unregistered (#727) 2022-12-03 21:18:38 +01:00
NEZNAMY cf25b84e86 Commands - Fix debug command showing sorting is disabled even if layout is enabled for sorting 2022-12-03 21:16:29 +01:00
NEZNAMY 86e1d22390 Sorting - Only use 1 character for sorting by groups, permissions and predefined values; Improve debug output of sorting results 2022-12-03 12:16:38 +01:00
NEZNAMY 1625be111f Global PlayerList - Make server names in server groups and spy-servers not case-sensitive 2022-12-02 23:18:40 +01:00
NEZNAMY 00cda3b2aa Placeholders - Load all used placeholders on join in placeholder manager to make cpu usage appear under that "feature" instead of a feature actually using the placeholders 2022-12-01 22:46:40 +01:00
NEZNAMY 85a00a4436 Code style - Extract minimum placeholder refresh interval into an editable constant 2022-12-01 22:36:33 +01:00
NEZNAMY 37aec1a497 Global PlayerList - Add %playerlist-group_<group>% placeholder for online count in each group 2022-12-01 22:32:43 +01:00
NEZNAMY ef637616ee Placeholders - Improve error message 2022-11-27 17:13:24 +01:00
NEZNAMY af3b5067fb BossBar - Ignore players in disabled worlds/servers in announce command (#748) 2022-11-25 11:23:48 +01:00
NEZNAMY 53e5b15f96 Tab Expansion - Fix error in nested placeholder parsing 2022-11-24 11:01:55 +01:00
NEZNAMY 6a3afae607 Implement empty method 2022-11-16 12:24:03 +01:00
NEZNAMY 2e05c7d9d1 Another attempt for #727 2022-11-15 19:15:49 +01:00
NEZNAMY fb698a04b7 Configuration - Merge default config.yml files into one 2022-11-12 19:11:36 +01:00
NEZNAMY 52bdf30ddb Merge remote-tracking branch 'origin/master' 2022-11-09 22:34:22 +01:00
NEZNAMY a92f07aa40 Tab Expansion - Resend values to bridge on server switch (#740) 2022-11-09 22:34:09 +01:00
NEZNAMY 003ff7f99d Merge pull request #739 from CoachYT1/patch-1
bump org.yaml:snakeyaml to 1.33
2022-11-09 21:59:08 +01:00
NEZNAMY 47b9806ae7 Tab Expansion - Parse nested placeholders in replace function 2022-11-07 22:26:17 +01:00
CoachYT d2a7c65e08 bump org.yaml:snakeyaml to 1.33
snakeyaml 1.30 is affected by 5 vulnerabilities and should be upgraded to the latest version
2022-11-07 17:53:24 +01:00
NEZNAMY e232336a5f Add check for modifying non-existing teams (#727) 2022-11-06 22:42:23 +01:00
NEZNAMY 8816c75212 BungeeCord - Fix crash on 1.7 caused by unregistering non-existing objectives (login packet cleared them) (#727) 2022-11-04 13:31:34 +01:00
NEZNAMY f9d54751ba Fix rare ConcurrentModificationException 2022-11-04 12:53:00 +01:00
NEZNAMY 43d41ee761 BungeeCord - Improve unsupported BungeeCord build message 2022-11-01 17:55:43 +01:00
NEZNAMY 4512ba8d5e NameTags - Print a console warn if both unlimited nametags and invisible nametags are enabled 2022-10-30 17:56:00 +01:00
NEZNAMY 8b55bca564 Remove redundant variable 2022-10-30 13:40:32 +01:00
NEZNAMY d8d54970d3 Code style - Clean up vanish check code 2022-10-30 13:29:45 +01:00
NEZNAMY 15ea4482f9 Bukkit - Drop direct Essentials hook for vanishing as it supports metadata 2022-10-30 13:27:24 +01:00
NEZNAMY 87f9f38eac Update issue templates 2022-10-29 18:35:11 +02:00
NEZNAMY dba559f041 Layout - Fix startup error caused by invalid number as slot 2022-10-29 12:17:04 +02:00
NEZNAMY c610b6f323 PlayerList - Fix name applying on server switch when pipeline injection is disabled 2022-10-26 17:36:08 +02:00
NEZNAMY a5fb900e19 API - Add back TabPlayer#getTeamName() 2022-10-24 14:05:39 +02:00
NEZNAMY 09d6e455f5 Layout - Implement a fix for compatibility with plugins which add empty player name into their 2022-10-21 19:55:59 +02:00
NEZNAMY e68097b0f4 Layout - Fix startup error if slots is a single number instead of interval 2022-10-16 15:32:26 +02:00
NEZNAMY 0744fed953 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-10-16 14:40:42 +02:00
NEZNAMY de0adf4067 MySQL - Attempt to fix startup error (#717) 2022-10-16 13:12:54 +02:00
NEZNAMY 76bf2b3efc Update links in README 2022-10-16 13:11:56 +02:00
NEZNAMY c089badeb4 Configuration - Remove owner and admin settings from default configuration 2022-10-15 19:15:38 +02:00
NEZNAMY b3b99d366a Placeholders - Fix relational placeholders updating causing normal placeholders to not refresh 2022-10-14 19:28:05 +02:00
NEZNAMY 4a36486f65 Bukkit - Fix startup on 1.8 2022-10-08 12:41:05 +02:00
NEZNAMY 75126d00d9 BossBar - Fix countdown placeholder not activating on usage in PlaceholderAPI 2022-10-06 16:43:06 +02:00
NEZNAMY 17ebf95d5b BossBar - Fix announcement bars disappearing on refresh 2022-10-06 16:42:32 +02:00
NEZNAMY c764420cfe Code Style - Simplify field value assignment 2022-10-06 15:33:50 +02:00
Callum Seabrook 28f05fc2af Changed publishing artifact IDs to begin with "tab-" 2022-10-06 12:02:28 +01:00
Callum Seabrook a420892887 Fixed publishing ID 2022-10-06 11:54:06 +01:00
NEZNAMY d910dc5913 Header / Footer - Prioritize world over server in config.yml as well 2022-10-06 11:11:00 +02:00
NEZNAMY c9c1cbc349 Layout - Fix anti-override for playerlist names being disabled even when no layout is visible 2022-09-26 11:02:14 +02:00
NEZNAMY fd4704f75e 3.1.5 2022-09-20 15:46:28 +02:00
NEZNAMY 5400ca637f Global PlayerList - Do not fill display name for 1.7 players 2022-09-19 18:23:16 +02:00
NEZNAMY 79d2610117 Global PlayerList - Fix server switch when pipeline injection is disabled 2022-09-17 18:38:06 +02:00
NEZNAMY 6a51f4cb9d BungeeCord - Fix 1.7 players not being processed fully on server switch (reapplying scoreboards) 2022-09-13 18:33:52 +02:00
NEZNAMY c190776665 Scoreboard - Fix show command enabling scoreboard for players who disabled it using toggle command 2022-09-13 18:32:54 +02:00
NEZNAMY 3765a1232e Merge pull request #697 from JT122406/master
Updates and Velocity Deprecation Fix
2022-08-27 19:48:00 +02:00
J.T. McQuigg 28866eba21 More Updates and Build Fix 2022-08-27 13:07:28 -04:00
J.T. McQuigg 08adee3dc3 Recomment this out for PR 2022-08-27 11:57:34 -04:00
J.T. McQuigg 8a1adaf2cd More Updates 2022-08-27 11:57:08 -04:00
J.T. McQuigg fdb9accaa6 Updates and don't use Deprecation in Velocity module 2022-08-27 11:42:15 -04:00
NEZNAMY 4f7656748b Merge pull request #695 from Tanguygab/master
added StartWith `|-` and EndsWith `-|` condition types
2022-08-25 22:25:48 +02:00
Tanguygab 03e4495ac7 added |- and -| conditions 2022-08-25 15:15:21 +02:00
NEZNAMY 01af5cb9e1 Placeholders - Fix nested placeholders not being applied when updating from nested?!! (#694) 2022-08-24 16:52:01 +02:00
NEZNAMY 8d49f32df2 Code style - Change the way API instance is set 2022-08-22 20:30:16 +02:00
NEZNAMY 8a955b3bc8 Javadoc - Further improve documentation 2022-08-22 20:27:20 +02:00
NEZNAMY ead40cb413 Merge pull request #691 from Tanguygab/master
Commands - Added target and -s arguments for /tab nametag preview/toggle (#690)
2022-08-22 16:22:32 +02:00
Tanguygab 5e07f78d4f updated messages.yml 2022-08-22 16:20:43 +02:00
Tanguygab c7d98795bc Revert "* for all players in /tab scoreboard show"
This reverts commit e4f33b58f7.
2022-08-22 16:13:46 +02:00
Tanguygab 9f637e3d6a added target and -s arguments for /tab nametag preview/toggle 2022-08-22 16:09:23 +02:00
Tanguygab 2d5f074c78 Merge remote-tracking branch 'upstream/master' 2022-08-22 15:35:23 +02:00
NEZNAMY 3be6ba484d API - Fix show_item hover event on 1.16+ 2022-08-18 22:36:43 +02:00
NEZNAMY d9d74e7a32 Placeholders - Fix NPE in %essentialsnick% event listener due to Essentials bug (#684) 2022-08-18 12:46:54 +02:00
NEZNAMY 887306a5ed Bukkit - Include 1.19.2 in supported version range 2022-08-16 01:25:15 +02:00
NEZNAMY 4b2542eb4f Global Playerlist - Add config option to disable (and enable) profile key filling (#680) 2022-08-15 13:28:56 +02:00
NEZNAMY 66bd3baa3e Layout - Add config option to show vanished players in layout 2022-08-14 15:41:19 +02:00
NEZNAMY bf32004f8f Layout - Do not format original entries in any way (can cause spaces) 2022-08-13 17:50:28 +02:00
NEZNAMY bfb2df9c95 Commands - Show storage type in debug output 2022-08-13 17:44:46 +02:00
Callum Seabrook ddeb05e408 Merge pull request #662 from JT122406/Updates
Update Gradle Wrapper to 7.5
2022-08-12 16:58:38 +01:00
Callum Seabrook 9312d61258 Fix some more BS 2022-08-12 16:49:30 +01:00
Callum Seabrook d44909bc42 Fixed some BS 2022-08-12 16:41:26 +01:00
Callum Seabrook 1d77df335c Switched to Indra for publishing and improved code reuse in build scripts 2022-08-12 16:29:12 +01:00
NEZNAMY a268dc25f4 BungeeCord - Use direct code for proxy module again 2022-08-12 15:46:14 +02:00
NEZNAMY 8c5696faa0 Bukkit - Register universal placeholders after bukkit ones to fix reference issue with nested placeholders 2022-08-12 15:05:40 +02:00
NEZNAMY 94f3ccfb7d Javadoc - Further improve documentation 2022-08-12 15:04:49 +02:00
NEZNAMY 576329214d Add 1.19.2 to known versions 2022-08-11 17:57:49 +02:00
NEZNAMY 5c13c2e225 Velocity - Remove join processing delay since the required delay is already included elsewhere 2022-08-11 17:49:42 +02:00
NEZNAMY 1070f85617 Placeholders - Retry updateValue if player is not loaded yet (might fix #564) 2022-08-11 17:48:26 +02:00
NEZNAMY 7c9dc87fcb NameTags - Make canSeeFriendlyInvisibles flag configurable 2022-08-09 14:12:14 +02:00
NEZNAMY 363e58bf91 API - Expose another method 2022-08-04 17:06:30 +02:00
NEZNAMY daca3f5b71 3.1.4 2022-08-04 15:43:53 +02:00
NEZNAMY a7d2ec084b Use profile key in player info packet (#673) 2022-08-03 12:57:48 +02:00
NEZNAMY 167f8cd0ff BungeeCord - Remove Waterfall tablist rewrite hook since the option doesn't work as I expected 2022-08-03 11:14:10 +02:00
NEZNAMY 170307f45f Catch everything 2022-07-29 15:49:59 +02:00
NEZNAMY 2f36377780 Revert "Placeholder output replacements - Apply again after applying nested placeholders"
This reverts commit 2706cd5881.
2022-07-29 12:48:13 +02:00
J.T. McQuigg dc09e77190 Update Gradle Wrapper to 7.5 2022-07-26 17:16:56 -04:00
Tanguygab d5b0ff7f40 Merge remote-tracking branch 'upstream/master' 2022-07-24 13:11:11 +02:00
Tanguygab e4f33b58f7 * for all players in /tab scoreboard show 2022-07-04 13:34:04 +02:00
140 changed files with 2297 additions and 1559 deletions

No files matched your search

+2
View File
@@ -57,5 +57,7 @@ body:
required: true
- label: I have read the wiki to make sure it's not an issue with configuration
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
+2
View File
@@ -51,5 +51,7 @@ body:
required: true
- label: I have included a paste of the error
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
+4 -4
View File
@@ -17,10 +17,10 @@ Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
# Links
The precompiled JAR can be downloaded for free from:
* GitHub releases: https://github.com/NEZNAMY/TAB/releases
* MC-Market: https://www.mc-market.org/resources/20631/
* BuiltByBit: https://builtbybit.com/resources/20631/
Supporting my work:
* MC-Market: https://www.mc-market.org/resources/14009/
Support my work & receive official assistance with configuration:
* BuiltByBit: https://builtbybit.com/resources/14009/
* Polymart: https://polymart.org/resource/484
Other links:
@@ -44,4 +44,4 @@ is in quality of the report (typically providing correct steps to reproduce that
for problems of any kind including bugs. This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
* [Buy a copy](https://www.mc-market.org/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
@@ -1,5 +1,9 @@
package me.neznamy.tab.api;
/**
* Interface representing an armor stand attached to player
* for displaying text in game.
*/
public interface ArmorStand {
/**
@@ -92,6 +96,13 @@ 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);
}
@@ -10,5 +10,11 @@ public interface ArmorStandManager {
*/
void destroy();
/**
* Refreshes text of all armor stands for all nearby players
*
* @param force
* If refresh should be force despite no update seemingly being needed
*/
void refresh(boolean force);
}
@@ -91,7 +91,26 @@ public interface FeatureManager {
*/
boolean onCommand(TabPlayer sender, String command);
/**
* Internal function that marks objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markObjective();
/**
* Internal function that marks display objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markDisplayObjective();
/**
* Returns array of all currently enabled features
*
* @return array of all currently enabled features
*/
TabFeature[] getValues();
}
@@ -1,16 +1,82 @@
package me.neznamy.tab.api;
/**
* Interface allowing to send header/footer to players.
* <p>
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
* This requires the Header/footer feature to be enabled in config, otherwise
* the method will return {@code null}.
*/
public interface HeaderFooterManager {
/**
* Sets raw value of header to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
*
* @param player
* Player to set header for
* @param header
* Raw header value
*/
void setHeader(TabPlayer player, String header);
/**
* Sets raw value of footer to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
*
* @param player
* Player to set header for
* @param footer
* Raw footer value
*/
void setFooter(TabPlayer player, String footer);
/**
* Sets raw value of header and footer to specified values. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
*
* @param player
* Player to set header for
* @param header
* Raw header value
* @param footer
* Raw footer value
*/
void setHeaderAndFooter(TabPlayer player, String header, String footer);
/**
* Resets header value previously set using {@link #setHeader(TabPlayer, String)}
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
* logic using configuration files.
*
* @param player
* Player to reset header for
*/
void resetHeader(TabPlayer player);
/**
* Resets footer value previously set using {@link #setFooter(TabPlayer, String)}
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
* logic using configuration files.
*
* @param player
* Player to reset header for
*/
void resetFooter(TabPlayer player);
/**
* Resets values previously set using {@link #setHeader(TabPlayer, String)},
* {@link #setFooter(TabPlayer, String)} or {@link #setHeaderAndFooter(TabPlayer, String, String)}
* and restores internal logic using configuration files.
*
* @param player
* Player to reset header and footer for
*/
void resetHeaderAndFooter(TabPlayer player);
}
@@ -8,6 +8,8 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_19_3 (761),
V1_19_2 (760),
V1_19_1 (760),
V1_19 (759),
V1_18_2 (758),
@@ -18,6 +18,13 @@ public abstract class TabAPI {
/** Instance of the API */
private static TabAPI instance;
/**
* Constructs new instance and sets it as new API instance
*/
public TabAPI() {
instance = this;
}
/**
* Returns API instance. If instance was not set by the plugin, throws
* {@code IllegalStateException}. This is usually caused by shading the API
@@ -34,16 +41,6 @@ public abstract class TabAPI {
return instance;
}
/**
* Instance setter for internal use by the plugin only.
*
* @param instance
* API instance
*/
public static void setInstance(TabAPI instance) {
TabAPI.instance = instance;
}
/**
* Returns player object from given UUID
*
@@ -83,6 +83,8 @@ public class TabConstants {
public static final String NAMETAGS_TEAM_UNREGISTER = "NameTags (Team unregister)";
public static final String NAMETAGS_TEAM_UPDATE = "NameTags (Team update)";
public static final String SORTING_CHANGING_TEAM_NAME = "Sorting (Changing team name)";
public static final String SCOREBOARD_TITLE = "Scoreboard (Title)";
public static final String SCOREBOARD_LINES = "Scoreboard (Lines)";
@@ -116,7 +118,11 @@ public class TabConstants {
public static final String COMMAND_MYSQL_DOWNLOAD = "tab.mysql.download";
public static final String COMMAND_MYSQL_UPLOAD = "tab.mysql.upload";
public static final String COMMAND_NAMETAG_TOGGLE = "tab.nametag.toggle";
public static final String COMMAND_NAMETAG_TOGGLE_OTHER = "tab.nametag.toggle.other";
public static final String COMMAND_NAMETAG_PREVIEW = "tab.nametag.preview";
public static final String COMMAND_NAMETAG_PREVIEW_OTHER = "tab.nametag.preview.other";
public static final String COMMAND_PARSE = "tab.parse";
public static final String COMMAND_SCOREBOARD_ANNOUNCE = "tab.announce.scoreboard";
public static final String COMMAND_SCOREBOARD_TOGGLE = "tab.scoreboard.toggle";
@@ -329,6 +335,9 @@ public class TabConstants {
public static final String VAULT_SUFFIX = "%vault-suffix%";
public static final String HEALTH = "%health%";
public static final int MINIMUM_REFRESH_INTERVAL = 50;
public static String condition(String name) {
return "%condition:" + name + "%";
}
@@ -336,6 +345,10 @@ public class TabConstants {
public static String animation(String name) {
return "%animation:" + name + "%";
}
public static String globalPlayerListGroup(String group) {
return "%playerlist-group_" + group + "%";
}
}
/**
@@ -212,13 +212,6 @@ public interface TabPlayer {
*/
String getTeamName();
/**
* Returns user-friendly explanation of team name
*
* @return explanation behind team name
*/
String getTeamNameNote();
/**
* Returns {@code true} if player is disguised using LibsDisguises, {@code false} if not
*
@@ -310,4 +303,18 @@ public interface TabPlayer {
* @return player's game profile name as seen by other players
*/
String getNickname();
/**
* Returns player's chat message signing key. Returned object is a direct
* object from the server, which is different per platform.
*
* @return Player's direct chat message signing key
*/
Object getProfilePublicKey();
/**
* Return player's chat session ID
* @return player's chat session ID
*/
UUID getChatSessionId();
}
@@ -2,21 +2,23 @@ package me.neznamy.tab.api.chat;
import java.util.UUID;
import com.google.gson.JsonObject;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
public class ChatComponentEntity extends IChatBaseComponent {
private final String type;
private final UUID id;
private final String name;
private final String CONTENTS;
private final String VALUE_1_13;
private final String VALUE_1_12;
private final String VALUE_1_8;
public ChatComponentEntity(String type, UUID id, String name) {
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(id, "id");
this.type = type;
this.id = id;
this.name = name;
this.CONTENTS = String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
this.VALUE_1_13 = String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name);
this.VALUE_1_12 = String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name);
this.VALUE_1_8 = String.format("{type:%s,id:%s,name:%s}", type, id, name);
}
@Override
@@ -26,16 +28,10 @@ public class ChatComponentEntity extends IChatBaseComponent {
@Override
public String toString() {
return String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
}
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("type", type);
json.addProperty("id", id.toString());
JsonObject json2 = new JsonObject();
json2.addProperty("text", name);
json.add("name", json2);
return json;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) return CONTENTS;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) return VALUE_1_13;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) return VALUE_1_12;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) return VALUE_1_8;
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
}
}
@@ -53,8 +53,18 @@ public class ChatHoverable {
*/
public enum EnumHoverAction {
SHOW_TEXT,
SHOW_ITEM,
SHOW_ENTITY
SHOW_TEXT("contents"),
SHOW_ITEM("value"),
SHOW_ENTITY("contents");
private final String preferredKey;
EnumHoverAction(String preferredKey) {
this.preferredKey = preferredKey;
}
public String getPreferredKey() {
return preferredKey;
}
}
}
@@ -249,17 +249,7 @@ public class ChatModifier {
public void onHoverShowEntity(String type, UUID id, String name) {
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(id, "id");
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:%s,id:%s,name:%s}", type, id, name)));
} else {
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
}
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
}
public void onHover(EnumHoverAction action, IChatBaseComponent value) {
@@ -295,7 +285,7 @@ public class ChatModifier {
JSONObject hover = new JSONObject();
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hover.put("contents", hoverEvent.getValue());
hover.put(hoverEvent.getAction().getPreferredKey(), hoverEvent.getValue());
} else {
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
@@ -4,7 +4,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for #<RRGGBB>
* Formatter for #&lt;RRGGBB&gt;
*/
public class HtmlFormat implements RGBFormatter {
@@ -4,7 +4,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for &lt;#RRGGBB>
* Formatter for &lt;#RRGGBB&gt;
*/
public class KyoriFormat implements RGBFormatter {
@@ -5,7 +5,7 @@ import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
/**
* Formatter for &lt;#RRGGBB>
* Formatter for &lt;#RRGGBB&gt;
*/
public class MiniMessageFormat implements RGBFormatter {
@@ -15,7 +15,7 @@ public class MiniMessageFormat implements RGBFormatter {
String format = text.replace(EnumChatFormat.RESET.getFormat(), ""); //remove &r from header/footer newline inserts
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(format));
} catch (Exception ignored) {
} catch (Throwable ignored) {
return text;
}
}
@@ -7,8 +7,8 @@ import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
/**
* Gradient applier for &lt;gradient:#RRGGBB:#RRGGBB>Text&lt;/gradient>
* and &lt;gradient:#RRGGBB|L:#RRGGBB>Text&lt;/gradient>
* Gradient applier for &lt;gradient:#RRGGBB:#RRGGBB&gt;Text&lt;/gradient&gt;
* and &lt;gradient:#RRGGBB|L:#RRGGBB&gt;Text&lt;/gradient&gt;
*/
public class KyoriGradient implements GradientPattern {
@@ -202,7 +202,7 @@ public class PacketBuilder {
}
/**
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is cut to {@code length} characters if
@@ -1,6 +1,7 @@
package me.neznamy.tab.api.protocol;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
@@ -12,8 +13,8 @@ import me.neznamy.tab.api.util.Preconditions;
*/
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet action */
private final EnumPlayerInfoAction action;
/** Packet actions */
private final EnumSet<EnumPlayerInfoAction> actions;
/** List of affected entries */
private final List<PlayerInfoData> entries;
@@ -27,10 +28,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(entries, "entries");
this.action = action;
this.entries = Arrays.asList(entries);
this(action, Arrays.asList(entries));
}
/**
@@ -44,22 +42,61 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(entries, "entries");
this.action = action;
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.entries = entries;
}
/**
* Constructs new instance with given parameters.
*
* @param actions
* Packet actions
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumSet<EnumPlayerInfoAction> actions, PlayerInfoData... entries) {
Preconditions.checkNotNull(actions, "action");
Preconditions.checkNotNull(entries, "entries");
this.actions = actions;
this.entries = Arrays.asList(entries);
}
/**
* Constructs new instance with given parameters.
*
* @param actions
* Packet actions
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumSet<EnumPlayerInfoAction> actions, List<PlayerInfoData> entries) {
Preconditions.checkNotNull(actions, "action");
Preconditions.checkNotNull(entries, "entries");
this.actions = actions;
this.entries = entries;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerInfo{action=%s,entries=%s}", action, entries);
return String.format("PacketPlayOutPlayerInfo{actions=%s,entries=%s}", actions, entries);
}
/**
* Returns {@link #action}
* Returns {@link #actions}
*
* @return packet action
*/
public EnumPlayerInfoAction getAction() {
return action;
public EnumSet<EnumPlayerInfoAction> getActions() {
return actions;
}
/**
@@ -79,6 +116,9 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
/** Latency */
private int latency;
/** I have no idea what this is */
private boolean listed;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
@@ -97,9 +137,15 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
/** Player's skin, null for empty skin */
private Skin skin;
/** Chat session ID */
private UUID chatSessionId;
/** Player's chat signing key */
private Object profilePublicKey;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.ADD_PLAYER action
* {@link EnumPlayerInfoAction#ADD_PLAYER} action
*
* @param name
* Player's name
@@ -113,20 +159,25 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* Player's GameMode
* @param displayName
* Player's display name
* @param profilePublicKey
* Player's chat signing key
*/
public PlayerInfoData(String name, UUID uniqueId, Skin skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName) {
public PlayerInfoData(String name, UUID uniqueId, Skin skin, boolean listed, int latency, EnumGamemode gameMode, IChatBaseComponent displayName, UUID chatSessionId, Object profilePublicKey) {
Preconditions.checkNotNull(uniqueId, "uuid");
this.name = name;
this.uniqueId = uniqueId;
this.skin = skin;
this.listed = listed;
this.latency = latency;
this.gameMode = gameMode;
this.displayName = displayName;
this.chatSessionId = chatSessionId;
this.profilePublicKey = profilePublicKey;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_GAME_MODE action
* {@link EnumPlayerInfoAction#UPDATE_GAME_MODE} action
*
* @param uniqueId
* Player's uuid
@@ -141,7 +192,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_LATENCY action
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
*
* @param uniqueId
* Player's uuid
@@ -156,7 +207,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_DISPLAY_NAME action
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
*
* @param uniqueId
* Player's uuid
@@ -171,7 +222,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction}.REMOVE_PLAYER action
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
*
* @param uniqueId
* Player's uuid
@@ -182,8 +233,8 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
@Override
public String toString() {
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s}",
latency, gameMode, displayName, name, uniqueId, skin);
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s,profilePublicKey=%s}",
latency, gameMode, displayName, name, uniqueId, skin, profilePublicKey);
}
/**
@@ -205,6 +256,25 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
this.latency = latency;
}
/**
* Returns {@link #listed}
*
* @return listed
*/
public boolean isListed() {
return listed;
}
/**
* Sets {@link #listed} to specified value
*
* @param listed
* Listed flag
*/
public void setListed(boolean listed) {
this.listed = listed;
}
/**
* Returns {@link #displayName}
*
@@ -299,16 +369,55 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
public void setSkin(Skin skin) {
this.skin = skin;
}
/**
* Returns {@link #chatSessionId}
*
* @return chatSessionId
*/
public UUID getChatSessionId() {
return chatSessionId;
}
/**
* Sets {@link #chatSessionId} to specified value
*
* @param chatSessionId
* chatSessionId to use
*/
public void setChatSessionId(UUID chatSessionId) {
this.chatSessionId = chatSessionId;
}
/**
* Returns {@link #profilePublicKey}
*
* @return profilePublicKey
*/
public Object getProfilePublicKey() {
return profilePublicKey;
}
/**
* Sets {@link #profilePublicKey} to specified value
*
* @param profilePublicKey
* profilePublicKey to use
*/
public void setProfilePublicKey(Object profilePublicKey) {
this.profilePublicKey = profilePublicKey;
}
}
/**
* En enum representing packet action
* Calling ordinal() will return action's network ID.
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
@@ -20,10 +20,10 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
*/
private final String name;
/** Team prefix, limited to 16 characters, on <1.13 */
/** Team prefix, limited to 16 characters, on &lt;1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on <1.13 */
/** Team suffix, limited to 16 characters, on &lt;1.13 */
private String playerSuffix;
/**
@@ -46,7 +46,7 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Affected entities. For players, it is their name, for other entities their UUID
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
* Player name length is limited to 16 characters for &lt;1.8, 40 characters since 1.8.
* Entity UUID option was added in 1.8.
*/
private Collection<String> players = Collections.emptyList();
@@ -74,7 +74,7 @@ public interface Scoreboard {
* @param index
* Index of line to remove, starting at 0
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index >= {@link #getLines()}.size())
* if the index is out of range (index &lt; 0 || index &gt;= {@link #getLines()}.size())
* @see #getLines()
* @see #addLine(String)
*/
@@ -1,10 +0,0 @@
package me.neznamy.tab.api.task;
public interface RepeatingTask {
int getInterval();
void setInterval(int paramInt);
void cancel();
}
@@ -4,6 +4,10 @@ import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
/**
* Manager for scheduling and executing tasks in TAB's
* main thread to avoid concurrency issues.
*/
public interface ThreadManager {
/**
@@ -2,7 +2,7 @@ package me.neznamy.tab.api.util;
/**
* An interface for BiFunction which can throw an exception.
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* call build method for packet classes, which may throw an
* exception due to reflection.
* @param <A>
@@ -14,5 +14,16 @@ package me.neznamy.tab.api.util;
*/
public interface BiFunctionWithException<A, B, C> {
/**
* Runs the function and returns the result
*
* @param a
* First argument
* @param b
* Second argument
* @return Output from function
* @throws ReflectiveOperationException
* If reflective operation fails
*/
C apply(A a, B b) throws ReflectiveOperationException;
}
@@ -2,24 +2,76 @@ package me.neznamy.tab.api.util;
import me.neznamy.tab.api.TabPlayer;
/**
* Class with utility methods used in the plugin
*/
public class Preconditions {
private Preconditions(){}
/**
* Private constructor preventing the class from being instantiated
*/
private Preconditions() {}
public static void checkNotNull(Object obj, String name){
/**
* Checks object and throws {@code IllegalArgumentException} if it is {@code null}.
*
* @param obj
* Object to check
* @param name
* Name of the variable to use in exception message
* @throws IllegalArgumentException
* if {@code obj} is {@code null}
*/
public static void checkNotNull(Object obj, String name) {
if (obj == null) throw new IllegalArgumentException(name + " cannot be null");
}
/**
* Checks length of String and throws {@code IllegalArgumentException} if it is
* longer than limit.
*
* @param string
* String to check
* @param maxLength
* Maximum length of the string
* @param name
* Name of the variable to use in exception message
* @throws IllegalArgumentException
* if {@code string} is {@code null} or longer than {@code maxLength} characters
*/
public static void checkMaxLength(String string, int maxLength, String name) {
checkNotNull(string, name);
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
}
public static void checkRange(Number number, Number min, Number max, String variable){
/**
* Checks number range and throws {@code IllegalArgumentException} if number
* is out of range.
*
* @param number
* Number to check
* @param min
* Minimum allowed value
* @param max
* Maximum allowed value
* @param variable
* Variable name to use in exception message
* @throws IllegalArgumentException
* if {@code number} is out of range ({@code number < min || number > max})
*/
public static void checkRange(Number number, Number min, Number max, String variable) {
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + ")");
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + "), was " + number);
}
/**
* Checks if player is loaded and throws {@code IllegalStateException} if not.
*
* @param player
* Player to check
* @throws IllegalStateException
* If player is not loaded
*/
public static void checkLoaded(TabPlayer player) {
if (!player.isLoaded()) throw new IllegalStateException("Player is not loaded yet. Try again later.");
}
+9 -4
View File
@@ -2,11 +2,16 @@ plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
dependencies {
implementation(libs.plugin.indra)
implementation(libs.plugin.shadow)
}
dependencies {
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.0")
compileOnly(files(libs::class.java.protectionDomain.codeSource.location))
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
+16
View File
@@ -0,0 +1,16 @@
rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
// You're a great IDE IntelliJ, but sometimes you really annoy me. >:(
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
versionCatalogs {
register("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
@@ -1,7 +1,16 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
`java-library`
id("net.kyori.indra")
}
indra {
javaVersions {
target(8)
}
github("NEZNAMY", "TAB") {
ci(true)
}
}
tasks {
@@ -18,15 +27,4 @@ tasks {
options.encoding = Charsets.UTF_8.name()
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(8)
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
@@ -1,31 +1,12 @@
import gradle.kotlin.dsl.accessors._4cbb0020bd75d48acc42cabded5fdbb7.publishing
import org.gradle.api.artifacts.repositories.PasswordCredentials
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.repositories
plugins {
id("tab.base-conventions")
`maven-publish`
id("net.kyori.indra.publishing")
}
java {
withSourcesJar()
withJavadocJar()
}
publishing {
repositories {
maven {
name = "krypton"
url = uri("https://repo.kryptonmc.org/releases")
credentials(PasswordCredentials::class)
}
}
publications.create<MavenPublication>("mavenJava") {
groupId = rootProject.group as String
artifactId = "tab-${project.name}"
version = rootProject.version as String
from(components["java"])
indra {
publishReleasesTo("krypton", "https://repo.kryptonmc.org/releases")
publishSnapshotsTo("krypton", "https://repo.kryptonmc.org/snapshots")
configurePublications {
artifactId = "tab-$artifactId"
}
}
@@ -48,7 +48,8 @@ public class BukkitEventListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
}
/**
@@ -60,7 +61,8 @@ public class BukkitEventListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e){
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
}
/**
@@ -72,6 +74,7 @@ public class BukkitEventListener implements Listener {
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
if (TabAPI.getInstance().getFeatureManager().onCommand(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
if (TabAPI.getInstance().getFeatureManager().onCommand(
TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
}
}
@@ -14,11 +14,13 @@ import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.platforms.bukkit.nms.*;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Collectors;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitPacketBuilder extends PacketBuilder {
@@ -76,9 +78,9 @@ public class BukkitPacketBuilder extends PacketBuilder {
return nms.newPacketPlayOutChat.newInstance(component, packet.getType().ordinal());
}
if (nms.getMinorVersion() >= 16)
return nms.newPacketPlayOutChat.newInstance(component, nms.ChatMessageType_values[packet.getType().ordinal()], UUID.randomUUID());
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf(nms.ChatMessageType, packet.getType().toString()), UUID.randomUUID());
if (nms.getMinorVersion() >= 12)
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf((Class<Enum>) nms.ChatMessageType, packet.getType().toString()));
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf(nms.ChatMessageType, packet.getType().toString()));
if (nms.getMinorVersion() >= 8)
return nms.newPacketPlayOutChat.newInstance(component, (byte) packet.getType().ordinal());
if (nms.getMinorVersion() == 7)
@@ -89,24 +91,56 @@ public class BukkitPacketBuilder extends PacketBuilder {
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(nms.EnumPlayerInfoAction_values[packet.getAction().ordinal()], Array.newInstance(nms.EntityPlayer, 0));
List<Object> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (nms.newPlayerInfoData.getParameterTypes()[0] == nms.PacketPlayOutPlayerInfo) {
parameters.add(nmsPacket);
if (nms.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
if (packet.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
return nms.newClientboundPlayerInfoRemovePacket.newInstance(
packet.getEntries().stream().map(PlayerInfoData::getUniqueId).collect(Collectors.toList())
);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : nms.EnumGamemode_values[nms.EnumGamemode_values.length-EnumGamemode.VALUES.length+data.getGameMode().ordinal()]); //not_set was removed in 1.17
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(null);
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
Enum[] array = packet.getActions().stream().map(action -> Enum.valueOf(nms.EnumPlayerInfoAction, action.toString())).toArray(Enum[]::new);
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(array[0], nms.getHandle.invoke(Bukkit.getOnlinePlayers().iterator().next()));
List<Object> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures",
new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
Object obj = nms.newPlayerInfoData.newInstance(
data.getUniqueId(),
profile,
data.isListed(),
data.getLatency(),
data.getGameMode() == null ? null : Enum.valueOf(nms.EnumGamemode, data.getGameMode().toString()),
data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion),
data.getProfilePublicKey() == null ? null : nms.newRemoteChatSession$Data.newInstance(data.getChatSessionId(), data.getProfilePublicKey()));
items.add(obj);
}
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_ACTION, EnumSet.of(array[0], array));
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
return nmsPacket;
} else {
//1.19.2-
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(nms.EnumPlayerInfoAction, action.toString()), Array.newInstance(nms.EntityPlayer, 0));
List<Object> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (nms.newPlayerInfoData.getParameterTypes()[0] == nms.PacketPlayOutPlayerInfo) {
parameters.add(nmsPacket);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : Enum.valueOf(nms.EnumGamemode, data.getGameMode().toString()));
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(data.getProfilePublicKey());
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
}
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
return nmsPacket;
}
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
return nmsPacket;
}
@Override
@@ -132,7 +166,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
if (nms.getMinorVersion() >= 13) {
return nms.newPacketPlayOutScoreboardObjective.newInstance(nms.newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
packet.getRenderType() == null ? null : nms.EnumScoreboardHealthDisplay_values[packet.getRenderType().ordinal()]),
packet.getRenderType() == null ? null : Enum.valueOf(nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString())),
packet.getAction());
}
@@ -140,7 +174,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME, packet.getObjectiveName());
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_DISPLAYNAME, displayName);
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_RENDERTYPE, Enum.valueOf((Class<Enum>) nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_RENDERTYPE, Enum.valueOf(nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
}
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_METHOD, packet.getAction());
return nmsPacket;
@@ -149,7 +183,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 13) {
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(nms.EnumScoreboardAction_values[packet.getAction().ordinal()], packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(Enum.valueOf(nms.EnumScoreboardAction, packet.getAction().toString()), packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
}
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
return nms.newPacketPlayOutScoreboardScore_String.newInstance(packet.getPlayer());
@@ -189,9 +223,9 @@ public class BukkitPacketBuilder extends PacketBuilder {
case 2:
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, false);
case 3:
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[0]);
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(nms.PacketPlayOutScoreboardTeam_PlayerAction, "ADD"));
case 4:
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[1]);
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(nms.PacketPlayOutScoreboardTeam_PlayerAction, "REMOVE"));
default:
throw new IllegalArgumentException("Invalid action: " + packet.getAction());
}
@@ -227,7 +261,12 @@ public class BukkitPacketBuilder extends PacketBuilder {
* if thrown by reflective operation
*/
public Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), packet.getDataWatcher().toNMS(), true);
if (nms.newPacketPlayOutEntityMetadata.getParameterCount() == 2) {
//1.19.3+
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), nms.DataWatcher_b.invoke(packet.getDataWatcher().toNMS()));
} else {
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), packet.getDataWatcher().toNMS(), true);
}
}
/**
@@ -264,7 +303,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
}
int id = entityIds.get(packet.getEntityType());
if (nms.getMinorVersion() >= 19) {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, nms.Registry_a.invoke(nms.IRegistry_X, id));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, nms.EntityTypes_ARMOR_STAND); // :(
} else {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, id);
}
@@ -322,9 +361,9 @@ public class BukkitPacketBuilder extends PacketBuilder {
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
nms.ScoreboardTeam_setColor.invoke(team, nms.EnumChatFormat_values[format.ordinal()]);
nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
nms.ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, format.toString()));
nms.ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(nms.EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
nms.ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(nms.EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
}
/**
@@ -344,30 +383,71 @@ public class BukkitPacketBuilder extends PacketBuilder {
private void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix) throws ReflectiveOperationException {
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, prefix);
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, suffix);
if (nms.getMinorVersion() >= 8) nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
if (nms.getMinorVersion() >= 9) nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
if (nms.getMinorVersion() >= 8) nms.ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(nms.EnumNameTagVisibility, packet.getNameTagVisibility().equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 9) nms.ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(nms.EnumTeamPush, packet.getCollisionRule().equals("always") ? "ALWAYS" : "NEVER"));
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
List<PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
IChatBaseComponent listName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
Skin skin = null;
if (!profile.getProperties().get("textures").isEmpty()) {
Property pr = profile.getProperties().get("textures").iterator().next();
skin = new Skin(pr.getValue(), pr.getSignature());
if (nms.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
if (nms.ClientboundPlayerInfoRemovePacket.isInstance(nmsPacket)) {
List<UUID> entries = (List<UUID>) nms.ClientboundPlayerInfoRemovePacket_getEntries.invoke(nmsPacket);
return new PacketPlayOutPlayerInfo(
EnumPlayerInfoAction.REMOVE_PLAYER,
entries.stream().map(PlayerInfoData::new).collect(Collectors.toList())
);
}
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), skin,
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName));
EnumSet<?> set = (EnumSet<?>) nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket);
EnumPlayerInfoAction[] array = set.stream().map(action -> EnumPlayerInfoAction.valueOf(action.toString())).toArray(EnumPlayerInfoAction[]::new);
EnumSet<EnumPlayerInfoAction> actions = EnumSet.of(array[0], array);
List<PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
Skin skin = null;
if (!profile.getProperties().get("textures").isEmpty()) {
Property pr = profile.getProperties().get("textures").iterator().next();
skin = new Skin(pr.getValue(), pr.getSignature());
}
Object remoteChatSession = nms.PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData);
listData.add(
new PlayerInfoData(
profile.getName(),
profile.getId(),
skin,
(boolean) nms.PlayerInfoData_isListed.invoke(nmsData),
(int) nms.PlayerInfoData_getLatency.invoke(nmsData),
gameMode,
nmsComponent == null ? null : new WrappedChatComponent(nmsComponent),
remoteChatSession == null ? null : (UUID) nms.RemoteChatSession$Data_getSessionId.invoke(remoteChatSession),
remoteChatSession == null ? null : nms.RemoteChatSession$Data_getProfilePublicKey.invoke(remoteChatSession)));
}
return new PacketPlayOutPlayerInfo(actions, listData);
} else {
//1.19.2-
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
List<PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
IChatBaseComponent listName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
Skin skin = null;
if (!profile.getProperties().get("textures").isEmpty()) {
Property pr = profile.getProperties().get("textures").iterator().next();
skin = new Skin(pr.getValue(), pr.getSignature());
}
Object profilePublicKey = nms.getMinorVersion() >= 19 ? nms.PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData) : null;
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), skin, true,
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName, null, profilePublicKey));
}
return new PacketPlayOutPlayerInfo(action, listData);
}
return new PacketPlayOutPlayerInfo(action, listData);
}
@Override
@@ -387,7 +467,7 @@ public class BukkitPacketBuilder extends PacketBuilder {
}
/**
* Builds entity BossBar packet
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
*
* @param packet
* packet to build
@@ -435,13 +515,15 @@ public class BukkitPacketBuilder extends PacketBuilder {
}
/**
* Converts TAB's IChatBaseComponent into minecraft's component.
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
* If {@code component} is {@code null}, returns {@code null}
*
* @param component
* component to convert
* @param clientVersion
* client version used to decide RGB conversion
* @return converted component
* @return converted component or {@code null} if {@code component} is {@code null}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@@ -13,6 +13,7 @@ import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
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.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
/**
@@ -66,7 +67,8 @@ public class BukkitPipelineInjector extends PipelineInjector {
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (nms.PacketPlayOutPlayerInfo.isInstance(packet)) {
if (nms.PacketPlayOutPlayerInfo.isInstance(packet) ||
(nms.ClientboundPlayerInfoRemovePacket != null && nms.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
@@ -117,9 +119,10 @@ public class BukkitPipelineInjector extends PipelineInjector {
newList.add(entry);
continue;
}
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(p.getTeamName())) {
logTeamOverride(teamName, p.getName(), p.getTeamName());
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
} else {
newList.add(entry);
}
@@ -33,15 +33,33 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
/** Number formatter for 2 decimal places */
private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.US);
/** JavaPlugin reference for registering events */
private final JavaPlugin plugin;
/** Vault Chat hook */
private Chat chat;
private final Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
/** NMS server to get TPS from on spigot */
private Object server;
/** TPS field*/
private Field recentTps;
/** Detection for presence of Paper's TPS getter */
private Method paperTps;
/** Detection for presence of Paper's MSPT getter */
private Method paperMspt;
/** Detection for presence of Purpur's AFK getter */
private Method purpurIsAfk;
/**
* Constructs new instance with given parameter and loads hooks
*
* @param plugin
* reference to the plugin's main class
*/
public BukkitPlaceholderRegistry(JavaPlugin plugin) {
this.plugin = plugin;
numberFormat.setMaximumFractionDigits(2);
@@ -63,7 +81,6 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(PlaceholderManager manager) {
super.registerPlaceholders(manager);
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
@@ -81,6 +98,7 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
if (paperMspt != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> numberFormat.format(Bukkit.getAverageTickTime()));
}
Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
manager.registerPlayerPlaceholder(TabConstants.Placeholder.AFK, 500, p -> {
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
return purpurIsAfk != null && ((Player)p.getPlayer()).isAfk();
@@ -93,8 +111,8 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
Listener nickListener = new Listener() {
@EventHandler
public void onNickChange(NickChangeEvent e) {
String name = e.getValue() == null ? e.getAffected().getName() : e.getValue();
TabPlayer player = TAB.getInstance().getPlayer(e.getAffected().getUUID());
String name = e.getValue() == null ? e.getController().getName() : e.getValue();
TabPlayer player = TAB.getInstance().getPlayer(e.getController().getUUID());
if (player == null) return;
nick.updateValue(player, name);
}
@@ -113,8 +131,16 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, -1, () -> "");
}
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
super.registerPlaceholders(manager);
}
/**
* Formats TPS using number formatter with 2 decimal places.
*
* @param tps
* TPS to format
* @return Formatted TPS as a String
*/
private String formatTPS(double tps) {
return numberFormat.format(Math.min(20, tps));
}
@@ -1,12 +1,10 @@
package me.neznamy.tab.platforms.bukkit;
import com.earth2me.essentials.Essentials;
import com.viaversion.viaversion.api.Via;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.event.TabLoadEvent;
import me.neznamy.tab.platforms.bukkit.event.TabPlayerLoadEvent;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.PetFix;
@@ -15,10 +13,10 @@ import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.Platform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.permission.LuckPerms;
import me.neznamy.tab.shared.permission.None;
import me.neznamy.tab.shared.permission.PermissionPlugin;
@@ -43,7 +41,6 @@ public class BukkitPlatform extends Platform {
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API);
private boolean libsDisguises = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
private final Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
private Plugin viaVersion;
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
@@ -88,6 +85,7 @@ public class BukkitPlatform extends Platform {
tab.getFeatureManager().registerFeature(TabConstants.Feature.PIPELINE_INJECTION, new BukkitPipelineInjector());
new BukkitPlaceholderRegistry(plugin).registerPlaceholders(tab.getPlaceholderManager());
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.enabled", true)) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.SORTING, new Sorting());
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && tab.getServerVersion().getMinorVersion() >= 8) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, new BukkitNameTagX(plugin));
} else {
@@ -182,16 +180,6 @@ public class BukkitPlatform extends Platform {
}
}
@Override
public void callLoadEvent() {
Bukkit.getPluginManager().callEvent(new TabLoadEvent());
}
@Override
public void callLoadEvent(TabPlayer player) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> Bukkit.getPluginManager().callEvent(new TabPlayerLoadEvent(player)));
}
/**
* Returns status of LibsDisguises plugin presence
*
@@ -213,20 +201,6 @@ public class BukkitPlatform extends Platform {
libsDisguises = enabled;
}
/**
* Returns Essentials' main class if the plugin is installed, {@code null} if not
*
* @return Essentials instance or {@code null} if plugin is not installed
*/
public Essentials getEssentials() {
return (Essentials) essentials;
}
@Override
public String getConfigName() {
return "bukkitconfig.yml";
}
/**
* Gets protocol version of requested player and returns it.
*
@@ -16,7 +16,6 @@ import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import com.earth2me.essentials.Essentials;
import com.mojang.authlib.GameProfile;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
@@ -56,7 +55,7 @@ public class BukkitTabPlayer extends ITabPlayer {
* Player's protocol network id
*/
public BukkitTabPlayer(Player p, int protocolVersion){
super(p, p.getUniqueId(), p.getName(), "N/A", p.getWorld().getName(), protocolVersion, true);
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion, true);
try {
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
@@ -277,10 +276,7 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public boolean isVanished() {
Essentials essentials = ((BukkitPlatform)TAB.getInstance().getPlatform()).getEssentials();
if (essentials != null && essentials.getUser(getUniqueId()).isVanished()) return true;
List<MetadataValue> metadata = getPlayer().getMetadata("vanished");
return !metadata.isEmpty() && metadata.get(0).asBoolean();
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
}
@SuppressWarnings("deprecation")
@@ -289,6 +285,40 @@ public class BukkitTabPlayer extends ITabPlayer {
return getPlayer().getGameMode().getValue();
}
@Override
public Object getProfilePublicKey() {
if (NMSStorage.getInstance().getMinorVersion() < 19) return null;
try {
if (NMSStorage.getInstance().RemoteChatSession != null) {
//1.19.3+
Object session = NMSStorage.getInstance().EntityPlayer_RemoteChatSession.get(handle);
if (session == null) return null;
Object key = NMSStorage.getInstance().RemoteChatSession_getProfilePublicKey.invoke(session);
return NMSStorage.getInstance().ProfilePublicKey_getRecord.invoke(key);
} else {
Object key = NMSStorage.getInstance().EntityHuman_ProfilePublicKey.get(handle);
if (key == null) return null;
return NMSStorage.getInstance().ProfilePublicKey_getRecord.invoke(key);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get profile key of " + getName(), e);
return null;
}
}
@Override
public UUID getChatSessionId() {
if (NMSStorage.getInstance().RemoteChatSession != null) return null;
try {
Object session = NMSStorage.getInstance().EntityPlayer_RemoteChatSession.get(handle);
if (session == null) return null;
return (UUID) NMSStorage.getInstance().RemoteChatSession_getSessionId.invoke(session);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get chat session of " + getName(), e);
return null;
}
}
@Override
public Channel getChannel() {
try {
@@ -55,7 +55,7 @@ public class Main extends JavaPlugin {
@Override
public void onDisable() {
//null check due to L34 return making L37 not run
//null check due to compatibility check making instance not get set on unsupported versions
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
@@ -72,7 +72,7 @@ public class Main 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_18_R2", "v1_19_R1", "v1_19_R2");
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
long time = System.currentTimeMillis();
@@ -81,6 +81,7 @@ public class Main extends JavaPlugin {
try {
NMSStorage.setInstance(new BukkitModernNMSStorage());
} catch (ClassNotFoundException e) {
e.printStackTrace();
NMSStorage.setInstance(new MojangModernNMSStorage());
}
} else {
@@ -90,14 +91,14 @@ public class Main 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.x - 1.19. 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 1.5.2 - 1.19.3. Disabling just to stay safe."));
}
} catch (Exception 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 the error below (include your server version & fork too)"));
getLogger().log(Level.SEVERE, "", ex);
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.x - 1.19. Disabling."));
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."));
}
}
return false;
@@ -1,25 +0,0 @@
package me.neznamy.tab.platforms.bukkit.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* Bukkit event that is called each time TAB fully loads. This includes server startup, reload,
* /tab reload and /plugman reload tab
*
* @deprecated for removal, use {@link me.neznamy.tab.api.event.plugin.TabLoadEvent}
*/
@Deprecated
public class TabLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@Override
public HandlerList getHandlers(){
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.bukkit.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import me.neznamy.tab.api.TabPlayer;
/**
* Bukkit event that is called when player is successfully loaded after joining. This also includes plugin reloading.
*
* @deprecated for removal, use {@link me.neznamy.tab.api.event.player.PlayerLoadEvent}
*/
@Deprecated
public class TabPlayerLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TabPlayer player;
public TabPlayerLoadEvent(TabPlayer player) {
this.player = player;
}
@Override
public HandlerList getHandlers(){
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
public TabPlayer getPlayer() {
return player;
}
}
@@ -6,6 +6,7 @@ import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.TabExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -19,7 +20,7 @@ import java.util.WeakHashMap;
*/
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all placeholder values for all players */
/** Map holding all values for all players for easy and high-performance access */
private final Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
@Override
@@ -45,9 +46,16 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
@Override
public String onPlaceholderRequest(Player player, @NotNull String identifier){
if (identifier.startsWith("replace_")) {
String placeholder = "%" + identifier.substring(8) + "%";
String output = PlaceholderAPI.setPlaceholders(player, placeholder);
return TabAPI.getInstance().getPlaceholderManager().findReplacement(placeholder, output);
String text = "%" + identifier.substring(8) + "%";
String textBefore;
do {
textBefore = text;
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
text = text.replace(placeholder, TabAPI.getInstance().getPlaceholderManager().findReplacement(placeholder,
PlaceholderAPI.setPlaceholders(player, placeholder)));
}
} while (!textBefore.equals(text));
return text;
}
if (identifier.startsWith("placeholder_")) {
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
@@ -25,10 +25,10 @@ import me.neznamy.tab.shared.TAB;
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener {
//plugin instance
/** Reference to main class */
private final JavaPlugin plugin;
//config options
/** Config options */
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
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");
@@ -42,8 +42,8 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
public PerWorldPlayerList(JavaPlugin plugin) {
super("Per world PlayerList", null);
this.plugin = plugin;
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
Bukkit.getPluginManager().registerEvents(this, plugin);
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
}
@Override
@@ -75,6 +75,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
* Performs visibility check on the player. Shows players this player should see and does not,
* hides players the player should not see, shows the player to those who should see player and hides
* from those, who should not see.
*
* @param p
* Player to update
*/
private void checkPlayer(Player p) {
for (Player all : Bukkit.getOnlinePlayers()){
if (all == p) continue;
@@ -85,6 +93,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
}
}
/**
* Returns {@code true} if viewer should see target player, {@code false} if not.
* @param viewer
* Player viewing the TabList
* @param target
* Target displayed in the TabList
* @return {@code true} if viewer should see target, {@code false} if not.
*/
private boolean shouldSee(Player viewer, Player target) {
if (target == viewer) return true;
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
@@ -22,13 +22,13 @@ import java.util.WeakHashMap;
*/
public class PetFix extends TabFeature {
//nms storage
/** NMS Storage reference for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
//DataWatcher position of pet owner field
/** DataWatcher position of pet owner field */
private final int petOwnerPosition = getPetOwnerPosition();
//logger of last interacts to prevent feature not working on 1.16
/** Logger of last interacts to prevent feature not working on 1.16 */
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
/**
@@ -94,6 +94,13 @@ public class PetFix extends TabFeature {
return false;
}
/**
* Checks if the provided entity use action is INTERACT or not.
*
* @param action
* Action to check
* @return {@code true} if action is INTERACT, {@code false} if not.
*/
private boolean isInteract(Object action) {
if (nms.getMinorVersion() >= 17) {
return nms.PacketPlayInUseEntity$d.isInstance(action);
@@ -118,8 +125,16 @@ public class PetFix extends TabFeature {
try {
for (Object item : items) {
if (item == null) continue;
if (nms.DataWatcherObject_SLOT.getInt(nms.DataWatcherItem_TYPE.get(item)) == petOwnerPosition) {
Object value = nms.DataWatcherItem_VALUE.get(item);
int slot;
Object value;
if (nms.is1_19_3Plus()) {
slot = nms.DataWatcher$DataValue_POSITION.getInt(item);
value = nms.DataWatcher$DataValue_VALUE.get(item);
} else {
slot = nms.DataWatcherObject_SLOT.getInt(nms.DataWatcherItem_TYPE.get(item));
value = nms.DataWatcherItem_VALUE.get(item);
}
if (slot == petOwnerPosition) {
if (value instanceof Optional || value instanceof com.google.common.base.Optional) {
removedEntry = item;
}
@@ -21,11 +21,11 @@ import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
*/
public class WitherBossBar extends BossBarManagerImpl implements Listener {
//distance of wither in blocks
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/**
* Constructs a new instance of the class
* Constructs a new instance of the class and registers events and task
*
* @param plugin
* plugin instance
@@ -44,6 +44,9 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
teleport();
}
/**
* Updates Wither's location for all online players
*/
private void teleport() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
@@ -18,48 +18,63 @@ import org.bukkit.entity.Pose;
import java.util.UUID;
/**
* A class representing an armor stand attached to a player (if the feature is enabled)
* A class representing an armor stand attached to a player
*/
public class BukkitArmorStand implements ArmorStand {
//entity id counter to pick unique entity IDs
/** Entity id counter to pick unique entity ID for each armor stand */
private static int idCounter = 2000000000;
//NameTag feature
/** NameTag feature */
private final BukkitNameTagX manager = (BukkitNameTagX) TabAPI.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
/** Armor stand manager which this armor stand belongs to */
private final BukkitArmorStandManager asm;
//owner of the armor stand
/** Owner of the armor stand */
private final TabPlayer owner;
//instance of Bukkit player
/** Owner as a Bukkit player */
private final Player player;
//offset in blocks, 0 for original height
/** Offset in blocks, 0 for original height */
private double yOffset;
//entity ID of this armor stand
private final int entityId = idCounter++;
//unique ID of this armor stand
private final UUID uuid = UUID.randomUUID();
//sneaking flag of armor stands
private boolean sneaking;
//armor stand visibility
private boolean visible;
//property dedicated to this armor stand
private final Property property;
//if offset is static or dynamic based on other armor stands
/** If offset is static, or dynamic based on other armor stands */
private final boolean staticOffset;
//entity destroy packet
/** Entity ID of this armor stand */
private final int entityId = idCounter++;
/** Unique ID of this armor stand */
private final UUID uuid = UUID.randomUUID();
/** Sneaking flag of armor stands */
private boolean sneaking;
/** Armor stand visibility */
private boolean visible;
/** Refresh property dedicated to this armor stand */
private final Property property;
/** Entity destroy packet */
private final PacketPlayOutEntityDestroy 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(BukkitArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
this.asm = asm;
this.owner = owner;
@@ -189,7 +204,7 @@ public class BukkitArmorStand implements ArmorStand {
/**
* Returns general visibility rule for everyone with limited info
*
* @return true if armor stand should be visible, false if not
* @return {@code true} if armor stand should be visible, {@code false} if not
*/
public boolean getVisibility() {
if (manager.isArmorStandsAlwaysVisible()) return true;
@@ -198,11 +213,13 @@ public class BukkitArmorStand implements ArmorStand {
}
/**
* Returns general location where armor stand should be at time of calling
* 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 everyone
* @return Location where armor stand should be for specified viewer
*/
public Location getLocation(TabPlayer viewer) {
double x = player.getLocation().getX();
@@ -255,6 +272,10 @@ public class BukkitArmorStand implements ArmorStand {
return player.getLocation().getY();
}
/**
* 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();
@@ -291,11 +312,12 @@ public class BukkitArmorStand implements ArmorStand {
}
/**
* Returns true if display name is in fact empty, for example only containing color codes
* Returns {@code true} if display name is in fact empty,
* for example only containing color codes, {@code false} if not.
*
* @param displayName
* string to check
* @return true if it's empty, false if not
* @return {@code true} if it's empty, {@code false} if not
*/
private boolean isNameVisiblyEmpty(String displayName) {
if (displayName.length() == 0) return true;
@@ -16,17 +16,26 @@ import java.util.Map;
*/
public class BukkitArmorStandManager implements ArmorStandManager {
//map of registered armor stands
/** Map of registered armor stands by name */
private final Map<String, ArmorStand> armorStands = new LinkedHashMap<>();
//players in entity tracking range
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
//array to iterate over to avoid concurrent modification and slightly boost performance & memory
/** Armor stands in an array for speed while iterating */
private ArmorStand[] armorStandArray = new ArmorStand[0];
/** Players in entity tracking range of owner */
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
/** Nearby players in an array for speed while iterating */
private TabPlayer[] nearbyPlayerArray = new TabPlayer[0];
/**
* Constructs new instance with given parameters and loads armor stands.
*
* @param nameTagX
* Main feature
* @param owner
* Owner of this armor stand manager
*/
public BukkitArmorStandManager(NameTagX nameTagX, TabPlayer owner) {
owner.setProperty(nameTagX, TabConstants.Property.NAMETAG, owner.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue()
+ owner.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue()
@@ -61,7 +70,7 @@ public class BukkitArmorStandManager implements ArmorStandManager {
/**
* Returns array of nearby players
* @return array of nearby players
* @return array of nearby players
*/
public TabPlayer[] getNearbyPlayers(){
return nearbyPlayerArray;
@@ -79,11 +88,11 @@ public class BukkitArmorStandManager implements ArmorStandManager {
}
/**
* Returns true if manager contains armor stand with specified entity id, false if not
* Returns {@code true} if manager contains armor stand with specified entity id, {@code false} if not
*
* @param entityId
* entity id
* @return true if armor stand with specified entity id exists, false if not
* @return {@code true} if armor stand with specified entity id exists, {@code false} if not
*/
public boolean hasArmorStandWithID(int entityId) {
for (ArmorStand a : armorStandArray) {
@@ -11,17 +11,19 @@ import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The core class for unlimited NameTag mode
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends NameTagX {
//bukkit event listener
/** Bukkit event listener */
private final EventListener eventListener = new EventListener(this);
/** Vehicle manager reference */
private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
/**
* Constructs new instance with given parameters and loads config options
* Constructs new instance with given parameter, loads config options, registers events
* and registers sub-features.
*
* @param plugin
* plugin instance
@@ -45,6 +47,9 @@ public class BukkitNameTagX extends NameTagX {
startVisibilityRefreshTask();
}
/**
* Starts task checking for player visibility to hide armor stands of invisible players.
*/
private void startVisibilityRefreshTask() {
TabAPI.getInstance().getThreadManager().startRepeatingMeasuredTask(500, this, TabConstants.CpuUsageCategory.REFRESHING_NAME_TAG_VISIBILITY, () -> {
@@ -76,6 +81,16 @@ public class BukkitNameTagX extends NameTagX {
return vehicleManager != null && vehicleManager.isOnBoat(player);
}
/**
* Spawns armor stands of target player to viewer if all requirements are met.
* These include players being in the same world, distance being less than 48 blocks
* and target player being visible to viewer.
*
* @param viewer
* Player viewing armor stands
* @param target
* Target player with armor stands
*/
private void spawnArmorStands(TabPlayer viewer, TabPlayer target) {
if (viewer.getVersion().getMinorVersion() < 8) return;
if (target == viewer || isPlayerDisabled(target)) return;
@@ -163,6 +178,10 @@ public class BukkitNameTagX extends NameTagX {
return (BukkitArmorStandManager) armorStandManagerMap.get(player);
}
/**
* Returns {@link #vehicleManager}
* @return {@link #vehicleManager}
*/
public VehicleRefresher getVehicleManager() {
return vehicleManager;
}
@@ -10,15 +10,16 @@ import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
/**
* The event listener part for securing proper functionality of armor stands
* Class listening to Bukkit events which the feature requires and are
* sufficient, without packets being requires.
*/
public class EventListener implements Listener {
//the NameTag feature handler
/** Reference to the main feature */
private final BukkitNameTagX feature;
/**
* Constructs new instance with given parameters
* Constructs new instance with given parameter
*
* @param feature
* NameTag feature handler
@@ -13,22 +13,25 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* The packet listening part for securing proper functionality of armor stands
* The packet listening part for securing proper functionality of armor stands.
* Bukkit events are too unreliable and delayed/ahead which causes de-sync
* if trying to listen to move event.
* For entering/leaving tracking range there are no events and
* periodic / move-triggered distance checks would cause high CPU usage.
*/
public class PacketListener extends TabFeature {
//main feature
/** Reference to the main feature */
private final BukkitNameTagX nameTagX;
//player data by entityId, used for better performance
/** A player map by entity id, used for better performance */
private final Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
//nms storage
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/**
* Constructs new instance with given parameters and loads config options
* Constructs new instance with given parameter
*
* @param nameTagX
* main feature
@@ -100,7 +103,10 @@ public class PacketListener extends TabFeature {
}
/**
* Processes entity move packet
* Processes entity move packet. If entity ID belongs to a player,
* armor stands of that player are teleported to player who received the packet.
* If it belongs to a vehicle carrying a player, that player's armor stands are
* teleported as well.
*
* @param receiver
* packet receiver
@@ -126,7 +132,16 @@ public class PacketListener extends TabFeature {
}
}
}
/**
* Processes named entity spawn packet and spawns armor stands if
* entity ID belongs to an online player.
*
* @param receiver
* packet receiver
* @param entityId
* spawned entity
*/
private void onEntitySpawn(TabPlayer receiver, int entityId) {
TabPlayer spawnedPlayer = entityIdMap.get(entityId);
if (spawnedPlayer != null && spawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(spawnedPlayer)) {
@@ -135,18 +150,45 @@ public class PacketListener extends TabFeature {
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
*
* @param receiver
* packet receiver
* @param entities
* de-spawned entities
*/
private void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
*
* @param receiver
* packet receiver
* @param entities
* de-spawned entities
*/
private void onEntityDestroy(TabPlayer receiver, int... entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
*
* @param receiver
* packet receiver
* @param entity
* de-spawned entity
*/
private void onEntityDestroy(TabPlayer receiver, int entity) {
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer)) {
@@ -10,19 +10,35 @@ import org.bukkit.entity.Player;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Sub-feature for unlimited name tag mode to secure
* proper functionality when using vehicles.
* A config option allows to disable the feature on boats,
* where it would cause a massive de-sync due to boat movement
* animation.
* Additionally, when entering a vehicle, no move packet is sent
* and therefore manual teleporting of armor stands is required.
*/
public class VehicleRefresher extends TabFeature {
//map of players currently in a vehicle
/** Map of players currently in a vehicle */
private final WeakHashMap<TabPlayer, Entity> playersInVehicle = new WeakHashMap<>();
//map of vehicles carrying players
/** Map of vehicles carrying players */
private final Map<Integer, List<Entity>> vehicles = new ConcurrentHashMap<>();
//set of players currently on boats
/** set of players currently on boats */
private final Set<TabPlayer> playersOnBoats = Collections.newSetFromMap(new WeakHashMap<>());
/** Reference to the main feature */
private final BukkitNameTagX feature;
/**
* Constructs new instance with given parameter and starts tasks.
*
* @param feature
* Main feature
*/
public VehicleRefresher(BukkitNameTagX feature) {
super(feature.getFeatureName(), "Refreshing vehicles");
this.feature = feature;
@@ -96,10 +112,22 @@ public class VehicleRefresher extends TabFeature {
}
}
/**
* Returns {@code true} if the player is in a boat, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if in a boat, {@code false} if not
*/
public boolean isOnBoat(TabPlayer p) {
return playersOnBoats.contains(p);
}
/**
* Returns vehicle map
*
* @return vehicle map
*/
public Map<Integer, List<Entity>> getVehicles() {
return vehicles;
}
@@ -119,7 +147,7 @@ public class VehicleRefresher extends TabFeature {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger());
} else {
return new ArrayList<>();
return Collections.emptyList();
}
}
}
@@ -3,6 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitModernNMSStorage extends ModernNMSStorage {
/**
@@ -26,8 +27,7 @@ public class BukkitModernNMSStorage extends ModernNMSStorage {
ScoreboardTeam_setSuffix = getMethod(ScoreboardTeam, new String[]{"setSuffix", "c"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
ScoreboardTeam_setNameTagVisibility = getMethod(ScoreboardTeam, new String[]{"setNameTagVisibility", "a"}, EnumNameTagVisibility); // {Bukkit, Bukkit 1.18+}
if (minorVersion >= 19) {
Registry_a = Registry.getMethod("a", int.class);
IRegistry_X = IRegistry.getDeclaredField("X").get(null);
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("d").get(null);
PacketPlayOutSpawnEntityLiving_ENTITYTYPE = getField(PacketPlayOutSpawnEntityLiving, "e");
}
}
@@ -41,16 +41,17 @@ public class BukkitModernNMSStorage extends ModernNMSStorage {
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = Class.forName("net.minecraft.EnumChatFormat");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
PacketPlayOutPlayerListHeaderFooter = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
PacketPlayOutChat = getModernClass("net.minecraft.network.protocol.game.ClientboundSystemChatPacket",
"net.minecraft.network.protocol.game.PacketPlayOutChat");
if (minorVersion < 19) {
ChatMessageType = Class.forName("net.minecraft.network.chat.ChatMessageType");
ChatMessageType = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatMessageType");
}
// DataWatcher
@@ -59,6 +60,9 @@ public class BukkitModernNMSStorage extends ModernNMSStorage {
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
if (is1_19_3Plus()) {
DataWatcher$DataValue = Class.forName("net.minecraft.network.syncher.DataWatcher$b");
}
// Entities
PacketPlayOutSpawnEntityLiving = getModernClass("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving",
@@ -71,17 +75,30 @@ public class BukkitModernNMSStorage extends ModernNMSStorage {
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadata = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
EnumEntityUseAction = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction");
if (minorVersion >= 19) {
Registry = Class.forName("net.minecraft.core.Registry");
IRegistry = Class.forName("net.minecraft.core.IRegistry");
EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
}
// Player Info
PacketPlayOutPlayerInfo = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
EnumPlayerInfoAction = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
PlayerInfoData = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
EnumGamemode = Class.forName("net.minecraft.world.level.EnumGamemode");
if (minorVersion >= 19) {
ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$a");
}
if (is1_19_3Plus()) {
//1.19.3+
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PacketPlayOutPlayerInfo = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
EnumPlayerInfoAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
PlayerInfoData = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession = Class.forName("net.minecraft.network.chat.RemoteChatSession");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PacketPlayOutPlayerInfo = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
EnumPlayerInfoAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
PlayerInfoData = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
EnumGamemode = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
// Scoreboard
PacketPlayOutScoreboardDisplayObjective = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
@@ -91,12 +108,12 @@ public class BukkitModernNMSStorage extends ModernNMSStorage {
ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
ScoreboardScore = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
EnumScoreboardHealthDisplay = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = Class.forName("net.minecraft.server.ScoreboardServer$Action");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
PacketPlayOutScoreboardTeam = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
EnumNameTagVisibility = Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
PacketPlayOutScoreboardTeam_PlayerAction = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$a");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
PacketPlayOutScoreboardTeam_PlayerAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$a");
}
}
@@ -7,6 +7,7 @@ import java.util.Arrays;
/**
* NMS loader for Minecraft 1.16.5 and lower using old NMS class structure.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class LegacyNMSStorage extends NMSStorage {
/**
@@ -72,7 +73,7 @@ public class LegacyNMSStorage extends NMSStorage {
ChatSerializer_DESERIALIZE = getMethod(ChatSerializer, new String[]{"a", "func_150699_a"}, String.class); // {Bukkit, Thermos}
}
if (minorVersion >= 8) {
ScoreboardTeam_setNameTagVisibility = getMethod(ScoreboardTeam, "setNameTagVisibility", EnumNameTagVisibility);
ScoreboardTeam_setNameTagVisibility = getMethod(ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, EnumNameTagVisibility); // {1.8.1+, 1.8}
}
if (minorVersion >= 9) {
DataWatcher_REGISTER = getMethod(DataWatcher, "register", DataWatcherObject, Object.class);
@@ -93,7 +94,7 @@ public class LegacyNMSStorage extends NMSStorage {
EntityHuman = getLegacyClass("EntityHuman");
World = getLegacyClass("World");
Packet = getLegacyClass("Packet");
EnumChatFormat = getLegacyClass("EnumChatFormat");
EnumChatFormat = (Class<Enum>) getLegacyClass("EnumChatFormat");
EntityPlayer = getLegacyClass("EntityPlayer");
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
@@ -109,7 +110,7 @@ public class LegacyNMSStorage extends NMSStorage {
EntityArmorStand = getLegacyClass("EntityArmorStand");
}
if (minorVersion >= 12) {
ChatMessageType = getLegacyClass("ChatMessageType");
ChatMessageType = (Class<Enum>) getLegacyClass("ChatMessageType");
}
// DataWatcher
@@ -131,15 +132,15 @@ public class LegacyNMSStorage extends NMSStorage {
PacketPlayOutEntityMetadata = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
if (minorVersion >= 7) {
EnumEntityUseAction = getLegacyClass("PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction");
EnumEntityUseAction = (Class<Enum>) getLegacyClass("PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction");
}
// Player Info
if (minorVersion >= 8) {
PacketPlayOutPlayerInfo = getLegacyClass("PacketPlayOutPlayerInfo");
EnumPlayerInfoAction = getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
EnumPlayerInfoAction = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
PlayerInfoData = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemode = getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
EnumGamemode = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
// Scoreboard
@@ -153,12 +154,12 @@ public class LegacyNMSStorage extends NMSStorage {
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria");
ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay = getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
EnumTeamPush = getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
}
@@ -7,9 +7,6 @@ import java.util.Arrays;
*/
public abstract class ModernNMSStorage extends NMSStorage {
protected Class<?> IRegistry;
protected Class<?> Registry;
/**
* Creates new instance, initializes required NMS classes and fields
*
@@ -3,6 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class MojangModernNMSStorage extends ModernNMSStorage {
/**
@@ -26,8 +27,7 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
ScoreboardTeam_setSuffix = getMethod(ScoreboardTeam, "setPlayerSuffix", IChatBaseComponent);
ScoreboardTeam_setNameTagVisibility = getMethod(ScoreboardTeam, "setNameTagVisibility", EnumNameTagVisibility);
if (minorVersion >= 19) {
Registry_a = Registry.getMethod("byId", int.class);
IRegistry_X = IRegistry.getDeclaredField("ENTITY_TYPE").get(null);
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
PacketPlayOutSpawnEntityLiving_ENTITYTYPE = getField(PacketPlayOutSpawnEntityLiving, "type");
}
}
@@ -41,7 +41,7 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = Class.forName("net.minecraft.ChatFormatting");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
@@ -50,7 +50,7 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
PacketPlayOutChat = getModernClass("net.minecraft.network.protocol.game.ClientboundSystemChatPacket",
"net.minecraft.network.protocol.game.ClientboundChatPacket");
if (minorVersion < 19) {
ChatMessageType = Class.forName("net.minecraft.network.chat.ChatType");
ChatMessageType = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatType");
}
// DataWatcher
@@ -59,6 +59,9 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
if (is1_19_3Plus()) {
DataWatcher$DataValue = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataValue");
}
// Entities
PacketPlayOutSpawnEntityLiving = getModernClass("net.minecraft.network.protocol.game.ClientboundAddMobPacket",
@@ -71,17 +74,29 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadata = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
EnumEntityUseAction = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action");
if (minorVersion >= 19) {
Registry = Class.forName("net.minecraft.core.IdMap");
IRegistry = Class.forName("net.minecraft.core.Registry");
EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
}
// Player Info
PacketPlayOutPlayerInfo = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
EnumPlayerInfoAction = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
PlayerInfoData = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
EnumGamemode = Class.forName("net.minecraft.world.level.GameType");
if (minorVersion >= 19) {
ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$Data");
}
if (is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PacketPlayOutPlayerInfo = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
EnumPlayerInfoAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
PlayerInfoData = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession = Class.forName("net.minecraft.network.chat.RemoteChatSession");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PacketPlayOutPlayerInfo = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
EnumPlayerInfoAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
PlayerInfoData = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
EnumGamemode = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
// Scoreboard
PacketPlayOutScoreboardDisplayObjective = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
@@ -91,12 +106,12 @@ public class MojangModernNMSStorage extends ModernNMSStorage {
ScoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
ScoreboardScore = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
EnumScoreboardHealthDisplay = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = Class.forName("net.minecraft.server.ServerScoreboard$Method");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
PacketPlayOutScoreboardTeam = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
ScoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
EnumNameTagVisibility = Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = Class.forName("net.minecraft.world.scores.Team$CollisionRule");
PacketPlayOutScoreboardTeam_PlayerAction = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket$Action");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
PacketPlayOutScoreboardTeam_PlayerAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket$Action");
}
}
@@ -9,27 +9,27 @@ import org.bukkit.Bukkit;
import java.lang.reflect.*;
import java.util.*;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
@SuppressWarnings("rawtypes")
public abstract class NMSStorage {
//instance of this class
/** Instance of this class */
private static NMSStorage instance;
//server package, such as "v1_16_R3"
/** Server's NMS/CraftBukkit package */
protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
//server minor version such as "16"
/** Server's minor version */
protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
//another field due to fabric which uses 1.16 package on 1.17 server
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
//base
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
protected Class<?> EntityArmorStand;
protected Class<?> World;
public Class<?> EnumChatFormat;
public Class<Enum> EnumChatFormat;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
protected Class<?> Entity;
@@ -39,23 +39,23 @@ public abstract class NMSStorage {
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Field EntityHuman_ProfilePublicKey;
public Field EntityPlayer_RemoteChatSession;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
public Enum[] EnumChatFormat_values;
//chat
/** Chat components */
protected Class<?> IChatBaseComponent;
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
//PacketPlayOutChat
/** PacketPlayOutChat */
protected Class<?> PacketPlayOutChat;
public Class<?> ChatMessageType;
public Class<Enum> ChatMessageType;
public Constructor<?> newPacketPlayOutChat;
public Enum[] ChatMessageType_values;
//DataWatcher
/** DataWatcher */
protected Class<?> DataWatcher;
protected Class<?> DataWatcherItem;
protected Class<?> DataWatcherObject;
@@ -68,10 +68,17 @@ public abstract class NMSStorage {
public Field DataWatcherObject_SLOT;
public Field DataWatcherObject_SERIALIZER;
public Method DataWatcher_REGISTER;
public Method DataWatcher_b;
private final DataWatcherRegistry registry;
//1.19.3+
protected Class<?> DataWatcher$DataValue;
public Field DataWatcher$DataValue_POSITION;
public Field DataWatcher$DataValue_VALUE;
public Method DataWatcher_markDirty;
//PacketPlayOutSpawnEntityLiving
/** PacketPlayOutSpawnEntityLiving */
public Class<?> PacketPlayOutSpawnEntityLiving;
public Class<?> EntityTypes;
public Constructor<?> newPacketPlayOutSpawnEntityLiving;
public Field PacketPlayOutSpawnEntityLiving_ENTITYID;
public Field PacketPlayOutSpawnEntityLiving_ENTITYTYPE;
@@ -82,10 +89,9 @@ public abstract class NMSStorage {
public Field PacketPlayOutSpawnEntityLiving_Y;
public Field PacketPlayOutSpawnEntityLiving_Z;
public Field PacketPlayOutSpawnEntityLiving_DATAWATCHER;
public Method Registry_a;
public Object IRegistry_X;
public Object EntityTypes_ARMOR_STAND;
//PacketPlayOutEntityTeleport
/** PacketPlayOutEntityTeleport */
public Class<?> PacketPlayOutEntityTeleport;
public Constructor<?> newPacketPlayOutEntityTeleport;
public Field PacketPlayOutEntityTeleport_ENTITYID;
@@ -95,16 +101,16 @@ public abstract class NMSStorage {
public Field PacketPlayOutEntityTeleport_YAW;
public Field PacketPlayOutEntityTeleport_PITCH;
//PacketPlayOutPlayerListHeaderFooter
/** PacketPlayOutPlayerListHeaderFooter */
protected Class<?> PacketPlayOutPlayerListHeaderFooter;
public Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
public Field PacketPlayOutPlayerListHeaderFooter_HEADER;
public Field PacketPlayOutPlayerListHeaderFooter_FOOTER;
//other entity packets
/** Other entity packets */
public Class<?> PacketPlayInUseEntity;
public Class<?> PacketPlayInUseEntity$d;
protected Class<?> EnumEntityUseAction;
protected Class<Enum> EnumEntityUseAction;
public Field PacketPlayInUseEntity_ENTITY;
public Field PacketPlayInUseEntity_ACTION;
@@ -124,23 +130,39 @@ public abstract class NMSStorage {
public Class<?> PacketPlayOutNamedEntitySpawn;
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
//PacketPlayOutPlayerInfo
/** PacketPlayOutPlayerInfo */
public Class<?> PacketPlayOutPlayerInfo;
protected Class<?> EnumPlayerInfoAction;
public Class<Enum> EnumPlayerInfoAction;
protected Class<?> PlayerInfoData;
protected Class<?> EnumGamemode;
public Class<Enum> EnumGamemode;
protected Class<?> ProfilePublicKey;
protected Class<?> ProfilePublicKey$a;
public Constructor<?> newPacketPlayOutPlayerInfo;
public Constructor<?> newPlayerInfoData;
public Field PacketPlayOutPlayerInfo_ACTION;
public Field PacketPlayOutPlayerInfo_PLAYERS;
public Method PlayerInfoData_getProfile;
public Method PlayerInfoData_isListed;
public Method PlayerInfoData_getLatency;
public Method PlayerInfoData_getGamemode;
public Method PlayerInfoData_getDisplayName;
public Enum[] EnumPlayerInfoAction_values;
public Enum[] EnumGamemode_values;
public Method PlayerInfoData_getProfilePublicKeyRecord;
public Method ProfilePublicKey_getRecord;
//1.19.3+
public Class<?> ClientboundPlayerInfoRemovePacket;
public Class<?> RemoteChatSession;
public Class<?> RemoteChatSession$Data;
public Constructor<?> newClientboundPlayerInfoRemovePacket;
public Constructor<?> newRemoteChatSession;
public Constructor<?> newRemoteChatSession$Data;
public Method ClientboundPlayerInfoRemovePacket_getEntries;
public Method RemoteChatSession_getSessionId;
public Method RemoteChatSession_getProfilePublicKey;
public Method RemoteChatSession$Data_getSessionId;
public Method RemoteChatSession$Data_getProfilePublicKey;
//scoreboard objectives
/** Scoreboard objectives */
public Class<?> PacketPlayOutScoreboardDisplayObjective;
public Class<?> PacketPlayOutScoreboardObjective;
protected Class<?> Scoreboard;
@@ -148,8 +170,8 @@ public abstract class NMSStorage {
protected Class<?> ScoreboardObjective;
protected Class<?> ScoreboardScore;
protected Class<?> IScoreboardCriteria;
public Class<?> EnumScoreboardHealthDisplay;
protected Class<?> EnumScoreboardAction;
public Class<Enum> EnumScoreboardHealthDisplay;
public Class<Enum> EnumScoreboardAction;
public Constructor<?> newScoreboardObjective;
protected Constructor<?> newScoreboard;
public Constructor<?> newScoreboardScore;
@@ -166,15 +188,13 @@ public abstract class NMSStorage {
public Field PacketPlayOutScoreboardObjective_RENDERTYPE;
public Field PacketPlayOutScoreboardObjective_DISPLAYNAME;
public Method ScoreboardScore_setScore;
public Enum[] EnumScoreboardHealthDisplay_values;
public Enum[] EnumScoreboardAction_values;
//PacketPlayOutScoreboardTeam
/** PacketPlayOutScoreboardTeam */
public Class<?> PacketPlayOutScoreboardTeam;
protected Class<?> ScoreboardTeam;
protected Class<?> EnumNameTagVisibility;
protected Class<?> EnumTeamPush;
protected Class<?> PacketPlayOutScoreboardTeam_PlayerAction;
public Class<Enum> EnumNameTagVisibility;
public Class<Enum> EnumTeamPush;
public Class<Enum> PacketPlayOutScoreboardTeam_PlayerAction;
public Constructor<?> newScoreboardTeam;
public Constructor<?> newPacketPlayOutScoreboardTeam;
public Field PacketPlayOutScoreboardTeam_NAME;
@@ -191,14 +211,17 @@ public abstract class NMSStorage {
public Method PacketPlayOutScoreboardTeam_of;
public Method PacketPlayOutScoreboardTeam_ofBoolean;
public Method PacketPlayOutScoreboardTeam_ofString;
public Enum[] EnumNameTagVisibility_values;
public Enum[] EnumTeamPush_values;
public Enum[] PacketPlayOutScoreboardTeam_PlayerAction_values;
//other
/** Other */
public Object emptyScoreboard;
public Object dummyEntity;
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*
* @throws ReflectiveOperationException
* If some field, constructor or method was not found
*/
public NMSStorage() throws ReflectiveOperationException {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
@@ -215,7 +238,6 @@ public abstract class NMSStorage {
PLAYER_CONNECTION = getFields(EntityPlayer, PlayerConnection).get(0);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
sendPacket = getMethods(PlayerConnection, void.class, Packet).get(0);
EnumChatFormat_values = getEnumValues(EnumChatFormat);
registry = new DataWatcherRegistry(this);
dataWatcher();
entityTeleport();
@@ -248,10 +270,13 @@ public abstract class NMSStorage {
return instance;
}
/**
* Loads PacketPlayOutChat's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void chat() throws ReflectiveOperationException {
if (minorVersion >= 12 && minorVersion <= 18) {
ChatMessageType_values = getEnumValues(ChatMessageType);
}
if (minorVersion >= 19) {
try {
newPacketPlayOutChat = PacketPlayOutChat.getConstructor(IChatBaseComponent, boolean.class);
@@ -270,23 +295,60 @@ public abstract class NMSStorage {
}
}
/**
* Loads PacketPlayOutPlayerInfo's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void playerInfo() throws ReflectiveOperationException {
if (minorVersion < 8) return;
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfo.getConstructor(EnumPlayerInfoAction, Array.newInstance(EntityPlayer, 0).getClass());
if (is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfo.getConstructor(EnumPlayerInfoAction, EntityPlayer);
ClientboundPlayerInfoRemovePacket_getEntries = getMethods(ClientboundPlayerInfoRemovePacket, List.class).get(0);
PacketPlayOutPlayerInfo_ACTION = getFields(PacketPlayOutPlayerInfo, EnumSet.class).get(0);
newRemoteChatSession$Data = RemoteChatSession$Data.getConstructor(UUID.class, ProfilePublicKey$a);
RemoteChatSession$Data_getSessionId = getMethods(RemoteChatSession$Data, UUID.class).get(0);
RemoteChatSession$Data_getProfilePublicKey = getMethods(RemoteChatSession$Data, ProfilePublicKey$a).get(0);
PlayerInfoData_getProfilePublicKeyRecord = getMethods(PlayerInfoData, RemoteChatSession$Data).get(0);
EntityPlayer_RemoteChatSession = getFields(EntityPlayer, RemoteChatSession).get(0);
PlayerInfoData_isListed = getMethods(PlayerInfoData, boolean.class).get(0);
} else {
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfo.getConstructor(EnumPlayerInfoAction, Array.newInstance(EntityPlayer, 0).getClass());
PacketPlayOutPlayerInfo_ACTION = getFields(PacketPlayOutPlayerInfo, EnumPlayerInfoAction).get(0);
if (minorVersion >= 19) {
PlayerInfoData_getProfilePublicKeyRecord = getMethods(PlayerInfoData, ProfilePublicKey$a).get(0);
EntityHuman_ProfilePublicKey = getFields(EntityHuman, ProfilePublicKey).get(0);
}
}
newPlayerInfoData = PlayerInfoData.getConstructors()[0];
PacketPlayOutPlayerInfo_ACTION = getFields(PacketPlayOutPlayerInfo, EnumPlayerInfoAction).get(0);
PacketPlayOutPlayerInfo_PLAYERS = getFields(PacketPlayOutPlayerInfo, List.class).get(0);
PlayerInfoData_getProfile = getMethods(PlayerInfoData, GameProfile.class).get(0);
PlayerInfoData_getLatency = getMethods(PlayerInfoData, int.class).get(0);
for (Method m: getMethods(PlayerInfoData, int.class)) {
// do not take .hashCode(), which is final
if (!Modifier.isFinal(m.getModifiers())) PlayerInfoData_getLatency = m;
}
PlayerInfoData_getGamemode = getMethods(PlayerInfoData, EnumGamemode).get(0);
PlayerInfoData_getDisplayName = getMethods(PlayerInfoData, IChatBaseComponent).get(0);
EnumPlayerInfoAction_values = getEnumValues(EnumPlayerInfoAction);
EnumGamemode_values = getEnumValues(EnumGamemode);
if (minorVersion >= 19) {
ProfilePublicKey_getRecord = getMethods(ProfilePublicKey, ProfilePublicKey$a).get(0);
}
}
/**
* Loads PacketPlayOutSpawnEntityLiving's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void spawnEntityLiving() throws ReflectiveOperationException {
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor(EntityLiving);
if (minorVersion >= 17) {
if (is1_19_3Plus()) {
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor(Entity);
} else {
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor(EntityLiving);
}
} else {
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor();
}
@@ -317,11 +379,17 @@ public abstract class NMSStorage {
}
}
/**
* Loads PacketPlayOutEntityTeleport's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void entityTeleport() throws ReflectiveOperationException {
PacketPlayOutEntityTeleport_ENTITYID = getFields(PacketPlayOutEntityTeleport, int.class).get(0);
PacketPlayOutEntityTeleport_YAW = getFields(PacketPlayOutEntityTeleport, byte.class).get(0);
PacketPlayOutEntityTeleport_PITCH = getFields(PacketPlayOutEntityTeleport, byte.class).get(1);
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
if (minorVersion >= 17) {
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor(Entity);
} else {
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor();
@@ -337,21 +405,37 @@ public abstract class NMSStorage {
}
}
/**
* Loads PacketPlayOutPlayerListHeaderFooter's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void playerListHeaderFooter() throws ReflectiveOperationException {
if (minorVersion < 8) return;
PacketPlayOutPlayerListHeaderFooter_HEADER = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent).get(0);
PacketPlayOutPlayerListHeaderFooter_FOOTER = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent).get(1);
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
if (minorVersion >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor();
}
}
/**
* Loads methods, fields and constructors for other entity packets
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void otherEntity() throws ReflectiveOperationException {
PacketPlayOutEntity_ENTITYID = getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutEntityDestroy_ENTITIES = setAccessible(PacketPlayOutEntityDestroy.getDeclaredFields()[0]);
newPacketPlayOutEntityMetadata = PacketPlayOutEntityMetadata.getConstructor(int.class, DataWatcher, boolean.class);
if (is1_19_3Plus()) {
newPacketPlayOutEntityMetadata = PacketPlayOutEntityMetadata.getConstructor(int.class, List.class);
} else {
newPacketPlayOutEntityMetadata = PacketPlayOutEntityMetadata.getConstructor(int.class, DataWatcher, boolean.class);
}
PacketPlayOutEntityMetadata_LIST = getFields(PacketPlayOutEntityMetadata, List.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion >= 7) {
@@ -366,19 +450,37 @@ public abstract class NMSStorage {
}
}
/**
* Loads DataWatcher's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void dataWatcher() throws ReflectiveOperationException {
newDataWatcher = DataWatcher.getConstructors()[0];
DataWatcherItem_VALUE = getFields(DataWatcherItem, Object.class).get(0);
DataWatcher_b = getMethods(DataWatcher, List.class).get(0);
if (minorVersion >= 9) {
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, DataWatcherSerializer);
DataWatcherItem_TYPE = getFields(DataWatcherItem, DataWatcherObject).get(0);
DataWatcherObject_SLOT = getFields(DataWatcherObject, int.class).get(0);
DataWatcherObject_SERIALIZER = getFields(DataWatcherObject, DataWatcherSerializer).get(0);
if (is1_19_3Plus()) {
DataWatcher$DataValue_POSITION = getFields(DataWatcher$DataValue, int.class).get(0);
DataWatcher$DataValue_VALUE = getFields(DataWatcher$DataValue, Object.class).get(0);
DataWatcher_markDirty = getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
} else {
DataWatcherItem_TYPE = getFields(DataWatcherItem, int.class).get(1);
}
}
/**
* Loads Scoreboard methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void scoreboard() throws ReflectiveOperationException {
newScoreboardObjective = ScoreboardObjective.getConstructors()[0];
newScoreboard = Scoreboard.getConstructor();
@@ -392,8 +494,6 @@ public abstract class NMSStorage {
List<Field> list = getFields(PacketPlayOutScoreboardObjective, int.class);
PacketPlayOutScoreboardObjective_METHOD = list.get(list.size()-1);
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay_values = getEnumValues(EnumScoreboardHealthDisplay);
EnumScoreboardAction_values = getEnumValues(EnumScoreboardAction);
PacketPlayOutScoreboardObjective_RENDERTYPE = getFields(PacketPlayOutScoreboardObjective, EnumScoreboardHealthDisplay).get(0);
}
if (minorVersion >= 13) {
@@ -412,17 +512,19 @@ public abstract class NMSStorage {
}
}
/**
* Loads PacketPlayOutScoreboardTeam's methods, fields and constructors
*
* @throws ReflectiveOperationException
* If some method, field or constructor was not found
*/
protected void scoreboardTeam() throws ReflectiveOperationException {
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
PacketPlayOutScoreboardTeam_NAME = getFields(PacketPlayOutScoreboardTeam, String.class).get(0);
PacketPlayOutScoreboardTeam_ACTION = getInstanceFields(PacketPlayOutScoreboardTeam, int.class).get(0);
PacketPlayOutScoreboardTeam_PLAYERS = getFields(PacketPlayOutScoreboardTeam, Collection.class).get(0);
ScoreboardTeam_getPlayerNameSet = getMethods(ScoreboardTeam, Collection.class).get(0);
if (minorVersion >= 8) {
EnumNameTagVisibility_values = getEnumValues(EnumNameTagVisibility);
}
if (minorVersion >= 9) {
EnumTeamPush_values = getEnumValues(EnumTeamPush);
ScoreboardTeam_setCollisionRule = getMethods(ScoreboardTeam, void.class, EnumTeamPush).get(0);
}
if (minorVersion >= 13) {
@@ -432,14 +534,26 @@ public abstract class NMSStorage {
PacketPlayOutScoreboardTeam_of = getMethods(PacketPlayOutScoreboardTeam, PacketPlayOutScoreboardTeam, ScoreboardTeam).get(0);
PacketPlayOutScoreboardTeam_ofBoolean = getMethods(PacketPlayOutScoreboardTeam, PacketPlayOutScoreboardTeam, ScoreboardTeam, boolean.class).get(0);
PacketPlayOutScoreboardTeam_ofString = getMethods(PacketPlayOutScoreboardTeam, PacketPlayOutScoreboardTeam, ScoreboardTeam, String.class, PacketPlayOutScoreboardTeam_PlayerAction).get(0);
PacketPlayOutScoreboardTeam_PlayerAction_values = getEnumValues(PacketPlayOutScoreboardTeam_PlayerAction);
} else {
newPacketPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam.getConstructor(ScoreboardTeam, int.class);
}
}
/**
* Loads all classes used by the plugin
*
* @throws ClassNotFoundException
* If a class was not found
*/
public abstract void loadClasses() throws ClassNotFoundException;
/**
* Loads all fields and methods which must be loaded by their name due to
* lack of other methods of reliably retrieving them.
*
* @throws ReflectiveOperationException
* If a field or method was not found
*/
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
/**
@@ -480,6 +594,21 @@ public abstract class NMSStorage {
Arrays.toString(parameterTypes) + " in class " + clazz.getName() + ". Methods with matching parameters: " + list);
}
/**
* Returns Method from class with given name and parameter types. This should be equal to
* simply calling Class#getMethod, which however does not work with a few specific methods on
* CatServer fork. This workaround works for them.
*
* @param clazz
* Class to get method from
* @param name
* Name of the method
* @param parameterTypes
* Parameter types of the method
* @return Method with specified name and parameter types
* @throws NoSuchMethodException
* If no such method was found
*/
protected Method getMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
List<Method> list = new ArrayList<>();
for (Method m : clazz.getMethods()) {
@@ -498,7 +627,18 @@ public abstract class NMSStorage {
throw new NoSuchMethodException("No method found with name " + name + " in class " + clazz.getName() + " with parameters " + Arrays.toString(parameterTypes));
}
protected List<Method> getMethods(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes){
/**
* Returns all methods from class which return specified class type and have specified parameter types.
*
* @param clazz
* Class to get methods from
* @param returnType
* Return type of methods
* @param parameterTypes
* Parameter types of methods
* @return List of found methods matching requirements. If nothing is found, empty list is returned.
*/
protected List<Method> getMethods(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) {
List<Method> list = new ArrayList<>();
for (Method m : clazz.getDeclaredMethods()) {
if (m.getReturnType() != returnType || m.getParameterCount() != parameterTypes.length || !Modifier.isPublic(m.getModifiers())) continue;
@@ -524,8 +664,7 @@ public abstract class NMSStorage {
* field type to check for
* @return list of all fields with specified class type
*/
protected List<Field> getFields(Class<?> clazz, Class<?> type){
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
protected List<Field> getFields(Class<?> clazz, Class<?> type) {
List<Field> list = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == type) {
@@ -544,8 +683,7 @@ public abstract class NMSStorage {
* field type to check for
* @return list of all fields with specified class type
*/
public List<Field> getInstanceFields(Class<?> clazz, Class<?> type){
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
public List<Field> getInstanceFields(Class<?> clazz, Class<?> type) {
List<Field> list = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) {
@@ -575,6 +713,19 @@ public abstract class NMSStorage {
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
}
/**
* Returns field from class with specified possible names of the field.
* Names are checked in specified order and first match is returned.
* If no match is found, {@link NoSuchFieldException} is thrown.
*
* @param clazz
* Class to get field from
* @param potentialNames
* Potential names of the field
* @return Field with specified name
* @throws NoSuchFieldException
* If no match is found
*/
protected Field getField(Class<?> clazz, String... potentialNames) throws NoSuchFieldException {
for (String name : potentialNames) {
try {
@@ -586,29 +737,45 @@ public abstract class NMSStorage {
throw new NoSuchFieldException("No field found in class " + clazz.getName() + " with potential names " + Arrays.toString(potentialNames));
}
protected Enum[] getEnumValues(Class<?> enumClass) {
if (enumClass == null) throw new IllegalArgumentException("Class cannot be null");
if (!enumClass.isEnum()) throw new IllegalArgumentException(enumClass.getName() + " is not an enum class");
try {
return (Enum[]) enumClass.getMethod("values").invoke(null);
} catch (ReflectiveOperationException e) {
//this should never happen
return new Enum[0];
}
}
/**
* Returns server's minor version
* @return server's minor version
*/
public int getMinorVersion() {
return minorVersion;
}
/**
* Sets value of a field in object
*
* @param obj
* Instance of object where field should be set
* @param field
* Field to set value of
* @param value
* Value to set field to
* @throws IllegalAccessException
* If thrown by reflective operation
*/
public void setField(Object obj, Field field, Object value) throws IllegalAccessException {
field.set(obj, value);
}
/**
* Returns data watcher registry
* @return data watcher registry
*/
public DataWatcherRegistry getDataWatcherRegistry() {
return registry;
}
/**
* Makes object accessible and returns it for chaining.
*
* @param o
* Object to make accessible
* @return Entered object, for chaining
*/
public <T extends AccessibleObject> T setAccessible(T o) {
o.setAccessible(true);
return o;
@@ -635,4 +802,13 @@ public abstract class NMSStorage {
}
return fields;
}
public boolean is1_19_3Plus() {
try {
Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -4,10 +4,20 @@ import java.util.Arrays;
import me.neznamy.tab.api.protocol.TabPacket;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
public class PacketPlayOutEntityDestroy implements TabPacket {
/** Destroyed entities */
private final int[] entities;
/**
* Constructs new instance with given parameter
*
* @param entities
* Destroyed entities
*/
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
@@ -17,6 +27,10 @@ public class PacketPlayOutEntityDestroy implements TabPacket {
return String.format("PacketPlayOutEntityDestroy{entities=%s}", Arrays.toString(entities));
}
/**
* Returns {@link #entities}
* @return {@link #entities}
*/
public int[] getEntities() {
return entities;
}
@@ -4,12 +4,25 @@ import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
public class PacketPlayOutEntityMetadata implements TabPacket {
/** Entity's id */
private final int entityId;
/** Entity's data that changed */
private final DataWatcher dataWatcher;
/**
* Constructs new instance with given parameters
*
* @param entityId
* Entity's id
* @param dataWatcher
* Entity's data that changed
*/
public PacketPlayOutEntityMetadata(int entityId, DataWatcher dataWatcher) {
Preconditions.checkNotNull(dataWatcher, "data watcher");
this.entityId = entityId;
@@ -21,10 +34,18 @@ public class PacketPlayOutEntityMetadata implements TabPacket {
return String.format("PacketPlayOutEntityMetadata{entityId=%s,dataWatcher=%s}", entityId, dataWatcher);
}
/**
* Returns {@link #entityId}
* @return {@link #entityId}
*/
public int getEntityId() {
return entityId;
}
/**
* Returns {@link #dataWatcher}
* @return {@link #dataWatcher}
*/
public DataWatcher getDataWatcher() {
return dataWatcher;
}
@@ -5,12 +5,25 @@ import org.bukkit.Location;
import me.neznamy.tab.api.protocol.TabPacket;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
public class PacketPlayOutEntityTeleport implements TabPacket {
/** Entity's id */
private final int entityId;
/** Entity's new location */
private final Location location;
/**
* Constructs new instance with given parameters
*
* @param entityId
* Entity's id
* @param location
* Entity's spawn location
*/
public PacketPlayOutEntityTeleport(int entityId, Location location) {
Preconditions.checkNotNull(location, "location");
this.entityId = entityId;
@@ -22,10 +35,18 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
return String.format("PacketPlayOutEntityTeleport{entityId=%s,location=%s}", entityId, location);
}
/**
* Returns {@link #entityId}
* @return {@link #entityId}
*/
public int getEntityId() {
return entityId;
}
/**
* Returns {@link #location}
* @return {@link #location}
*/
public Location getLocation() {
return location;
}
@@ -9,18 +9,40 @@ import org.bukkit.entity.EntityType;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
/** Entity's id */
private final int entityId;
/** Entity's UUID (for 1.9+ servers) */
private final UUID uuid;
/** Type of entity */
private final EntityType entityType;
/** Entity's spawn location */
private final Location location;
/** Entity's data for 1.14- servers */
private final DataWatcher dataWatcher;
/**
* Constructs new instance with given parameters
*
* @param entityId
* Entity's id
* @param uuid
* Entity's UUID (for 1.9+ servers)
* @param entityType
* Type of entity
* @param location
* Entity's spawn location
* @param dataWatcher
* Entity's data for 1.14- servers
*/
public PacketPlayOutSpawnEntityLiving(int entityId, UUID uuid, EntityType entityType, Location location, DataWatcher dataWatcher) {
Preconditions.checkNotNull(uuid, "uuid");
Preconditions.checkNotNull(entityType, "entityType");
@@ -38,22 +60,42 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
entityId, uuid, entityType, location, dataWatcher);
}
/**
* Returns {@link #entityId}
* @return {@link #entityId}
*/
public int getEntityId() {
return entityId;
}
/**
* Returns {@link #uuid}
* @return {@link #uuid}
*/
public UUID getUniqueId() {
return uuid;
}
/**
* Returns {@link #entityType}
* @return {@link #entityType}
*/
public EntityType getEntityType() {
return entityType;
}
/**
* Returns {@link #location}
* @return {@link #location}
*/
public Location getLocation() {
return location;
}
/**
* Returns {@link #dataWatcher}
* @return {@link #dataWatcher}
*/
public DataWatcher getDataWatcher() {
return dataWatcher;
}
@@ -12,10 +12,10 @@ import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
*/
public class DataWatcher {
//DataWatcher data
/** Watched data */
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
//a helper for easier data write
/** Helper for easier data write */
private final DataWatcherHelper helper = new DataWatcherHelper(this);
/**
@@ -73,19 +73,19 @@ public class DataWatcher {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (nms.newDataWatcher.getParameterCount() == 1) { //1.7+
Object[] args = new Object[] {null};
nmsWatcher = nms.newDataWatcher.newInstance(args);
nmsWatcher = nms.newDataWatcher.newInstance(new Object[] {null});
} else {
nmsWatcher = nms.newDataWatcher.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object position;
if (nms.getMinorVersion() >= 9) {
position = nms.newDataWatcherObject.newInstance(item.getType().getPosition(), item.getType().getClassType());
position = nms.newDataWatcherObject.newInstance(item.getType().getPosition(), item.getType().getSerializer());
} else {
position = item.getType().getPosition();
}
nms.DataWatcher_REGISTER.invoke(nmsWatcher, position, item.getValue());
if (nms.is1_19_3Plus()) nms.DataWatcher_markDirty.invoke(nmsWatcher, position);
}
return nmsWatcher;
}
@@ -14,13 +14,13 @@ import java.util.Optional;
*/
public class DataWatcherHelper {
//position of armor stand flags
/** Position of armor stand flags */
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
//original DataWatcher to write to
/** DataWatcher to write to */
private final DataWatcher data;
//data watcher registry
/** Data Watcher registry reference */
private final DataWatcherRegistry registry;
/**
@@ -4,12 +4,15 @@ import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
/**
* Class representing NMS Data Watcher Item
*/
public class DataWatcherItem {
//type of value (position + data type (1.9+))
/** Value type */
private final DataWatcherObject type;
//actual data value
/** Data value */
private final Object value;
/**
@@ -38,18 +41,29 @@ public class DataWatcherItem {
*/
public static DataWatcherItem fromNMS(Object nmsItem) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object value = nms.DataWatcherItem_VALUE.get(nmsItem);
DataWatcherObject object;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9) {
Object nmsObject = nms.DataWatcherItem_TYPE.get(nmsItem);
return new DataWatcherItem(new DataWatcherObject(nms.DataWatcherObject_SLOT.getInt(nmsObject), nms.DataWatcherObject_SERIALIZER.get(nmsObject)), nms.DataWatcherItem_VALUE.get(nmsItem));
object = new DataWatcherObject(nms.DataWatcherObject_SLOT.getInt(nmsObject), nms.DataWatcherObject_SERIALIZER.get(nmsObject));
} else {
return new DataWatcherItem(new DataWatcherObject(nms.DataWatcherItem_TYPE.getInt(nmsItem), null), nms.DataWatcherItem_VALUE.get(nmsItem));
object = new DataWatcherObject(nms.DataWatcherItem_TYPE.getInt(nmsItem), null);
}
return new DataWatcherItem(object, value);
}
/**
* Returns {@link #type}
* @return {@link #type}
*/
public DataWatcherObject getType() {
return type;
}
/**
* Returns {@link #value}
* @return {@link #value}
*/
public Object getValue() {
return value;
}
@@ -5,30 +5,38 @@ package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
*/
public class DataWatcherObject {
//position in DataWatcher
/** Object's position in DataWatcher */
private final int position;
//value class type used since 1.9
private final Object classType;
/** Value encoder for 1.9+ */
private final Object serializer;
/**
* Constructs a new instance of this class with given parameters
*
* @param position
* position in DataWatcher
* @param classType
* value class type
* @param serializer
* Serializer for value in this slot
*/
public DataWatcherObject(int position, Object classType){
public DataWatcherObject(int position, Object serializer){
this.position = position;
this.classType = classType;
this.serializer = serializer;
}
/**
* Returns {@link #position}
* @return {@link #position}
*/
public int getPosition() {
return position;
}
public Object getClassType() {
return classType;
/**
* Returns {@link #serializer}
* @return {@link #serializer}
*/
public Object getSerializer() {
return serializer;
}
}
@@ -9,10 +9,19 @@ import java.util.Map;
*/
public class DataWatcherRegistry {
/** Byte encoder */
private Object registryByte;
/** Float encoder */
private Object registryFloat;
/** String encoder */
private Object registryString;
/** Encoder for optional component */
private Object registryOptionalIChatBaseComponent;
/** Boolean encoder */
private Object registryBoolean;
/**
@@ -27,8 +36,13 @@ public class DataWatcherRegistry {
registryFloat = fields.get("c");
registryString = fields.get("d");
if (nms.getMinorVersion() >= 13) {
registryOptionalIChatBaseComponent = fields.get("f");
registryBoolean = fields.get("i");
if (nms.is1_19_3Plus()) {
registryOptionalIChatBaseComponent = fields.get("g");
registryBoolean = fields.get("j");
} else {
registryOptionalIChatBaseComponent = fields.get("f");
registryBoolean = fields.get("i");
}
} else {
registryBoolean = fields.get("h");
}
@@ -43,22 +57,42 @@ public class DataWatcherRegistry {
}
}
/**
* Returns {@link #registryByte}
* @return {@link #registryByte}
*/
public Object getByte() {
return registryByte;
}
/**
* Returns {@link #registryFloat}
* @return {@link #registryFloat}
*/
public Object getFloat() {
return registryFloat;
}
/**
* Returns {@link #registryString}
* @return {@link #registryString}
*/
public Object getString() {
return registryString;
}
/**
* Returns {@link #registryOptionalIChatBaseComponent}
* @return {@link #registryOptionalIChatBaseComponent}
*/
public Object getOptionalComponent() {
return registryOptionalIChatBaseComponent;
}
/**
* Returns {@link #registryBoolean}
* @return {@link #registryBoolean}
*/
public Object getBoolean() {
return registryBoolean;
}
-231
View File
@@ -1,231 +0,0 @@
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer
header-footer:
enabled: true
disable-in-worlds:
- disabledworld
header:
- "&3&lServer name"
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
- "&7Online players: &f%online%"
- "&6Online staff: &e%staffonline%"
- ""
- "%animation:MyAnimation1%"
footer:
- "%animation:MyAnimation1%"
- "%animation:time%"
- "%animation:ping-tps%"
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
- ""
- "&7Visit our webpage %animation:web%"
per-world:
world1:
header:
- "an example of world with custom"
footer:
- "header/footer and prefix/suffix"
world2;world3:
header:
- "This is a shared header for"
- "world2 and world3"
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting
tablist-name-formatting:
enabled: true
align-tabsuffix-on-the-right: false
character-width-overrides: {}
anti-override: true
disable-in-worlds:
- disabledworld
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags
scoreboard-teams:
enabled: true
enable-collision: true
invisible-nametags: false
anti-override: true
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist
sorting-types:
- "GROUPS:owner,admin,mod,helper,builder,vip,default"
- "PLACEHOLDER_A_TO_Z:%player%"
case-sensitive-sorting: true
disable-in-worlds:
- disabledworld
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Unlimited-nametag-mode
unlimited-nametag-mode:
enabled: false
use-marker-tag-for-1-8-x-clients: false
disable-on-boats: true
disable-in-worlds:
- disabledworld
dynamic-lines:
- abovename
- nametag #the original one, combination of tagprefix + customtagname + tagsuffix
- belowname
- another
static-lines:
myCustomLine: 0.66
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Yellow-number
yellow-number-in-tablist:
enabled: true
value: "%ping%"
disable-in-worlds:
- disabledworld
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname
belowname-objective:
enabled: false
number: "%health%"
text: "&cHealth"
disable-in-worlds:
- disabledworld
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix
prevent-spectator-effect:
enabled: false
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar
bossbar:
enabled: false
toggle-command: /bossbar
remember-toggle-choice: false
hidden-by-default: false
disable-in-worlds:
- disabledworld
bars:
ServerInfo:
style: "PROGRESS" # for 1.9+: PROGRESS, NOTCHED_6, NOTCHED_10, NOTCHED_12, NOTCHED_20
color: "%animation:barcolors%" # for 1.9+: BLUE, GREEN, PINK, PURPLE, RED, WHITE, YELLOW
progress: "100" # in %
text: "&fWebsite: &bwww.domain.com"
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist
per-world-playerlist:
enabled: false
# players with tab.staff will always see all players
allow-bypass-permission: false
# players in these worlds will always see all players
ignore-effect-in-worlds:
- ignoredworld
- build
shared-playerlist-world-groups:
lobby:
- lobby1
- lobby2
minigames:
- paintball
- bedwars
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard
scoreboard:
enabled: false
toggle-command: /sb
remember-toggle-choice: false
hidden-by-default: false
use-numbers: false
static-number: 0
delay-on-join-milliseconds: 0
respect-other-plugins: true
disable-in-worlds:
- disabledworld
scoreboards:
admin:
# Only players with tab.scoreboard.admin can see this scoreboard, others will see scoreboard1
display-condition: "permission:tab.scoreboard.admin"
title: "Admin scoreboard"
lines:
- "%animation:MyAnimation1%"
- "&6Online:"
- "* &eOnline&7: &f%online%"
- "* &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%"
scoreboard1:
title: "Default"
lines:
- "%animation:MyAnimation1%"
- "&6My Stats:"
- "* &eKills&7: &f%statistic_player_kills%"
- "* &eDeaths&7: &f%statistic_deaths%"
- "* &eHealth&7: &f%health%"
- " "
- "&6Personal Info:"
- "* &bRank&7: &f%group%"
- "* &bPing&7: &f%ping%&7ms"
- "* &bWorld&7: &f%world%"
- "%animation:MyAnimation1%"
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Ping-Spoof
ping-spoof:
enabled: false
value: 0
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Pet-name-fix
fix-pet-names:
enabled: false
placeholders:
date-format: "dd.MM.yyyy"
time-format: "[HH:mm:ss / h:mm a]"
time-offset: 0
register-tab-expansion: false
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements
placeholder-output-replacements:
"%essentials_vanished%":
"yes": "&7| Vanished"
"no": ""
"%afk%":
true: " &o[AFK]&r"
false: ""
"%essentials_nickname%":
"%essentials_nickname%": "%player%" #placeholder did not parse correctly, show real name instead
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders
conditions:
nick: # use it with %condition:nick%
conditions:
- "%player%=%essentials_nickname%"
yes: "%player%"
no: "~%essentials_nickname%"
placeholderapi-refresh-intervals:
default-refresh-interval: 500
server:
"%server_uptime%": 1000
"%server_tps_1_colored%": 1000
"%server_unique_joins%": 5000
player:
"%player_health%": 200
"%player_ping%": 1000
"%vault_prefix%": 1000
relational:
"%rel_factionsuuid_relation_color%": 1000
# assigning groups by permission nodes instead of taking them from permission plugin
assign-groups-by-permissions: false
# if the option above is true, all groups are taken based on permissions and the one higher in this list is used as primary
# Warning! This is not sorting list and has nothing to do with sorting players in tablist!
primary-group-finding-list:
- Owner
- Admin
- Mod
- Helper
- default
debug: false
# https://github.com/NEZNAMY/TAB/wiki/MySQL
mysql:
enabled: false
host: 127.0.0.1
port: 3306
database: tab
username: user
password: password
@@ -15,8 +15,15 @@ import net.md_5.bungee.event.EventHandler;
*/
public class BungeeEventListener implements Listener {
/** Reference to BungeeCord platform implementation */
private final BungeePlatform platform;
/**
* Constructs new instance with given parameter
*
* @param platform
* BungeeCord implementation of platform
*/
public BungeeEventListener(BungeePlatform platform) {
this.platform = platform;
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.platforms.bungeecord;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
@@ -10,6 +10,7 @@ import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import net.md_5.bungee.protocol.PlayerPublicKey;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
@@ -43,6 +44,37 @@ public class BungeePacketBuilder extends PacketBuilder {
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) {
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
if (packet.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(packet.getEntries().stream().map(PlayerInfoData::getUniqueId).toArray(UUID[]::new));
return remove;
}
List<Item> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
Item item = new Item();
if (data.getDisplayName() != null) item.setDisplayName(data.getDisplayName().toString(clientVersion));
if (data.getGameMode() != null) item.setGamemode(data.getGameMode().ordinal()-1);
item.setListed(data.isListed());
item.setPing(data.getLatency());
if (data.getSkin() != null) {
item.setProperties(new Property[]{new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
item.setChatSessionId(data.getChatSessionId());
item.setPublicKey((PlayerPublicKey) data.getProfilePublicKey());
items.add(item);
}
PlayerListItemUpdate bungeePacket = new PlayerListItemUpdate();
PlayerListItemUpdate.Action[] array = packet.getActions().stream().map(action -> PlayerListItemUpdate.Action.valueOf(
action.toString().replace("GAME_MODE", "GAMEMODE"))).toArray(PlayerListItemUpdate.Action[]::new);
bungeePacket.setActions(EnumSet.of(array[0], array));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
}
List<Item> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
Item item = new Item();
@@ -64,10 +96,13 @@ public class BungeePacketBuilder extends PacketBuilder {
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
item.setPublicKey((PlayerPublicKey) data.getProfilePublicKey());
items.add(item);
}
PlayerListItem bungeePacket = new PlayerListItem();
bungeePacket.setAction(PlayerListItem.Action.valueOf(packet.getAction().toString().replace("GAME_MODE", "GAMEMODE")));
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
bungeePacket.setAction(PlayerListItem.Action.valueOf(action.toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
}
@@ -104,13 +139,50 @@ public class BungeePacketBuilder extends PacketBuilder {
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object bungeePacket, ProtocolVersion clientVersion) {
PlayerListItem item = (PlayerListItem) bungeePacket;
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : item.getItems()) {
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
listData.add(new PlayerInfoData(i.getUsername(), i.getUuid(), skin, i.getPing(), EnumGamemode.VALUES[i.getGamemode()+1], IChatBaseComponent.deserialize(i.getDisplayName())));
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
if (bungeePacket instanceof PlayerListItemRemove) {
return new PacketPlayOutPlayerInfo(
EnumPlayerInfoAction.REMOVE_PLAYER,
Arrays.stream(((PlayerListItemRemove) bungeePacket).getUuids()).map(PlayerInfoData::new).collect(Collectors.toList())
);
}
PlayerListItemUpdate item = (PlayerListItemUpdate) bungeePacket;
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : item.getItems()) {
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
listData.add(new PlayerInfoData(
i.getUsername(),
i.getUuid(),
skin,
Boolean.TRUE.equals(i.getListed()),
i.getPing() == null ? 0 : i.getPing(),
i.getGamemode() == null ? null : EnumGamemode.VALUES[i.getGamemode()+1],
IChatBaseComponent.deserialize(i.getDisplayName()),
i.getChatSessionId(),
i.getPublicKey()));
}
EnumPlayerInfoAction[] array = item.getActions().stream().map(action ->
EnumPlayerInfoAction.valueOf(action.toString().replace("GAMEMODE", "GAME_MODE"))).toArray(EnumPlayerInfoAction[]::new);
EnumSet<EnumPlayerInfoAction> actions = EnumSet.of(array[0], array);
return new PacketPlayOutPlayerInfo(actions, listData);
} else {
PlayerListItem item = (PlayerListItem) bungeePacket;
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : item.getItems()) {
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
listData.add(new PlayerInfoData(
i.getUsername(),
i.getUuid(),
skin,
true,
i.getPing() == null ? 0 : i.getPing(),
i.getGamemode() == null ? null : EnumGamemode.VALUES[i.getGamemode()+1],
IChatBaseComponent.deserialize(i.getDisplayName()),
null,
i.getPublicKey()));
}
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.valueOf(item.getAction().toString().replace("GAMEMODE", "GAME_MODE")), listData);
}
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.valueOf(item.getAction().toString().replace("GAMEMODE", "GAME_MODE")), listData);
}
@Override
@@ -11,6 +11,7 @@ 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.redis.RedisPlayer;
import me.neznamy.tab.shared.features.sorting.Sorting;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
@@ -68,6 +69,8 @@ public class BungeePipelineInjector extends PipelineInjector {
try {
switch(packet.getClass().getSimpleName()) {
case "PlayerListItem":
case "PlayerListItemUpdate":
case "PlayerListItemRemove":
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
case "Team":
@@ -91,7 +94,7 @@ public class BungeePipelineInjector extends PipelineInjector {
default:
break;
}
} catch (Exception e){
} catch (Exception | NoSuchMethodError e){
TAB.getInstance().getErrorManager().printError("An error occurred when analyzing packets for player " + player.getName() + " with client version " + player.getVersion().getFriendlyName(), e);
}
try {
@@ -111,9 +114,10 @@ public class BungeePipelineInjector extends PipelineInjector {
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (col.contains(p.getNickname()) && !((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName(), p.getTeamName());
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !packet.getName().equals(sorting.getShortTeamName(p))) {
logTeamOverride(packet.getName(), p.getName(), sorting.getShortTeamName(p));
col.remove(p.getNickname());
}
}
@@ -14,7 +14,7 @@ import net.md_5.bungee.api.plugin.Plugin;
public class BungeePlatform extends ProxyPlatform {
/**
* Constructs new instance
* Constructs new instance with BungeeCord packet builder
*/
public BungeePlatform() {
super(new BungeePacketBuilder());
@@ -1,22 +1,25 @@
package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import io.netty.channel.Channel;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.api.ProxyConfig;
import net.md_5.bungee.UserConnection;
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;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* TabPlayer implementation for BungeeCord
@@ -24,47 +27,22 @@ import java.lang.reflect.Method;
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Inaccessible bungee internals */
private static Method InitialHandler_getLoginProfile;
private static Method ChannelWrapper_getHandle;
private static Method LoginResult_Property_getValue;
private static Method LoginResult_Property_getSignature;
private static Method LoginResult_getProperties;
private static Method UserConnection_getGamemode;
private static Field wrapperField;
private static Object directionData;
private static Method getId;
/** Rewriting offline tablist uuids coming from backend servers to online uuids on online servers */
private static boolean tablistRewriteEnabled = true;
static {
try {
Class<?> initialHandler = Class.forName("net.md_5.bungee.connection.InitialHandler");
InitialHandler_getLoginProfile = initialHandler.getMethod("getLoginProfile");
Class<?> channelWrapper = Class.forName("net.md_5.bungee.netty.ChannelWrapper");
ChannelWrapper_getHandle = channelWrapper.getMethod("getHandle");
Class<?> loginResult = Class.forName("net.md_5.bungee.connection.LoginResult");
Class<?> loginResult_Property = Class.forName("net.md_5.bungee.protocol.Property");
LoginResult_Property_getValue = loginResult_Property.getMethod("getValue");
LoginResult_Property_getSignature = loginResult_Property.getMethod("getSignature");
LoginResult_getProperties = loginResult.getMethod("getProperties");
Class<?> userConnection = Class.forName("net.md_5.bungee.UserConnection");
UserConnection_getGamemode = userConnection.getMethod("getGamemode");
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.getDeclaredField("ch");
wrapperField = InitialHandler.class.getDeclaredField("ch");
wrapperField.setAccessible(true);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
}
try {
tablistRewriteEnabled = !(boolean) ProxyConfig.class.getMethod("isDisableTabListRewrite").invoke(ProxyServer.getInstance().getConfig());
} catch (ReflectiveOperationException e) {
// not Waterfall, always true on BungeeCord
}
}
/**
@@ -74,10 +52,10 @@ 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, tablistRewriteEnabled);
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1, true);
try {
channel = (Channel) ChannelWrapper_getHandle.invoke(wrapperField.get(getPlayer().getPendingConnection()));
} catch (IllegalAccessException | InvocationTargetException e) {
channel = ((ChannelWrapper)wrapperField.get(getPlayer().getPendingConnection())).getHandle();
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + getPlayer().getName(), e);
}
}
@@ -105,17 +83,11 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public Skin getSkin() {
try {
Object loginResult = InitialHandler_getLoginProfile.invoke(getPlayer().getPendingConnection());
if (loginResult == null) return null;
Object[] properties = (Object[]) LoginResult_getProperties.invoke(loginResult);
if (properties == null || properties.length == 0) return null;
return new Skin((String) LoginResult_Property_getValue.invoke(properties[0]),
(String) LoginResult_Property_getSignature.invoke(properties[0]));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
return null;
}
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
if (loginResult == null) return null;
Property[] properties = loginResult.getProperties();
if (properties == null || properties.length == 0) return null;
return new Skin(properties[0].getValue(), properties[0].getSignature());
}
@Override
@@ -163,12 +135,17 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public int getGamemode() {
try {
return (int) UserConnection_getGamemode.invoke(player);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get gamemode of " + getPlayer().getName(), e);
return 0;
}
return ((UserConnection)player).getGamemode();
}
@Override
public Object getProfilePublicKey() {
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getPublicKey();
}
@Override
public UUID getChatSessionId() {
throw new UnsupportedOperationException("Not implemented yet");
}
@Override
@@ -26,7 +26,7 @@ public class Main extends Plugin {
@Override
public void onEnable(){
if (!isVersionSupported()) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1637 and up to work. Get it at https://ci.md-5.net/job/BungeeCord/"));
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
return;
}
BungeePlatform platform = new BungeePlatform();
@@ -41,13 +41,13 @@ public class Main extends Plugin {
}
/**
* Checks for compatibility and returns true if version is supported, false if not
* Checks for compatibility and returns {@code true} if version is supported, {@code false} if not
*
* @return true if version is compatible, false if not
* @return {@code true} if version is compatible, {@code false} if not
*/
private boolean isVersionSupported() {
try {
Class.forName("net.md_5.bungee.protocol.Property");
Class.forName("net.md_5.bungee.protocol.packet.PlayerListItemUpdate");
return true;
} catch (ClassNotFoundException e) {
return false;
+1 -1
View File
@@ -1,5 +1,5 @@
# Project properties - we put these here so they can be modified without causing a recompile of the build scripts
projectVersion=3.1.3
projectVersion=3.2.0
# Gradle properties
org.gradle.daemon=true
+20 -12
View File
@@ -3,36 +3,40 @@ metadata.format.version = "1.1"
[versions]
# API
netty = "4.1.78.Final"
netty = "4.1.80.Final"
jsonSimple = "1.1.1"
snakeyaml = "1.30"
snakeyaml = "1.33"
yamlAssist = "1.0.5"
gson = "2.9.0"
gson = "2.9.1"
slf4j = "1.7.36"
minimessage = "4.11.0"
# General
luckperms = "5.4"
guava = "31.1-jre"
floodgate = "2.0-SNAPSHOT"
kotlin = "1.6.10"
floodgate = "2.2.0-SNAPSHOT"
kotlin = "1.7.10"
event = "3.0.0"
# Platforms
bukkit = "1.16.5-R0.1-SNAPSHOT"
bungeecord = "1.19-R0.1-SNAPSHOT"
krypton = "0.59.2"
velocity = "3.0.1"
velocity = "3.1.2-SNAPSHOT"
# Platform extras
placeholderapi = "2.11.1"
placeholderapi = "2.11.2"
vault = "1.7"
via = "4.3.1"
via = "4.4.2"
authlib = "1.5.25"
bstats = "3.0.0"
essentials = "2.19.4"
premiumVanish = "2.0.3"
redisBungee = "0.7.2"
essentials = "2.19.7"
premiumVanish = "2.7.11-2"
redisBungee = "0.7.3"
# Plugins
indra = "2.1.1"
shadow = "7.1.2"
[libraries]
@@ -56,7 +60,7 @@ event = { group = "net.kyori", name = "event-method", version.ref = "event" }
# Platforms
bukkit = { group = "org.purpurmc.purpur", name = "purpur-api", version.ref = "bukkit" }
bungeecord = { group = "net.md-5", name = "bungeecord-api", version.ref = "bungeecord" }
bungeecord = { group = "net.md-5", name = "bungeecord-proxy", version.ref = "bungeecord" }
krypton-api = { group = "org.kryptonmc", name = "krypton-api", version.ref = "krypton" }
krypton-server = { group = "org.kryptonmc", name = "krypton-server", version.ref = "krypton" }
krypton-annotationProcessor = { group = "org.kryptonmc", name = "krypton-annotation-processor", version.ref = "krypton" }
@@ -75,3 +79,7 @@ redisBungee = { group = "com.github.limework.redisbungee", name = "RedisBungee-B
bstats-bukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
bstats-bungeecord = { group = "org.bstats", name = "bstats-bungeecord", version.ref = "bstats" }
bstats-velocity = { group = "org.bstats", name = "bstats-velocity", version.ref = "bstats" }
# Plugins
plugin-indra = { group = "net.kyori", name = "indra-common", version.ref = "indra" }
plugin-shadow = { group = "gradle.plugin.com.github.johnrengelman", name = "shadow", version.ref = "shadow" }
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -95,8 +95,6 @@ class KryptonPlatform(
override fun isPluginEnabled(plugin: String): Boolean = server.pluginManager.isLoaded(plugin)
override fun getConfigName(): String = "kryptonconfig.yml"
private fun loadNametagFeature() {
if (!TAB.getInstance().config.getBoolean("scoreboard-teams.enabled", true)) return
if (TAB.getInstance().config.getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false)) {
@@ -1,214 +0,0 @@
# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml
scoreboard-teams:
enabled: true
enable-collision: true
invisible-nametags: false
anti-override: true
# types: GROUPS:<group1>,<group2>... , PERMISSIONS:<permission1>,<perm2>... , PLACEHOLDER:<output1>,<output2>... , PLACEHOLDER_A_TO_Z, PLACEHOLDER_Z_TO_A, PLACEHOLDER_HIGH_TO_LOW, PLACEHOLDER_LOW_TO_HIGH
sorting-types:
- "GROUPS:owner,admin,mod,helper,builder,vip,default"
- "PLACEHOLDER_A_TO_Z:%player%"
case-sensitive-sorting: true
disable-in-worlds:
- disabledworld
unlimited-nametag-mode:
enabled: false
use-marker-tag-for-1-8-x-clients: false
disable-on-boats: true
space-between-lines: 0.22
disable-in-worlds:
- disabledworld
dynamic-lines:
- abovename
- nametag #the original one, combination of tagprefix + customtagname + tagsuffix
- belowname
- another
static-lines:
myCustomLine: 0.66
tablist-name-formatting:
enabled: true
align-tabsuffix-on-the-right: false
character-width-overrides: {}
anti-override: true
disable-in-worlds:
- disabledworld
header-footer:
enabled: true
disable-in-worlds:
- disabledworld
header:
- "%animation:ServerName%"
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
- "&7Online players: &f%online%"
- "&6Online staff: &e%staffonline%"
- ""
- "%animation:MyAnimation1%"
footer:
- "%animation:MyAnimation1%"
- "%animation:time%"
- "%animation:ping-tps%"
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
- ""
- "&7Visit our webpage %animation:web%"
per-world:
world1:
header:
- "an example of world with custom"
footer:
- "header/footer and prefix/suffix"
world2-world3:
header:
- "This is a shared header for"
- "world2 and world3"
yellow-number-in-tablist:
enabled: true
value: "%ping%"
disable-in-worlds:
- disabledworld
belowname-objective:
enabled: false
number: "%health%"
text: "&c❤"
disable-in-worlds:
- disabledworld
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix
prevent-spectator-effect:
enabled: false
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar
bossbar:
enabled: false
toggle-command: /bossbar
remember-toggle-choice: false
hidden-by-default: false
disable-in-worlds:
- disabledworld
bars:
ServerInfo:
style: "PROGRESS" # can be one of: PROGRESS, NOTCHED_6, NOTCHED_10, NOTCHED_12, NOTCHED_20
color: "%animation:barcolors%" # can be one of: BLUE, GREEN, PINK, PURPLE, RED, WHITE, YELLOW
progress: "100" # in %
text: "&fWebsite: &bwww.domain.com"
tpsbar:
announcement-bar: true #marks this bossbar as announcement one and doesn't send it always
style: NOTCHED_20
color: GREEN
progress: "%mspt_mean_10s%"
text: "&7TPS&e: %condition:tps_5s% &7MSPT&e: %condition:mspt_mean_10s% &7Ping&e: %condition:ping%&7ms"
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist
per-world-playerlist:
enabled: false
# players with tab.staff will always see all players
allow-bypass-permission: false
# players in those worlds will always see all players
ignore-effect-in-worlds:
- ignoredworld
- build
shared-playerlist-world-groups:
lobby:
- lobby1
- lobby2
minigames:
- paintball
- bedwars
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard-(premium-only)
scoreboard:
enabled: false
# show/hide the scoreboard
toggle-command: /sb
remember-toggle-choice: false
hidden-by-default: false
# when enabled, will show 0-14 in red next to lines
use-numbers: false
# if use-numbers is disabled, this number will show in red next to all lines
static-number: 0
delay-on-join-milliseconds: 0
respect-other-plugins: true
disable-in-worlds:
- disabledworld
scoreboards:
admin:
# Only players with tab.scoreboard.admin can see this scoreboard, others will see scoreboard1
display-condition: "permission:tab.scoreboard.admin"
title: "Admin scoreboard"
lines:
- "%animation:MyAnimation1%"
- "&6Online:"
- "* &eOnline&7: &f%online%&7/&4%maxplayers%"
- "* &eCurrent World&7: &f%worldonline%"
- "* &eStaff&7: &f%staffonline%"
- ""
- "&6Server Info:"
- "* &bTPS&7: %tps_5s%"
- "* &bUptime&7: &f%server_uptime%"
- "* &bMemory&7: &f%memory-used%&7/&4%memory-max%"
- "%animation:MyAnimation1%"
scoreboard1:
title: "Default"
lines:
- "%animation:MyAnimation1%"
- "&6My Stats:"
- "* &eKills&7: &f%statistic_player_kills%"
- "* &eDeaths&7: &f%statistic_deaths%"
- "* &eHealth&7: &f%health%"
- ""
- "&6Personal Info:"
- "* &bRank&7: &f%rank%"
- "* &bPing&7: &f%ping%&7ms"
- "* &bWorld&7: &f%world%"
- "%animation:MyAnimation1%"
ping-spoof:
enabled: false
# when enabled, all players will have this value as their ping
value: 0
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Pet-name-fix
fix-pet-names:
enabled: false
# https://github.com/NEZNAMY/TAB/wiki/config.yml#placeholders
placeholders:
date-format: "dd.MM.yyyy"
time-format: "[HH:mm:ss / h:mm a]"
time-offset: 0
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements-(premium-only)
placeholder-output-replacements:
"%essentials_vanished%":
"yes": "&7| Vanished"
"no": ""
"%afk%":
true: " &o[AFK]&r"
false: ""
#if none of the above fits your needs, you can enable this to give groups using tab.group.<group name from the list below> permission
assign-groups-by-permissions: false
#if the option above is true, all groups are taken based on permissions and the one higher in this list is used as primary
primary-group-finding-list:
- Owner
- Admin
- Mod
- Helper
- default
debug: false
mysql:
enabled: false
host: 127.0.0.1
port: 3306
database: tab
username: user
password: password
+6 -1
View File
@@ -8,8 +8,9 @@ dependencyResolutionManagement {
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.essentialsx.net/releases/") // Essentials
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee
maven("https://repo.kryptonmc.org/releases") // YamlAssist, LuckPerms
maven("https://repo.kryptonmc.org/snapshots") // BungeeCord
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.purpurmc.org/snapshots") // Purpur, Vault
maven("https://repo.papermc.io/repository/maven-public/") // Velocity
@@ -19,6 +20,10 @@ dependencyResolutionManagement {
pluginManagement {
includeBuild("build-logic")
repositories {
gradlePluginPortal()
mavenCentral()
}
}
rootProject.name = "TAB"
@@ -89,7 +89,7 @@ public class CpuManager implements ThreadManager {
*/
public void enable() {
enabled = true;
taskQueue.forEach(this::submit);
new ArrayList<>(taskQueue).forEach(this::submit);
taskQueue.clear();
}
@@ -227,7 +227,8 @@ public class ErrorManager {
}
/**
* Makes interval divisible by 50 and sends error message if it was not already or was 0 or less
* Makes interval divisible by {@link me.neznamy.tab.api.TabConstants.Placeholder#MINIMUM_REFRESH_INTERVAL}
* and sends error message if it was not already or was 0 or less
*
* @param name
* name of animation used in error message
@@ -244,10 +245,10 @@ public class ErrorManager {
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s. Refresh cannot be negative! &bUsing 1000.", name, interval));
return 1000;
}
if (interval % 50 != 0) {
int newInterval = interval - interval%50;
if (newInterval == 0) newInterval = 50;
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s which is not divisible by 50! &bUsing %s.", name, interval, newInterval));
if (interval % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL != 0) {
int newInterval = interval - interval % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
if (newInterval == 0) newInterval = TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s which is not divisible by " + TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL + "! &bUsing %s.", name, interval, newInterval));
return newInterval;
}
return interval;
@@ -159,7 +159,8 @@ public class FeatureManagerImpl implements FeatureManager {
String from = changed.getServer();
((ITabPlayer)changed).setServer(to);
((ProxyTabPlayer)changed).sendJoinPluginMessage();
if (!isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION)) onLoginPacket(changed);
if (!isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION) || changed.getVersion().getMinorVersion() < 8)
onLoginPacket(changed);
for (TabFeature f : values) {
if (!f.overridesMethod("onServerChange")) continue;
long time = System.nanoTime();
@@ -327,4 +328,9 @@ public class FeatureManagerImpl implements FeatureManager {
public TabFeature getFeature(String name) {
return features.get(name);
}
@Override
public TabFeature[] getValues() {
return values;
}
}
@@ -9,9 +9,9 @@ import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutChat.ChatMessageType;
import me.neznamy.tab.api.team.TeamManager;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.shared.event.impl.PlayerLoadEventImpl;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.geysermc.floodgate.api.FloodgateApi;
/**
@@ -47,12 +47,6 @@ public abstract class ITabPlayer implements TabPlayer {
/** Player's permission group override using API */
private String temporaryGroup;
/** Player's personal scoreboard team name */
private String teamName;
/** Note explaining the team name, used in debug command */
private String teamNameNote;
/** Player's game type, {@code true} for Bedrock, {@code false} for Java */
private final boolean bedrockPlayer;
@@ -72,10 +66,10 @@ public abstract class ITabPlayer implements TabPlayer {
private boolean onJoinFinished;
/** Scoreboard teams player has registered */
private final List<String> registeredTeams = new ArrayList<>();
private final Set<String> registeredTeams = new HashSet<>();
/** Scoreboard objectives player has registered */
private final List<String> registeredObjectives = new ArrayList<>();
private final Set<String> registeredObjectives = new HashSet<>();
/** Player's name as seen in GameProfile, can be altered by nick plugins */
private String nickname;
@@ -146,26 +140,6 @@ public abstract class ITabPlayer implements TabPlayer {
return setProperty(feature, identifier, rawValue, null, false);
}
/**
* Sets team name to given value
*
* @param name
* Team name to use
*/
public void setTeamName(String name) {
teamName = name;
}
/**
* Sets team name note to given value
*
* @param note
* New team name note
*/
public void setTeamNameNote(String note) {
teamNameNote = note;
}
/**
* Marks the player as loaded and calls PlayerLoadEvent
*
@@ -175,7 +149,6 @@ public abstract class ITabPlayer implements TabPlayer {
public void markAsLoaded(boolean join) {
onJoinFinished = true;
if (TAB.getInstance().getEventBus() != null) TAB.getInstance().getEventBus().fire(new PlayerLoadEventImpl(this, join));
TAB.getInstance().getPlatform().callLoadEvent(this);
}
/**
@@ -298,13 +271,20 @@ public abstract class ITabPlayer implements TabPlayer {
String team = ((PacketPlayOutScoreboardTeam) packet).getName();
int method = ((PacketPlayOutScoreboardTeam) packet).getAction();
if (method == 0) {
if (registeredTeams.contains(team)) {
if (!registeredTeams.add(team)) {
TAB.getInstance().getErrorManager().printError("Tried to register duplicated team " + team + " to player " + getName());
return;
}
registeredTeams.add(team);
} else if (method == 1) {
registeredTeams.remove(team);
if (!registeredTeams.remove(team)) {
TAB.getInstance().getErrorManager().printError("Tried to unregister non-existing team " + team + " for player " + getName());
return;
}
} else if (method == 2) {
if (!registeredTeams.contains(team)) {
TAB.getInstance().getErrorManager().printError("Tried to modify non-existing team " + team + " for player " + getName());
return;
}
}
}
//avoiding BungeeCord bug kicking all players
@@ -312,13 +292,20 @@ public abstract class ITabPlayer implements TabPlayer {
String objective = ((PacketPlayOutScoreboardObjective) packet).getObjectiveName();
int method = ((PacketPlayOutScoreboardObjective) packet).getAction();
if (method == 0) {
if (registeredObjectives.contains(objective)) {
if (!registeredObjectives.add(objective)) {
TAB.getInstance().getErrorManager().printError("Tried to register duplicated objective " + objective + " to player " + getName());
return;
}
registeredObjectives.add(objective);
} else if (method == 1) {
registeredObjectives.remove(objective);
if (!registeredObjectives.remove(objective)) {
TAB.getInstance().getErrorManager().printError("Tried to unregister non-existing objective " + objective + " for player " + getName());
return;
}
} else if (method == 2) {
if (!registeredObjectives.contains(objective)) {
TAB.getInstance().getErrorManager().printError("Tried to modify non-existing objective " + objective + " for player " + getName());
return;
}
}
}
//avoiding console spam from geyser
@@ -391,20 +378,15 @@ public abstract class ITabPlayer implements TabPlayer {
}
@Override
public String getTeamName() {
TeamManager teams = TAB.getInstance().getTeamManager();
if (teams != null && teams.getForcedTeamName(this) != null) return teams.getForcedTeamName(this);
return teamName;
public boolean isBedrockPlayer() {
return bedrockPlayer;
}
@Override
public String getTeamNameNote() {
return teamNameNote;
}
@Override
public boolean isBedrockPlayer() {
return bedrockPlayer;
public String getTeamName() {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (sorting == null) return null;
return sorting.getShortTeamName(this);
}
@Override
@@ -415,4 +397,8 @@ public abstract class ITabPlayer implements TabPlayer {
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
}
@@ -1,6 +1,5 @@
package me.neznamy.tab.shared;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.protocol.PacketBuilder;
import me.neznamy.tab.shared.permission.PermissionPlugin;
@@ -25,22 +24,6 @@ public abstract class Platform {
this.packetBuilder = packetBuilder;
}
/**
* Calls platform-specific load event.
* This method is called when plugin is fully enabled.
*/
public void callLoadEvent(){
//do nothing by default, old event system will be removed
}
/**
* Calls platform-specific player load event.
* This method is called when player is fully loaded.
*/
public void callLoadEvent(TabPlayer player){
//do nothing by default, old event system will be removed
}
/**
* Returns platform-specific packet builder implementation
*
@@ -89,12 +72,4 @@ public abstract class Platform {
* @return Version string if plugin is installed, {@code null} if not
*/
public abstract String getPluginVersion(String plugin);
/**
* Returns name of default config file for this platform
* as it appears in the final jar in root directory.
*
* @return name of default config file for this platform
*/
public abstract String getConfigName();
}
@@ -114,7 +114,6 @@ public class TAB extends TabAPI {
this.serverVersionString = serverVersionString;
this.dataFolder = dataFolder;
this.logger = logger;
TabAPI.setInstance(this);
try {
Class.forName("org.geysermc.floodgate.api.FloodgateApi");
floodgate = true;
@@ -161,7 +160,6 @@ public class TAB extends TabAPI {
for (TabPlayer p : players) ((ITabPlayer)p).markAsLoaded(false);
cpu.enable();
if (eventBus != null) eventBus.fire(TabLoadEventImpl.getInstance());
platform.callLoadEvent();
disabled = false;
sendConsoleMessage("&aEnabled in " + (System.currentTimeMillis()-time) + "ms", true);
return configuration.getMessages().getReloadSuccess();
@@ -233,7 +231,7 @@ public class TAB extends TabAPI {
if (serverVersion.getMinorVersion() >= 8 && configuration.getLayout().getBoolean("enabled", false)) {
if (getTeamManager() == null) {
//sorting is disabled, but layout needs team names
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting(null));
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
}
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManager());
}
@@ -7,11 +7,12 @@ import java.util.Map;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.shared.DynamicText;
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.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
/**
* Handler for "/tab debug" subcommand
@@ -59,6 +60,7 @@ public class DebugCommand extends SubCommand {
sendMessage(sender, "&6Permission plugin: &b" + TAB.getInstance().getGroupManager().getPlugin().getName());
sendMessage(sender, "&6Permission group choice logic: &b" + getGroupChoiceLogic());
sendMessage(sender, "&6Sorting system: &b" + getSortingType());
sendMessage(sender, "&6Storage type: &b" + (tab.getConfiguration().getGroups() instanceof ConfigurationFile ? "File" : "MySQL"));
sendMessage(sender, separator);
if (analyzed == null) return;
sendMessage(sender, "&ePlayer: &a" + analyzed.getName());
@@ -109,9 +111,9 @@ public class DebugCommand extends SubCommand {
* @return sorting type
*/
private String getSortingType() {
NameTag nametag = (NameTag) TAB.getInstance().getTeamManager();
if (nametag != null) {
return nametag.getSorting().typesToString();
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (sorting != null) {
return sorting.typesToString();
} else {
return "&cDISABLED";
}
@@ -139,17 +141,16 @@ public class DebugCommand extends SubCommand {
* @return team name of specified player
*/
private String getTeamName(TabPlayer analyzed) {
if (TAB.getInstance().getTeamManager() != null) {
if (((TabFeature) TAB.getInstance().getTeamManager()).isDisabled(analyzed.getServer(), analyzed.getWorld())) {
return "&eTeam name: &cSorting is disabled in player's world/server";
} else {
return "&eTeam name: &a" + analyzed.getTeamName();
}
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (sorting == null) return "";
if (TAB.getInstance().getTeamManager() != null &&
((TabFeature) TAB.getInstance().getTeamManager()).isDisabled(analyzed.getServer(), analyzed.getWorld())) {
return "&eTeam name: &cSorting is disabled in player's world/server";
}
return "";
return "&eTeam name: &a" + (TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.LAYOUT)
? sorting.getFullTeamName(analyzed) : sorting.getShortTeamName(analyzed));
}
/**
* Returns team name note of specified player
*
@@ -158,11 +159,13 @@ public class DebugCommand extends SubCommand {
* @return team name note of specified player
*/
private String getTeamNameNote(TabPlayer analyzed) {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (sorting == null) return "";
if (TAB.getInstance().getTeamManager() != null &&
!((TabFeature) TAB.getInstance().getTeamManager()).isDisabled(analyzed.getServer(), analyzed.getWorld()) &&
analyzed.getTeamNameNote() != null)
return "&eTeam name note: &r" + analyzed.getTeamNameNote();
return "";
((TabFeature) TAB.getInstance().getTeamManager()).isDisabled(analyzed.getServer(), analyzed.getWorld())) {
return "";
}
return "&eSorting note: &r" + sorting.getTeamNameNote(analyzed);
}
/**
@@ -6,9 +6,7 @@ import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.*;
/**
* Handler for "/tab nametag" subcommand
@@ -24,16 +22,18 @@ public class NameTagCommand extends SubCommand {
@Override
public void execute(TabPlayer sender, String[] args) {
if (args.length != 1) {
if (args.length == 0 || args.length > 3) {
sendMessages(sender, getMessages().getNameTagHelpMenu());
return;
}
boolean silent = args.length >= 3 && args[2].equals("-s");
switch (args[0].toLowerCase(Locale.US)) {
case "preview":
preview(sender);
preview(sender,getTarget(sender, args, TabConstants.Permission.COMMAND_NAMETAG_PREVIEW_OTHER, TabConstants.Permission.COMMAND_NAMETAG_PREVIEW),silent);
break;
case "toggle":
toggle(sender);
toggle(sender,getTarget(sender, args, TabConstants.Permission.COMMAND_NAMETAG_TOGGLE_OTHER, TabConstants.Permission.COMMAND_NAMETAG_TOGGLE),silent);
break;
default:
sendMessages(sender, getMessages().getNameTagHelpMenu());
@@ -41,46 +41,54 @@ public class NameTagCommand extends SubCommand {
}
}
private void preview(TabPlayer sender) {
if (sender == null) {
sendMessage(null, getMessages().getCommandOnlyFromGame());
return;
}
if (!hasPermission(sender, TabConstants.Permission.COMMAND_NAMETAG_PREVIEW)) {
sendMessage(sender, getMessages().getNoPermission());
return;
}
private void preview(TabPlayer sender, TabPlayer target, boolean silent) {
if (target == null) return;
NameTagX nameTagX = (NameTagX) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
if (nameTagX == null) {
sendMessage(sender, getMessages().getUnlimitedNametagModeNotEnabled());
return;
}
if (nameTagX.hasDisabledArmorStands(sender)) {
if (nameTagX.hasDisabledArmorStands(target)) {
sendMessage(sender, getMessages().getArmorStandsDisabledCannotPreview());
return;
}
nameTagX.toggleNametagPreview(sender);
nameTagX.toggleNametagPreview(target, !silent);
}
private void toggle(TabPlayer sender) {
if (sender == null) {
sendMessage(null, getMessages().getCommandOnlyFromGame());
return;
}
if (!hasPermission(sender, TabConstants.Permission.COMMAND_NAMETAG_TOGGLE)) {
sendMessage(sender, getMessages().getNoPermission());
return;
}
private void toggle(TabPlayer sender, TabPlayer target, boolean silent) {
if (target == null) return;
TeamManager teams = TAB.getInstance().getTeamManager();
if (teams == null) {
sendMessage(sender, getMessages().getNameTagFeatureNotEnabled());
return;
}
teams.toggleNameTagVisibilityView(sender, true);
teams.toggleNameTagVisibilityView(target, !silent);
}
private TabPlayer getTarget(TabPlayer sender, String[] args, String permissionOther, String permission) {
if (args.length >= 2 && TAB.getInstance().getPlayer(args[1]) != null) {
if (hasPermission(sender, permissionOther)) {
return TAB.getInstance().getPlayer(args[1]);
} else {
sendMessage(sender, getMessages().getNoPermission());
}
} else {
if (hasPermission(sender, permission)) {
return sender;
} else {
sendMessage(sender, getMessages().getNoPermission());
}
}
return null;
}
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
return getStartingArgument(Arrays.asList("preview", "toggle"), arguments[0]);
if (arguments.length == 1) return getStartingArgument(Arrays.asList("toggle", "preview"), arguments[0]);
if (arguments.length == 2) return getOnlinePlayers(arguments[1]);
if (arguments.length == 3) return getStartingArgument(Collections.singletonList("-s"), arguments[2]);
return new ArrayList<>();
}
}
@@ -14,7 +14,7 @@ import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
/**
* Handler for "/tab parse <player> <placeholder>" subcommand
* Handler for "/tab parse &lt;player&gt; &lt;placeholder&gt;" subcommand
*/
public class ParseCommand extends SubCommand {
@@ -13,7 +13,7 @@ import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
/**
* Handler for "/tab scoreboard [on/off/toggle] [player] [options]" subcommand
* and "/tab scoreboard show <name> [player]"
* and "/tab scoreboard show &lt;name&gt; [player]"
*/
public class ScoreboardCommand extends SubCommand {
@@ -21,13 +21,14 @@ import java.util.List;
public class Configs {
//config.yml file
private final ConfigurationFile config = new YamlConfigurationFile(getClass().getClassLoader().getResourceAsStream(TAB.getInstance().getPlatform().getConfigName()),
private final ConfigurationFile config = new YamlConfigurationFile(getClass().getClassLoader().getResourceAsStream("config.yml"),
new File(TAB.getInstance().getDataFolder(), "config.yml"));
private final boolean bukkitPermissions = TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY && config.getBoolean("use-bukkit-permissions-manager", false);
private final boolean debugMode = config.getBoolean("debug", false);
private final boolean removeGhostPlayers = getSecretOption("remove-ghost-players", false);
private final boolean pipelineInjection = getSecretOption("pipeline-injection", true) && TAB.getInstance().getServerVersion().getMinorVersion() >= 8;
private final String serverName = getSecretOption("server-name", "N/A");
//animations.yml file
private final ConfigurationFile animation = new YamlConfigurationFile(getClass().getClassLoader().getResourceAsStream("animations.yml"),
@@ -64,12 +65,18 @@ public class Configs {
converter.convertAnimationFile(animation);
if (config.getBoolean("mysql.enabled", false)) {
try {
// Initialization to try to avoid java.sql.SQLException: No suitable driver found
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
Class.forName("com.mysql.jdbc.Driver");
}
mysql = new MySQL(config.getString("mysql.host", "127.0.0.1"), config.getInt("mysql.port", 3306),
config.getString("mysql.database", "tab"), config.getString("mysql.username", "user"), config.getString("mysql.password", "password"));
groupFile = new MySQLGroupConfiguration(mysql);
userFile = new MySQLUserConfiguration(mysql);
return;
} catch (SQLException e) {
} catch (SQLException | ClassNotFoundException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to connect to MySQL", e);
}
}
@@ -163,4 +170,8 @@ public class Configs {
public boolean isDebugMode() {
return debugMode;
}
public String getServerName() {
return serverName;
}
}
@@ -25,7 +25,7 @@ public class Converter {
* The method contains a check if the conversion is necessary, so it is always safe
* to call this method, even if conversion is not needed.
* <p>
* This change was made from 2.8.10 -> 2.9.0
* This change was made from 2.8.10 -&gt; 2.9.0
*
* @param animations
* animation file to convert
@@ -214,8 +214,8 @@ public class MessageFile extends YamlConfigurationFile {
public List<String> getNameTagHelpMenu() {
return getStringList("nametag-help-menu", Arrays.asList(
"/tab nametag preview - toggles armor stand preview mode",
"/tab nametag toggle - toggles nametags on all players for command sender"
"/tab nametag preview [player] - toggles armor stand preview mode",
"/tab nametag toggle [player] - toggles nametags on all players for command sender"
));
}
@@ -25,7 +25,7 @@ public final class EventBusImpl implements EventBus {
this.bus = new SimpleEventBus<TabEvent>(TabEvent.class) {
@Override
protected boolean shouldPost(@NonNull TabEvent event, @NonNull EventSubscriber subscriber) {
protected boolean shouldPost(@NonNull TabEvent event, @NonNull EventSubscriber<?> subscriber) {
return true;
}
};
@@ -107,9 +107,9 @@ public class HeaderFooter extends TabFeature implements HeaderFooterManager {
if (value.length > 0) {
return value[0];
}
List<String> lines = TAB.getInstance().getConfiguration().getConfig().getStringList("header-footer.per-server." + TAB.getInstance().getConfiguration().getGroup(serverGroups, p.getServer()) + "." + property);
List<String> lines = TAB.getInstance().getConfiguration().getConfig().getStringList("header-footer.per-world." + TAB.getInstance().getConfiguration().getGroup(worldGroups, p.getWorld()) + "." + property);
if (lines == null) {
lines = TAB.getInstance().getConfiguration().getConfig().getStringList("header-footer.per-world." + TAB.getInstance().getConfiguration().getGroup(worldGroups, p.getWorld()) + "." + property);
lines = TAB.getInstance().getConfiguration().getConfig().getStringList("header-footer.per-server." + TAB.getInstance().getConfiguration().getGroup(serverGroups, p.getServer()) + "." + property);
}
if (lines == null) {
lines = TAB.getInstance().getConfiguration().getConfig().getStringList("header-footer." + property);
@@ -38,7 +38,7 @@ public class NickCompatibility extends TabFeature {
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo packet) {
if (packet.getAction() != EnumPlayerInfoAction.ADD_PLAYER) return;
if (!packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER)) return;
for (PlayerInfoData data : packet.getEntries()) {
TabPlayer packetPlayer = TAB.getInstance().getPlayerByTabListUUID(data.getUniqueId());
if (packetPlayer != null && packetPlayer != receiver && !packetPlayer.getNickname().equals(data.getName())) {
@@ -63,11 +63,11 @@ public class NickCompatibility extends TabFeature {
if (nameTags != null && !nameTags.hasTeamHandlingPaused(player)) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(player.getTeamName()), this);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(nameTags.getSorting().getShortTeamName(player)), this);
String replacedPrefix = player.getProperty(TabConstants.Property.TAGPREFIX).getFormat(viewer);
String replacedSuffix = player.getProperty(TabConstants.Property.TAGSUFFIX).getFormat(viewer);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(player.getTeamName(), replacedPrefix, replacedSuffix, nameTags.translate(nameTags.getTeamVisibility(player, viewer)),
nameTags.translate(nameTags.getCollisionManager().getCollision(player)), Collections.singletonList(player.getNickname()), 2), this);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(nameTags.getSorting().getShortTeamName(player), replacedPrefix, replacedSuffix, nameTags.translate(nameTags.getTeamVisibility(player, viewer)),
nameTags.translate(nameTags.getCollisionManager().getCollision(player)), Collections.singletonList(player.getNickname()), nameTags.getTeamOptions()), this);
}
}
if (belowname != null) {
@@ -36,7 +36,7 @@ public class PingSpoof extends TabFeature {
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (info.getAction() != EnumPlayerInfoAction.UPDATE_LATENCY && info.getAction() != EnumPlayerInfoAction.ADD_PLAYER) return;
if (!info.getActions().contains(EnumPlayerInfoAction.UPDATE_LATENCY)) return;
for (PlayerInfoData playerInfoData : info.getEntries()) {
if (TAB.getInstance().getPlayerByTabListUUID(playerInfoData.getUniqueId()) != null) playerInfoData.setLatency(value);
if (layoutManager != null) {
@@ -72,19 +72,7 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
}
private void refresh(Map<TabPlayer, Set<TabFeature>> forceUpdate, Map<TabPlayer, Set<TabFeature>> update) {
for (Entry<TabPlayer, Set<TabFeature>> entry : update.entrySet()) {
if (forceUpdate.containsKey(entry.getKey())) {
entry.getValue().removeAll(forceUpdate.get(entry.getKey()));
}
}
long startRefreshTime = System.nanoTime();
for (Entry<TabPlayer, Set<TabFeature>> entry : forceUpdate.entrySet()) {
for (TabFeature r : entry.getValue()) {
long startTime = System.nanoTime();
r.refresh(entry.getKey(), true);
TAB.getInstance().getCPUManager().addTime(r.getFeatureName(), r.getRefreshDisplayName(), System.nanoTime() - startTime);
}
}
for (Entry<TabPlayer, Set<TabFeature>> entry : update.entrySet()) {
for (TabFeature r : entry.getValue()) {
long startTime = System.nanoTime();
@@ -92,6 +80,13 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
TAB.getInstance().getCPUManager().addTime(r.getFeatureName(), r.getRefreshDisplayName(), System.nanoTime() - startTime);
}
}
for (Entry<TabPlayer, Set<TabFeature>> entry : forceUpdate.entrySet()) {
for (TabFeature r : entry.getValue()) {
long startTime = System.nanoTime();
r.refresh(entry.getKey(), true);
TAB.getInstance().getCPUManager().addTime(r.getFeatureName(), r.getRefreshDisplayName(), System.nanoTime() - startTime);
}
}
//subtracting back usage by this method from placeholder refreshing usage, since it is already counted under different name in this method
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REFRESHING, startRefreshTime-System.nanoTime());
}
@@ -252,7 +247,7 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
tabExpansion.setPlaceholderValue(all, p.getIdentifier(), p.getLastValue(all));
}
}
if (p.getRefresh() % 50 == 0 && p.getRefresh() > 0) {
if (p.getRefresh() % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL == 0 && p.getRefresh() > 0) {
int refresh = gcd(p.getRefresh(), refreshInterval);
if (refreshInterval != refresh) {
TAB.getInstance().debug("Decreasing refresh interval of placeholder refreshing task to " + refresh + "ms due to placeholder " + identifier);
@@ -301,9 +296,17 @@ public class PlaceholderManagerImpl extends TabFeature implements PlaceholderMan
@Override
public void onJoin(TabPlayer connectedPlayer) {
if (tabExpansion == null) return;
for (Placeholder p : usedPlaceholders) {
if (p instanceof ServerPlaceholder) { // server placeholders don't update on join
if (p instanceof RelationalPlaceholderImpl) {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
((RelationalPlaceholderImpl)p).update(connectedPlayer, all);
((RelationalPlaceholderImpl)p).update(all, connectedPlayer);
}
}
if (p instanceof PlayerPlaceholderImpl) {
((PlayerPlaceholderImpl)p).update(connectedPlayer);
}
if (tabExpansion != null && p instanceof ServerPlaceholder) { // server placeholders don't update on join
tabExpansion.setPlaceholderValue(connectedPlayer, p.getIdentifier(), ((ServerPlaceholder) p).getLastValue());
}
}
@@ -5,16 +5,12 @@ import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.TablistFormatManager;
import me.neznamy.tab.api.*;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
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.layout.Layout;
import me.neznamy.tab.shared.features.layout.LayoutManager;
@@ -150,12 +146,14 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
public void onServerChange(TabPlayer p, String from, String to) {
onWorldChange(p, null, null);
if (TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.PIPELINE_INJECTION)) return;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() >= 8) p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME,
new PlayerInfoData(getTablistUUID(all, p), getTabFormat(all, p))), this);
if (all.getVersion().getMinorVersion() >= 8) all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME,
new PlayerInfoData(getTablistUUID(p, all), getTabFormat(p, all))), this);
}
TAB.getInstance().getCPUManager().runTaskLater(300, this, TabConstants.CpuUsageCategory.PLAYER_JOIN, () -> {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() >= 8) p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME,
new PlayerInfoData(getTablistUUID(all, p), getTabFormat(all, p))), this);
if (all.getVersion().getMinorVersion() >= 8) all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME,
new PlayerInfoData(getTablistUUID(p, all), getTabFormat(p, all))), this);
}
});
}
@Override
@@ -222,10 +220,10 @@ public class PlayerList extends TabFeature implements TablistFormatManager {
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (disabling || !antiOverrideTabList) return;
if (info.getAction() != EnumPlayerInfoAction.UPDATE_DISPLAY_NAME && info.getAction() != EnumPlayerInfoAction.ADD_PLAYER) return;
if (!info.getActions().contains(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME)) return;
for (PlayerInfoData playerInfoData : info.getEntries()) {
TabPlayer packetPlayer = TAB.getInstance().getPlayerByTabListUUID(playerInfoData.getUniqueId());
if (packetPlayer != null && !isDisabledPlayer(packetPlayer)) {
if (packetPlayer != null && !isDisabledPlayer(packetPlayer) && packetPlayer.getTablistUUID() == getTablistUUID(packetPlayer, receiver)) {
playerInfoData.setDisplayName(getTabFormat(packetPlayer, receiver));
}
}
@@ -48,7 +48,7 @@ public class SpectatorFix extends TabFeature {
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (info.getAction() != EnumPlayerInfoAction.UPDATE_GAME_MODE && info.getAction() != EnumPlayerInfoAction.ADD_PLAYER) return;
if (!info.getActions().contains(EnumPlayerInfoAction.UPDATE_GAME_MODE)) return;
for (PlayerInfoData playerInfoData : info.getEntries()) {
if (playerInfoData.getGameMode() != EnumGamemode.SPECTATOR) continue;
if (receiver.hasPermission(TabConstants.Permission.SPECTATOR_BYPASS)) continue;
@@ -93,13 +93,13 @@ public class PlayerView {
/**
* Returns a combination of normal and bold spaces to build exactly the requested amount of pixels.
* Must be at least 12 as lower numbers cannot always be built using numbers 4 (normal space + 1 pixel) and 5 (bold space + 1 pixel)
* Returns the result string with normal then bold spaces, such as " &l &r"
* Returns the result string with normal then bold spaces, such as " &amp;l &amp;r"
*
* @param pixelWidth
* amount of pixels to be built
* @return string consisting of spaces and &l &r
* @return string consisting of spaces and &amp;l &amp;r
* @throws IllegalArgumentException
* if pixelWidth is < 12
* if pixelWidth is &lt; 12
*/
private String buildSpaces(int pixelWidth) {
if (pixelWidth < 12) throw new IllegalArgumentException("Cannot build space lower than 12 pixels wide");
@@ -36,7 +36,7 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
private final List<BossBar> announcements = new ArrayList<>();
//players with toggled BossBar
private final List<String> bossBarOffPlayers;
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;
@@ -54,11 +54,6 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
if (!line.isAnnouncementBar()) defaultBars.add(bar.toString());
}
lineValues = lines.values().toArray(new BossBar[0]);
if (rememberToggleChoice) {
bossBarOffPlayers = TAB.getInstance().getConfiguration().getPlayerDataFile().getStringList("bossbar-off", new ArrayList<>());
} else {
bossBarOffPlayers = Collections.emptyList();
}
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));
@@ -111,6 +106,7 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
line.removePlayer(p); //remove all BossBars and then resend them again to keep them displayed in defined order
}
showBossBars(p, defaultBars);
showBossBars(p, announcements.stream().map(BossBar::getName).collect(Collectors.toList()));
}
@Override
@@ -178,7 +174,6 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
* list of BossBars to check
*/
private void showBossBars(TabPlayer p, List<String> bars) {
if (bars == null) return;
for (String defaultBar : bars) {
BossBarLine bar = (BossBarLine) lines.get(defaultBar);
if (bar.isConditionMet(p) && !bar.containsPlayer(p)) {
@@ -277,18 +272,19 @@ public class BossBarManagerImpl extends TabFeature implements BossBarManager {
public void announceBossBar(String bossBar, int duration) {
BossBar line = lines.get(bossBar);
if (line == null) throw new IllegalArgumentException("No registered BossBar found with name " + bossBar);
List<TabPlayer> players = Arrays.stream(TAB.getInstance().getOnlinePlayers()).filter(
p -> !isDisabledPlayer(p) && hasBossBarVisible(p)).collect(Collectors.toList());
TAB.getInstance().getCPUManager().runTask(() -> {
TAB.getInstance().getPlaceholderManager().getPlaceholder(TabConstants.Placeholder.COUNTDOWN).markAsUsed();
announcements.add(line);
announceEndTime = System.currentTimeMillis() + duration* 1000L;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (!hasBossBarVisible(all)) continue;
for (TabPlayer all : players) {
if (((BossBarLine)line).isConditionMet(all)) line.addPlayer(all);
}
});
TAB.getInstance().getCPUManager().runTaskLater(duration*1000,
this, "Removing announced BossBar", () -> {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (!hasBossBarVisible(all)) continue;
for (TabPlayer all : players) {
line.removePlayer(all);
}
announcements.remove(line);
@@ -6,6 +6,7 @@ import java.util.Map.Entry;
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;
@@ -24,12 +25,20 @@ public class GlobalPlayerList extends TabFeature {
private final boolean displayAsSpectators = TAB.getInstance().getConfiguration().getConfig().getBoolean("global-playerlist.display-others-as-spectators", false);
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 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));
}
@@ -42,18 +51,19 @@ public class GlobalPlayerList extends TabFeature {
if (shouldSee(viewer, displayed)) viewer.sendCustomPacket(getAddPacket(displayed, viewer), this);
}
}
placeholders.forEach(pl -> pl.updateValue(pl.request()));
}
public boolean shouldSee(TabPlayer viewer, TabPlayer displayed) {
if (displayed == viewer) return true;
if (displayed.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return false;
if (spyServers.contains(viewer.getServer())) return true;
if (isSpyServer(viewer.getServer())) return true;
return getServerGroup(viewer.getServer()).equals(getServerGroup(displayed.getServer()));
}
public String getServerGroup(String serverName) {
for (Entry<String, List<String>> group : sharedServers.entrySet()) {
if (group.getValue().contains(serverName)) return group.getKey();
if (group.getValue().stream().anyMatch(serverName::equalsIgnoreCase)) return group.getKey();
}
return isolateUnlistedServers ? "isolated:" + serverName : "DEFAULT";
}
@@ -79,6 +89,7 @@ public class GlobalPlayerList extends TabFeature {
connectedPlayer.sendCustomPacket(getAddPacket(all, connectedPlayer), this);
}
}
placeholders.forEach(pl -> pl.updateValue(pl.request()));
}
@Override
@@ -92,31 +103,41 @@ public class GlobalPlayerList extends TabFeature {
if (all == disconnectedPlayer) continue;
all.sendCustomPacket(remove, this);
}
//update after player is removed
placeholders.forEach(pl -> pl.updateValue(pl.request()));
});
}
@Override
public void onServerChange(TabPlayer p, String from, String to) {
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(p);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == p) continue;
if (shouldSee(all, p)) {
all.sendCustomPacket(getAddPacket(p, all), this);
} else {
all.sendCustomPacket(removeChanged, this);
}
if (shouldSee(p, all)) {
p.sendCustomPacket(getAddPacket(all, p), this);
} else {
p.sendCustomPacket(getRemovePacket(all), this);
lastServerSwitch.put(p, System.currentTimeMillis());
Runnable r = () -> {
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(p);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all == p) continue;
if (shouldSee(all, p)) {
all.sendCustomPacket(getAddPacket(p, 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) {
PlayerInfoData data = new PlayerInfoData(p.getTablistUUID());
data.setName(p.getName());
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, data);
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER,
new PlayerInfoData(p.getTablistUUID()), new PlayerInfoData(EMPTY_ID));
}
public PacketPlayOutPlayerInfo getAddPacket(TabPlayer p, TabPlayer viewer) {
@@ -125,17 +146,30 @@ public class GlobalPlayerList extends TabFeature {
if (playerlist != null) {
format = playerlist.getTabFormat(p, viewer);
}
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new PlayerInfoData(p.getName(), p.getTablistUUID(), p.getSkin(),
p.getPing(), vanishedAsSpectators && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE, format));
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER,
new PlayerInfoData(
p.getName(),
p.getTablistUUID(),
p.getSkin(),
true,
p.getPing(),
vanishedAsSpectators && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE,
viewer.getVersion().getMinorVersion() >= 8 ? format : null,
fillProfileKey ? p.getChatSessionId() : null,
fillProfileKey ? p.getProfilePublicKey() : null
)
);
}
@Override
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (info.getAction() == EnumPlayerInfoAction.REMOVE_PLAYER) {
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 && (playerInfoData.getName() == null || playerInfoData.getName().length() == 0) && !packetPlayer.isVanished()) {
if (packetPlayer != null && !packetFromTAB && !packetPlayer.isVanished() &&
(System.currentTimeMillis()-lastServerSwitch.getOrDefault(packetPlayer, 0L) < 500)) {
//remove packet not coming from tab
//changing to random non-existing player, the easiest way to cancel the removal
playerInfoData.setUniqueId(UUID.randomUUID());
@@ -143,7 +177,7 @@ public class GlobalPlayerList extends TabFeature {
}
}
if (!displayAsSpectators) return;
if (info.getAction() == EnumPlayerInfoAction.ADD_PLAYER || info.getAction() == EnumPlayerInfoAction.UPDATE_GAME_MODE) {
if (info.getActions().contains(EnumPlayerInfoAction.UPDATE_GAME_MODE)) {
for (PlayerInfoData playerInfoData : info.getEntries()) {
TabPlayer packetPlayer = TAB.getInstance().getPlayerByTabListUUID(playerInfoData.getUniqueId());
if (packetPlayer != null && !receiver.getServer().equals(packetPlayer.getServer())) {
@@ -172,7 +206,7 @@ public class GlobalPlayerList extends TabFeature {
}
}
public List<String> getSpyServers() {
return spyServers;
public boolean isSpyServer(String server) {
return spyServers.stream().anyMatch(server::equalsIgnoreCase);
}
}
@@ -2,7 +2,9 @@ package me.neznamy.tab.shared.features.layout;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
@@ -41,15 +43,19 @@ public class Layout extends TabFeature {
groups.forEach(g -> list.addAll(g.getSlots(p)));
for (FixedSlot slot : fixedSlots.values()) {
p.setProperty(slot, slot.getPropertyName(), slot.getText());
list.add(new PlayerInfoData("", slot.getId(), slot.getSkin(), slot.getPing(), EnumGamemode.CREATIVE, IChatBaseComponent.optimizedComponent(p.getProperty(slot.getPropertyName()).updateAndGet())));
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));
}
for (int slot : emptySlots) {
list.add(new PlayerInfoData("", manager.getUUID(slot), manager.getSkinManager().getDefaultSkin(), manager.getEmptySlotPing(), EnumGamemode.CREATIVE, new IChatBaseComponent("")));
list.add(new PlayerInfoData(getEntryName(p, slot), manager.getUUID(slot), manager.getSkinManager().getDefaultSkin(), true, manager.getEmptySlotPing(), EnumGamemode.CREATIVE, new IChatBaseComponent(""), null, null));
}
if (p.getVersion().getMinorVersion() < 8 || p.isBedrockPlayer()) return;
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, list), this);
}
public String getEntryName(TabPlayer viewer, long slot) {
return viewer.getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ? "|slot_" + (10+slot) : "";
}
public void removeFrom(TabPlayer p) {
if (!viewers.contains(p)) return;
viewers.remove(p);
@@ -70,7 +76,11 @@ public class Layout extends TabFeature {
}
public void tick() {
List<TabPlayer> players = manager.getSortedPlayers().keySet().stream().filter(player -> !player.isVanished()).collect(Collectors.toList());
Stream<TabPlayer> str = manager.getSortedPlayers().keySet().stream();
if (manager.isHideVanishedPlayers()) {
str = str.filter(player -> !player.isVanished());
}
List<TabPlayer> players = str.collect(Collectors.toList());
for (ParentGroup group : groups) {
group.tick(players);
}
Loaded 100 of 140 files, more files were not shown because too many files have changed in this diff. Show more