Compare commits

...
47 Commits
Author SHA1 Message Date
NEZNAMY 87e9a6defd 3.2.5 2023-02-13 16:40:36 +01:00
NEZNAMY 3d896ff828 More abstraction of unlimited nametag mode (also a small fix from previous commit) 2023-02-13 14:25:38 +01:00
NEZNAMY bf93240888 Unlimited NameTags - Make armor stands visible for spectators (#785) 2023-02-13 13:57:49 +01:00
NEZNAMY 0b0e30c3c9 Disable sponge module until compilation is fixed 2023-02-13 12:54:05 +01:00
NEZNAMY 84d3816bd1 Fix NPE 2023-02-12 12:51:37 +01:00
NEZNAMY 04ff606947 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-02-12 12:29:06 +01:00
NEZNAMY 1cb8e6e238 Almost finish Sponge 8 2023-02-12 12:29:01 +01:00
NEZNAMY 4a2226170d Merge pull request #819 from Tanguygab/master
added support for *server or *world (just like server* and world*)
2023-02-12 12:27:03 +01:00
Tanguygab 1469124cfc added support for *server or *world (just like server* and world*) 2023-02-12 11:41:11 +01:00
NEZNAMY 0826095e73 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-02-11 15:34:05 +01:00
NEZNAMY 1bd698adde Code style - Create custom cache object to avoid code duplication 2023-02-11 14:33:31 +00:00
Callum Seabrook 1058d269ff Start of rewrite of Sponge for v8 over v7 2023-02-11 14:13:05 +00:00
NEZNAMY bacd87b78e Code style - Create custom cache object to avoid code duplication 2023-02-11 13:08:20 +01:00
Callum Seabrook 5befbe8780 Updated Sponge platform 2023-02-10 23:11:38 +00:00
Callum Seabrook e159b2adae Finished Sponge implementation (mostly) 2023-02-10 23:08:21 +00:00
Callum Seabrook 52f7418853 The start of Sponge 2023-02-10 23:08:21 +00:00
Callum Seabrook 76061f7ccd Set up Sponge skeleton project 2023-02-10 23:08:21 +00:00
NEZNAMY 7d39e21eae Fix mistake after recode 2023-02-11 00:07:10 +01:00
NEZNAMY fee3723474 Code style - More abstraction of features and feature loading 2023-02-10 23:06:52 +01:00
NEZNAMY 2f0e4c6888 Merge remote-tracking branch 'origin/master' 2023-02-10 12:58:49 +01:00
NEZNAMY b04cb6d3c5 Config converter - Reverse old config detection (#762) 2023-02-10 12:58:07 +01:00
Callum Seabrook a9228a698a Fixed NPE being thrown when player info data name is null for Krypton 2023-02-09 16:18:13 +00:00
NEZNAMY 27102495d8 Code style - Use lombok in more locations 2023-02-08 00:49:48 +01:00
NEZNAMY d1bf96ab33 Code style - Use lombok for getters/setters 2023-02-07 23:14:02 +01:00
NEZNAMY 3360dbb468 Update README.md 2023-02-07 20:21:14 +01:00
NEZNAMY d224e7584e Create CONTRIBUTING.md 2023-02-07 20:20:31 +01:00
Callum Seabrook 6373540076 Updated Krypton platform and removed Indra 2023-02-07 16:56:58 +00:00
NEZNAMY 5f78aca997 Hide proxy-only methods into proxy player object 2023-02-07 15:33:24 +01:00
NEZNAMY 145d944d33 Bukkit - Remove unused (and incorrectly implemented) code 2023-02-07 15:26:01 +01:00
NEZNAMY ac91940e3b Velocity - Send packet using wanted player uuid instead of random if entry does not exist (apparently it goes through even if not registered) 2023-01-31 21:36:26 +01:00
NEZNAMY ef8452be73 Code style - Simplify BungeeCord info packet read/write based on client version 2023-01-31 18:23:24 +01:00
NEZNAMY 47e0810697 Placeholders - Remove %essentialsnick% placeholder due to being pretty redundant (keeping empty implementation so users don't get it suddenly broken) 2023-01-31 17:11:45 +01:00
NEZNAMY 1f30f225c7 Code style - Use reflection for PremiumVanish API instead of direct code with dependencies 2023-01-31 16:17:33 +01:00
NEZNAMY fcbfd60ddb Code style - Call method directly when command sender is console 2023-01-31 15:55:28 +01:00
NEZNAMY 46f9033713 Code style - Add final modifier to variable 2023-01-31 15:54:48 +01:00
NEZNAMY 7e65018a0e Velocity - Switch to non-deprecated method 2023-01-31 15:51:28 +01:00
NEZNAMY 9ef3485968 Code style - Create a method for convenience to avoid duplicated code 2023-01-27 15:52:38 +01:00
NEZNAMY 257f8f1145 Commands - Remove properly length from debug output 2023-01-27 15:24:26 +01:00
NEZNAMY 8d65cbb0e5 3.2.5-SNAPSHOT 2023-01-26 10:35:08 +01:00
NEZNAMY 20d221e3ca Merge pull request #806 from NotAlexNoyle/proposal
Propose adopting open source license
2023-01-25 12:17:56 +01:00
NEZNAMY 94072acf10 3.2.4 2023-01-25 10:39:44 +01:00
NEZNAMY 0422cc2597 Update issue templates 2023-01-24 14:00:13 +01:00
NEZNAMY 58f61e645b 3.2.4-SNAPSHOT 2023-01-24 10:45:11 +01:00
NEZNAMY 5f416bd9e5 Unlimited NameTags - Avoid Bukkit API NPE (#800, #804) 2023-01-24 10:44:35 +01:00
NotAlexNoyle 64528c913c Propose adopting open source license 2023-01-23 22:33:21 -05:00
NEZNAMY 357e7c6a49 Revert "Remove redundant try/catch"
This reverts commit 1777d8bf01.
2023-01-23 14:39:54 +01:00
NEZNAMY 81dd202053 3.2.3 2023-01-21 14:58:34 +01:00
162 changed files with 3732 additions and 5090 deletions

No files matched your search

+3 -3
View File
@@ -1,6 +1,6 @@
name: Bug Report
description: Unwanted behavior that does not match configuration.
labels: ["Bug"]
description: Plugin does not work as described.
labels: ["Type: Bug"]
body:
- type: input
attributes:
@@ -45,7 +45,7 @@ body:
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide, such as screenshots.
description: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
validations:
required: false
- type: checkboxes
+1 -1
View File
@@ -1,6 +1,6 @@
name: Error
description: Error message with a stack trace in console or error file.
labels: ["Error"]
labels: ["Type: Error"]
body:
- type: input
attributes:
+1 -1
View File
@@ -1,6 +1,6 @@
name: Wiki change
description: Change to text on a wiki page or adding a new section.
labels: ["Wiki"]
labels: ["Type: Wiki change"]
body:
- type: input
attributes:
+10
View File
@@ -0,0 +1,10 @@
If this plugin has helped you in any way, and you would like to return something back
to make the plugin even better, there is a lot of ways to contribute:
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report (typically providing correct steps to reproduce that actually work).
* Test **experimental builds**, which can be found in [Github Actions](https://github.com/NEZNAMY/TAB/actions) 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://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
+174
View File
@@ -0,0 +1,174 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
+1 -10
View File
@@ -31,13 +31,4 @@ allowing us to know who bought it and who didn't. It also serves as a much faste
You will be provided with a much faster and higher quality service.**
# Contributing
If this plugin has helped you in any way, and you would like to return something back
to make the plugin even better, there is a lot of ways to contribute:
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report (typically providing correct steps to reproduce that actually work).
* Test **experimental builds**, which can be found in [Github Actions](https://github.com/NEZNAMY/TAB/actions) 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://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
@@ -11,7 +11,7 @@ public interface ArmorStand {
*
* @return true if static, false if dynamic
*/
boolean hasStaticOffset();
boolean isStaticOffset();
/**
* Changes offset of the armor stand
@@ -1,5 +1,8 @@
package me.neznamy.tab.api;
import lombok.Getter;
import lombok.Setter;
/**
* Enum class representing all possibly used protocol versions
*/
@@ -70,13 +73,16 @@ public enum ProtocolVersion {
public static final ProtocolVersion[] VALUES = values();
/** Version's network id found at https://wiki.vg/Protocol_version_numbers */
private final int networkId;
@Getter private final int networkId;
/** Version's minor version, such as 16 for 1.16.x */
private int minorVersion;
/** Version's minor version, such as 16 for 1.16.x. Allowing override to
* set minor version of UNKNOWN_SERVER_VERSION value to value from package to fix compatibility
* with server forks that set bukkit version field value to "Unknown".
*/
@Getter @Setter private int minorVersion;
/** Version's friendly name displayed in %player-version% placeholder */
private final String friendlyName;
@Getter private final String friendlyName;
/**
* Constructs new instance with given network id
@@ -102,45 +108,6 @@ public enum ProtocolVersion {
this.friendlyName = friendlyName;
}
/**
* Returns the version's network id
*
* @return version's network id
*/
public int getNetworkId() {
return networkId;
}
/**
* Returns user-friendly name of the version (such as 1.16.2 instead of V1_16_2)
*
* @return user-friendly name of the version
*/
public String getFriendlyName() {
return friendlyName;
}
/**
* Returns minor version of this release, such as 8 for 1.8.x or 13 for 1.13.x
*
* @return version's minor version
*/
public int getMinorVersion() {
return minorVersion;
}
/**
* Overrides minor version of the version instance. This is used internally to
* set minor version of UNKNOWN_SERVER_VERSION value to value from package to fix compatibility
* with server forks that set bukkit version field value to "Unknown".
*
* @param minorVersion
* minor version to change value to
*/
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
}
/**
* Returns enum constant of entered version or UNKNOWN_SERVER_VERSION if unknown version
*
@@ -244,6 +244,7 @@ public class TabConstants {
public static final String UNLIMITED_NAME_TAGS = "NameTagX";
public static final String UNLIMITED_NAME_TAGS_PACKET_LISTENER = "nametagx-packet";
public static final String UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER = "nametagx-vehicle";
public static final String UNLIMITED_NAME_TAGS_GAMEMODE_LISTENER = "nametagx-gamemode";
//Bukkit only
public static final String PER_WORLD_PLAYER_LIST = "PerWorldPlayerList";
@@ -2,6 +2,7 @@ package me.neznamy.tab.api;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
@@ -14,10 +15,10 @@ import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
public abstract class TabFeature {
/** Feature's name displayed in /tab cpu */
private final String featureName;
@Getter private final String featureName;
/** Feature's function name displayed in place of refreshing in /tab cpu */
private final String refreshDisplayName;
@Getter private final String refreshDisplayName;
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
@@ -340,24 +341,6 @@ public abstract class TabFeature {
return false;
}
/**
* Returns name of the feature displayed in /tab cpu.
*
* @return name of the feature displayed in /tab cpu
*/
public String getFeatureName() {
return featureName;
}
/**
* Returns text to display in /tab cpu of this feature refreshing visuals
*
* @return Text to display as refreshing of this feature
*/
public String getRefreshDisplayName() {
return refreshDisplayName;
}
/**
* Returns {@code true} if method with specified name is overridden
* in the implementation, {@code false} if not
@@ -303,18 +303,4 @@ 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();
}
@@ -1,10 +1,14 @@
package me.neznamy.tab.api.bossbar;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
@AllArgsConstructor
public enum BarStyle {
PROGRESS("SOLID"),
@@ -14,23 +18,5 @@ public enum BarStyle {
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
private final String bukkitName;
/**
* Initializes enum constant with given bukkit name
*
* @param bukkitName
* Name of color to be returned in {@code getBukkitName()}
*/
BarStyle(String bukkitName){
this.bukkitName = bukkitName;
}
/**
* Returns name of this style in Bukkit API
* @return name of this style in Bukkit API
*/
public String getBukkitName() {
return bukkitName;
}
@Getter private final String bukkitName;
}
@@ -1,52 +1,19 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
/**
* Class for click event action in chat component
*/
@Data
public class ChatClickable {
/** Click action */
private final EnumClickAction action;
@NonNull private final EnumClickAction action;
/** Click value */
private final String value;
/**
* Constructs new instance with given action and value.
*
* @param action
* click event action
* @param value
* click event value
* @throws IllegalArgumentException
* if {@code action} is null or {@code value} is null
*/
public ChatClickable(EnumClickAction action, String value) {
Preconditions.checkNotNull(action, "hover action");
Preconditions.checkNotNull(value, "hover value");
this.action = action;
this.value = value;
}
/**
* Returns click action defined in constructor
*
* @return click action
*/
public EnumClickAction getAction() {
return action;
}
/**
* Returns click action defined in constructor
*
* @return click action
*/
public String getValue() {
return value;
}
@NonNull private final String value;
/**
* Enum for all possible click actions
@@ -1,9 +1,9 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
import java.util.UUID;
public class ChatComponentEntity extends IChatBaseComponent {
@@ -12,9 +12,7 @@ public class ChatComponentEntity extends IChatBaseComponent {
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");
public ChatComponentEntity(@NonNull String type, @NonNull UUID id, String 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);
@@ -1,70 +1,32 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
/**
* Class for hover event action in chat component
*/
@Data
public class ChatHoverable {
/** Hover action */
private final EnumHoverAction action;
@NonNull private final EnumHoverAction action;
/** Hover value */
private final IChatBaseComponent value;
/**
* Constructs new instance with given action and value.
*
* @param action
* hover event action
* @param value
* hover event value
* @throws IllegalArgumentException
* if {@code action} is null or {@code value} is null
*/
public ChatHoverable(EnumHoverAction action, IChatBaseComponent value) {
Preconditions.checkNotNull(action, "hover action");
Preconditions.checkNotNull(value, "hover value");
this.action = action;
this.value = value;
}
/**
* Returns hover action defined in constructor
*
* @return hover action
*/
public EnumHoverAction getAction() {
return action;
}
/**
* Returns hover value defined in constructor
*
* @return hover value
*/
public IChatBaseComponent getValue() {
return value;
}
@NonNull private final IChatBaseComponent value;
/**
* Enum for all possible hover actions
*/
@AllArgsConstructor
public enum EnumHoverAction {
SHOW_TEXT("contents"),
SHOW_ITEM("value"),
SHOW_ENTITY("contents");
private final String preferredKey;
EnumHoverAction(String preferredKey) {
this.preferredKey = preferredKey;
}
public String getPreferredKey() {
return preferredKey;
}
@Getter private final String preferredKey;
}
}
@@ -1,37 +1,31 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import me.neznamy.tab.api.util.Preconditions;
import org.json.simple.JSONObject;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
import org.json.simple.JSONObject;
import java.util.UUID;
@Data @NoArgsConstructor
public class ChatModifier {
private TextColor color;
private Boolean bold;
private Boolean italic;
private Boolean underlined;
private Boolean strikethrough;
private Boolean obfuscated;
private ChatClickable clickEvent;
private ChatHoverable hoverEvent;
private String font;
private ProtocolVersion targetVersion;
public ChatModifier() {
}
public ChatModifier(ChatModifier modifier) {
Preconditions.checkNotNull(modifier, "modifier");
public ChatModifier(@NonNull ChatModifier modifier) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
this.bold = modifier.bold;
this.italic = modifier.italic;
@@ -44,53 +38,6 @@ public class ChatModifier {
this.targetVersion = modifier.targetVersion;
}
public TextColor getColor() {
return color;
}
public void setColor(TextColor color) {
this.color = color;
}
public Boolean getBold() {
return bold;
}
public void setBold(Boolean bold) {
this.bold = bold;
}
public Boolean getItalic() {
return italic;
}
public void setItalic(Boolean italic) {
this.italic = italic;
}
public Boolean getUnderlined() {
return underlined;
}
public void setUnderlined(Boolean underlined) {
this.underlined = underlined;
}
public Boolean getStrikethrough() {
return strikethrough;
}
public void setStrikethrough(Boolean strikethrough) {
this.strikethrough = strikethrough;
}
public Boolean getObfuscated() {
return obfuscated;
}
public void setObfuscated(Boolean obfuscated) {
this.obfuscated = obfuscated;
}
/**
* Returns true if bold is defined and set to true, false otherwise
@@ -137,30 +84,13 @@ public class ChatModifier {
return Boolean.TRUE.equals(obfuscated);
}
public ChatClickable getClickEvent() {
return clickEvent;
}
public void setClickEvent(ChatClickable clickEvent) {
this.clickEvent = clickEvent;
}
public ChatHoverable getHoverEvent() {
return hoverEvent;
}
public void setHoverEvent(ChatHoverable hoverEvent) {
this.hoverEvent = hoverEvent;
}
/**
* Sets click action to OPEN_URL and url to given value
*
* @param url
* url to open
*/
public void onClickOpenUrl(String url) {
Preconditions.checkNotNull(url, "url");
public void onClickOpenUrl(@NonNull String url) {
clickEvent = new ChatClickable(EnumClickAction.OPEN_URL, url);
}
@@ -170,8 +100,7 @@ public class ChatModifier {
* @param command
* command to perform, might be without / to send a chat message
*/
public void onClickRunCommand(String command) {
Preconditions.checkNotNull(command, "command");
public void onClickRunCommand(@NonNull String command) {
clickEvent = new ChatClickable(EnumClickAction.RUN_COMMAND, command);
}
@@ -181,8 +110,7 @@ public class ChatModifier {
* @param command
* command to suggest
*/
public void onClickSuggestCommand(String command) {
Preconditions.checkNotNull(command, "command");
public void onClickSuggestCommand(@NonNull String command) {
clickEvent = new ChatClickable(EnumClickAction.SUGGEST_COMMAND, command);
}
@@ -203,14 +131,11 @@ public class ChatModifier {
* @param text
* text to copy to clipboard on click
*/
public void onClickCopyToClipBoard(String text) {
Preconditions.checkNotNull(text, "text");
public void onClickCopyToClipBoard(@NonNull String text) {
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
}
public void onClick(EnumClickAction action, String value) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(value, "value");
public void onClick(@NonNull EnumClickAction action, @NonNull String value) {
clickEvent = new ChatClickable(action, value);
}
@@ -220,8 +145,7 @@ public class ChatModifier {
* @param text
* text to show
*/
public void onHoverShowText(IChatBaseComponent text) {
Preconditions.checkNotNull(text, "text");
public void onHoverShowText(@NonNull IChatBaseComponent text) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_TEXT, text);
}
@@ -231,8 +155,7 @@ public class ChatModifier {
* @param serializedItem
* item to show
*/
public void onHoverShowItem(String serializedItem) {
Preconditions.checkNotNull(serializedItem, "item");
public void onHoverShowItem(@NonNull String serializedItem) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ITEM, new IChatBaseComponent(serializedItem));
}
@@ -246,26 +169,14 @@ public class ChatModifier {
* @param name
* entity custom name
*/
public void onHoverShowEntity(String type, UUID id, String name) {
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(id, "id");
public void onHoverShowEntity(@NonNull String type, @NonNull UUID id, String name) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
}
public void onHover(EnumHoverAction action, IChatBaseComponent value) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(value, "value");
public void onHover(@NonNull EnumHoverAction action, @NonNull IChatBaseComponent value) {
hoverEvent = new ChatHoverable(action, value);
}
public String getFont() {
return font;
}
public void setFont(String font) {
this.font = font;
}
@SuppressWarnings("unchecked")
public JSONObject serialize() {
JSONObject json = new JSONObject();
@@ -305,12 +216,4 @@ public class ChatModifier {
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
return builder.toString();
}
public void setTargetVersion(ProtocolVersion targetVersion) {
this.targetVersion = targetVersion;
}
public ProtocolVersion getTargetVersion() {
return targetVersion;
}
}
@@ -1,8 +1,10 @@
package me.neznamy.tab.api.chat;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
@@ -23,10 +25,11 @@ import java.util.List;
* will act like a normal chat component.
*/
@SuppressWarnings("unchecked")
@RequiredArgsConstructor
public class DeserializedChatComponent extends IChatBaseComponent {
/** The original serialized component string */
private final String json;
@NonNull private final String json;
/**
* Flag tracking whether this component was fully deserialized
@@ -41,17 +44,6 @@ public class DeserializedChatComponent extends IChatBaseComponent {
*/
private boolean modified;
/**
* Constructs new instance with given parameter
*
* @param json
* serialized component as string
*/
public DeserializedChatComponent(String json) {
Preconditions.checkNotNull(json, "json");
this.json = json;
}
@Override
public String toString() {
if (modified) return super.toString();
@@ -77,7 +69,7 @@ public class DeserializedChatComponent extends IChatBaseComponent {
}
@Override
public ChatModifier getModifier() {
public @NotNull ChatModifier getModifier() {
if (!deserialized) deserialize();
return super.getModifier();
}
@@ -90,14 +82,14 @@ public class DeserializedChatComponent extends IChatBaseComponent {
}
@Override
public void addExtra(IChatBaseComponent child) {
public void addExtra(@NotNull IChatBaseComponent child) {
if (!deserialized) deserialize();
modified = true;
super.addExtra(child);
}
@Override
public void setModifier(ChatModifier modifier) {
public void setModifier(@NotNull ChatModifier modifier) {
if (!deserialized) deserialize();
modified = true;
super.setModifier(modifier);
@@ -162,9 +154,7 @@ public class DeserializedChatComponent extends IChatBaseComponent {
* name of key
* @return value from json object or null if not present
*/
private static Boolean getBoolean(JSONObject jsonObject, String key) {
Preconditions.checkNotNull(jsonObject, "json object");
Preconditions.checkNotNull(key, "key");
private static Boolean getBoolean(@NonNull JSONObject jsonObject, @NonNull String key) {
String value = String.valueOf(jsonObject.getOrDefault(key, null));
return "null".equals(value) ? null : Boolean.parseBoolean(value);
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.api.chat;
import lombok.Getter;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
/**
@@ -40,22 +41,22 @@ public enum EnumChatFormat {
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
/** Character representing the color or magic code */
private final char character;
@Getter private final char character;
/** Red value of this constant, 0 for magic codes */
private final short red;
@Getter private final short red;
/** Green value of this constant, 0 for magic codes */
private final short green;
@Getter private final short green;
/** Blue value of this constant, 0 for magic codes */
private final short blue;
@Getter private final short blue;
/** Color as 6-digit hex code, null for magic codes */
private final String hexCode;
@Getter private final String hexCode;
/** Color symbol followed by constant's character */
private final String chatFormat;
@Getter private final String format;
/**
* Constructs new color instance with given character and hex code
@@ -67,7 +68,7 @@ public enum EnumChatFormat {
*/
EnumChatFormat(char character, String hexCode) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
this.format = String.valueOf(COLOR_CHAR) + character;
this.hexCode = hexCode;
int hexColor = Integer.parseInt(hexCode, 16);
red = (short) ((hexColor >> 16) & 0xFF);
@@ -83,40 +84,13 @@ public enum EnumChatFormat {
*/
EnumChatFormat(char character) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
this.format = String.valueOf(COLOR_CHAR) + character;
red = 0;
green = 0;
blue = 0;
hexCode = null;
}
/**
* Returns red value of this color code
*
* @return red value
*/
public short getRed() {
return red;
}
/**
* Returns green value of this color code
*
* @return green value
*/
public short getGreen() {
return green;
}
/**
* Returns blue value of this color code
*
* @return blue value
*/
public short getBlue() {
return blue;
}
/**
* Returns enum value based on provided character or null if character is not valid
*
@@ -152,33 +126,6 @@ public enum EnumChatFormat {
return EnumChatFormat.WHITE;
}
/**
* Returns color char followed by color's character
*
* @return color char followed by color's character
*/
public String getFormat() {
return chatFormat;
}
/**
* Returns character representing this color
*
* @return character representing this color
*/
public char getCharacter() {
return character;
}
/**
* Returns hex code of this format, null if this is a magic code
*
* @return hex code of this format
*/
public String getHexCode() {
return hexCode;
}
/**
* Returns enum value with exact red, green and blue values or null if no match was found
*
@@ -1,9 +1,13 @@
package me.neznamy.tab.api.chat;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.api.util.ComponentCache;
import org.json.simple.JSONObject;
import java.util.*;
@@ -12,39 +16,37 @@ import java.util.*;
* A class representing the n.m.s.IChatBaseComponent class to make work with it much easier
*/
@SuppressWarnings("unchecked")
@NoArgsConstructor
public class IChatBaseComponent {
/**
* Component cache maps to avoid large memory allocations as well as
* higher CPU usage when using animations which send the same text on repeat.
*/
private static final Map<String, IChatBaseComponent> componentCache = new HashMap<>();
private static final Map<IChatBaseComponent, String> serializeCacheModern = new HashMap<>();
private static final Map<IChatBaseComponent, String> serializeCacheLegacy = new HashMap<>();
private static final ComponentCache<String, IChatBaseComponent> stringCache = new ComponentCache<>(10000, (text, clientVersion) -> {
return text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x") || text.contains("<") ?
IChatBaseComponent.fromColoredText(text) : //contains RGB colors
new IChatBaseComponent(text); //no RGB
});
private static final ComponentCache<IChatBaseComponent, String> serializeCache = new ComponentCache<>(10000,
(component, clientVersion) -> component.toString());
/** Text of the component */
private String text;
@Getter @Setter private String text;
/** Chat modifier containing color, magic codes, hover and click event */
private ChatModifier modifier = new ChatModifier();
@Getter @Setter @NonNull private ChatModifier modifier = new ChatModifier();
/** Extra components used in "extra" field */
private List<IChatBaseComponent> extra;
/**
* Constructs a new empty component
*/
public IChatBaseComponent() {
}
/**
* Constructs a new component which is a clone of provided component
*
* @param component
* component to clone
*/
public IChatBaseComponent(IChatBaseComponent component) {
Preconditions.checkNotNull(component, "component");
public IChatBaseComponent(@NonNull IChatBaseComponent component) {
this.text = component.text;
this.modifier = new ChatModifier(component.modifier);
for (IChatBaseComponent child : component.getExtra()) {
@@ -93,53 +95,11 @@ public class IChatBaseComponent {
* @param child
* component to append
*/
public void addExtra(IChatBaseComponent child) {
Preconditions.checkNotNull(child, "extra");
public void addExtra(@NonNull IChatBaseComponent child) {
if (extra == null) extra = new ArrayList<>();
extra.add(child);
}
/**
* Returns text of this component
*
* @return text of this component
*/
public String getText() {
return text;
}
/**
* Returns chat modifier of this component
*
* @return chat modifier of this component
*/
public ChatModifier getModifier() {
return modifier;
}
/**
* Sets modifier to provided value
*
* @param modifier
* modifier to set value to
* @throws IllegalArgumentException
* if {@code modifier} is null
*/
public void setModifier(ChatModifier modifier) {
Preconditions.checkNotNull(modifier, "modifier");
this.modifier = modifier;
}
/**
* Changes text of this component
*
* @param text
* text to show
*/
public void setText(String text) {
this.text = text;
}
/**
* Deserializes string and returns created component. If provided string is null, returns null.
*
@@ -207,19 +167,7 @@ public class IChatBaseComponent {
for (IChatBaseComponent child : getExtra()) {
child.modifier.setTargetVersion(clientVersion);
}
String string;
if (clientVersion.getMinorVersion() >= 16) {
if (serializeCacheModern.containsKey(this)) return serializeCacheModern.get(this);
string = toString();
if (serializeCacheModern.size() > 10000) serializeCacheModern.clear();
serializeCacheModern.put(this, string);
} else {
if (serializeCacheLegacy.containsKey(this)) return serializeCacheLegacy.get(this);
string = toString();
if (serializeCacheLegacy.size() > 10000) serializeCacheLegacy.clear();
serializeCacheLegacy.put(this, string);
}
return string;
return serializeCache.get(this, clientVersion);
}
/**
@@ -229,8 +177,7 @@ public class IChatBaseComponent {
* text to convert
* @return organized component from colored text
*/
public static IChatBaseComponent fromColoredText(String originalText){
Preconditions.checkNotNull(originalText, "text");
public static IChatBaseComponent fromColoredText(@NonNull String originalText){
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
List<IChatBaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
@@ -421,18 +368,6 @@ public class IChatBaseComponent {
* @return The most performance-optimized component based on text
*/
public static IChatBaseComponent optimizedComponent(String text){
if (text == null) return null;
if (componentCache.containsKey(text)) return componentCache.get(text);
IChatBaseComponent component;
if (text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x") || text.contains("<")){
//contains RGB colors
component = IChatBaseComponent.fromColoredText(text);
} else {
//no RGB
component = new IChatBaseComponent(text);
}
if (componentCache.size() > 10000) componentCache.clear();
componentCache.put(text, component);
return component;
return stringCache.get(text, null);
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.api.chat;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
@@ -24,7 +26,7 @@ public class TextColor {
* Boolean value whether the legacy color was forced with constructor or should be
* automatically assigned as closest color.
* This value is used in gradients when converting text for legacy players. */
private boolean legacyColorForced;
@Getter private boolean legacyColorForced;
/**
* Constructs new instance as a clone of the provided color.
@@ -34,8 +36,7 @@ public class TextColor {
* @throws IllegalArgumentException
* if color is {@code null}
*/
public TextColor(TextColor color) {
Preconditions.checkNotNull(color, "color");
public TextColor(@NonNull TextColor color) {
rgb = color.rgb;
legacyColor = color.legacyColor;
hexCode = color.hexCode;
@@ -50,8 +51,7 @@ public class TextColor {
* @throws IllegalArgumentException
* if hexCode is {@code null}
*/
public TextColor(String hexCode) {
Preconditions.checkNotNull(hexCode, "hex code");
public TextColor(@NonNull String hexCode) {
this.hexCode = hexCode;
}
@@ -65,9 +65,7 @@ public class TextColor {
* @throws IllegalArgumentException
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
*/
public TextColor(String hexCode, EnumChatFormat legacyColor) {
Preconditions.checkNotNull(hexCode, "hex code");
Preconditions.checkNotNull(legacyColor, "legacy color");
public TextColor(@NonNull String hexCode, EnumChatFormat legacyColor) {
this.hexCode = hexCode;
this.legacyColorForced = true;
this.legacyColor = legacyColor;
@@ -81,8 +79,7 @@ public class TextColor {
* @throws IllegalArgumentException
* if {@code legacyColor} is {@code null}
*/
public TextColor(EnumChatFormat legacyColor) {
Preconditions.checkNotNull(legacyColor, "legacy color");
public TextColor(@NonNull EnumChatFormat legacyColor) {
this.rgb = (legacyColor.getRed() << 16) + (legacyColor.getGreen() << 8) + legacyColor.getBlue();
this.hexCode = legacyColor.getHexCode();
}
@@ -195,15 +192,6 @@ public class TextColor {
return "#" + getHexCode();
}
/**
* Returns true if legacy color was forced with a constructor, false if not
*
* @return true if forced, false if not
*/
public boolean isLegacyColorForced() {
return legacyColorForced;
}
/**
* Reads the string as it appears in chat component and turns it into the color object.
* If the entered string is null, returns null.
@@ -1,6 +1,9 @@
package me.neznamy.tab.api.chat;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -10,21 +13,12 @@ import java.util.List;
* slows down the performance, as well as makes code more complicated
* and open to problems.
*/
@AllArgsConstructor
public class WrappedChatComponent extends IChatBaseComponent {
/** Original NMS component */
private final Object originalComponent;
/**
* Constructs new instance with given parameter
*
* @param originalComponent
* Original NMS component
*/
public WrappedChatComponent(Object originalComponent) {
this.originalComponent = originalComponent;
}
/**
* Returns the original NMS component
* @return the original NMS component
@@ -54,7 +48,7 @@ public class WrappedChatComponent extends IChatBaseComponent {
}
@Override
public ChatModifier getModifier() {
public @NotNull ChatModifier getModifier() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@@ -64,12 +58,12 @@ public class WrappedChatComponent extends IChatBaseComponent {
}
@Override
public void addExtra(IChatBaseComponent child) {
public void addExtra(@NonNull IChatBaseComponent child) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public void setModifier(ChatModifier modifier) {
public void setModifier(@NotNull ChatModifier modifier) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
}
@@ -1,10 +1,7 @@
package me.neznamy.tab.api.chat.rgb;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
import me.neznamy.tab.api.chat.rgb.format.*;
@@ -12,7 +9,12 @@ import me.neznamy.tab.api.chat.rgb.gradient.CMIGradient;
import me.neznamy.tab.api.chat.rgb.gradient.CommonGradient;
import me.neznamy.tab.api.chat.rgb.gradient.GradientPattern;
import me.neznamy.tab.api.chat.rgb.gradient.KyoriGradient;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.api.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
@@ -20,7 +22,7 @@ import me.neznamy.tab.api.util.Preconditions;
public class RGBUtils {
/** Instance of the class */
private static final RGBUtils instance = new RGBUtils();
@Getter private static final RGBUtils instance = new RGBUtils();
/** Registered RGB formatters */
private final RGBFormatter[] formats;
@@ -39,10 +41,9 @@ public class RGBUtils {
*/
public RGBUtils() {
List<RGBFormatter> list = new ArrayList<>();
try {
Class.forName("net.kyori.adventure.text.minimessage.MiniMessage");
if (ReflectionUtils.classExists("net.kyori.adventure.text.minimessage.MiniMessage")) {
list.add(new MiniMessageFormat());
} catch (ClassNotFoundException ignored) {}
}
list.add(new BukkitFormat());
list.add(new CMIFormat());
list.add(new UnnamedFormat1());
@@ -65,15 +66,6 @@ public class RGBUtils {
};
}
/**
* Returns instance of this class
*
* @return instance
*/
public static RGBUtils getInstance() {
return instance;
}
/**
* Applies all RGB formats and gradients to text and returns it.
*
@@ -81,8 +73,7 @@ public class RGBUtils {
* original text
* @return text where everything is converted to #RRGGBB
*/
public String applyFormats(String text) {
Preconditions.checkNotNull(text, "text");
public String applyFormats(@NonNull String text) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, false);
@@ -101,8 +92,7 @@ public class RGBUtils {
* original text
* @return text where all gradients with static text are converted to #RRGGBB
*/
public String applyCleanGradients(String text) {
Preconditions.checkNotNull(text, "text");
public String applyCleanGradients(@NonNull String text) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, true);
@@ -183,8 +173,7 @@ public class RGBUtils {
* string to check
* @return {@code true} if valid, {@code false} if not
*/
public boolean isHexCode(String string) {
Preconditions.checkNotNull(string, "string");
public boolean isHexCode(@NonNull String string) {
if (string.length() != 6) return false;
for (int i=0; i<6; i++) {
char c = string.charAt(i);
@@ -3,9 +3,11 @@ package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
@AllArgsConstructor
public class CommonGradient implements GradientPattern {
private final Pattern pattern;
@@ -15,18 +17,7 @@ public class CommonGradient implements GradientPattern {
private final int startColorStart;
private final int messageStart;
private final int endColorStartSub;
public CommonGradient(Pattern pattern, Pattern legacyPattern, String containCheck, int legacyCharPosition,
int startColorStart, int messageStart, int endColorStartSub) {
this.pattern = pattern;
this.legacyPattern = legacyPattern;
this.containCheck = containCheck;
this.legacyCharPosition = legacyCharPosition;
this.startColorStart = startColorStart;
this.messageStart = messageStart;
this.endColorStartSub = endColorStartSub;
}
@Override
public String applyPattern(String text, boolean ignorePlaceholders) {
if (!text.contains(containCheck)) return text;
@@ -1,7 +1,9 @@
package me.neznamy.tab.api.config;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
import java.io.File;
import java.io.IOException;
@@ -23,10 +25,10 @@ public abstract class ConfigurationFile {
protected List<String> header;
/** Configuration file content */
protected Map<String, Object> values;
@Getter @Setter protected Map<String, Object> values;
/** File to use */
protected final File file;
@Getter protected final File file;
/**
* Constructs new instance and attempts to load specified configuration file.
@@ -43,8 +45,7 @@ public abstract class ConfigurationFile {
* @throws IOException
* if I/O operation with the file unexpectedly fails
*/
protected ConfigurationFile(InputStream source, File destination) throws IOException {
Preconditions.checkNotNull(destination, "destination");
protected ConfigurationFile(InputStream source, @NonNull File destination) throws IOException {
this.file = destination;
if (file.getParentFile() != null && !file.getParentFile().exists()) Files.createDirectories(file.getParentFile().toPath());
if (!file.exists()) {
@@ -68,19 +69,6 @@ public abstract class ConfigurationFile {
return file.getName();
}
/**
* Returns the root value map
*
* @return the root value map
*/
public Map<String, Object> getValues(){
return values;
}
public void setValues(Map<String, Object> values) {
this.values = values;
}
/**
* Gets config option with specified path. If the option is not present and
* {@code defaultValue} is not {@code null}, value is inserted, {@link #save()}
@@ -408,8 +396,4 @@ public abstract class ConfigurationFile {
Files.write(file.toPath(), content);
}
}
public File getFile() {
return file;
}
}
@@ -1,14 +1,14 @@
package me.neznamy.tab.api.protocol;
import java.util.HashMap;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.BiFunctionWithException;
import me.neznamy.tab.api.util.Preconditions;
import java.util.HashMap;
import java.util.Map;
/**
* A class for packet building, methods are overridden in
@@ -48,9 +48,7 @@ public class PacketBuilder {
* @throws ReflectiveOperationException
* if reflection fails
*/
public Object build(TabPacket packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
Preconditions.checkNotNull(packet, "packet");
Preconditions.checkNotNull(clientVersion, "clientVersion");
public Object build(@NonNull TabPacket packet, @NonNull ProtocolVersion clientVersion) throws ReflectiveOperationException {
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
}
@@ -1,21 +1,23 @@
package me.neznamy.tab.api.protocol;
import java.util.UUID;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.util.Preconditions;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private final Action action;
private Action action;
/** BossBar title */
private String name;
@@ -53,11 +55,7 @@ public class PacketPlayOutBoss implements TabPacket {
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, String name, float pct, BarColor color, BarStyle overlay) {
Preconditions.checkNotNull(id, "uuid");
Preconditions.checkNotNull(name, "title");
Preconditions.checkNotNull(color, "color");
Preconditions.checkNotNull(overlay, "style");
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name, float pct, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.ADD;
this.id = id;
this.name = name;
@@ -68,28 +66,26 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.REMOVE action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#REMOVE} action
*
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(UUID id) {
Preconditions.checkNotNull(id, "uuid");
public PacketPlayOutBoss(@NonNull UUID id) {
this.action = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PCT action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PCT} action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(UUID id, float pct) {
Preconditions.checkNotNull(id, "uuid");
public PacketPlayOutBoss(@NonNull UUID id, float pct) {
this.action = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
@@ -97,16 +93,14 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_NAME action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_NAME} action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(UUID id, String name) {
Preconditions.checkNotNull(id, "uuid");
Preconditions.checkNotNull(name, "title");
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name) {
this.action = Action.UPDATE_NAME;
this.id = id;
this.name = name;
@@ -114,7 +108,7 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_STYLE action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_STYLE} action
*
* @param id
* BossBar uuid
@@ -123,10 +117,7 @@ public class PacketPlayOutBoss implements TabPacket {
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, BarColor color, BarStyle overlay) {
Preconditions.checkNotNull(id, "uuid");
Preconditions.checkNotNull(color, "color");
Preconditions.checkNotNull(overlay, "style");
public PacketPlayOutBoss(@NonNull UUID id, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
@@ -135,7 +126,7 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PROPERTIES action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PROPERTIES} action
*
* @param darkenScreen
* Darken screen flag
@@ -144,8 +135,7 @@ public class PacketPlayOutBoss implements TabPacket {
* @param createWorldFog
* Create fog flag
*/
public PacketPlayOutBoss(UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
Preconditions.checkNotNull(id, "uuid");
public PacketPlayOutBoss(@NonNull UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.action = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
@@ -169,123 +159,6 @@ public class PacketPlayOutBoss implements TabPacket {
return value;
}
@Override
public String toString() {
return String.format("PacketPlayOutBoss{id=%s,operation=%s,name=%s,pct=%s,color=%s,overlay=%s,darkenScreen=%s,playMusic=%s,createWorldFog=%s}",
id, action, name, pct, color, overlay, darkenScreen, playMusic, createWorldFog);
}
/**
* Returns {@link #color}
*
* @return color
*/
public BarColor getColor() {
return color;
}
/**
* Returns {@link #overlay}
*
* @return style
*/
public BarStyle getOverlay() {
return overlay;
}
/**
* Returns {@link #name}
*
* @return name
*/
public String getName() {
return name;
}
/**
* Returns {@link #id}
*
* @return id
*/
public UUID getId() {
return id;
}
/**
* Returns {@link #pct}
*
* @return progress
*/
public float getPct() {
return pct;
}
/**
* Returns {@link #action}
*
* @return packet action
*/
public Action getAction() {
return action;
}
/**
* Returns {@link #darkenScreen}
*
* @return darkenScreen
*/
public boolean isDarkenScreen() {
return darkenScreen;
}
/**
* Returns {@link #createWorldFog}
*
* @return createWorldFog
*/
public boolean isCreateWorldFog() {
return createWorldFog;
}
/**
* Returns {@link #playMusic}
*
* @return playMusic
*/
public boolean isPlayMusic() {
return playMusic;
}
/**
* Sets {@link #darkenScreen} to specified value
*
* @param darkenScreen
* Darken screen flag
*/
public void setDarkenScreen(boolean darkenScreen) {
this.darkenScreen = darkenScreen;
}
/**
* Sets {@link #createWorldFog} to specified value
*
* @param createWorldFog
* Create fog flag
*/
public void setCreateWorldFog(boolean createWorldFog) {
this.createWorldFog = createWorldFog;
}
/**
* Sets {@link #playMusic} to specified value
*
* @param playMusic
* Play music flag
*/
public void setPlayMusic(boolean playMusic) {
this.playMusic = playMusic;
}
/**
* An enum representing all valid boss packet actions.
* Calling ordinal() will return action's network ID.
@@ -1,56 +1,20 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
private final IChatBaseComponent message;
@NonNull private final IChatBaseComponent message;
/** Message position */
private final ChatMessageType type;
/**
* Constructs new instance with given parameters
*
* @param message
* Chat message to be sent
* @param type
* Message position
*/
public PacketPlayOutChat(IChatBaseComponent message, ChatMessageType type) {
Preconditions.checkNotNull(message, "message");
Preconditions.checkNotNull(type, "message type");
this.message = message;
this.type = type;
}
@Override
public String toString() {
return String.format("PacketPlayOutChat{message=%s,type=%s}", message, type);
}
/**
* Returns {@link #message}
*
* @return message
*/
public IChatBaseComponent getMessage() {
return message;
}
/**
* Returns {@link #type}
*
* @return type
*/
public ChatMessageType getType() {
return type;
}
@NonNull private final ChatMessageType type;
/**
* An enum representing positions of a chat message
@@ -1,16 +1,19 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet actions */
@@ -39,9 +42,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(entries, "entries");
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
actions = EnumSet.of(EnumPlayerInfoAction.ADD_PLAYER,
EnumPlayerInfoAction.INITIALIZE_CHAT,
@@ -63,62 +64,32 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumSet<EnumPlayerInfoAction> actions, PlayerInfoData... entries) {
Preconditions.checkNotNull(actions, "action");
Preconditions.checkNotNull(entries, "entries");
public PacketPlayOutPlayerInfo(@NonNull EnumSet<EnumPlayerInfoAction> actions, @NonNull PlayerInfoData... 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{actions=%s,entries=%s}", actions, entries);
}
/**
* Returns {@link #actions}
*
* @return packet action
*/
public EnumSet<EnumPlayerInfoAction> getActions() {
return actions;
}
/**
* Returns {@link #entries}
*
* @return affected entries
*/
public List<PlayerInfoData> getEntries() {
return entries;
}
/**
* A subclass representing player list entry
*/
@Data @AllArgsConstructor
public static class PlayerInfoData {
/** Latency */
private int latency;
/** Real name of affected player */
private String name;
/** Player UUID */
@NonNull private UUID uniqueId;
/** Player's skin, null for empty skin */
private Skin skin;
/** I have no idea what this is */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
@@ -128,53 +99,12 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
*/
private IChatBaseComponent displayName;
/** Real name of affected player */
private String name;
/** Player UUID */
private UUID uniqueId;
/** 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
*
* @param name
* Player's name
* @param uniqueId
* Player's uuid
* @param skin
* Player's platform-specific skin object
* @param latency
* Player's ping
* @param gameMode
* Player's GameMode
* @param displayName
* Player's display name
* @param profilePublicKey
* Player's chat signing key
*/
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
@@ -184,8 +114,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(UUID uniqueId, EnumGamemode gameMode) {
Preconditions.checkNotNull(uniqueId, "uuid");
public PlayerInfoData(@NonNull UUID uniqueId, EnumGamemode gameMode) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
@@ -199,8 +128,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* @param latency
* Player's ping
*/
public PlayerInfoData(UUID uniqueId, int latency) {
Preconditions.checkNotNull(uniqueId, "uuid");
public PlayerInfoData(@NonNull UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
@@ -214,8 +142,7 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* @param displayName
* Player's display name
*/
public PlayerInfoData(UUID uniqueId, IChatBaseComponent displayName) {
Preconditions.checkNotNull(uniqueId, "uuid");
public PlayerInfoData(@NonNull UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
@@ -227,186 +154,9 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(UUID uniqueId) {
public PlayerInfoData(@NonNull UUID uniqueId) {
this.uniqueId = uniqueId;
}
@Override
public String toString() {
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);
}
/**
* Returns {@link #latency}
*
* @return latency
*/
public int getLatency() {
return latency;
}
/**
* Sets {@link #latency} to specified value
*
* @param latency
* Latency to use
*/
public void setLatency(int latency) {
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}
*
* @return displayName
*/
public IChatBaseComponent getDisplayName() {
return displayName;
}
/**
* Sets {@link #displayName} to specified value
*
* @param displayName
* Display name to use
*/
public void setDisplayName(IChatBaseComponent displayName) {
this.displayName = displayName;
}
/**
* Returns {@link #uniqueId}
*
* @return uniqueId
*/
public UUID getUniqueId() {
return uniqueId;
}
/**
* Sets {@link #uniqueId} to specified value
*
* @param uniqueId
* UUID to use
*/
public void setUniqueId(UUID uniqueId) {
this.uniqueId = uniqueId;
}
/**
* Returns {@link #name}
*
* @return name
*/
public String getName() {
return name;
}
/**
* Sets {@link #name} to specified value
*
* @param name
* name to use
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns {@link #gameMode}
*
* @return gameMode
*/
public EnumGamemode getGameMode() {
return gameMode;
}
/**
* Sets {@link #gameMode} to specified value
*
* @param gameMode
* GameMode to use
*/
public void setGameMode(EnumGamemode gameMode) {
this.gameMode = gameMode;
}
/**
* Returns {@link #skin}
*
* @return skin
*/
public Skin getSkin() {
return skin;
}
/**
* Sets {@link #skin} to specified value
*
* @param skin
* Skin to use
*/
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;
}
}
/**
@@ -1,18 +1,21 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
private final IChatBaseComponent header;
@NonNull private final IChatBaseComponent header;
/** TabList footer */
private final IChatBaseComponent footer;
@NonNull private final IChatBaseComponent footer;
/**
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
@@ -23,48 +26,8 @@ public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(String header, String footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
public PacketPlayOutPlayerListHeaderFooter(@NonNull String header, @NonNull String footer) {
this.header = IChatBaseComponent.optimizedComponent(header);
this.footer = IChatBaseComponent.optimizedComponent(footer);
}
/**
* Constructs new instance with given parameters.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent header, IChatBaseComponent footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
this.header = header;
this.footer = footer;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerListHeaderFooter{header=%s,footer=%s}", header, footer);
}
/**
* Returns {@link #header}
*
* @return header
*/
public IChatBaseComponent getHeader() {
return header;
}
/**
* Returns {@link #footer}
*
* @return footer
*/
public IChatBaseComponent getFooter() {
return footer;
}
}
@@ -1,10 +1,12 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
@@ -16,45 +18,5 @@ public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
private final int slot;
/** Up to 16 characters long objective name */
private final String objectiveName;
/**
* Constructs new instance with given parameters
*
* @param slot
* Display slot
* @param objectiveName
* Internal name of the objective
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardDisplayObjective(int slot, String objectiveName) {
Preconditions.checkNotNull(objectiveName, "objective name");
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.slot = slot;
this.objectiveName = objectiveName;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardDisplayObjective{slot=%s,objectiveName=%s}", slot, objectiveName);
}
/**
* Returns {@link #slot}
*
* @return slot
*/
public int getSlot() {
return slot;
}
/**
* Returns {@link #objectiveName}
*
* @return objectiveName
*/
public String getObjectiveName() {
return objectiveName;
}
@NonNull private final String objectiveName;
}
@@ -1,21 +1,16 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutScoreboardObjective implements TabPacket {
/** Up to 16 characters long objective name */
private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Packet action.
* 0 = register,
@@ -24,30 +19,14 @@ public class PacketPlayOutScoreboardObjective implements TabPacket {
*/
private final int action;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action (0 = add, 1 = remove, 2 = update title)
* @param objectiveName
* objective name, up to 16 characters long
* @param displayName
* title
* @param renderType
* display type
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(int action, String objectiveName, String displayName, EnumScoreboardHealthDisplay renderType) {
Preconditions.checkRange(action, 0, 2, "action");
Preconditions.checkNotNull(objectiveName, "objective name");
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
Preconditions.checkNotNull(renderType, "render type");
this.objectiveName = objectiveName;
this.displayName = displayName;
this.renderType = renderType;
this.action = action;
}
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Constructs new packet with given objective name and 1 (unregister) action.
@@ -57,8 +36,7 @@ public class PacketPlayOutScoreboardObjective implements TabPacket {
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(String objectiveName) {
Preconditions.checkNotNull(objectiveName, "objective name");
public PacketPlayOutScoreboardObjective(@NonNull String objectiveName) {
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
@@ -66,48 +44,6 @@ public class PacketPlayOutScoreboardObjective implements TabPacket {
this.renderType = null;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardObjective{objectiveName=%s,displayName=%s,renderType=%s,method=%s}",
objectiveName, displayName, renderType, action);
}
/**
* Returns {@link #displayName}
*
* @return displayName
*/
public String getDisplayName() {
return displayName;
}
/**
* Returns {@link #objectiveName}
*
* @return objectiveName
*/
public String getObjectiveName() {
return objectiveName;
}
/**
* Returns {@link #renderType}
*
* @return renderType
*/
public EnumScoreboardHealthDisplay getRenderType() {
return renderType;
}
/**
* Returns {@link #action}
*
* @return packet action
*/
public int getAction() {
return action;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
@@ -1,91 +1,26 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
private final Action action;
@NonNull private final Action action;
/** Objective name */
private final String objectiveName;
@NonNull private final String objectiveName;
/** Affected player */
private final String player;
@NonNull private final String player;
/** Player's score */
private final int score;
/**
* Constructs new instance with given parameters
*
* @param action
* Packet action
* @param objectiveName
* Objective name
* @param player
* Affected player
* @param score
* Player's score
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardScore(Action action, String objectiveName, String player, int score) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(objectiveName, "objective name");
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
Preconditions.checkNotNull(player, "player");
this.action = action;
this.objectiveName = objectiveName;
this.player = player;
this.score = score;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardScore{action=%s,objectiveName=%s,player=%s,score=%s}",
action, objectiveName, player, score);
}
/**
* Returns {@link #action}
*
* @return packet action
*/
public Action getAction() {
return action;
}
/**
* Returns {@link #objectiveName}
*
* @return objective name
*/
public String getObjectiveName() {
return objectiveName;
}
/**
* Returns {@link #player}
*
* @return player
*/
public String getPlayer() {
return player;
}
/**
* Returns {@link #score}
*
* @return score
*/
public int getScore() {
return score;
}
/**
* An enum representing action types
*/
@@ -1,14 +1,17 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
@@ -18,7 +21,7 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
private final String name;
@NonNull private final String name;
/** Team prefix, limited to 16 characters, on &lt;1.13 */
private String playerPrefix;
@@ -49,7 +52,7 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* 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();
@NonNull private Collection<String> players = Collections.emptyList();
/**
* Packet action.
@@ -80,8 +83,7 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
private PacketPlayOutScoreboardTeam(int action, String name) {
Preconditions.checkNotNull(name, "team name");
private PacketPlayOutScoreboardTeam(int action, @NonNull String name) {
Preconditions.checkMaxLength(name, 16, "team name");
this.action = action;
this.name = name;
@@ -107,13 +109,9 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix, String visibility,
String collision, @NonNull Collection<String> players, int options) {
this(0, team);
Preconditions.checkNotNull(prefix, "prefix");
Preconditions.checkNotNull(suffix, "suffix");
Preconditions.checkNotNull(visibility, "visibility");
Preconditions.checkNotNull(collision, "collision");
Preconditions.checkNotNull(players, "players");
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
@@ -152,13 +150,9 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, int options) {
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix,
String visibility, String collision, int options) {
this(2, team);
Preconditions.checkNotNull(prefix, "prefix");
Preconditions.checkNotNull(suffix, "suffix");
Preconditions.checkNotNull(visibility, "visibility");
Preconditions.checkNotNull(collision, "collision");
Preconditions.checkNotNull(players, "players");
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
@@ -179,107 +173,8 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, Collection<String> players, boolean add) {
public PacketPlayOutScoreboardTeam(String team, @NonNull Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
Preconditions.checkNotNull(players, "players");
this.players = players;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardTeam{name=%s,playerPrefix=%s,playerSuffix=%s,nameTagVisibility=%s,"
+ "collisionRule=%s,color=%s,players=%s,method=%s,options=%s}",
name, playerPrefix, playerSuffix, nameTagVisibility, collisionRule, color, players, action, options);
}
/**
* Returns {@link #name}
*
* @return team name
*/
public String getName() {
return name;
}
/**
* Returns {@link #playerPrefix}
*
* @return playerPrefix
*/
public String getPlayerPrefix() {
return playerPrefix;
}
/**
* Returns {@link #playerSuffix}
*
* @return playerSuffix
*/
public String getPlayerSuffix() {
return playerSuffix;
}
/**
* Returns {@link #nameTagVisibility}
*
* @return nameTagVisibility
*/
public String getNameTagVisibility() {
return nameTagVisibility;
}
/**
* Returns {@link #collisionRule}
*
* @return collisionRule
*/
public String getCollisionRule() {
return collisionRule;
}
/**
* Sets {@link #color} to specified color
*
* @param color
* color to use
*/
public void setColor(EnumChatFormat color) {
this.color = color;
}
/**
* Returns {@link #color}
*
* @return color
*/
public EnumChatFormat getColor() {
return color;
}
/**
* Returns {@link #players}
*
* @return players
*/
public Collection<String> getPlayers() {
return players;
}
/**
* Returns {@link #options}
*
* @return team options
*/
public int getOptions() {
return options;
}
/**
* Returns {@link #action}
*
* @return packet action
*/
public int getAction() {
return action;
}
}
@@ -1,59 +1,18 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.util.Preconditions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
/**
* Class representing a minecraft skin as a value - signature pair.
*/
@Data @AllArgsConstructor
public class Skin {
/** Skin value */
private final String value;
@NonNull private final String value;
/** Skin signature, can be null */
private final String signature;
/**
* Constructs new instance with given parameters
*
* @param value
* skin value
* @param signature
* skin signature
* @throws IllegalArgumentException
* if {@code value} is {@code null}
*/
public Skin(String value, String signature) {
Preconditions.checkNotNull(value, "value");
this.value = value;
this.signature = signature;
}
/**
* Returns skin value
*
* @return skin value
*/
public String getValue() {
return value;
}
/**
* Returns skin signature
*
* @return skin signature
*/
public String getSignature() {
return signature;
}
/**
* Returns user-friendly representation of this object
*
* @return user-friendly representation of this object
*/
@Override
public String toString() {
return String.format("Skin{value=%s,signature=%s}", value, signature);
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.api.util;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
public class ComponentCache<K, V> {
private final int cacheSize;
private final BiFunctionWithException<K, ProtocolVersion, V> function;
private final Map<K, V> cacheModern = new HashMap<>();
private final Map<K, V> cacheLegacy = new HashMap<>();
public V get(K key, ProtocolVersion clientVersion) {
try {
if (key == null) return null;
Map<K, V> cache = clientVersion == null || clientVersion.getMinorVersion() >= 16 ? cacheModern : cacheLegacy;
if (cache.containsKey(key)) return cache.get(key);
V value = function.apply(key, clientVersion);
if (cache.size() > cacheSize) cache.clear();
cache.put(key, value);
return value;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.api.util;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
@@ -12,20 +13,6 @@ public class Preconditions {
*/
private Preconditions() {}
/**
* 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.
@@ -39,8 +26,7 @@ public class Preconditions {
* @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);
public static void checkMaxLength(@NonNull String string, int maxLength, String name) {
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.util;
/**
* Utility class storing methods working with reflection
*/
public class ReflectionUtils {
/**
* Returns {@code true} if class with given full name exists,
* {@code false} if not.
*
* @param path
* Full class path and name
* @return {@code true} if exists, {@code false} if not
*/
public static boolean classExists(String path) {
try {
Class.forName(path);
return true;
} catch (ClassNotFoundException | NullPointerException e) {
return false;
}
}
}
+1 -1
View File
@@ -3,8 +3,8 @@ plugins {
}
dependencies {
implementation(libs.plugin.indra)
implementation(libs.plugin.shadow)
implementation(libs.plugin.lombok)
}
dependencies {
@@ -1,16 +1,8 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
id("net.kyori.indra")
}
indra {
javaVersions {
target(8)
}
github("NEZNAMY", "TAB") {
ci(true)
}
`java-library`
id("io.freefair.lombok")
}
tasks {
@@ -27,4 +19,15 @@ 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,12 +1,25 @@
plugins {
id("tab.base-conventions")
id("net.kyori.indra.publishing")
`maven-publish`
}
indra {
publishReleasesTo("krypton", "https://repo.kryptonmc.org/releases")
publishSnapshotsTo("krypton", "https://repo.kryptonmc.org/snapshots")
configurePublications {
artifactId = "tab-$artifactId"
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"])
}
}
+3 -2
View File
@@ -11,8 +11,9 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
// projects.krypton,
projects.velocity
projects.krypton,
projects.velocity,
//projects.sponge
).map { it.dependencyProject }
val special = setOf(
-3
View File
@@ -6,7 +6,4 @@ dependencies {
compileOnly(libs.vault)
compileOnly(libs.via)
compileOnly(libs.authlib)
compileOnly(libs.essentials) {
exclude("org.spigotmc", "spigot-api")
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -12,20 +13,11 @@ import org.bukkit.event.player.PlayerQuitEvent;
/**
* The core for bukkit forwarding events into all enabled features
*/
@RequiredArgsConstructor
public class BukkitEventListener implements Listener {
/** Platform instance */
private final BukkitPlatform platform;
/**
* Constructs new instance with given parameter
*
* @param platform
* Platform instance
*/
public BukkitEventListener(BukkitPlatform platform) {
this.platform = platform;
}
/**
* Listener to PlayerQuitEvent to remove player data and forward the event to features
@@ -12,6 +12,7 @@ import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
@@ -35,10 +36,14 @@ public class BukkitPacketBuilder extends PacketBuilder {
private final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Component cache for better performance (1.16+ players) */
private final Map<IChatBaseComponent, Object> componentCacheModern = new HashMap<>();
private final ComponentCache<IChatBaseComponent, Object> componentCacheModern = new ComponentCache<>(10000,
(component, clientVersion) -> nms.ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Component cache for better performance (1.15- players) */
private final Map<IChatBaseComponent, Object> componentCacheLegacy = new HashMap<>();
private final ComponentCache<IChatBaseComponent, Object> componentCacheLegacy = new ComponentCache<>(10000,
(component, clientVersion) -> nms.ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance
@@ -490,14 +495,14 @@ public class BukkitPacketBuilder extends PacketBuilder {
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.helper().setHealth(health);
w.getHelper().setHealth(health);
}
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
w.helper().setCustomName(packet.getName(), clientVersion);
w.getHelper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getAction() == Action.ADD) {
w.helper().setEntityFlags((byte) 32);
w.helper().setWitherInvulnerableTime(880); // Magic number
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
return build(new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w));
} else {
return build(new PacketPlayOutEntityMetadata(entityId, w));
@@ -530,14 +535,8 @@ public class BukkitPacketBuilder extends PacketBuilder {
* if thrown by reflective operation
*/
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (component == null) return null;
if (component instanceof WrappedChatComponent) return ((WrappedChatComponent) component).get();
Map<IChatBaseComponent, Object> cache = clientVersion.getMinorVersion() >= 16 ? componentCacheModern : componentCacheLegacy;
if (cache.containsKey(component)) return cache.get(component);
if (cache.size() > 10000) cache.clear();
Object chat = nms.ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion));
cache.put(component, chat);
return chat;
return (clientVersion.getMinorVersion() >= 16 ? componentCacheModern : componentCacheLegacy).get(component, clientVersion);
}
/**
@@ -7,6 +7,7 @@ import java.util.function.Function;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
@@ -39,21 +40,12 @@ public class BukkitPipelineInjector extends PipelineInjector {
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
/** Injected player */
private final TabPlayer player;
/**
* Constructs new instance with given player
*
* @param player
* player to inject
*/
public BukkitChannelDuplexHandler(TabPlayer player) {
this.player = player;
}
@Override
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
try {
@@ -72,7 +64,7 @@ public class BukkitPipelineInjector extends PipelineInjector {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam != null && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
long time = System.nanoTime();
modifyPlayers(packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
@@ -1,29 +1,21 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.util.Locale;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.shared.TAB;
import net.ess3.api.events.NickChangeEvent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import com.earth2me.essentials.Essentials;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.UUID;
/**
* Bukkit registry to register bukkit-only and universal placeholders
@@ -33,9 +25,6 @@ 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;
@@ -55,13 +44,9 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
private Method purpurIsAfk;
/**
* Constructs new instance with given parameter and loads hooks
*
* @param plugin
* reference to the plugin's main class
* Constructs new instance and loads hooks
*/
public BukkitPlaceholderRegistry(JavaPlugin plugin) {
this.plugin = plugin;
public BukkitPlaceholderRegistry() {
numberFormat.setMaximumFractionDigits(2);
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
@@ -100,28 +85,18 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
}
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;
if (essentials != null) {
try {
Object user = essentials.getClass().getMethod("getUser", UUID.class).invoke(essentials, p.getUniqueId());
if ((boolean) user.getClass().getMethod("isAfk").invoke(user)) return true;
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get AFK status of " + p.getName() + " using Essentials", e);
}
}
return purpurIsAfk != null && ((Player)p.getPlayer()).isAfk();
});
if (essentials != null) {
PlayerPlaceholder nick = manager.registerPlayerPlaceholder(TabConstants.Placeholder.ESSENTIALS_NICK, -1, p -> {
String nickname = ((Essentials)essentials).getUser(p.getUniqueId()).getNickname();
return nickname == null ? p.getName() : nickname;
});
Listener nickListener = new Listener() {
@EventHandler
public void onNickChange(NickChangeEvent e) {
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);
}
};
nick.enableTriggerMode(() -> Bukkit.getPluginManager().registerEvents(nickListener, plugin),
() -> HandlerList.unregisterAll(nickListener));
} else {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.ESSENTIALS_NICK, -1, TabPlayer::getName);
}
// Removed placeholder, keeping the implementation to avoid placeholder breaking for users on update
manager.registerPlayerPlaceholder(TabConstants.Placeholder.ESSENTIALS_NICK, -1, TabPlayer::getName);
if (chat != null) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
@@ -1,23 +1,28 @@
package me.neznamy.tab.platforms.bukkit;
import com.viaversion.viaversion.api.Via;
import lombok.Getter;
import lombok.Setter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.PetFix;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
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.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.TabExpansion;
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;
@@ -28,20 +33,21 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
public class BukkitPlatform extends Platform {
public class BukkitPlatform extends BackendPlatform {
/** Plugin instance for registering tasks and events */
private final JavaPlugin plugin;
/** 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);
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
private Plugin viaVersion;
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
@@ -54,6 +60,15 @@ public class BukkitPlatform extends Platform {
public BukkitPlatform(JavaPlugin plugin) {
super(new BukkitPacketBuilder());
this.plugin = plugin;
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VIAVERSION)) {
if (ReflectionUtils.classExists("com.viaversion.viaversion.api.Via")) {
viaVersion = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.VIAVERSION);
} else {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion(TabConstants.Plugin.VIAVERSION) + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
}
@Override
@@ -69,50 +84,8 @@ public class BukkitPlatform extends Platform {
}
}
@Override
public void loadFeatures() {
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VIAVERSION)) {
try {
Class.forName("com.viaversion.viaversion.api.Via");
viaVersion = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.VIAVERSION);
} catch (ClassNotFoundException e) {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion(TabConstants.Plugin.VIAVERSION) + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
TAB tab = TAB.getInstance();
if (tab.getConfiguration().isPipelineInjection())
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 {
tab.getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
tab.loadUniversalFeatures();
if (tab.getConfiguration().getConfig().getBoolean("bossbar.enabled", false)) {
if (tab.getServerVersion().getMinorVersion() < 9) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new WitherBossBar(plugin));
} else {
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new BossBarManagerImpl());
}
}
if (tab.getServerVersion().getMinorVersion() >= 9 && tab.getConfiguration().getConfig().getBoolean("fix-pet-names.enabled", false))
tab.getFeatureManager().registerFeature(TabConstants.Feature.PET_FIX, new PetFix());
if (tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.enabled", false))
tab.getFeatureManager().registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, new PerWorldPlayerList(plugin));
if (placeholderAPI && tab.getConfiguration().getConfig().getBoolean("placeholders.register-tab-expansion", true)) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
TAB.getInstance().getPlaceholderManager().setTabExpansion(expansion);
}
for (Player p : getOnlinePlayers()) {
tab.addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
}
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
@@ -121,6 +94,48 @@ public class BukkitPlatform extends Platform {
return pl == null ? null : pl.getDescription().getVersion();
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector getPipelineInjector() {
return new BukkitPipelineInjector();
}
@Override
public NameTag getUnlimitedNametags() {
return new BukkitNameTagX(plugin);
}
@Override
public TabExpansion getTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return null;
}
@Override
public TabFeature getPetFix() {
return new PetFix();
}
@Override
public @Nullable TabFeature getPerWorldPlayerlist() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
*
@@ -181,27 +196,6 @@ public class BukkitPlatform extends Platform {
}
}
/**
* Returns status of LibsDisguises plugin presence
*
* @return {@code true} if plugin is enabled, {@code false} if not
*/
public boolean isLibsDisguisesEnabled() {
return libsDisguises;
}
/**
* Sets LibsDisguises presence status to provided value. This is used
* to disable LibsDisguises hook in case the plugin is not correctly loaded
* for any reason to avoid error spam in the hook.
*
* @param enabled
* New status of LibsDisguises presence
*/
public void setLibsDisguisesEnabled(boolean enabled) {
libsDisguises = enabled;
}
/**
* Gets protocol version of requested player and returns it.
*
@@ -1,11 +1,20 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.*;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import lombok.NonNull;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarFlag;
@@ -16,18 +25,10 @@ import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import com.mojang.authlib.GameProfile;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
@@ -65,8 +66,7 @@ public class BukkitTabPlayer extends ITabPlayer {
}
@Override
public boolean hasPermission(String permission) {
Preconditions.checkNotNull(permission, "permission");
public boolean hasPermission(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
@@ -285,40 +285,6 @@ 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 {
@@ -1,65 +1,33 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.*;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import java.util.UUID;
/**
* A class representing an armor stand attached to a player
*/
public class BukkitArmorStand implements ArmorStand {
/** Entity id counter to pick unique entity ID for each armor stand */
private static int idCounter = 2000000000;
/** 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 */
private final TabPlayer owner;
public class BukkitArmorStand extends BackendArmorStand {
/** Owner as a Bukkit player */
private final Player player;
/** Offset in blocks, 0 for original height */
private double yOffset;
/** If offset is static, or dynamic based on other armor stands */
private final boolean staticOffset;
/** 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);
protected TabPacket destroyPacket = new PacketPlayOutEntityDestroy(entityId);
/**
* Constructs new instance with given parameters.
@@ -75,47 +43,12 @@ public class BukkitArmorStand implements ArmorStand {
* @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;
this.staticOffset = staticOffset;
public BukkitArmorStand(BackendNameTagX feature, BackendArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
super(feature, asm, owner, propertyName, yOffset, staticOffset);
player = (Player) owner.getPlayer();
this.yOffset = yOffset;
this.property = owner.getProperty(propertyName);
visible = getVisibility();
sneaking = player.isSneaking();
}
@Override
public void refresh() {
visible = getVisibility();
updateMetadata();
}
@Override
public Property getProperty() {
return property;
}
@Override
public boolean hasStaticOffset() {
return staticOffset;
}
@Override
public double getOffset() {
return yOffset;
}
@Override
public void setOffset(double offset) {
if (yOffset == offset) return;
yOffset = offset;
for (TabPlayer all : asm.getNearbyPlayers()) {
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_OFFSET_CHANGE);
}
}
@Override
public void spawn(TabPlayer viewer) {
for (TabPacket packet : getSpawnPackets(viewer)) {
@@ -123,95 +56,11 @@ public class BukkitArmorStand implements ArmorStand {
}
}
@Override
public void destroy() {
for (TabPlayer all : asm.getNearbyPlayers()) all.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
@Override
public void destroy(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
@Override
public void teleport() {
for (TabPlayer all : asm.getNearbyPlayers()) {
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
}
@Override
public void teleport(TabPlayer viewer) {
if (!asm.isNearby(viewer) && viewer != owner) {
asm.spawn(viewer);
} else {
viewer.sendCustomPacket(getTeleportPacket(viewer), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
}
@Override
public void sneak(boolean sneaking) {
if (this.sneaking == sneaking) return; //idk
this.sneaking = sneaking;
for (TabPlayer viewer : asm.getNearbyPlayers()) {
if (viewer.getVersion().getMinorVersion() == 14 && !manager.isArmorStandsAlwaysVisible()) {
//1.14.x client sided bug, de-spawning completely
if (sneaking) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SNEAK);
} else {
spawn(viewer);
}
} else {
//respawning so there's no animation and it's instant
respawn(viewer);
}
}
}
@Override
public void updateVisibility(boolean force) {
boolean visibility = getVisibility();
if (visible != visibility || force) {
refresh();
}
}
@Override
public int getEntityId() {
return entityId;
}
/**
* Returns teleport packet for specified viewer
*
* @param viewer
* player to get location for
* @return teleport packet
*/
public PacketPlayOutEntityTeleport getTeleportPacket(TabPlayer viewer) {
return new PacketPlayOutEntityTeleport(entityId, getArmorStandLocationFor(viewer));
}
/**
* Updates armor stand's metadata
*/
public void updateMetadata() {
for (TabPlayer viewer : asm.getNearbyPlayers()) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
}
/**
* Returns general visibility rule for everyone with limited info
*
* @return {@code true} if armor stand should be visible, {@code false} if not
*/
public boolean getVisibility() {
if (manager.isArmorStandsAlwaysVisible()) return true;
if (owner.isDisguised() || manager.getVehicleManager().isOnBoat(owner)) return false;
return !owner.hasInvisibilityPotion() && owner.getGamemode() != 3 && !manager.hasHiddenNametag(owner) && property.get().length() > 0;
}
/**
* Returns location where armor stand should be at time of calling.
* This takes into account everything that affects height, including
@@ -222,54 +71,43 @@ public class BukkitArmorStand implements ArmorStand {
* @return Location where armor stand should be for specified viewer
*/
public Location getLocation(TabPlayer viewer) {
double x = player.getLocation().getX();
double y = getY() + yOffset;
double z = player.getLocation().getZ();
if (!player.isSleeping()) {
if (sneaking) {
if (viewer.getVersion().getMinorVersion() >= 15) {
y += 1.37;
} else if (viewer.getVersion().getMinorVersion() >= 9) {
y += 1.52;
} else {
y += 1.7;
}
} else {
y += viewer.getVersion().getMinorVersion() >= 9 ? 1.8 : 1.84; // Normal
}
} else {
y += viewer.getVersion().getMinorVersion() >= 9 ? 0.2 : 0.26; // Sleeping
}
return new Location(null,x,y,z);
}
Location loc = player.getLocation();
double y = loc.getY();
/**
* Returns Y where player is based on player's vehicle due to bukkit API bug
*
* @return correct player's Y
*/
protected double getY() {
//1.14+ server sided bug
Entity vehicle = player.getVehicle();
if (vehicle != null) {
if (vehicle.getType().toString().contains("HORSE")) { //covering all 3 horse types
return vehicle.getLocation().getY() + 0.85;
y = vehicle.getLocation().getY() + 0.85;
}
if (vehicle.getType().toString().equals("DONKEY")) { //1.11+
return vehicle.getLocation().getY() + 0.525;
y = vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
return vehicle.getLocation().getY() + 0.325;
y = vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("STRIDER")) { //1.16+
return vehicle.getLocation().getY() + 1.15;
y = vehicle.getLocation().getY() + 1.15;
}
} else {
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
y = loc.getY()-1.22;
}
}
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
return player.getLocation().getY()-1.22;
}
return player.getLocation().getY();
y += getYAdd(player.isSleeping(), sneaking, viewer);
return new Location(null, loc.getX(), y, loc.getZ());
}
@Override
public void updateMetadata(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
@Override
public void sendTeleportPacket(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityTeleport(entityId, getLocation(viewer)),
TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
/**
@@ -281,6 +119,21 @@ public class BukkitArmorStand implements ArmorStand {
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
}
private TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = calculateVisibility();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), dataWatcher),
};
}
}
/**
* Creates data watcher with specified display name for viewer
*
@@ -292,79 +145,11 @@ public class BukkitArmorStand implements ArmorStand {
*/
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
byte flag = 32; //invisible
if (sneaking) flag += (byte)2;
datawatcher.helper().setEntityFlags(flag);
datawatcher.helper().setCustomName(displayName, viewer.getVersion());
boolean visibility;
if (isNameVisiblyEmpty(displayName) || !((Player) viewer.getPlayer()).canSee(player) ||
manager.hasHiddenNametag(owner, viewer) || manager.hasHiddenNameTagVisibilityView(viewer)) {
visibility = false;
} else {
visibility = visible;
}
datawatcher.helper().setCustomNameVisible(visibility);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x()) datawatcher.helper().setArmorStandFlags((byte)16);
datawatcher.getHelper().setEntityFlags((byte) (sneaking ? 34 : 32));
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(!shouldBeInvisibleFor(viewer, displayName) && visible);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x())
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
/**
* 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 {@code true} if it's empty, {@code false} if not
*/
private boolean isNameVisiblyEmpty(String displayName) {
if (displayName.length() == 0) return true;
if (!displayName.startsWith(EnumChatFormat.COLOR_STRING) && !displayName.startsWith("&") && !displayName.startsWith("#")) return false;
String text = IChatBaseComponent.fromColoredText(displayName).toRawText();
if (text.contains(" ")) text = text.replace(" ", "");
return text.length() == 0;
}
/**
* Returns list of packets to send to make armor stand spawn with metadata
*
* @param viewer
* viewer to apply relational placeholders for
* @return List of packets that spawn the armor stand
*/
public TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = getVisibility();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), dataWatcher),
};
}
}
/**
* Returns location where armor stand should be for specified viewer
*
* @param viewer
* player to get location for
* @return location of armor stand
*/
public Location getArmorStandLocationFor(TabPlayer viewer) {
return viewer.getVersion().getMinorVersion() == 8 && !manager.isMarkerFor18x() ? getLocation(viewer).add(0,-2,0) : getLocation(viewer);
}
@Override
public void respawn(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
// 1.8.0 will not see entity that respawned in the same tick
// creating new delayed task every time someone sneaks can be abused and cause OOM
spawn(viewer);
}
}
@@ -3,24 +3,32 @@ package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends NameTagX {
public class BukkitNameTagX extends BackendNameTagX {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/** 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 parameter, loads config options, registers events
* and registers sub-features.
@@ -29,160 +37,116 @@ public class BukkitNameTagX extends NameTagX {
* plugin instance
*/
public BukkitNameTagX(JavaPlugin plugin) {
super(BukkitArmorStandManager::new);
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_PACKET_LISTENER, new PacketListener(this));
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER, vehicleManager);
}
@Override
public void load() {
super.load();
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(all)) continue;
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, all);
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.setField(packet, nms.PacketPlayInUseEntity_ENTITY, getEntityId(attacked));
}
}
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, () -> {
for (TabPlayer p : TabAPI.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(p)) continue;
getArmorStandManager(p).updateVisibility(false);
}
});
return false;
}
@SuppressWarnings("unchecked")
@Override
public void unload() {
super.unload();
HandlerList.unregisterAll(eventListener);
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
super.onJoin(connectedPlayer);
if (isPlayerDisabled(connectedPlayer)) return;
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, connectedPlayer);
spawnArmorStands(connectedPlayer, viewer);
}
}
@Override
public boolean isOnBoat(TabPlayer player) {
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;
if (((Player) viewer.getPlayer()).getWorld() != ((Player) target.getPlayer()).getWorld()) return;
if (getDistance(viewer, target) <= 48 && ((Player)viewer.getPlayer()).canSee((Player)target.getPlayer()) && !target.isVanished())
getArmorStandManager(target).spawn(viewer);
}
@Override
public void setNameTagPreview(TabPlayer player, boolean status) {
if (status) {
getArmorStandManager(player).spawn(player);
} else {
getArmorStandManager(player).destroy(player);
}
}
@Override
public void resumeArmorStands(TabPlayer player) {
if (isPlayerDisabled(player)) return;
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, player);
}
}
@Override
public void pauseArmorStands(TabPlayer player) {
getArmorStandManager(player).destroy();
}
@Override
public void updateNameTagVisibilityView(TabPlayer player) {
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
getArmorStandManager(all).updateVisibility(true);
}
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
super.onQuit(disconnectedPlayer);
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
getArmorStandManager(all).unregisterPlayer(disconnectedPlayer);
}
armorStandManagerMap.get(disconnectedPlayer).destroy();
armorStandManagerMap.remove(disconnectedPlayer); // WeakHashMap doesn't clear this due to value referencing the key
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
super.onWorldChange(p, from, to);
if (isUnlimitedDisabled(p.getServer(), to)) {
getDisabledUnlimitedPlayers().add(p);
updateTeamData(p);
} else if (getDisabledUnlimitedPlayers().remove(p)) {
updateTeamData(p);
}
if (isPreviewingNametag(p)) {
getArmorStandManager(p).spawn(p);
}
//for some reason this is needed for some users
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
if (viewer.getWorld().equals(from)) {
getArmorStandManager(p).destroy(viewer);
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutEntityTeleport.isInstance(packet)) {
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntityTeleport_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutEntityDestroy.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy(receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy(receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy(receiver, (int[]) nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet));
}
}
}
/**
* Returns flat distance between two players ignoring Y value
*
* @param player1
* first player
* @param player2
* second player
* @return flat distance in blocks
*/
private double getDistance(TabPlayer player1, TabPlayer player2) {
@Override
public double getDistance(TabPlayer player1, TabPlayer player2) {
Location loc1 = ((Player) player1.getPlayer()).getLocation();
Location loc2 = ((Player) player2.getPlayer()).getLocation();
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
}
@Override
public BukkitArmorStandManager getArmorStandManager(TabPlayer player) {
return (BukkitArmorStandManager) armorStandManagerMap.get(player);
public boolean areInSameWorld(TabPlayer player1, TabPlayer player2) {
return ((Player) player1.getPlayer()).getWorld() == ((Player) player1.getPlayer()).getWorld();
}
/**
* Returns {@link #vehicleManager}
* @return {@link #vehicleManager}
*/
public VehicleRefresher getVehicleManager() {
return vehicleManager;
@Override
public boolean canSee(TabPlayer viewer, TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player)target.getPlayer());
}
@Override
public void unregisterListener() {
HandlerList.unregisterAll(eventListener);
}
@SuppressWarnings("deprecation")
@Override
public List<Integer> getPassengers(Object entity) {
Entity vehicle = (Entity) entity;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger().getEntityId());
} else {
return Collections.emptyList();
}
}
}
@Override
public void registerVehiclePlaceholder() {
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder(TabConstants.Placeholder.VEHICLE, 100, p -> {
Entity v = ((Player)p.getPlayer()).getVehicle();
//There's a bug in Bukkit 1.19.3 throwing NPE on .toString(), use default toString implementation
return v == null ? "" : v.getClass().getName() + "@" + Integer.toHexString(v.hashCode());
});
}
@Override
public Object getVehicle(TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@Override
public int getEntityId(Object entity) {
return ((Entity)entity).getEntityId();
}
@Override
public String getEntityType(Object entity) {
return ((Entity)entity).getType().toString();
}
@Override
public BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset) {
return new BukkitArmorStand(this, feature, owner, lineName, yOffset, staticOffset);
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
@@ -13,21 +14,12 @@ import org.bukkit.event.player.PlayerToggleSneakEvent;
* Class listening to Bukkit events which the feature requires and are
* sufficient, without packets being requires.
*/
@RequiredArgsConstructor
public class EventListener implements Listener {
/** Reference to the main feature */
private final BukkitNameTagX feature;
/**
* Constructs new instance with given parameter
*
* @param feature
* NameTag feature handler
*/
public EventListener(BukkitNameTagX feature) {
this.feature = feature;
}
/**
* Sneak event listener to de-spawn and spawn armor stands to skip animation
*
@@ -1,12 +1,12 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.util.Arrays;
import lombok.Data;
import me.neznamy.tab.api.protocol.TabPacket;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
@Data
public class PacketPlayOutEntityDestroy implements TabPacket {
/** Destroyed entities */
@@ -21,17 +21,4 @@ public class PacketPlayOutEntityDestroy implements TabPacket {
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
@Override
public String toString() {
return String.format("PacketPlayOutEntityDestroy{entities=%s}", Arrays.toString(entities));
}
/**
* Returns {@link #entities}
* @return {@link #entities}
*/
public int[] getEntities() {
return entities;
}
}
@@ -1,52 +1,19 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Data;
import lombok.NonNull;
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.
*/
@Data
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;
this.dataWatcher = dataWatcher;
}
@Override
public String toString() {
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;
}
@NonNull private final DataWatcher dataWatcher;
}
@@ -1,53 +1,19 @@
package me.neznamy.tab.platforms.bukkit.nms;
import me.neznamy.tab.api.util.Preconditions;
import org.bukkit.Location;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.protocol.TabPacket;
import org.bukkit.Location;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
@Data
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;
this.location = location;
}
@Override
public String toString() {
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;
}
@NonNull private final Location location;
}
@@ -1,102 +1,32 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.util.UUID;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
@Data
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
/** Entity's id */
private final int entityId;
/** Entity's UUID (for 1.9+ servers) */
private final UUID uuid;
@NonNull private final UUID uniqueId;
/** Type of entity */
private final EntityType entityType;
@NonNull private final EntityType entityType;
/** Entity's spawn location */
private final Location location;
@NonNull 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");
Preconditions.checkNotNull(location, "location");
this.entityId = entityId;
this.uuid = uuid;
this.entityType = entityType;
this.location = location;
this.dataWatcher = dataWatcher;
}
@Override
public String toString() {
return String.format("PacketPlayOutSpawnEntityLiving{entityId=%s,uuid=%s,entityType=%s,location=%s,dataWatcher=%s}",
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;
}
}
@@ -1,22 +1,25 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher {
/** Watched data */
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
/** Helper for easier data write */
private final DataWatcherHelper helper = new DataWatcherHelper(this);
@Getter private final DataWatcherHelper helper = new DataWatcherHelper(this);
/**
* Sets value into data values
@@ -26,9 +29,7 @@ public class DataWatcher {
* @param value
* value
*/
public void setValue(DataWatcherObject type, Object value){
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(value, "value");
public void setValue(@NonNull DataWatcherObject type, @NonNull Object value){
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
}
@@ -53,15 +54,6 @@ public class DataWatcher {
return dataValues.get(position);
}
/**
* Returns helper created by this instance
*
* @return data write helper
*/
public DataWatcherHelper helper() {
return helper;
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
@@ -100,8 +92,7 @@ public class DataWatcher {
* if thrown by reflective operation
*/
@SuppressWarnings("unchecked")
public static DataWatcher fromNMS(Object nmsWatcher) throws ReflectiveOperationException {
Preconditions.checkNotNull(nmsWatcher, "nmsWatcher");
public static DataWatcher fromNMS(@NonNull Object nmsWatcher) throws ReflectiveOperationException {
DataWatcher watcher = new DataWatcher();
List<Object> items = (List<Object>) nmsWatcher.getClass().getMethod("c").invoke(nmsWatcher);
if (items != null) {
@@ -112,9 +103,4 @@ public class DataWatcher {
}
return watcher;
}
@Override
public String toString() {
return String.format("DataWatcher{values=%s}", dataValues);
}
}
@@ -65,7 +65,7 @@ public class DataWatcherHelper {
* flags to write
*/
public void setEntityFlags(byte flags) {
data.setValue(new DataWatcherObject(0, registry.getByte()), flags);
data.setValue(new DataWatcherObject(0, registry.getTypeByte()), flags);
}
/**
@@ -79,12 +79,12 @@ public class DataWatcherHelper {
public void setCustomName(String customName, ProtocolVersion clientVersion) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
try {
data.setValue(new DataWatcherObject(2, registry.getOptionalComponent()), Optional.ofNullable(((BukkitPacketBuilder)TAB.getInstance().getPlatform().getPacketBuilder()).toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
data.setValue(new DataWatcherObject(2, registry.getTypeOptionalComponent()), Optional.ofNullable(((BukkitPacketBuilder)TAB.getInstance().getPlatform().getPacketBuilder()).toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to create component", e);
}
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8){
data.setValue(new DataWatcherObject(2, registry.getString()), customName);
data.setValue(new DataWatcherObject(2, registry.getTypeString()), customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
@@ -104,7 +104,7 @@ public class DataWatcherHelper {
*/
public void setCustomNameVisible(boolean visible) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, registry.getBoolean()), visible);
data.setValue(new DataWatcherObject(3, registry.getTypeBoolean()), visible);
} else {
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
}
@@ -118,7 +118,7 @@ public class DataWatcherHelper {
*/
public void setHealth(float health) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(6, registry.getFloat()), health);
data.setValue(new DataWatcherObject(6, registry.getTypeFloat()), health);
} else {
data.setValue(new DataWatcherObject(16, null), (int)health);
}
@@ -131,7 +131,7 @@ public class DataWatcherHelper {
* flags to write
*/
public void setArmorStandFlags(byte flags) {
data.setValue(new DataWatcherObject(armorStandFlagsPosition, registry.getByte()), flags);
data.setValue(new DataWatcherObject(armorStandFlagsPosition, registry.getTypeByte()), flags);
}
/**
@@ -1,12 +1,13 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Data;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
/**
* Class representing NMS Data Watcher Item
*/
@Data
public class DataWatcherItem {
/** Value type */
@@ -14,22 +15,7 @@ public class DataWatcherItem {
/** Data value */
private final Object value;
/**
* Constructs new instance of the object with given parameters
*
* @param type
* value type
* @param value
* value
*/
public DataWatcherItem(DataWatcherObject type, Object value){
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(value, "value");
this.type = type;
this.value = value;
}
/**
* Returns and instance of this class from given NMS item
*
@@ -51,25 +37,4 @@ public class DataWatcherItem {
}
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;
}
@Override
public String toString() {
return String.format("DataWatcherItem{type=%s,value=%s}", type, value);
}
}
@@ -1,8 +1,12 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
*/
@Data @AllArgsConstructor
public class DataWatcherObject {
/** Object's position in DataWatcher */
@@ -10,38 +14,4 @@ public class DataWatcherObject {
/** 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 serializer
* Serializer for value in this slot
*/
public DataWatcherObject(int position, Object serializer){
this.position = position;
this.serializer = serializer;
}
/**
* Returns {@link #position}
* @return {@link #position}
*/
public int getPosition() {
return position;
}
/**
* Returns {@link #serializer}
* @return {@link #serializer}
*/
public Object getSerializer() {
return serializer;
}
@Override
public String toString() {
return String.format("DataWatcherObject{position=%d,serializer=%s}", position, serializer);
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
import java.util.Map;
@@ -10,19 +11,19 @@ import java.util.Map;
public class DataWatcherRegistry {
/** Byte encoder */
private Object registryByte;
@Getter private Object typeByte;
/** Float encoder */
private Object registryFloat;
@Getter private Object typeFloat;
/** String encoder */
private Object registryString;
@Getter private Object typeString;
/** Encoder for optional component */
private Object registryOptionalIChatBaseComponent;
@Getter private Object typeOptionalComponent;
/** Boolean encoder */
private Object registryBoolean;
@Getter private Object typeBoolean;
/**
* Initializes required NMS classes and fields
@@ -32,68 +33,28 @@ public class DataWatcherRegistry {
Map<String, Object> fields = nms.getStaticFields(nms.DataWatcherRegistry);
if (fields.containsKey("a")) {
// Bukkit mapping
registryByte = fields.get("a");
registryFloat = fields.get("c");
registryString = fields.get("d");
typeByte = fields.get("a");
typeFloat = fields.get("c");
typeString = fields.get("d");
if (nms.getMinorVersion() >= 13) {
if (nms.is1_19_3Plus()) {
registryOptionalIChatBaseComponent = fields.get("g");
registryBoolean = fields.get("j");
typeOptionalComponent = fields.get("g");
typeBoolean = fields.get("j");
} else {
registryOptionalIChatBaseComponent = fields.get("f");
registryBoolean = fields.get("i");
typeOptionalComponent = fields.get("f");
typeBoolean = fields.get("i");
}
} else {
registryBoolean = fields.get("h");
typeBoolean = fields.get("h");
}
} else {
// Mojang mapping
registryByte = fields.get("BYTE");
registryFloat = fields.get("FLOAT");
registryString = fields.get("STRING");
registryOptionalIChatBaseComponent = fields.get("OPTIONAL_COMPONENT");
registryBoolean = fields.get("BOOLEAN");
typeByte = fields.get("BYTE");
typeFloat = fields.get("FLOAT");
typeString = fields.get("STRING");
typeOptionalComponent = fields.get("OPTIONAL_COMPONENT");
typeBoolean = fields.get("BOOLEAN");
}
}
}
/**
* 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;
}
}
@@ -2,7 +2,10 @@ package me.neznamy.tab.platforms.bukkit.nms.storage;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherRegistry;
import org.bukkit.Bukkit;
@@ -16,16 +19,16 @@ import java.util.*;
public abstract class NMSStorage {
/** Instance of this class */
private static NMSStorage instance;
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether the server version is at least 1.19.3 or not */
private boolean is1_19_3Plus;
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
/** Basic universal values */
protected Class<?> Packet;
@@ -42,8 +45,6 @@ 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;
@@ -71,7 +72,7 @@ public abstract class NMSStorage {
public Field DataWatcherObject_SLOT;
public Field DataWatcherObject_SERIALIZER;
public Method DataWatcher_REGISTER;
private final DataWatcherRegistry registry;
@Getter private final DataWatcherRegistry dataWatcherRegistry;
//1.19.3+
protected Class<?> DataWatcher$DataValue;
public Field DataWatcher$DataValue_POSITION;
@@ -150,17 +151,13 @@ public abstract class NMSStorage {
public Method PlayerInfoData_getGamemode;
public Method PlayerInfoData_getDisplayName;
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;
@@ -226,10 +223,6 @@ public abstract class NMSStorage {
* If some field, constructor or method was not found
*/
public NMSStorage() throws ReflectiveOperationException {
try {
Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
is1_19_3Plus = true;
} catch (ClassNotFoundException | NullPointerException ignored) {}
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
@@ -245,7 +238,7 @@ 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);
registry = new DataWatcherRegistry(this);
dataWatcherRegistry = new DataWatcherRegistry(this);
dataWatcher();
entityTeleport();
spawnEntityLiving();
@@ -258,25 +251,6 @@ public abstract class NMSStorage {
loadNamedFieldsAndMethods();
}
/**
* Sets new instance
*
* @param instance
* new instance
*/
public static void setInstance(NMSStorage instance) {
NMSStorage.instance = instance;
}
/**
* Returns instance
*
* @return instance
*/
public static NMSStorage getInstance() {
return instance;
}
/**
* Loads PacketPlayOutChat's methods, fields and constructors
*
@@ -319,14 +293,12 @@ public abstract class NMSStorage {
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];
@@ -338,9 +310,6 @@ public abstract class NMSStorage {
}
PlayerInfoData_getGamemode = getMethods(PlayerInfoData, EnumGamemode).get(0);
PlayerInfoData_getDisplayName = getMethods(PlayerInfoData, IChatBaseComponent).get(0);
if (minorVersion >= 19) {
ProfilePublicKey_getRecord = getMethods(ProfilePublicKey, ProfilePublicKey$a).get(0);
}
}
/**
@@ -686,14 +655,6 @@ public abstract class NMSStorage {
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
}
/**
* Returns server's minor version
* @return server's minor version
*/
public int getMinorVersion() {
return minorVersion;
}
/**
* Sets value of a field in object
*
@@ -710,14 +671,6 @@ public abstract class NMSStorage {
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.
*
@@ -751,8 +704,4 @@ public abstract class NMSStorage {
}
return fields;
}
public boolean is1_19_3Plus() {
return is1_19_3Plus;
}
}
-1
View File
@@ -2,6 +2,5 @@ dependencies {
implementation(projects.shared)
implementation(libs.bstats.bungeecord)
compileOnly(libs.bungeecord)
compileOnly(libs.premiumVanish)
compileOnly(libs.redisBungee)
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -13,21 +14,12 @@ import net.md_5.bungee.event.EventHandler;
/**
* The core for BungeeCord forwarding events into all enabled features
*/
@AllArgsConstructor
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;
}
/**
* Disconnect event listener to forward the event to all features
*
@@ -44,36 +44,11 @@ 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;
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() &&
packet.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(packet.getEntries().stream().map(PlayerInfoData::getUniqueId).toArray(UUID[]::new));
return remove;
}
List<Item> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
@@ -87,7 +62,8 @@ public class BungeePacketBuilder extends PacketBuilder {
} else if (clientVersion.getMinorVersion() < 8) {
item.setDisplayName(String.valueOf(data.getName())); //avoiding NPE, 1.7 client requires this, 1.8 added a leading boolean
}
if (data.getGameMode() != null) item.setGamemode(data.getGameMode().ordinal()-1);
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())});
@@ -96,15 +72,25 @@ public class BungeePacketBuilder extends PacketBuilder {
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
item.setChatSessionId(data.getChatSessionId());
item.setPublicKey((PlayerPublicKey) data.getProfilePublicKey());
items.add(item);
}
PlayerListItem bungeePacket = new PlayerListItem();
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
bungeePacket.setAction(PlayerListItem.Action.valueOf(action.toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
PlayerListItemUpdate bungeePacket = new PlayerListItemUpdate();
PlayerListItemUpdate.Action[] array = packet.getActions().stream().map(action -> PlayerListItemUpdate.Action.valueOf(
action.toString().replace("GAME_MODE", "GAMEMODE"))).toArray(PlayerListItemUpdate.Action[]::new);
bungeePacket.setActions(EnumSet.of(array[0], array));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
} else {
PlayerListItem bungeePacket = new PlayerListItem();
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
bungeePacket.setAction(PlayerListItem.Action.valueOf(action.toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
}
}
@Override
@@ -139,50 +125,37 @@ public class BungeePacketBuilder extends PacketBuilder {
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object bungeePacket, ProtocolVersion clientVersion) {
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);
if (bungeePacket instanceof PlayerListItemRemove) {
return new PacketPlayOutPlayerInfo(
EnumPlayerInfoAction.REMOVE_PLAYER,
Arrays.stream(((PlayerListItemRemove) bungeePacket).getUuids()).map(PlayerInfoData::new).collect(Collectors.toList())
);
}
Item[] items = bungeePacket instanceof PlayerListItemUpdate ? ((PlayerListItemUpdate) bungeePacket).getItems() : ((PlayerListItem) bungeePacket).getItems();
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : items) {
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
listData.add(new PlayerInfoData(
i.getUsername(),
i.getUuid(),
skin,
Boolean.TRUE.equals(i.getListed()),
i.getPing() == null ? 0 : i.getPing(),
i.getGamemode() == null ? null : EnumGamemode.VALUES[i.getGamemode()+1],
IChatBaseComponent.deserialize(i.getDisplayName()),
i.getChatSessionId(),
i.getPublicKey()));
}
if (bungeePacket instanceof PlayerListItemUpdate) {
EnumPlayerInfoAction[] array = ((PlayerListItemUpdate) bungeePacket).getActions().stream().map(this::toCustomAction).toArray(EnumPlayerInfoAction[]::new);
return new PacketPlayOutPlayerInfo(EnumSet.of(array[0], array), listData);
} else {
return new PacketPlayOutPlayerInfo(toCustomAction(((PlayerListItem) bungeePacket).getAction()), listData);
}
}
private EnumPlayerInfoAction toCustomAction(Object action) {
return EnumPlayerInfoAction.valueOf(action.toString().replace("GAMEMODE", "GAME_MODE"));
}
@Override
@@ -5,6 +5,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
@@ -49,21 +50,12 @@ public class BungeePipelineInjector extends PipelineInjector {
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BungeeChannelDuplexHandler extends ChannelDuplexHandler {
/** Injected player */
protected final TabPlayer player;
/**
* Constructs new instance with given player
*
* @param player
* player to inject
*/
public BungeeChannelDuplexHandler(TabPlayer player) {
this.player = player;
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
@@ -124,9 +116,9 @@ public class BungeePipelineInjector extends PipelineInjector {
RedisBungeeSupport redis = (RedisBungeeSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getNickName()) && !packet.getName().equals(p.getTeamName())) {
if (col.contains(p.getNickname()) && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName(), p.getTeamName());
col.remove(p.getNickName());
col.remove(p.getNickname());
}
}
}
@@ -3,10 +3,13 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
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.RedisSupport;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
/**
* BungeeCord implementation of Platform
@@ -21,22 +24,20 @@ public class BungeePlatform extends ProxyPlatform {
}
@Override
public void loadFeatures() {
TAB tab = TAB.getInstance();
if (tab.getConfiguration().isPipelineInjection())
tab.getFeatureManager().registerFeature(TabConstants.Feature.PIPELINE_INJECTION, new BungeePipelineInjector());
tab.getPlaceholderManager().registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((ProxiedPlayer) p.getPlayer()).getDisplayName());
super.loadFeatures();
public @Nullable PipelineInjector getPipelineInjector() {
return new BungeePipelineInjector();
}
@Override
public @Nullable RedisSupport getRedisSupport() {
if (ProxyServer.getInstance().getPluginManager().getPlugin(TabConstants.Plugin.REDIS_BUNGEE) != null) {
if (RedisBungeeAPI.getRedisBungeeApi() != null) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, new RedisBungeeSupport());
return new RedisBungeeSupport();
} else {
TAB.getInstance().getErrorManager().criticalError("RedisBungee plugin was detected, but it returned null API instance. Disabling hook.", null);
}
}
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
tab.addPlayer(new BungeeTabPlayer(p));
}
return null;
}
@Override
@@ -44,4 +45,11 @@ public class BungeePlatform extends ProxyPlatform {
Plugin pl = ProxyServer.getInstance().getPluginManager().getPlugin(plugin);
return pl == null ? null : pl.getDescription().getVersion();
}
@Override
public void loadPlayers() {
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(p));
}
}
}
@@ -1,10 +1,9 @@
package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.NonNull;
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.UserConnection;
@@ -61,8 +60,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
@Override
public boolean hasPermission0(String permission) {
Preconditions.checkNotNull(permission, "permission");
public boolean hasPermission0(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
@@ -102,8 +100,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* packet class
* @return packet ID
*/
public int getPacketId(Class<? extends DefinedPacket> clazz) {
Preconditions.checkNotNull(clazz, "class");
public int getPacketId(@NonNull Class<? extends DefinedPacket> clazz) {
try {
return (int) getId.invoke(directionData, clazz, getPlayer().getPendingConnection().getVersion());
} catch (ReflectiveOperationException e) {
@@ -120,7 +117,8 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public boolean isVanished() {
try {
if (ProxyServer.getInstance().getPluginManager().getPlugin(TabConstants.Plugin.PREMIUM_VANISH) != null && BungeeVanishAPI.isInvisible(getPlayer())) return true;
if (ProxyServer.getInstance().getPluginManager().getPlugin(TabConstants.Plugin.PREMIUM_VANISH) != null &&
(boolean) Class.forName("de.myzelyam.api.vanish.BungeeVanishAPI").getMethod("isInvisible", ProxiedPlayer.class).invoke(null, getPlayer())) return true;
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("PremiumVanish v" + TAB.getInstance().getPlatform().getPluginVersion(TabConstants.Plugin.PREMIUM_VANISH) +
" generated an error when retrieving vanish status of " + getName(), e);
@@ -145,12 +143,11 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public UUID getChatSessionId() {
throw new UnsupportedOperationException("Not implemented yet");
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getUuid();
}
@Override
public void sendPluginMessage(byte[] message) {
Preconditions.checkNotNull(message, "message");
if (getPlayer().getServer() == null) return;
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
TAB.getInstance().getCPUManager().packetSent("Plugin Message (" + new String(message) + ")");
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bungeecord;
import java.util.ArrayList;
import me.neznamy.tab.api.util.ReflectionUtils;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
@@ -25,7 +26,7 @@ public class Main extends Plugin {
@Override
public void onEnable(){
if (!isVersionSupported()) {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
return;
}
@@ -40,20 +41,6 @@ public class Main extends Plugin {
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
}
/**
* Checks for compatibility and returns {@code true} if version is supported, {@code 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.packet.PlayerListItemUpdate");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
@Override
public void onDisable() {
if (TAB.getInstance() != null) TAB.getInstance().unload();
+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.2.3-SNAPSHOT
projectVersion=3.2.5
# Gradle properties
org.gradle.daemon=true
+5 -7
View File
@@ -21,8 +21,9 @@ event = "3.0.0"
# Platforms
bukkit = "1.16.5-R0.1-SNAPSHOT"
bungeecord = "1.19-R0.1-SNAPSHOT"
krypton = "0.59.2"
krypton = "98fe09edf8" # Krypton uses commit-based versioning
velocity = "3.1.2-SNAPSHOT"
sponge = "8.1.0"
# Platform extras
placeholderapi = "2.11.2"
@@ -30,13 +31,11 @@ vault = "1.7"
via = "4.5.1"
authlib = "1.5.25"
bstats = "3.0.0"
essentials = "2.19.7"
premiumVanish = "2.7.11-2"
redisBungee = "0.10.1"
# Plugins
indra = "2.1.1"
shadow = "7.1.2"
lombok = "6.6.1"
[libraries]
@@ -65,14 +64,13 @@ krypton-api = { group = "org.kryptonmc", name = "krypton-api", version.ref = "kr
krypton-server = { group = "org.kryptonmc", name = "krypton-server", version.ref = "krypton" }
krypton-annotationProcessor = { group = "org.kryptonmc", name = "krypton-annotation-processor", version.ref = "krypton" }
velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" }
sponge = { group = "org.spongepowered", name = "spongeapi", version.ref = "sponge" }
# Platform extras
placeholderapi = { group = "me.clip", name = "placeholderapi", version.ref = "placeholderapi" }
vault = { group = "com.github.MilkBowl", name = "VaultAPI", version.ref = "vault" }
via = { group = "com.viaversion", name = "viaversion-api", version.ref = "via" }
authlib = { group = "com.mojang", name = "authlib", version.ref = "authlib" }
essentials = { group = "net.essentialsx", name = "EssentialsX", version.ref = "essentials" }
premiumVanish = { group = "com.github.MyzelYam", name = "PremiumVanishAPI", version.ref = "premiumVanish" }
redisBungee = { group = "com.github.limework.redisbungee", name = "RedisBungee-Bungee", version.ref = "redisBungee" }
# Metrics
@@ -81,5 +79,5 @@ bstats-bungeecord = { group = "org.bstats", name = "bstats-bungeecord", version.
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" }
plugin-lombok = { group = "io.freefair.gradle", name = "lombok-plugin", version.ref = "lombok"}
+3 -2
View File
@@ -7,8 +7,9 @@ plugins {
val platforms = setOf(
rootProject.projects.bukkit,
rootProject.projects.bungeecord,
// rootProject.projects.krypton,
rootProject.projects.velocity
rootProject.projects.krypton,
rootProject.projects.velocity,
//rootProject.projects.sponge
).map { it.dependencyProject }
tasks {
+12 -6
View File
@@ -1,6 +1,8 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "1.6.0"
kotlin("kapt") version "1.6.0"
kotlin("jvm") version "1.8.0"
kotlin("kapt") version "1.8.0"
}
dependencies {
@@ -8,15 +10,19 @@ dependencies {
compileOnly(libs.krypton.api)
compileOnly(libs.krypton.server) {
exclude("ca.spottedleaf", "dataconverter")
exclude("org.kryptonmc", "nbt-common")
exclude("org.kryptonmc", "nbt-extra-kotlin")
exclude("org.kryptonmc", "serialization-core")
exclude("org.kryptonmc", "serialization-gson")
exclude("org.kryptonmc", "serialization-nbt")
}
compileOnly(libs.kotlin.stdlib)
compileOnly(libs.via)
kapt(libs.krypton.annotationProcessor)
}
tasks.compileKotlin {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = listOf("-Xjvm-default=enable")
tasks {
compileKotlin {
compilerOptions.jvmTarget.set(JvmTarget.JVM_17)
}
}
@@ -1,36 +1,35 @@
package me.neznamy.tab.platforms.krypton
import me.neznamy.tab.shared.TAB
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.Listener
import org.kryptonmc.api.event.ListenerPriority
import org.kryptonmc.api.event.command.CommandExecuteEvent
import org.kryptonmc.api.event.command.CommandResult
import org.kryptonmc.api.event.player.JoinEvent
import org.kryptonmc.api.event.player.QuitEvent
class KryptonEventListener(private val plugin: Main) {
@Listener(ListenerPriority.NONE)
@Listener
fun onJoin(event: JoinEvent) {
if (TAB.getInstance().isDisabled) return
TAB.getInstance().cpuManager.runTask() {
TAB.getInstance().featureManager.onJoin(KryptonTabPlayer(event.player, plugin.protocolVersion(event.player)))
TAB.getInstance().cpuManager.runTask {
TAB.getInstance().featureManager.onJoin(KryptonTabPlayer(event.player, plugin.getProtocolVersion(event.player)))
}
}
@Listener(ListenerPriority.NONE)
@Listener
fun onQuit(event: QuitEvent) {
if (TAB.getInstance().isDisabled) return
TAB.getInstance().cpuManager.runTask() {
TAB.getInstance().cpuManager.runTask {
TAB.getInstance().featureManager.onQuit(TAB.getInstance().getPlayer(event.player.uuid))
}
}
@Listener(ListenerPriority.NONE)
@Listener
fun onCommand(event: CommandExecuteEvent) {
if (TAB.getInstance().isDisabled) return
if (TAB.getInstance().featureManager.onCommand(TAB.getInstance().getPlayer(event.sender.uuid), event.command)) {
event.result = CommandResult.denied()
val player = event.sender
if (player !is Player) return
if (TAB.getInstance().featureManager.onCommand(TAB.getInstance().getPlayer(player.uuid), event.command)) {
event.deny()
}
}
}
@@ -4,22 +4,32 @@ import me.neznamy.tab.api.ProtocolVersion
import me.neznamy.tab.api.chat.EnumChatFormat
import me.neznamy.tab.api.chat.IChatBaseComponent
import me.neznamy.tab.api.protocol.*
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import org.kryptonmc.api.auth.GameProfile
import org.kryptonmc.api.auth.ProfileProperty
import org.kryptonmc.api.world.GameMode
import org.kryptonmc.krypton.adventure.KryptonAdventure
import org.kryptonmc.krypton.entity.player.PlayerPublicKey
import org.kryptonmc.krypton.network.chat.RemoteChatSession
import org.kryptonmc.krypton.packet.out.play.PacketOutDisplayObjective
import org.kryptonmc.krypton.packet.out.play.PacketOutObjective
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfo
import org.kryptonmc.krypton.packet.out.play.PacketOutTeam
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoRemove
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoUpdate
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateObjectives
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateScore
import org.kryptonmc.krypton.packet.out.play.PacketOutUpdateTeams
import java.util.EnumSet
// All build functions that just return the packet parameter will be passed through to be handled in KryptonTabPlayer.
object KryptonPacketBuilder : PacketBuilder() {
private val GAME_MODES = GameMode.values()
@JvmStatic
fun toComponent(text: String?, clientVersion: ProtocolVersion): Component {
if (text == null || text.isEmpty()) return Component.empty()
if (text.isNullOrEmpty()) return Component.empty()
return GsonComponentSerializer.gson().deserialize(IChatBaseComponent.optimizedComponent(text).toString(clientVersion))
}
@@ -28,84 +38,108 @@ object KryptonPacketBuilder : PacketBuilder() {
override fun build(packet: PacketPlayOutChat, clientVersion: ProtocolVersion?): Any = packet
@Suppress("UNCHECKED_CAST")
override fun build(packet: PacketPlayOutPlayerInfo, clientVersion: ProtocolVersion): Any = PacketOutPlayerInfo(
PacketOutPlayerInfo.Action.valueOf(packet.action.name),
packet.entries.map {
val displayName = it.displayName?.toString(clientVersion)
val skin = if (it.skin != null) listOf(ProfileProperty.of("textures", it.skin!!.value, it.skin!!.signature)) else emptyList()
PacketOutPlayerInfo.PlayerData(
it.uniqueId,
it.name ?: "",
skin,
GameMode.fromId((it.gameMode?.ordinal ?: 0) - 1) ?: GameMode.SURVIVAL,
it.latency,
if (displayName != null) GsonComponentSerializer.gson().deserialize(displayName) else Component.empty()
override fun build(packet: PacketPlayOutPlayerInfo, clientVersion: ProtocolVersion): Any {
if (packet.actions.contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
return PacketOutPlayerInfoRemove(packet.entries.map { it.uniqueId })
}
val actions = EnumSet.noneOf(PacketOutPlayerInfoUpdate.Action::class.java)
packet.actions.forEach { PacketOutPlayerInfoUpdate.Action.fromId(it.ordinal) }
val entries = packet.entries.map { data ->
val displayName = data.displayName?.toString(clientVersion)
val skin = if (data.skin != null) listOf(ProfileProperty.of("textures", data.skin!!.value, data.skin!!.signature)) else emptyList()
val sessionId = data.chatSessionId
val publicKey = data.profilePublicKey as? PlayerPublicKey.Data
val session = if (sessionId != null && publicKey != null) RemoteChatSession.Data(sessionId, publicKey) else null
PacketOutPlayerInfoUpdate.Entry(
data.uniqueId,
GameProfile.of(data.name ?: "", data.uniqueId, skin),
data.isListed,
data.latency,
GAME_MODES[data.gameMode.ordinal - 1],
displayName?.let { GsonComponentSerializer.gson().deserialize(it) },
session
)
}
)
return PacketOutPlayerInfoUpdate(actions, entries)
}
override fun build(packet: PacketPlayOutPlayerListHeaderFooter, clientVersion: ProtocolVersion?): Any = packet
override fun build(packet: PacketPlayOutScoreboardDisplayObjective, clientVersion: ProtocolVersion?): Any = PacketOutDisplayObjective(
packet.slot,
packet.objectiveName
)
override fun build(packet: PacketPlayOutScoreboardDisplayObjective, clientVersion: ProtocolVersion?): Any {
return PacketOutDisplayObjective(packet.slot, packet.objectiveName)
}
override fun build(packet: PacketPlayOutScoreboardObjective, clientVersion: ProtocolVersion): Any = PacketOutObjective(
PacketOutObjective.Action.fromId(packet.method)!!,
packet.objectiveName,
toComponent(packet.displayName, clientVersion),
packet.renderType?.ordinal ?: -1
)
override fun build(packet: PacketPlayOutScoreboardObjective, clientVersion: ProtocolVersion): Any {
return PacketOutUpdateObjectives(
packet.objectiveName,
packet.action,
toComponent(packet.displayName, clientVersion),
packet.renderType?.ordinal ?: -1
)
}
override fun build(packet: PacketPlayOutScoreboardScore, clientVersion: ProtocolVersion?): Any = PacketOutUpdateScore(
PacketOutUpdateScore.Action.fromId(packet.action.ordinal)!!,
Component.text(packet.player),
packet.objectiveName,
packet.score
)
override fun build(packet: PacketPlayOutScoreboardScore, clientVersion: ProtocolVersion?): Any {
return PacketOutUpdateScore(packet.player, packet.action.ordinal, packet.objectiveName, packet.score)
}
override fun build(packet: PacketPlayOutScoreboardTeam, clientVersion: ProtocolVersion): Any {
val action = PacketOutTeam.Action.fromId(packet.method)!!
val action = PacketOutUpdateTeams.Action.fromId(packet.action)!!
val players = packet.players.map(Component::text)
return PacketOutUpdateTeams(packet.name, action, createParameters(packet, clientVersion), players)
}
private fun createParameters(packet: PacketPlayOutScoreboardTeam, clientVersion: ProtocolVersion): PacketOutUpdateTeams.Parameters? {
if (packet.action != 0 && packet.action != 2) return null
var prefix = packet.playerPrefix
var suffix = packet.playerSuffix
if (clientVersion.minorVersion < 13) {
prefix = cutTo(prefix, 16)
suffix = cutTo(suffix, 16)
}
return PacketOutTeam(
action,
packet.name,
return PacketOutUpdateTeams.Parameters(
Component.text(packet.name),
packet.color?.ordinal ?: EnumChatFormat.lastColorsOf(packet.playerPrefix).ordinal,
packet.options,
packet.nameTagVisibility,
packet.collisionRule,
KryptonAdventure.getColorFromId(packet.color?.ordinal ?: EnumChatFormat.lastColorsOf(packet.playerPrefix).ordinal),
toComponent(prefix, clientVersion),
toComponent(suffix, clientVersion),
packet.options and 1 > 0,
packet.options and 2 > 0,
packet.nameTagVisibility ?: "",
packet.collisionRule ?: "",
players,
if (action == PacketOutTeam.Action.ADD_MEMBERS || action == PacketOutTeam.Action.REMOVE_MEMBERS) players else emptySet()
toComponent(suffix, clientVersion)
)
}
override fun readPlayerInfo(packet: Any?, clientVersion: ProtocolVersion?): PacketPlayOutPlayerInfo? {
if (packet !is PacketOutPlayerInfo) return null
val action = PacketPlayOutPlayerInfo.EnumPlayerInfoAction.valueOf(packet.action.name)
val listData = packet.players.map { data ->
val serializedListName = if (data.displayName != null) GsonComponentSerializer.gson().serialize(data.displayName!!) else null
val textures = data.properties.firstOrNull { it.name == "textures" } ?: return null
PacketPlayOutPlayerInfo.PlayerInfoData(
data.name,
data.uuid,
if (packet is PacketOutPlayerInfoRemove) {
return PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, packet.profileIds.map { PlayerInfoData(it) })
}
if (packet !is PacketOutPlayerInfoUpdate) return null
val actions = EnumSet.noneOf(EnumPlayerInfoAction::class.java)
packet.actions.forEach { actions.add(EnumPlayerInfoAction.valueOf(it.name)) }
val entries = packet.entries.map { entry ->
val displayName = entry.displayName
val serializedListName = if (displayName != null) GsonComponentSerializer.gson().serialize(displayName) else null
val textures = entry.profile.properties.firstOrNull { it.name == "textures" } ?: return null
PlayerInfoData(
entry.profile.name,
entry.profile.uuid,
Skin(textures.value, textures.signature),
data.latency,
PacketPlayOutPlayerInfo.EnumGamemode.VALUES[data.gameMode.ordinal + 1],
if (serializedListName != null) IChatBaseComponent.deserialize(serializedListName) else null
entry.listed,
entry.latency,
PacketPlayOutPlayerInfo.EnumGamemode.VALUES[entry.gameMode.ordinal + 1],
if (serializedListName != null) IChatBaseComponent.deserialize(serializedListName) else null,
entry.chatSession?.sessionId,
entry.chatSession?.publicKey
)
}
return PacketPlayOutPlayerInfo(action, listData)
return PacketPlayOutPlayerInfo(actions, entries)
}
override fun readObjective(packet: Any?): PacketPlayOutScoreboardObjective? = null
@@ -6,14 +6,13 @@ import io.netty.channel.ChannelPromise
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.features.PipelineInjector
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfo
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoRemove
import org.kryptonmc.krypton.packet.out.play.PacketOutPlayerInfoUpdate
import java.util.function.Function
class KryptonPipelineInjector : PipelineInjector("handler") {
init {
channelFunction = Function(::KryptonChannelDuplexHandler)
}
override fun getChannelFunction(): Function<TabPlayer, ChannelDuplexHandler> = Function(::KryptonChannelDuplexHandler)
inner class KryptonChannelDuplexHandler(private val player: TabPlayer) : ChannelDuplexHandler() {
@@ -28,7 +27,7 @@ class KryptonPipelineInjector : PipelineInjector("handler") {
override fun write(ctx: ChannelHandlerContext, msg: Any, promise: ChannelPromise) {
try {
if (msg is PacketOutPlayerInfo) {
if (msg is PacketOutPlayerInfoUpdate || msg is PacketOutPlayerInfoRemove) {
super.write(ctx, TAB.getInstance().featureManager.onPacketPlayOutPlayerInfo(player, msg), promise)
return
}
@@ -1,8 +1,5 @@
package me.neznamy.tab.platforms.krypton
import me.lucko.spark.api.statistic.StatisticWindow.CpuUsage
import me.lucko.spark.api.statistic.StatisticWindow.MillisPerTick
import me.lucko.spark.api.statistic.StatisticWindow.TicksPerSecond
import me.neznamy.tab.api.placeholder.PlaceholderManager
import me.neznamy.tab.shared.placeholders.PlaceholderRegistry
import org.kryptonmc.api.entity.player.Player
@@ -17,42 +14,42 @@ class KryptonPlaceholderRegistry(private val plugin: Main) : PlaceholderRegistry
manager.registerPlayerPlaceholder("%displayname%", 500) { (it.player as Player).displayName }
manager.registerPlayerPlaceholder("%health%", 100) { ceil((it.player as Player).health) }
// TPS
val tps = plugin.server.spark.tps()!!
manager.registerServerPlaceholder("%tps_5s%", 1000) { format(tps.poll(TicksPerSecond.SECONDS_5)) }
manager.registerServerPlaceholder("%tps_10s%", 1000) { format(tps.poll(TicksPerSecond.SECONDS_10)) }
manager.registerServerPlaceholder("%tps_1m%", 1000) { format(tps.poll(TicksPerSecond.MINUTES_1)) }
manager.registerServerPlaceholder("%tps_5m%", 1000) { format(tps.poll(TicksPerSecond.MINUTES_5)) }
manager.registerServerPlaceholder("%tps_15m%", 1000) { format(tps.poll(TicksPerSecond.MINUTES_15)) }
// MSPT
val mspt = plugin.server.spark.mspt()!!
manager.registerServerPlaceholder("%mspt_min_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).min()) }
manager.registerServerPlaceholder("%mspt_max_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).max()) }
manager.registerServerPlaceholder("%mspt_mean_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).mean()) }
manager.registerServerPlaceholder("%mspt_median_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).median()) }
manager.registerServerPlaceholder("%mspt_percentile95_10s%", 1000) {
formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).percentile95th())
}
manager.registerServerPlaceholder("%mspt_min_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).min()) }
manager.registerServerPlaceholder("%mspt_max_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).max()) }
manager.registerServerPlaceholder("%mspt_mean_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).mean()) }
manager.registerServerPlaceholder("%mspt_median_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).median()) }
manager.registerServerPlaceholder("%mspt_percentile95_1m%", 1000) {
formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).percentile95th())
}
// CPU process
val process = plugin.server.spark.cpuProcess()
manager.registerServerPlaceholder("%cpu_process_10s%", 1000) { format(process.poll(CpuUsage.SECONDS_10)) }
manager.registerServerPlaceholder("%cpu_process_1m%", 1000) { format(process.poll(CpuUsage.MINUTES_1)) }
manager.registerServerPlaceholder("%cpu_process_15m%", 1000) { format(process.poll(CpuUsage.MINUTES_15)) }
// CPU system
val system = plugin.server.spark.cpuSystem()
manager.registerServerPlaceholder("%cpu_system_10s%", 1000) { format(system.poll(CpuUsage.SECONDS_10)) }
manager.registerServerPlaceholder("%cpu_system_1m%", 1000) { format(system.poll(CpuUsage.MINUTES_1)) }
manager.registerServerPlaceholder("%cpu_system_15m%", 1000) { format(system.poll(CpuUsage.MINUTES_15)) }
// // TPS
// val tps = plugin.server.spark.tps()!!
// manager.registerServerPlaceholder("%tps_5s%", 1000) { format(tps.poll(TicksPerSecond.SECONDS_5)) }
// manager.registerServerPlaceholder("%tps_10s%", 1000) { format(tps.poll(TicksPerSecond.SECONDS_10)) }
// manager.registerServerPlaceholder("%tps_1m%", 1000) { format(tps.poll(TicksPerSecond.MINUTES_1)) }
// manager.registerServerPlaceholder("%tps_5m%", 1000) { format(tps.poll(TicksPerSecond.MINUTES_5)) }
// manager.registerServerPlaceholder("%tps_15m%", 1000) { format(tps.poll(TicksPerSecond.MINUTES_15)) }
//
// // MSPT
// val mspt = plugin.server.spark.mspt()!!
// manager.registerServerPlaceholder("%mspt_min_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).min()) }
// manager.registerServerPlaceholder("%mspt_max_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).max()) }
// manager.registerServerPlaceholder("%mspt_mean_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).mean()) }
// manager.registerServerPlaceholder("%mspt_median_10s%", 1000) { formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).median()) }
// manager.registerServerPlaceholder("%mspt_percentile95_10s%", 1000) {
// formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).percentile95th())
// }
// manager.registerServerPlaceholder("%mspt_min_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).min()) }
// manager.registerServerPlaceholder("%mspt_max_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).max()) }
// manager.registerServerPlaceholder("%mspt_mean_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).mean()) }
// manager.registerServerPlaceholder("%mspt_median_1m%", 1000) { formatMSPT(mspt.poll(MillisPerTick.MINUTES_1).median()) }
// manager.registerServerPlaceholder("%mspt_percentile95_1m%", 1000) {
// formatMSPT(mspt.poll(MillisPerTick.SECONDS_10).percentile95th())
// }
//
// // CPU process
// val process = plugin.server.spark.cpuProcess()
// manager.registerServerPlaceholder("%cpu_process_10s%", 1000) { format(process.poll(CpuUsage.SECONDS_10)) }
// manager.registerServerPlaceholder("%cpu_process_1m%", 1000) { format(process.poll(CpuUsage.MINUTES_1)) }
// manager.registerServerPlaceholder("%cpu_process_15m%", 1000) { format(process.poll(CpuUsage.MINUTES_15)) }
//
// // CPU system
// val system = plugin.server.spark.cpuSystem()
// manager.registerServerPlaceholder("%cpu_system_10s%", 1000) { format(system.poll(CpuUsage.SECONDS_10)) }
// manager.registerServerPlaceholder("%cpu_system_1m%", 1000) { format(system.poll(CpuUsage.MINUTES_1)) }
// manager.registerServerPlaceholder("%cpu_system_15m%", 1000) { format(system.poll(CpuUsage.MINUTES_15)) }
}
private fun format(value: Double): String = TWO_DECIMAL_PLACES.format(value)
@@ -1,13 +1,11 @@
package me.neznamy.tab.platforms.krypton
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.api.protocol.PacketBuilder
import me.neznamy.tab.platforms.krypton.features.PerWorldPlayerList
import me.neznamy.tab.platforms.krypton.features.unlimitedtags.NameTagX
import me.neznamy.tab.shared.Platform
import me.neznamy.tab.api.TabFeature
import me.neznamy.tab.platforms.krypton.features.unlimitedtags.KryptonNameTagX
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.TabConstants
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl
import me.neznamy.tab.shared.backend.BackendPlatform
import me.neznamy.tab.shared.features.PipelineInjector
import me.neznamy.tab.shared.features.TabExpansion
import me.neznamy.tab.shared.features.nametags.NameTag
import me.neznamy.tab.shared.permission.LuckPerms
import me.neznamy.tab.shared.permission.None
@@ -15,41 +13,16 @@ import me.neznamy.tab.shared.permission.PermissionPlugin
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import java.io.File
class KryptonPlatform(
private val plugin: Main,
private val folder: File
) : Platform {
class KryptonPlatform(private val plugin: Main) : BackendPlatform(KryptonPacketBuilder) {
private val server = plugin.server
override fun detectPermissionPlugin(): PermissionPlugin {
if (isPluginEnabled("luckperms")) return LuckPerms(server.pluginManager.plugin("luckperms")!!.description.version)
if (server.pluginManager.isLoaded("luckperms")) return LuckPerms(getPluginVersion("luckperms"))
return None()
}
override fun loadFeatures() {
if (TAB.getInstance().configuration.isPipelineInjection) {
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.PIPELINE_INJECTION, KryptonPipelineInjector())
}
// Placeholders
KryptonPlaceholderRegistry(plugin).registerPlaceholders(TAB.getInstance().placeholderManager)
UniversalPlaceholderRegistry().registerPlaceholders(TAB.getInstance().placeholderManager)
// Load features
loadNametagFeature()
TAB.getInstance().loadUniversalFeatures()
if (TAB.getInstance().config.getBoolean("bossbar.enabled", false)) {
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.BOSS_BAR, BossBarManagerImpl())
}
if (TAB.getInstance().config.getBoolean("per-world-playerlist.enabled", false)) {
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, PerWorldPlayerList(plugin))
}
server.players.forEach { TAB.getInstance().addPlayer(KryptonTabPlayer(it, plugin.protocolVersion(it))) }
}
override fun sendConsoleMessage(message: String, translateColors: Boolean) {
val component = if (translateColors) LegacyComponentSerializer.legacyAmpersand().deserialize(message) else Component.text(message)
server.console.sendMessage(component)
@@ -65,42 +38,44 @@ class KryptonPlatform(
val serverIntervals = manager.serverPlaceholderRefreshIntervals
val playerIntervals = manager.playerPlaceholderRefreshIntervals
if (serverIntervals.containsKey(identifier)) {
manager.registerServerPlaceholder(identifier, serverIntervals[identifier]!!) { identifier }
manager.registerServerPlaceholder(identifier, serverIntervals.get(identifier)!!) { identifier }
return
}
if (playerIntervals.containsKey(identifier)) {
manager.registerPlayerPlaceholder(identifier, playerIntervals[identifier]!!) { identifier }
manager.registerPlayerPlaceholder(identifier, playerIntervals.get(identifier)!!) { identifier }
return
}
manager.registerPlayerPlaceholder(identifier, manager.defaultRefresh) { identifier }
}
override fun getServerVersion(): String = "${server.platform.name} ${server.platform.version}"
override fun getPluginVersion(plugin: String): String? = server.pluginManager.getPlugin(plugin)?.description?.version
override fun getDataFolder(): File = folder
override fun callLoadEvent() {
// do nothing here, this will be removed
override fun loadPlayers() {
server.players.forEach { TAB.getInstance().addPlayer(KryptonTabPlayer(it, plugin.getProtocolVersion(it))) }
}
override fun callLoadEvent(player: TabPlayer) {
// do nothing here, this will be removed
override fun registerPlaceholders() {
KryptonPlaceholderRegistry(plugin).registerPlaceholders(TAB.getInstance().placeholderManager)
UniversalPlaceholderRegistry().registerPlaceholders(TAB.getInstance().placeholderManager)
}
override fun getMaxPlayers(): Int = server.maxPlayers
override fun getPipelineInjector(): PipelineInjector {
return KryptonPipelineInjector()
}
override fun getPacketBuilder(): PacketBuilder = KryptonPacketBuilder
override fun getUnlimitedNametags(): NameTag {
return KryptonNameTagX(plugin)
}
override fun isProxy(): Boolean = false
override fun getTabExpansion(): TabExpansion? {
return null
}
override fun isPluginEnabled(plugin: String): Boolean = server.pluginManager.isLoaded(plugin)
override fun getPetFix(): TabFeature? {
return null
}
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)) {
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, NameTagX(plugin))
} else {
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.NAME_TAGS, NameTag())
}
override fun getPerWorldPlayerlist(): TabFeature? {
return null
}
}
@@ -1,41 +1,45 @@
package me.neznamy.tab.platforms.krypton
import io.netty.channel.Channel
import me.neznamy.tab.api.chat.IChatBaseComponent
import me.neznamy.tab.api.protocol.PacketPlayOutBoss
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action
import me.neznamy.tab.api.protocol.PacketPlayOutChat
import me.neznamy.tab.api.protocol.PacketPlayOutChat.ChatMessageType
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerListHeaderFooter
import me.neznamy.tab.api.protocol.Skin
import me.neznamy.tab.shared.ITabPlayer
import me.neznamy.tab.shared.TAB
import net.kyori.adventure.audience.MessageType
import net.kyori.adventure.bossbar.BossBar
import net.kyori.adventure.bossbar.BossBar.Color
import net.kyori.adventure.bossbar.BossBar.Flag
import net.kyori.adventure.bossbar.BossBar.Overlay
import net.kyori.adventure.chat.ChatType
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.krypton.entity.player.KryptonPlayer
import org.kryptonmc.krypton.network.NettyConnection
import org.kryptonmc.krypton.packet.Packet
import java.util.UUID
class KryptonTabPlayer(
delegate: Player,
protocolVersion: Int
) : ITabPlayer(delegate, delegate.uuid, delegate.profile.name, "N/A", delegate.world.name, protocolVersion) {
) : ITabPlayer(delegate, delegate.uuid, delegate.profile.name, "N/A", delegate.world.name, protocolVersion, true) {
private val delegate = delegate as KryptonPlayer
private val bossBars = mutableMapOf<UUID, BossBar>()
init {
channel = this.delegate.session.channel
val connection = this.delegate.connection
channel = NettyConnection::class.java.getDeclaredField("channel").apply { isAccessible = true }.get(connection) as Channel
}
override fun hasPermission(permission: String): Boolean = delegate.hasPermission(permission)
override fun getPing(): Int {
val latency = delegate.session.latency
val latency = delegate.connection.latency()
if (latency > 10000 || latency < 0) return -1
return latency
}
@@ -44,7 +48,7 @@ class KryptonTabPlayer(
if (packet == null) return
try {
if (packet is Packet) {
delegate.session.send(packet)
delegate.connection.send(packet)
return
}
when (packet) {
@@ -71,12 +75,12 @@ class KryptonTabPlayer(
override fun isOnline(): Boolean = delegate.isOnline
override fun isVanished(): Boolean = delegate.isVanished
override fun isVanished(): Boolean = false
override fun getGamemode(): Int = delegate.gameMode.ordinal
private fun handle(packet: PacketPlayOutBoss) {
when (packet.operation) {
when (packet.action) {
Action.ADD -> {
if (bossBars.containsKey(packet.id)) return
val bar = BossBar.bossBar(
@@ -88,22 +92,22 @@ class KryptonTabPlayer(
if (packet.isCreateWorldFog) bar.addFlag(Flag.CREATE_WORLD_FOG)
if (packet.isDarkenScreen) bar.addFlag(Flag.DARKEN_SCREEN)
if (packet.isPlayMusic) bar.addFlag(Flag.PLAY_BOSS_MUSIC)
bossBars[packet.id] = bar
bossBars.put(packet.id, bar)
delegate.showBossBar(bar)
}
Action.REMOVE -> {
delegate.hideBossBar(bossBars[packet.id] ?: return)
delegate.hideBossBar(bossBars.get(packet.id) ?: return)
bossBars.remove(packet.id)
}
Action.UPDATE_PCT -> bossBars[packet.id]?.progress(packet.pct)
Action.UPDATE_NAME -> bossBars[packet.id]?.name(LegacyComponentSerializer.legacySection().deserialize(packet.name))
Action.UPDATE_PCT -> bossBars.get(packet.id)?.progress(packet.pct)
Action.UPDATE_NAME -> bossBars.get(packet.id)?.name(LegacyComponentSerializer.legacySection().deserialize(packet.name))
Action.UPDATE_STYLE -> {
val bar = bossBars[packet.id] ?: return
val bar = bossBars.get(packet.id) ?: return
bar.overlay(Overlay.valueOf(packet.overlay.toString()))
bar.color(Color.valueOf(packet.color.toString()))
}
Action.UPDATE_PROPERTIES -> {
val bar = bossBars[packet.id] ?: return
val bar = bossBars.get(packet.id) ?: return
processFlag(bar, packet.isCreateWorldFog, Flag.CREATE_WORLD_FOG)
processFlag(bar, packet.isDarkenScreen, Flag.DARKEN_SCREEN)
processFlag(bar, packet.isPlayMusic, Flag.PLAY_BOSS_MUSIC)
@@ -114,12 +118,11 @@ class KryptonTabPlayer(
private fun handle(packet: PacketPlayOutChat) {
val message = GsonComponentSerializer.gson().deserialize(packet.message.toString(version))
if (packet.type === PacketPlayOutChat.ChatMessageType.GAME_INFO) {
// Adventure recommends that we send action bars for GAME_INFO, which is why GAME_INFO isn't in MessageType
delegate.sendActionBar(message)
return
when (packet.type) {
ChatMessageType.CHAT -> delegate.sendMessage(message, ChatType.CHAT.bind(getPlayer().displayName))
ChatMessageType.SYSTEM -> delegate.sendMessage(message)
ChatMessageType.GAME_INFO -> delegate.sendActionBar(message)
}
delegate.sendMessage(message, MessageType.valueOf(packet.type.name))
}
private fun handle(packet: PacketPlayOutPlayerListHeaderFooter) {
@@ -3,16 +3,19 @@ package me.neznamy.tab.platforms.krypton
import com.google.inject.Inject
import com.viaversion.viaversion.api.Via
import me.neznamy.tab.api.ProtocolVersion
import me.neznamy.tab.api.TabConstants
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.TabConstants
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import org.kryptonmc.api.Server
import org.kryptonmc.api.command.CommandMeta
import org.kryptonmc.api.command.Sender
import org.kryptonmc.api.command.SimpleCommand
import org.kryptonmc.api.command.meta.SimpleCommandMeta
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.Event
import org.kryptonmc.api.event.EventFilter
import org.kryptonmc.api.event.EventNode
import org.kryptonmc.api.event.Listener
import org.kryptonmc.api.event.server.ServerStartEvent
import org.kryptonmc.api.event.server.ServerStopEvent
@@ -34,20 +37,34 @@ import java.nio.file.Path
)
class Main @Inject constructor(
val server: Server,
private val pluginEventNode: EventNode<Event>,
@DataFolder val folder: Path
) {
val eventNode: EventNode<Event> = EventNode.filteredForEvent("tab_events", EventFilter.ALL) { !TAB.getInstance().isPluginDisabled }
@Listener
fun onStart(event: ServerStartEvent) {
TAB.setInstance(TAB(KryptonPlatform(this, folder.toFile()), ProtocolVersion.fromNetworkId(server.platform.protocolVersion)))
if (TAB.getInstance().serverVersion == ProtocolVersion.UNKNOWN) {
pluginEventNode.addChild(eventNode)
val tab = TAB(
KryptonPlatform(this),
ProtocolVersion.fromNetworkId(server.platform.protocolVersion),
server.platform.version,
folder.toFile(),
null
)
TAB.setInstance(tab)
if (TAB.getInstance().serverVersion == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
server.console.sendMessage(Component.text(
"[TAB] Unknown server version: ${server.platform.version}! Plugin may not work correctly",
NamedTextColor.RED
))
}
server.eventManager.register(this, KryptonEventListener(this))
server.commandManager.register(KryptonTABCommand(), SimpleCommandMeta.builder("tab").build())
eventNode.registerListeners(KryptonEventListener(this))
server.commandManager.register(KryptonTABCommand(), CommandMeta.builder("tab").build())
TAB.getInstance().load()
}
@@ -56,12 +73,12 @@ class Main @Inject constructor(
TAB.getInstance()?.unload()
}
fun protocolVersion(player: Player): Int {
if (server.pluginManager.isLoaded("viaversion")) return viaProtocolVersion(player)
fun getProtocolVersion(player: Player): Int {
if (server.pluginManager.isLoaded("viaversion")) return getViaProtocolVersion(player)
return TAB.getInstance().serverVersion.networkId
}
private fun viaProtocolVersion(player: Player, retryLevel: Int = 0): Int {
private fun getViaProtocolVersion(player: Player, retryLevel: Int = 0): Int {
try {
if (retryLevel == 10) {
TAB.getInstance().debug("Failed to get protocol version of ${player.profile.name} after 10 retries")
@@ -70,7 +87,7 @@ class Main @Inject constructor(
val version = Via.getAPI().getPlayerVersion(player)
if (version == -1) {
Thread.sleep(5)
return viaProtocolVersion(player, retryLevel + 1)
return getViaProtocolVersion(player, retryLevel + 1)
}
TAB.getInstance().debug("ViaVersion returned protocol version $version for ${player.profile.name}")
return version
@@ -79,7 +96,7 @@ class Main @Inject constructor(
return -1
} catch (exception: Throwable) {
TAB.getInstance().errorManager.printError("Failed to get protocol version of ${player.profile.name} using ViaVersion " +
"v${server.pluginManager.plugin("viaversion")?.description?.version}")
"v${server.pluginManager.getPlugin("viaversion")?.description?.version}")
return TAB.getInstance().serverVersion.networkId
}
}
@@ -87,7 +104,7 @@ class Main @Inject constructor(
class KryptonTABCommand : SimpleCommand {
override fun execute(sender: Sender, args: Array<String>) {
if (TAB.getInstance().isDisabled) {
if (TAB.getInstance().isPluginDisabled) {
val canReload = sender.hasPermission("tab.reload")
val isAdmin = sender.hasPermission("tab.admin")
TAB.getInstance().disabledCommand.execute(args, canReload, isAdmin).forEach { sender.sendMessage(Component.text(it)) }
@@ -1,71 +0,0 @@
package me.neznamy.tab.platforms.krypton.features
import me.neznamy.tab.api.TabFeature
import me.neznamy.tab.platforms.krypton.Main
import me.neznamy.tab.shared.TabConstants
import me.neznamy.tab.shared.TAB
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.Listener
import org.kryptonmc.api.event.player.JoinEvent
// TODO: Also check player on world change when Krypton supports world changing
class PerWorldPlayerList(private val plugin: Main) : TabFeature("Per world playerlist", null) {
private val allowBypass = TAB.getInstance().configuration.config.getBoolean(
"per-world-playerlist.allow-bypass-permission",
false
)
private val ignoredWorlds = TAB.getInstance().configuration.config.getStringList(
"per-world-playerlist.ignore-effect-in-worlds",
listOf("ignoredworld", "build")
)
private val sharedWorlds = TAB.getInstance().configuration.config
.getConfigurationSection<String, List<String>>("per-world-playerlist.shared-playerlist-world-groups")
init {
TAB.getInstance().debug("Loaded PerWorldPlayerList feature with parameters allowBypass=$allowBypass, ignoredWorlds=$ignoredWorlds, " +
"sharedWorlds=$sharedWorlds")
plugin.server.eventManager.register(plugin, this)
}
@Listener
fun onJoin(event: JoinEvent) {
val time = System.nanoTime()
checkPlayer(event.player)
TAB.getInstance().cpuManager.addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime() - time)
}
override fun load() {
plugin.server.scheduler.run(plugin) { plugin.server.players.forEach(::checkPlayer) }
}
override fun unload() {
plugin.server.players.forEach { plugin.server.players.forEach(it::show) }
plugin.server.eventManager.unregisterListener(plugin, this)
}
private fun checkPlayer(player: Player) {
plugin.server.players.forEach {
if (it === player) return@forEach
if (!shouldSee(player, it) && player.canSee(it)) player.hide(it)
if (shouldSee(player, it) && !player.canSee(it)) player.show(it)
if (!shouldSee(it, player) && it.canSee(player)) it.hide(player)
if (shouldSee(it, player) && !it.canSee(player)) it.show(player)
}
}
private fun shouldSee(viewer: Player, target: Player): Boolean {
if (target === viewer) return true
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.world.name)) {
return true
}
var viewerWorldGroup = "${viewer.world.name}-default"
var targetWorldGroup = "${target.world.name}-default"
sharedWorlds.forEach { (key, value) ->
if (value == null) return@forEach
if (value.contains(viewer.world.name)) viewerWorldGroup = key
if (value.contains(target.world.name)) targetWorldGroup = key
}
return viewerWorldGroup == targetWorldGroup
}
}
@@ -1,20 +1,19 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabConstants
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.TabConstants.CpuUsageCategory
import org.kryptonmc.api.event.Listener
import org.kryptonmc.api.event.ListenerPriority
import org.kryptonmc.api.event.player.PerformActionEvent
class EventListener(private val feature: NameTagX) {
class EventListener(private val feature: KryptonNameTagX) {
@Listener(ListenerPriority.MAXIMUM)
fun onAction(event: PerformActionEvent) {
if (event.action != PerformActionEvent.Action.START_SNEAKING && event.action != PerformActionEvent.Action.STOP_SNEAKING) return
@Listener
fun onSneak(event: PerformActionEvent) {
val player = TAB.getInstance().getPlayer(event.player.uuid)
if (player == null || !player.isLoaded || feature.isPlayerDisabled(player)) return
TAB.getInstance().cpuManager.runMeasuredTask(feature, CpuUsageCategory.PLAYER_SNEAK) {
player.armorStandManager?.sneak(event.action == PerformActionEvent.Action.START_SNEAKING)
if (player == null || feature.isPlayerDisabled(player)) return
TAB.getInstance().threadManager.runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK) {
feature.getArmorStandManager(player)!!.sneak(event.action == PerformActionEvent.Action.START_SNEAKING)
}
}
}
@@ -1,246 +1,77 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.ArmorStand
import me.neznamy.tab.api.Property
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.api.chat.EnumChatFormat
import me.neznamy.tab.api.chat.IChatBaseComponent
import me.neznamy.tab.platforms.krypton.KryptonPacketBuilder
import me.neznamy.tab.shared.TabConstants
import me.neznamy.tab.shared.TAB
import org.kryptonmc.api.entity.EntityTypes
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.registry.Registries
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager
import org.kryptonmc.krypton.entity.KryptonEntityTypes
import org.kryptonmc.krypton.entity.metadata.MetadataHolder
import org.kryptonmc.krypton.entity.metadata.MetadataKeys
import org.kryptonmc.krypton.entity.player.KryptonPlayer
import org.kryptonmc.krypton.packet.Packet
import org.kryptonmc.krypton.packet.out.play.PacketOutDestroyEntities
import org.kryptonmc.krypton.packet.out.play.PacketOutEntityTeleport
import org.kryptonmc.krypton.packet.out.play.PacketOutMetadata
import org.kryptonmc.krypton.packet.out.play.PacketOutSpawnLivingEntity
import org.spongepowered.math.vector.Vector2f
import org.spongepowered.math.vector.Vector3d
import java.util.UUID
import java.util.concurrent.atomic.AtomicInteger
import org.kryptonmc.krypton.packet.out.play.PacketOutRemoveEntities
import org.kryptonmc.krypton.packet.out.play.PacketOutSetEntityMetadata
import org.kryptonmc.krypton.packet.out.play.PacketOutSpawnEntity
import org.kryptonmc.krypton.packet.out.play.PacketOutTeleportEntity
class KryptonArmorStand(
private val owner: TabPlayer,
private val property: Property,
private var yOffset: Double,
private val staticOffset: Boolean
) : ArmorStand {
manager: BackendArmorStandManager,
nameTagX: KryptonNameTagX,
owner: TabPlayer,
propertyName: String,
yOffset: Double,
staticOffset: Boolean
) : BackendArmorStand(nameTagX, manager, owner, propertyName, yOffset, staticOffset) {
private val manager = TAB.getInstance().featureManager.getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS) as NameTagX
private val player = owner.player as Player
private val entityId = ID_COUNTER.incrementAndGet()
private val uuid = UUID.randomUUID()
private var sneaking = false
private var visible = calculateVisibility()
private val destroyPacket = PacketOutDestroyEntities(entityId)
private val location: Vector3d
get() {
val x = player.location.x()
var y = calculateY() + yOffset + 2
val z = player.location.z()
y -= if (player.sleepingPosition != null) 1.76 else if (sneaking) 0.45 else 0.18
return Vector3d(x, y, z)
}
override fun refresh() {
visible = calculateVisibility()
updateMetadata()
}
override fun getProperty(): Property = property
override fun hasStaticOffset(): Boolean = staticOffset
override fun getOffset(): Double = yOffset
override fun setOffset(offset: Double) {
if (yOffset == offset) return
yOffset = offset
owner.armorStandManager.nearbyPlayers.forEach {
it.sendPacket(getTeleportPacket(it), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_OFFSET_CHANGE)
}
}
private val player = owner.player as KryptonPlayer
private val destroyPacket = PacketOutRemoveEntities(intArrayOf(entityId))
override fun spawn(viewer: TabPlayer) {
getSpawnPackets(viewer).forEach { viewer.sendPacket(it, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN) }
}
override fun destroy() {
TAB.getInstance().onlinePlayers.forEach { it.sendPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN) }
getSpawnPackets(viewer).forEach { viewer.sendPacket(it) }
}
override fun destroy(viewer: TabPlayer) {
viewer.sendPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN)
viewer.sendPacket(destroyPacket)
}
override fun teleport() {
owner.armorStandManager.nearbyPlayers.forEach {
it.sendPacket(getTeleportPacket(it), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT)
}
private fun getArmorStandYFor(viewer: TabPlayer): Double {
var y = player.position.y
if (player.isSwimming || player.isGliding) y -= 1.22
y += getYAdd(false, sneaking, viewer)
return y
}
override fun teleport(viewer: TabPlayer) {
if (!owner.armorStandManager.isNearby(viewer) && viewer !== owner) {
owner.armorStandManager.spawn(viewer)
return
}
viewer.sendPacket(getTeleportPacket(viewer), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT)
override fun updateMetadata(viewer: TabPlayer) {
viewer.sendPacket(PacketOutSetEntityMetadata(entityId, createMetadata(property.getFormat(viewer), viewer).collectAll()))
}
override fun sneak(sneaking: Boolean) {
if (this.sneaking == sneaking) return // idk
this.sneaking = sneaking
owner.armorStandManager.nearbyPlayers.forEach {
if (it.version.minorVersion == 14 && !ALWAYS_VISIBLE) {
// 1.14.x client sided bug, despawning completely
if (sneaking) {
it.sendPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SNEAK)
} else {
spawn(it)
}
} else {
// respawning so there's no animation and it's instant
respawn(it)
}
}
override fun sendTeleportPacket(viewer: TabPlayer) {
viewer.sendPacket(PacketOutTeleportEntity(entityId, player.position.x, getArmorStandYFor(viewer), player.position.z, 0F, 0F, false))
}
override fun updateVisibility(force: Boolean) {
val visibility = calculateVisibility()
if (visible != visibility || force) {
visible = visibility
updateMetadata()
}
}
override fun getEntityId(): Int = entityId
private fun updateMetadata() {
owner.armorStandManager.nearbyPlayers.forEach {
val packet = PacketOutMetadata(entityId, createMetadata(property.getFormat(it), it).all)
it.sendPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA)
}
}
private fun getTeleportPacket(viewer: TabPlayer): PacketOutEntityTeleport = PacketOutEntityTeleport(
entityId,
armorStandLocationFor(viewer),
Vector2f.ZERO,
false
)
// Using a sequence here makes sense because we only need the iteration behaviour, which is exactly what sequences are for.
// For those reading this from Java, a Sequence is kind of Kotlin's equivalent to Java's Stream.
private fun getSpawnPackets(viewer: TabPlayer): Sequence<Packet> {
private fun getSpawnPackets(viewer: TabPlayer): Array<Packet> {
visible = calculateVisibility()
val data = createMetadata(property.getFormat(viewer), viewer)
val location = armorStandLocationFor(viewer)
return sequenceOf(
PacketOutSpawnLivingEntity(
entityId,
uuid,
TYPE,
location.x(),
location.y(),
location.z(),
0F,
0F,
0F,
0,
0,
0
),
PacketOutMetadata(entityId, data.all)
return arrayOf(
PacketOutSpawnEntity(entityId, uuid, KryptonEntityTypes.ARMOR_STAND, player.position.x, getArmorStandYFor(viewer),
player.position.z, 0F, 0F, 0F, 0, 0, 0, 0),
PacketOutSetEntityMetadata(entityId, data.collectAll())
)
}
private fun createMetadata(displayName: String, viewer: TabPlayer): MetadataHolder {
val viewerPlayer = viewer.player as KryptonPlayer
val holder = MetadataHolder(viewerPlayer).apply {
add(MetadataKeys.FLAGS)
add(MetadataKeys.CUSTOM_NAME)
add(MetadataKeys.CUSTOM_NAME_VISIBILITY)
define(MetadataKeys.Entity.FLAGS, 0)
define(MetadataKeys.Entity.CUSTOM_NAME, null)
define(MetadataKeys.Entity.CUSTOM_NAME_VISIBILITY, false)
}
var flags = 32 // invisible
if (sneaking) flags += 2
holder[MetadataKeys.FLAGS] = flags.toByte()
holder[MetadataKeys.CUSTOM_NAME] = KryptonPacketBuilder.toComponent(displayName, viewer.version)
if (isNameVisibilityEmpty(displayName) || !viewerPlayer.canSee(player) || manager.hasHiddenNametag(owner, viewer)) {
holder[MetadataKeys.CUSTOM_NAME_VISIBILITY] = false
} else {
holder[MetadataKeys.CUSTOM_NAME_VISIBILITY] = visible
}
if (viewer.version.minorVersion > 8 || manager.markerFor18x) {
holder.add(MetadataKeys.ARMOR_STAND.FLAGS)
holder[MetadataKeys.ARMOR_STAND.FLAGS] = 16.toByte()
holder.set(MetadataKeys.Entity.FLAGS, (if (sneaking) 34 else 32).toByte())
holder.set(MetadataKeys.Entity.CUSTOM_NAME, KryptonPacketBuilder.toComponent(displayName, viewer.version))
holder.set(MetadataKeys.Entity.CUSTOM_NAME_VISIBILITY, !shouldBeInvisibleFor(viewer, displayName) && visible)
if (viewer.version.minorVersion > 8 || manager.isMarkerFor18x) {
holder.define(MetadataKeys.ArmorStand.FLAGS, 16.toByte())
}
return holder
}
private fun calculateVisibility(): Boolean {
if (owner.isDisguised || manager.vehicleManager.isOnBoat(owner)) return false
if (ALWAYS_VISIBLE) return true
return !owner.hasInvisibilityPotion() && owner.gamemode != 3 && !manager.hasHiddenNametag(owner) && property.get().isNotEmpty()
}
private fun calculateY(): Double {
// 1.14+ server sided bug
val vehicle = player.vehicle
if (vehicle != null) {
val key = vehicle.type.key().asString()
when {
key.contains("horse") -> return vehicle.location.y() + 0.85
key.contains("donkey") -> return vehicle.location.y() + 0.525
vehicle.type === EntityTypes.PIG -> return vehicle.location.y() + 0.325
key.contains("strider") -> vehicle.location.y() + 1.15
}
}
if (player.isSwimming || player.isGliding) return player.location.y() - 1.22
return player.location.y()
}
private fun armorStandLocationFor(viewer: TabPlayer): Vector3d {
if (viewer.version.minorVersion == 8 && !manager.markerFor18x) return location.add(0.0, -2.0, 0.0)
return location
}
private fun isNameVisibilityEmpty(displayName: String): Boolean {
if (displayName.isEmpty()) return true
if (!displayName.startsWith(EnumChatFormat.COLOR_CHAR) && !displayName.startsWith('&') && !displayName.startsWith('#')) return false
var text = IChatBaseComponent.fromColoredText(displayName).toRawText()
if (text.contains(' ')) text = text.replace(" ", "")
return text.isEmpty()
}
override fun respawn(viewer: TabPlayer) {
viewer.sendPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN)
val spawn = Runnable { spawn(viewer) }
if (viewer.version.minorVersion == 8) {
// 1.8.8 client sided bug
TAB.getInstance().cpuManager.runTaskLater(
50,
manager,
TabConstants.CpuUsageCategory.V1_8_0_BUG_COMPENSATION,
spawn
)
} else {
spawn.run()
}
}
companion object {
private val ALWAYS_VISIBLE = TAB.getInstance().configuration.isArmorStandsAlwaysVisible
// entity ID counter to pick (hopefully) unique entity IDs
private val ID_COUNTER = AtomicInteger(2000000000)
private val TYPE = Registries.ENTITY_TYPE.idOf(EntityTypes.ARMOR_STAND)
}
}
@@ -0,0 +1,118 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabConstants
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.platforms.krypton.Main
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX
import org.kryptonmc.api.entity.Entity
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.api.event.EventFilter
import org.kryptonmc.api.event.EventNode
import org.kryptonmc.api.event.player.PerformActionEvent
import org.kryptonmc.krypton.packet.EntityPacket
import org.kryptonmc.krypton.packet.MovementPacket
import org.kryptonmc.krypton.packet.`in`.play.PacketInInteract
import org.kryptonmc.krypton.packet.out.play.PacketOutRemoveEntities
import org.kryptonmc.krypton.packet.out.play.PacketOutSpawnPlayer
import kotlin.math.sqrt
class KryptonNameTagX(private val plugin: Main) : BackendNameTagX() {
private val eventListener = EventListener(this)
init {
val eventNode = EventNode.filteredForEvent("tab_nametagx", EventFilter.ALL) { event ->
if (event !is PerformActionEvent) return@filteredForEvent false
event.action == PerformActionEvent.Action.START_SNEAKING || event.action == PerformActionEvent.Action.STOP_SNEAKING
}
plugin.eventNode.addChild(eventNode)
eventNode.registerListeners(eventListener)
}
override fun onPacketReceive(sender: TabPlayer, packet: Any): Boolean {
if (sender.version.minorVersion == 8 && packet is PacketInInteract) {
val entityId = packet.entityId
var attacked: TabPlayer? = null
for (player in TAB.getInstance().onlinePlayers) {
if (player.isLoaded && getArmorStandManager(player)!!.hasArmorStandWithID(entityId)) {
attacked = player
break
}
}
if (attacked != null && attacked !== sender) updateEntityId(packet, entityId)
}
return false
}
override fun onPacketSend(receiver: TabPlayer, packet: Any) {
if (receiver.version.minorVersion < 8) return
if (!receiver.isLoaded || isPlayerDisabled(receiver) || disabledUnlimitedPlayers.contains(receiver)) return
when (packet) {
is MovementPacket -> {
if (packet !is EntityPacket) return
packetListener.onEntityMove(receiver, (packet as EntityPacket).entityId)
}
is PacketOutSpawnPlayer -> packetListener.onEntitySpawn(receiver, packet.entityId)
is PacketOutRemoveEntities -> packet.ids.forEach { packetListener.onEntityDestroy(receiver, it) }
}
}
override fun isOnBoat(player: TabPlayer): Boolean = vehicleManager.isOnBoat(player)
override fun getDistance(player1: TabPlayer, player2: TabPlayer): Double {
val pos1 = (player1.player as Player).position
val pos2 = (player2.player as Player).position
return sqrt((pos1.x - pos2.x) * (pos1.x - pos2.x) + (pos1.z - pos2.z) * (pos1.z - pos2.z))
}
override fun areInSameWorld(player1: TabPlayer, player2: TabPlayer): Boolean {
return (player1.player as Player).world != (player2.player as Player).world
}
override fun canSee(viewer: TabPlayer?, target: TabPlayer?): Boolean {
return true
}
override fun unregisterListener() {
plugin.eventNode.unregisterListeners(eventListener)
}
override fun getPassengers(vehicle: Any): List<Int> {
return (vehicle as Entity).passengers.map { it.id }
}
override fun registerVehiclePlaceholder() {
TAB.getInstance().placeholderManager.registerPlayerPlaceholder(TabConstants.Placeholder.VEHICLE, 100) {
(it.player as Player).vehicle.toString()
}
}
override fun getVehicle(player: TabPlayer): Any? {
return (player.player as Player).vehicle
}
override fun getEntityId(entity: Any): Int {
return (entity as Entity).id
}
override fun getEntityType(entity: Any): String {
return (entity as Entity).type.toString()
}
override fun createArmorStand(asm: BackendArmorStandManager, owner: TabPlayer, lineName: String, yOffset: Double, staticOffset: Boolean): BackendArmorStand {
return KryptonArmorStand(asm, this, owner, lineName, yOffset, staticOffset)
}
companion object {
private val INTERACT_ENTITY_ID_FIELD = PacketInInteract::class.java.getDeclaredField("entityId").apply { isAccessible = true }
@JvmStatic
private fun updateEntityId(packet: PacketInInteract, id: Int) {
INTERACT_ENTITY_ID_FIELD.setInt(packet, id)
}
}
}
@@ -1,23 +0,0 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabFeature
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.shared.TAB
import org.kryptonmc.api.entity.player.Player
class LocationRefresher(private val feature: NameTagX) : TabFeature(feature.featureName, "Processing passengers / preview") {
init {
TAB.getInstance().placeholderManager.registerPlayerPlaceholder("%location0%", 50) {
if (!feature.vehicleManager.isInVehicle(it) && !it.isPreviewingNametag) return@registerPlayerPlaceholder null
val location = (it.player as Player).location
location.x() + location.y() + location.z()
}
addUsedPlaceholders(listOf("%location0%"))
}
override fun refresh(refreshed: TabPlayer, force: Boolean) {
if (feature.vehicleManager.isInVehicle(refreshed)) feature.vehicleManager.processPassengers(refreshed.player as Player)
if (refreshed.isPreviewingNametag) refreshed.armorStandManager.teleport(refreshed)
}
}
@@ -1,323 +0,0 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.ArmorStandManager
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.api.team.UnlimitedNametagManager
import me.neznamy.tab.platforms.krypton.Main
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.TabConstants
import me.neznamy.tab.shared.features.nametags.NameTag
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.krypton.entity.player.KryptonPlayer
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.pow
import kotlin.math.sqrt
class NameTagX(plugin: Main) : NameTag(), UnlimitedNametagManager {
// config options
val markerFor18x = TAB.getInstance().configuration.config.getBoolean(
"scoreboard-teams.unlimited-nametag-mode.use-marker-tag-for-1-8-x-clients",
false
)
val disableOnBoats = TAB.getInstance().configuration.config.getBoolean(
"scoreboard-teams.unlimited-nametag-mode.disable-on-boats",
true
)
private val spaceBetweenLines = TAB.getInstance().configuration.config.getDouble(
"scoreboard-teams.unlimited-nametag-mode.space-between-lines",
0.22
)
private val disabledUnlimitedWorlds: List<String> = TAB.getInstance().configuration.config.getStringList(
"scoreboard-teams.unlimited-nametag-mode.disable-in-worlds",
emptyList()
)
private val dynamicLines = TAB.getInstance().configuration.config.getStringList(
"scoreboard-teams.unlimited-nametag-mode.dynamic-lines",
listOf(TabConstants.Property.ABOVENAME, TabConstants.Property.NAMETAG, TabConstants.Property.BELOWNAME, "another")
)
private val staticLines = TAB.getInstance().configuration.config
.getConfigurationSection<String, Any>("scoreboard-teams.unlimited-nametag-mode.static-lines")
// player data by entity ID, used for better performance
val entityIdMap = ConcurrentHashMap<Int, TabPlayer>()
private val playersInDisabledUnlimitedWorlds = mutableSetOf<TabPlayer>()
private val disableUnlimitedWorldsArray = disabledUnlimitedWorlds.toTypedArray()
private val unlimitedWorldWhitelistMode = disabledUnlimitedWorlds.contains("WHITELIST")
private val playersDisabledWithAPI = mutableListOf<TabPlayer>()
val vehicleManager = VehicleRefresher(this)
private val eventListener = EventListener(this)
init {
dynamicLines.reverse()
plugin.server.eventManager.register(plugin, eventListener)
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_PACKET_LISTENER, PacketListener(this))
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER, vehicleManager)
TAB.getInstance().featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_LOCATION_REFRESHER, LocationRefresher(this))
TAB.getInstance().debug("Loaded Unlimited nametag featured with parameters markerFor18x=$markerFor18x, disableOnBoats=$disableOnBoats, " +
"spaceBetweenLines=$spaceBetweenLines, disabledUnlimitedWorlds=$disabledUnlimitedWorlds")
}
fun isPlayerDisabled(player: TabPlayer): Boolean = isDisabledPlayer(player) || playersInDisabledUnlimitedWorlds.contains(player)
override fun load() {
TAB.getInstance().onlinePlayers.forEach { all ->
entityIdMap[(all.player as KryptonPlayer).id] = all
updateProperties(all)
loadArmorStands(all)
if (isDisabled(all.world)) playersInDisabledUnlimitedWorlds.add(all)
if (isPlayerDisabled(all)) return@forEach
vehicleManager.loadPassengers(all)
TAB.getInstance().onlinePlayers.forEach { spawnArmorStands(all, it, false) }
}
super.load()
startVisibilityRefreshTask()
}
override fun unload() {
super.unload()
TAB.getInstance().onlinePlayers.forEach { it.armorStandManager?.destroy() }
entityIdMap.clear()
}
override fun onJoin(connectedPlayer: TabPlayer) {
if (isDisabled(connectedPlayer.world) && !playersInDisabledUnlimitedWorlds.contains(connectedPlayer)) {
playersInDisabledUnlimitedWorlds.add(connectedPlayer)
}
super.onJoin(connectedPlayer)
entityIdMap[(connectedPlayer.player as KryptonPlayer).id] = connectedPlayer
loadArmorStands(connectedPlayer)
if (isPlayerDisabled(connectedPlayer)) return
vehicleManager.loadPassengers(connectedPlayer)
TAB.getInstance().onlinePlayers.forEach { spawnArmorStands(connectedPlayer, it, true) }
}
override fun onQuit(disconnectedPlayer: TabPlayer) {
super.onQuit(disconnectedPlayer)
TAB.getInstance().onlinePlayers.forEach { it.armorStandManager?.unregisterPlayer(disconnectedPlayer) }
entityIdMap.remove((disconnectedPlayer.player as KryptonPlayer).id)
playersInDisabledUnlimitedWorlds.remove(disconnectedPlayer)
playersDisabledWithAPI.remove(disconnectedPlayer)
if (disconnectedPlayer.armorStandManager != null) { // player was not loaded yet
disconnectedPlayer.armorStandManager.destroy()
TAB.getInstance().cpuManager.runTaskLater(
500,
this,
TabConstants.CpuUsageCategory.PLAYER_QUIT,
disconnectedPlayer.armorStandManager::destroy
)
}
}
override fun refresh(refreshed: TabPlayer, force: Boolean) {
super.refresh(refreshed, force)
if (isPlayerDisabled(refreshed)) return
if (force) {
refreshed.armorStandManager.destroy()
loadArmorStands(refreshed)
vehicleManager.loadPassengers(refreshed)
TAB.getInstance().onlinePlayers.forEach {
if (it === refreshed) return@forEach
if (it.world == refreshed.world) refreshed.armorStandManager.spawn(it)
}
return
}
var fix = false
refreshed.armorStandManager.armorStands.forEach {
if (it.property.update()) {
it.refresh()
fix = true
}
if (fix) fixArmorStandHeights(refreshed)
}
}
override fun updateProperties(player: TabPlayer): Boolean {
super.updateProperties(player)
player.loadPropertyFromConfig(this, TabConstants.Property.CUSTOMTAGNAME, player.name)
rebuildNametagLine(player)
dynamicLines.forEach { if (it != TabConstants.Property.NAMETAG) player.loadPropertyFromConfig(this, it) }
staticLines.keys.forEach { if (it != TabConstants.Property.NAMETAG) player.loadPropertyFromConfig(this, it) }
return true
}
override fun getFeatureName(): String = "Unlimited Nametags"
override fun getTeamVisibility(player: TabPlayer, viewer: TabPlayer): Boolean {
if (player.hasInvisibilityPotion()) return false // 1.8.x client sided bug
return vehicleManager.isOnBoat(player) || isPlayerDisabled(player)
}
override fun pauseTeamHandling(player: TabPlayer) {
if (teamHandlingPaused.contains(player)) return
if (!isDisabledPlayer(player)) unregisterTeam(player)
teamHandlingPaused.add(player) // adding after, so unregisterTeam method runs
player.armorStandManager.destroy()
}
override fun resumeTeamHandling(player: TabPlayer) {
if (!teamHandlingPaused.contains(player)) return
teamHandlingPaused.remove(player) // removing before, so registerTeam method runs
if (!isDisabledPlayer(player)) registerTeam(player)
if (!isPlayerDisabled(player)) TAB.getInstance().onlinePlayers.forEach { spawnArmorStands(player, it, false) }
}
override fun disableArmorStands(player: TabPlayer) {
if (playersDisabledWithAPI.contains(player)) return
playersDisabledWithAPI.add(player)
player.armorStandManager.destroy()
updateTeamData(player)
}
override fun enableArmorStands(player: TabPlayer) {
if (!playersDisabledWithAPI.contains(player)) return
playersDisabledWithAPI.remove(player)
if (!isPlayerDisabled(player)) TAB.getInstance().onlinePlayers.forEach { spawnArmorStands(player, it, false) }
updateTeamData(player)
}
override fun hasDisabledArmorStands(player: TabPlayer): Boolean = playersDisabledWithAPI.contains(player)
override fun setPrefix(player: TabPlayer, prefix: String) {
player.getProperty(TabConstants.Property.TAGPREFIX).temporaryValue = prefix
rebuildNametagLine(player)
player.forceRefresh()
}
override fun setSuffix(player: TabPlayer, suffix: String) {
player.getProperty(TabConstants.Property.TAGSUFFIX).temporaryValue = suffix
rebuildNametagLine(player)
player.forceRefresh()
}
override fun resetPrefix(player: TabPlayer) {
player.getProperty(TabConstants.Property.TAGPREFIX).temporaryValue = null
rebuildNametagLine(player)
player.forceRefresh()
}
override fun resetSuffix(player: TabPlayer) {
player.getProperty(TabConstants.Property.TAGSUFFIX).temporaryValue = null
rebuildNametagLine(player)
player.forceRefresh()
}
override fun setName(player: TabPlayer, customname: String) {
player.getProperty(TabConstants.Property.CUSTOMTAGNAME).temporaryValue = customname
rebuildNametagLine(player)
player.forceRefresh()
}
override fun setLine(player: TabPlayer, line: String, value: String) {
player.getProperty(line).temporaryValue = value
player.forceRefresh()
}
override fun resetName(player: TabPlayer) {
player.getProperty(TabConstants.Property.CUSTOMTAGNAME).temporaryValue = null
rebuildNametagLine(player)
player.forceRefresh()
}
override fun resetLine(player: TabPlayer, line: String) {
player.getProperty(line).temporaryValue = null
player.forceRefresh()
}
override fun getCustomName(player: TabPlayer): String = player.getProperty(TabConstants.Property.CUSTOMTAGNAME).temporaryValue
override fun getCustomLineValue(player: TabPlayer, line: String): String = player.getProperty(line).temporaryValue
override fun getOriginalName(player: TabPlayer): String = player.getProperty(TabConstants.Property.CUSTOMTAGNAME).originalRawValue
override fun getOriginalLineValue(player: TabPlayer, line: String): String = player.getProperty(line).originalRawValue
override fun getDefinedLines(): List<String> = dynamicLines.plus(staticLines.keys)
private fun isDisabled(world: String): Boolean {
var contains = contains(disableUnlimitedWorldsArray, world)
if (unlimitedWorldWhitelistMode) contains = !contains
return contains
}
private fun startVisibilityRefreshTask() {
TAB.getInstance().cpuManager.startRepeatingMeasuredTask(
500,
this,
TabConstants.CpuUsageCategory.REFRESHING_NAME_TAG_VISIBILITY
) {
TAB.getInstance().onlinePlayers.forEach {
if (!it.isLoaded || isPlayerDisabled(it)) return@forEach
it.armorStandManager.updateVisibility(false)
}
}
}
private fun spawnArmorStands(owner: TabPlayer, viewer: TabPlayer, sendMutually: Boolean) {
if (owner === viewer) return // not displaying own armor stands
val ownerPlayer = owner.player as Player
val viewerPlayer = viewer.player as Player
if (viewerPlayer.world !== ownerPlayer.world) return // in different worlds
if (isPlayerDisabled(owner)) return
if (owner.distanceTo(viewer) <= 48) {
if (viewerPlayer.canSee(ownerPlayer) && !owner.isVanished) {
owner.armorStandManager.spawn(viewer)
}
if (sendMutually && viewer.armorStandManager != null && ownerPlayer.canSee(viewerPlayer) && !viewer.isVanished) {
viewer.armorStandManager.spawn(owner)
}
}
}
private fun loadArmorStands(player: TabPlayer) {
// player.armorStandManager = ArmorStandManager()
rebuildNametagLine(player)
var height = 0.0
dynamicLines.forEach {
val property = player.getProperty(it)
if (property.currentRawValue.isEmpty()) return@forEach
player.armorStandManager.addArmorStand(it, KryptonArmorStand(player, property, height, false))
height += spaceBetweenLines
}
staticLines.forEach { (key, value) ->
val property = player.getProperty(key)
if (property.currentRawValue.isEmpty()) return@forEach
player.armorStandManager.addArmorStand(key, KryptonArmorStand(player, property, value.toString().toDouble(), true))
}
fixArmorStandHeights(player)
}
private fun fixArmorStandHeights(player: TabPlayer) {
var currentY = -spaceBetweenLines
player.armorStandManager.armorStands.forEach {
if (it.hasStaticOffset()) return@forEach
if (it.property.get().isNotEmpty()) {
currentY += spaceBetweenLines
it.offset = currentY
}
}
}
private fun rebuildNametagLine(player: TabPlayer) {
player.setProperty(
this,
TabConstants.Property.NAMETAG,
player.getProperty(TabConstants.Property.TAGPREFIX).currentRawValue +
player.getProperty(TabConstants.Property.CUSTOMTAGNAME).currentRawValue +
player.getProperty(TabConstants.Property.TAGSUFFIX).currentRawValue
)
}
companion object {
@JvmStatic
private fun TabPlayer.distanceTo(other: TabPlayer): Double {
val first = (player as Player).location
val second = (other.player as Player).location
return sqrt((first.x() - second.x()).pow(2) + (first.z() - second.z()).pow(2))
}
}
}
@@ -1,88 +0,0 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabFeature
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.shared.TAB
import me.neznamy.tab.shared.TabConstants.CpuUsageCategory
import org.kryptonmc.krypton.entity.KryptonEntity
import org.kryptonmc.krypton.packet.EntityPacket
import org.kryptonmc.krypton.packet.MovementPacket
import org.kryptonmc.krypton.packet.`in`.play.PacketInInteract
import org.kryptonmc.krypton.packet.out.play.PacketOutDestroyEntities
import org.kryptonmc.krypton.packet.out.play.PacketOutSpawnPlayer
class PacketListener(private val nameTagX: NameTagX) : TabFeature(nameTagX.featureName, null) {
override fun onPacketReceive(sender: TabPlayer, packet: Any): Boolean {
if (sender.version.minorVersion == 8 && packet is PacketInInteract) {
val entityId = packet.entityId
var attacked: TabPlayer? = null
for (player in TAB.getInstance().onlinePlayers) {
if (player.isLoaded && player.armorStandManager.hasArmorStandWithID(entityId)) {
attacked = player
break
}
}
if (attacked != null && attacked !== sender) packet.updateEntityId(entityId)
}
return false
}
override fun onPacketSend(receiver: TabPlayer, packet: Any) {
if (receiver.version.minorVersion < 8) return
if (!receiver.isLoaded || nameTagX.isPlayerDisabled(receiver)) return
when (packet) {
is MovementPacket -> {
if (packet !is EntityPacket) return
onEntityMove(receiver, (packet as EntityPacket).entityId)
}
is PacketOutSpawnPlayer -> onEntitySpawn(receiver, packet.entityId)
is PacketOutDestroyEntities -> packet.ids.forEach { onEntityDestroy(receiver, it) }
}
}
private fun onEntityMove(receiver: TabPlayer, entityId: Int) {
val player = nameTagX.entityIdMap[entityId]
if (player != null) {
TAB.getInstance().cpuManager.runMeasuredTask(nameTagX, CpuUsageCategory.PACKET_ENTITY_MOVE) {
player.armorStandManager.teleport(receiver)
}
return
}
nameTagX.vehicleManager.vehicles(entityId).forEach {
val passenger = nameTagX.entityIdMap[(it as KryptonEntity).id]
if (passenger != null && passenger.armorStandManager != null) {
TAB.getInstance().cpuManager.runMeasuredTask(nameTagX, CpuUsageCategory.PACKET_ENTITY_MOVE) {
passenger.armorStandManager.teleport(receiver)
}
}
}
}
private fun onEntitySpawn(receiver: TabPlayer, entityId: Int) {
val spawnedPlayer = nameTagX.entityIdMap[entityId]
if (spawnedPlayer != null && spawnedPlayer.isLoaded) {
TAB.getInstance().cpuManager.runMeasuredTask(nameTagX, CpuUsageCategory.PACKET_ENTITY_SPAWN) {
spawnedPlayer.armorStandManager.spawn(receiver)
}
}
}
private fun onEntityDestroy(receiver: TabPlayer, entity: Int) {
val deSpawnedPlayer = nameTagX.entityIdMap[entity]
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded) {
TAB.getInstance().cpuManager.runMeasuredTask(nameTagX, CpuUsageCategory.PACKET_ENTITY_DESTROY) {
deSpawnedPlayer.armorStandManager.destroy(receiver)
}
}
}
companion object {
private val INTERACT_ENTITY_ID_FIELD = PacketInInteract::class.java.getDeclaredField("entityId").apply { isAccessible = true }
private fun PacketInInteract.updateEntityId(id: Int) {
INTERACT_ENTITY_ID_FIELD.setInt(this, id)
}
}
}
@@ -1,72 +0,0 @@
package me.neznamy.tab.platforms.krypton.features.unlimitedtags
import me.neznamy.tab.api.TabFeature
import me.neznamy.tab.api.TabPlayer
import me.neznamy.tab.shared.TAB
import org.kryptonmc.api.entity.Entity
import org.kryptonmc.api.entity.EntityTypes
import org.kryptonmc.api.entity.player.Player
import org.kryptonmc.krypton.entity.KryptonEntity
import java.util.concurrent.ConcurrentHashMap
class VehicleRefresher(private val feature: NameTagX) : TabFeature(feature.featureName, "Refreshing vehicles") {
private val playersInVehicle = ConcurrentHashMap<TabPlayer, KryptonEntity>()
private val vehicles = ConcurrentHashMap<Int, List<Entity>>()
private val playersOnBoats = mutableListOf<TabPlayer>()
init {
addUsedPlaceholders(listOf("%vehicle%"))
TAB.getInstance().placeholderManager.registerPlayerPlaceholder("%vehicle%", 100) { (it.player as Player).vehicle.toString() }
}
fun isInVehicle(player: TabPlayer): Boolean = playersInVehicle.containsKey(player)
fun isOnBoat(player: TabPlayer): Boolean = playersOnBoats.contains(player)
fun vehicles(id: Int): List<Entity> = vehicles[id] ?: emptyList()
fun loadPassengers(player: TabPlayer) {
if ((player.player as Player).vehicle == null) return
val vehicle = (player.player as Player).vehicle
vehicles[(vehicle as KryptonEntity).id] = vehicle.passengers
}
fun processPassengers(vehicle: Entity) {
vehicle.passengers.forEach {
if (it is Player) TAB.getInstance().getPlayer(it.uuid).armorStandManager.teleport()
processPassengers(it)
}
}
override fun refresh(refreshed: TabPlayer, force: Boolean) {
if (feature.isPlayerDisabled(refreshed)) return
val vehicle = (refreshed.player as Player).vehicle
if (playersInVehicle.containsKey(refreshed) && vehicle == null) {
// vehicle exit
vehicles.remove((playersInVehicle[refreshed] as KryptonEntity).id)
refreshed.armorStandManager.teleport()
playersInVehicle.remove(refreshed)
if (feature.disableOnBoats && playersOnBoats.contains(refreshed)) {
playersOnBoats.remove(refreshed)
feature.updateTeamData(refreshed)
}
}
if (!playersInVehicle.containsKey(refreshed) && vehicle != null) {
// vehicle enter
vehicles[(vehicle as KryptonEntity).id] = vehicle.passengers
refreshed.armorStandManager.respawn() // making teleport instant instead of showing teleport animation
playersInVehicle[refreshed] = vehicle
if (feature.disableOnBoats && vehicle.type === EntityTypes.BOAT) {
playersOnBoats.add(refreshed)
feature.updateTeamData(refreshed)
}
}
}
override fun onQuit(disconnectedPlayer: TabPlayer) {
if (playersInVehicle.containsKey(disconnectedPlayer)) vehicles.remove(playersInVehicle[disconnectedPlayer]!!.id)
playersInVehicle.remove(disconnectedPlayer)
playersOnBoats.remove(disconnectedPlayer)
}
}
+4 -3
View File
@@ -4,7 +4,6 @@ enableFeaturePreview("VERSION_CATALOGS")
dependencyResolutionManagement {
repositories {
mavenCentral() // Netty, SnakeYaml, json-simple, Gson, slf4j, Guava, Kyori event, bStats, AuthLib
maven("https://jitpack.io") // PremiumVanish
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.essentialsx.net/releases/") // Essentials
@@ -15,13 +14,14 @@ dependencyResolutionManagement {
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
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
}
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
}
pluginManagement {
includeBuild("build-logic")
repositories {
maven("https://repo.spongepowered.org/repository/maven-public/")
gradlePluginPortal()
mavenCentral()
}
@@ -31,8 +31,9 @@ rootProject.name = "TAB"
include(":api")
include(":shared")
//include(":krypton")
include(":krypton")
include(":velocity")
include(":bukkit")
include(":bungeecord")
//include(":sponge")
include(":jar")
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
@@ -34,7 +35,7 @@ public class DynamicText implements Property {
private String rawValue;
/** Raw value assigned via API, null if not set */
private String temporaryValue;
@Getter private String temporaryValue;
/**
* Raw value using %s for each placeholder ready to be inserted
@@ -168,12 +169,7 @@ public class DynamicText implements Property {
public String getCurrentRawValue() {
return temporaryValue != null ? temporaryValue : rawValue;
}
@Override
public String getTemporaryValue() {
return temporaryValue;
}
@Override
public String getOriginalRawValue() {
return rawValue;
@@ -9,6 +9,7 @@ import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
@@ -26,7 +27,7 @@ public class ErrorManager {
private final File errorLog = new File(TAB.getInstance().getDataFolder(), "errors.log");
/** anti-override.log file when some plugin or server itself attempts to override the plugin */
private final File antiOverrideLog = new File(TAB.getInstance().getDataFolder(), "anti-override.log");
@Getter private final File antiOverrideLog = new File(TAB.getInstance().getDataFolder(), "anti-override.log");
/** placeholder-errors.log file for errors thrown by placeholders */
private final File placeholderErrorLog = new File(TAB.getInstance().getDataFolder(), "placeholder-errors.log");
@@ -294,13 +295,4 @@ public class ErrorManager {
public void missingAttribute(String objectType, Object objectName, String attribute) {
startupWarn(objectType + " \"&e" + objectName + "&c\" is missing \"&e" + attribute + "&c\" attribute!");
}
/**
* Returns anti-override.log file
*
* @return anti-override.log file
*/
public File getAntiOverrideLog() {
return antiOverrideLog;
}
}
@@ -119,11 +119,15 @@ public class FeatureManagerImpl implements FeatureManager {
long millis = System.currentTimeMillis();
TAB.getInstance().addPlayer(connectedPlayer);
for (TabFeature f : values) {
if (!f.overridesMethod("onJoin")) continue;
long time = System.nanoTime();
f.onJoin(connectedPlayer);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed player join in " + (System.nanoTime()-time)/1000000 + "ms");
try {
if (!f.overridesMethod("onJoin")) continue;
long time = System.nanoTime();
f.onJoin(connectedPlayer);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed player join in " + (System.nanoTime()-time)/1000000 + "ms");
} catch (Throwable e) {
TAB.getInstance().getErrorManager().criticalError("Feature " + f.getClass().getSimpleName() + " failed to process player " + connectedPlayer.getName(), e);
}
}
((ITabPlayer)connectedPlayer).markAsLoaded(true);
TAB.getInstance().debug("Player join of " + connectedPlayer.getName() + " processed in " + (System.currentTimeMillis()-millis) + "ms");
@@ -1,5 +1,6 @@
package me.neznamy.tab.shared;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
@@ -14,10 +15,10 @@ import java.util.List;
public class GroupManager extends TabFeature {
/** Detected permission plugin to take groups from */
private final PermissionPlugin plugin;
@Getter private final PermissionPlugin plugin;
/** If enabled, groups are assigned via permissions instead of permission plugin */
private final boolean groupsByPermissions = TAB.getInstance().getConfiguration().getConfig().getBoolean("assign-groups-by-permissions", false);
@Getter private final boolean groupsByPermissions = TAB.getInstance().getConfiguration().getConfig().getBoolean("assign-groups-by-permissions", false);
/** List of group permissions to iterate through if {@link #groupsByPermissions} is {@code true} */
private final List<String> primaryGroupFindingList = TAB.getInstance().getConfiguration().getConfig().getStringList("primary-group-finding-list", Arrays.asList("Owner", "Admin", "Helper", "default"));
@@ -84,23 +85,4 @@ public class GroupManager extends TabFeature {
}
return TabConstants.NO_GROUP;
}
/**
* Returns {@code true} if assigning by permissions is configured,
* {@code false} if not.
*
* @return {@code true} if assigning by permissions, {@code false} if not
*/
public boolean isGroupsByPermissions() {
return groupsByPermissions;
}
/**
* Returns detected permission plugin
*
* @return detected permission plugin
*/
public PermissionPlugin getPlugin() {
return plugin;
}
}
@@ -1,19 +1,21 @@
package me.neznamy.tab.shared;
import java.nio.charset.StandardCharsets;
import java.util.*;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.*;
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.util.Preconditions;
import me.neznamy.tab.shared.event.impl.PlayerLoadEventImpl;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.geysermc.floodgate.api.FloodgateApi;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* Abstract class storing common variables and functions for player,
* which are not specific to any feature.
@@ -24,10 +26,10 @@ public abstract class ITabPlayer implements TabPlayer {
protected final Object player;
/** Player's real name */
private final String name;
@Getter private final String name;
/** Player's unique ID */
private final UUID uniqueId;
@Getter private final UUID uniqueId;
/** Player's tablist UUID */
private final UUID tabListId;
@@ -36,10 +38,10 @@ public abstract class ITabPlayer implements TabPlayer {
* World the player is currently in, {@code "N/A"} if TAB is
* installed on proxy and bukkit bridge is not installed
*/
private String world;
@Getter @Setter private String world;
/** Server the player is currently in, {@code "N/A"} if TAB is installed on Bukkit */
private String server;
@Getter @Setter private String server;
/** Player's permission group defined in permission plugin or with permission nodes */
private String permissionGroup;
@@ -54,16 +56,16 @@ public abstract class ITabPlayer implements TabPlayer {
private final Map<String, Property> properties = new HashMap<>();
/** Player's game version */
protected final ProtocolVersion version;
@Getter protected final ProtocolVersion version;
/** Player's network channel */
protected Channel channel;
@Getter @Setter protected Channel channel;
/**
* Player's load status, {@code true} when player is fully loaded,
* {@code false} if not yet
*/
private boolean onJoinFinished;
@Getter private boolean loaded;
/** Scoreboard teams player has registered */
private final Set<String> registeredTeams = new HashSet<>();
@@ -72,7 +74,7 @@ public abstract class ITabPlayer implements TabPlayer {
private final Set<String> registeredObjectives = new HashSet<>();
/** Player's name as seen in GameProfile, can be altered by nick plugins */
private String nickname;
@Getter @Setter private String nickname;
/**
* Constructs new instance with given parameters
@@ -147,7 +149,7 @@ public abstract class ITabPlayer implements TabPlayer {
* {@code true} if this is a player join, {@code false} if reload
*/
public void markAsLoaded(boolean join) {
onJoinFinished = true;
loaded = true;
if (TAB.getInstance().getEventBus() != null) TAB.getInstance().getEventBus().fire(new PlayerLoadEventImpl(this, join));
}
@@ -157,34 +159,13 @@ public abstract class ITabPlayer implements TabPlayer {
* @param permissionGroup
* New permission group
*/
public void setGroup(String permissionGroup) {
Preconditions.checkNotNull(permissionGroup, "permissionGroup");
public void setGroup(@NonNull String permissionGroup) {
if (this.permissionGroup.equals(permissionGroup)) return;
this.permissionGroup = permissionGroup;
((PlayerPlaceholder)TAB.getInstance().getPlaceholderManager().getPlaceholder(TabConstants.Placeholder.GROUP)).updateValue(this, permissionGroup);
forceRefresh();
}
/**
* Sets player's world to given value
*
* @param name
* Name of the new world
*/
public void setWorld(String name) {
world = name;
}
/**
* Sets player's server to given value
*
* @param name
* Name of the new server
*/
public void setServer(String name) {
server = name;
}
/**
* Clears maps of registered teams and objectives when Login packet is sent
*/
@@ -227,16 +208,6 @@ public abstract class ITabPlayer implements TabPlayer {
sendCustomPacket(new PacketPlayOutChat(message, ChatMessageType.SYSTEM));
}
@Override
public String getName() {
return name;
}
@Override
public UUID getUniqueId() {
return uniqueId;
}
@Override
public UUID getTablistUUID() {
return tabListId;
@@ -244,25 +215,10 @@ public abstract class ITabPlayer implements TabPlayer {
@Override
public void forceRefresh() {
if (!onJoinFinished) return;
if (!loaded) return;
TAB.getInstance().getFeatureManager().refresh(this, true);
}
@Override
public ProtocolVersion getVersion() {
return version;
}
@Override
public String getWorld() {
return world;
}
@Override
public String getServer() {
return server;
}
@Override
public void sendCustomPacket(TabPacket packet) {
if (packet == null) return;
@@ -347,16 +303,6 @@ public abstract class ITabPlayer implements TabPlayer {
return temporaryGroup != null ? temporaryGroup : permissionGroup;
}
@Override
public Channel getChannel() {
return channel;
}
@Override
public boolean isLoaded() {
return onJoinFinished;
}
@Override
public boolean loadPropertyFromConfig(TabFeature feature, String property) {
return loadPropertyFromConfig(feature, property, "");
@@ -388,17 +334,4 @@ public abstract class ITabPlayer implements TabPlayer {
if (sorting == null) return null;
return sorting.getShortTeamName(this);
}
@Override
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void setChannel(Channel channel) {
this.channel = channel;
}
}
@@ -1,37 +1,34 @@
package me.neznamy.tab.shared;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.FeatureManager;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.protocol.PacketBuilder;
import me.neznamy.tab.shared.config.Configs;
import me.neznamy.tab.shared.features.*;
import me.neznamy.tab.shared.features.alignedplayerlist.AlignedPlayerList;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.layout.LayoutManager;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
/**
* An interface with methods that are called in universal code,
* but require platform-specific API calls
*/
@AllArgsConstructor
public abstract class Platform {
/** Platform's packet builder implementation */
private final PacketBuilder packetBuilder;
/**
* Constructs new instance with given parameter
*
* @param packetBuilder
* Platform's packet builder
*/
protected Platform(PacketBuilder packetBuilder) {
this.packetBuilder = packetBuilder;
}
/**
* Returns platform-specific packet builder implementation
*
* @return platform-specific packet builder
*/
public PacketBuilder getPacketBuilder(){
return packetBuilder;
}
@Getter private final PacketBuilder packetBuilder;
public void sendConsoleMessage(String message, boolean translateColors) {
Object logger = TAB.getInstance().getLogger();
@@ -50,9 +47,101 @@ public abstract class Platform {
public abstract PermissionPlugin detectPermissionPlugin();
/**
* Loads platform-specific features
* Loads features
*/
public abstract void loadFeatures();
public void loadFeatures() {
Configs configuration = TAB.getInstance().getConfiguration();
FeatureManager featureManager = TAB.getInstance().getFeatureManager();
if (configuration.isPipelineInjection() && TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
PipelineInjector inj = getPipelineInjector();
if (inj != null) featureManager.registerFeature(TabConstants.Feature.PIPELINE_INJECTION, inj);
}
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true)) {
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
if (configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false)
&& TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, getUnlimitedNametags());
} else {
featureManager.registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
if (configuration.getConfig().getBoolean("bossbar.enabled", false)) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR, new BossBarManagerImpl());
} else {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR, getLegacyBossBar());
}
}
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9 &&
configuration.getConfig().getBoolean("fix-pet-names.enabled", false)) {
TabFeature petFix = getPetFix();
if (petFix != null) featureManager.registerFeature(TabConstants.Feature.PET_FIX, getPetFix());
}
if (configuration.getConfig().getBoolean("header-footer.enabled", true))
featureManager.registerFeature(TabConstants.Feature.HEADER_FOOTER, new HeaderFooter());
if (configuration.isRemoveGhostPlayers())
featureManager.registerFeature(TabConstants.Feature.GHOST_PLAYER_FIX, new GhostPlayerFix());
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8 && configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true)) {
if (configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false)) {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new AlignedPlayerList());
} else {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new PlayerList());
}
}
if (configuration.getConfig().getBoolean("ping-spoof.enabled", false))
featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true))
featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (configuration.getConfig().getBoolean("belowname-objective.enabled", true))
featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
if (configuration.getConfig().getBoolean("scoreboard.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8 && configuration.getLayout().getBoolean("enabled", false)) {
if (TAB.getInstance().getTeamManager() == null) {
//sorting is disabled, but layout needs team names
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
}
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManager());
}
if (configuration.getConfig().getBoolean("placeholders.register-tab-expansion", false)) {
TAB.getInstance().getPlaceholderManager().setTabExpansion(getTabExpansion());
}
if (configuration.getConfig().getBoolean("global-playerlist.enabled", false)) {
TabFeature global = getGlobalPlayerlist();
if (global != null) featureManager.registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST, getGlobalPlayerlist());
}
if (configuration.getConfig().getBoolean("per-world-playerlist.enabled", false)) {
TabFeature pwp = getPerWorldPlayerlist();
if (pwp != null) featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, pwp);
}
RedisSupport redis = getRedisSupport();
if (redis != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, redis);
featureManager.registerFeature(TabConstants.Feature.NICK_COMPATIBILITY, new NickCompatibility());
}
public BossBarManagerImpl getLegacyBossBar() {
return new BossBarManagerImpl();
}
/**
* Creates an instance of {@link me.neznamy.tab.api.placeholder.Placeholder}
@@ -72,4 +161,29 @@ public abstract class Platform {
* @return Version string if plugin is installed, {@code null} if not
*/
public abstract String getPluginVersion(String plugin);
/**
* Creates instance for all online players and adds them to the plugin
*/
public abstract void loadPlayers();
/**
* Registers all placeholders, including universal and platform-specific ones
*/
public abstract void registerPlaceholders();
public abstract @Nullable PipelineInjector getPipelineInjector();
public abstract NameTag getUnlimitedNametags();
public abstract TabExpansion getTabExpansion();
public abstract @Nullable TabFeature getPetFix();
public abstract @Nullable TabFeature getGlobalPlayerlist();
public abstract @Nullable RedisSupport getRedisSupport();
public abstract @Nullable TabFeature getPerWorldPlayerlist();
}
@@ -1,21 +1,20 @@
package me.neznamy.tab.shared;
import lombok.Getter;
import lombok.Setter;
import me.neznamy.tab.api.*;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.team.TeamManager;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.shared.command.DisabledCommand;
import me.neznamy.tab.shared.command.TabCommand;
import me.neznamy.tab.shared.config.Configs;
import me.neznamy.tab.shared.event.EventBusImpl;
import me.neznamy.tab.shared.event.impl.TabLoadEventImpl;
import me.neznamy.tab.shared.features.*;
import me.neznamy.tab.shared.features.alignedplayerlist.AlignedPlayerList;
import me.neznamy.tab.shared.features.layout.LayoutManager;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.yaml.snakeyaml.error.YAMLException;
import java.io.File;
@@ -29,7 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class TAB extends TabAPI {
/** Instance of this class */
private static TAB instance;
@Getter @Setter private static TAB instance;
/** Player data storage */
private final Map<UUID, TabPlayer> data = new ConcurrentHashMap<>();
@@ -38,16 +37,16 @@ public class TAB extends TabAPI {
private final Map<UUID, TabPlayer> playersByTabListId = new ConcurrentHashMap<>();
/** Online player array to avoid memory allocation when iterating */
private TabPlayer[] players = new TabPlayer[0];
@Getter private TabPlayer[] onlinePlayers = new TabPlayer[0];
/** Instance of plugin's main command */
private TabCommand command;
@Getter private TabCommand command;
/** Command executor to use when the plugin is disabled due to an error */
private final DisabledCommand disabledCommand = new DisabledCommand();
@Getter private final DisabledCommand disabledCommand = new DisabledCommand();
/** Implementation of platform the plugin is installed on for platform-specific calls */
private final Platform platform;
@Getter private final Platform platform;
/**
* CPU manager for thread and task management as well as
@@ -60,44 +59,44 @@ public class TAB extends TabAPI {
* Platform-independent event executor allowing other plugins
* to listen to universal platform-independent event objects
*/
private EventBusImpl eventBus;
@Getter private EventBusImpl eventBus;
/**
* Error manager for printing any and all errors that may
* occur in any part of the code including hooks into other plugins
* into files instead of flooding the already flooded console.
*/
private ErrorManager errorManager;
@Getter private ErrorManager errorManager;
/** Feature manager forwarding events into all loaded features */
private FeatureManagerImpl featureManager;
@Getter private FeatureManagerImpl featureManager;
/** Plugin's configuration files and values storage */
private Configs configuration;
@Getter private Configs configuration;
/**
* Boolean tracking whether this plugin is enabled or not,
* which is due to either internal error on load or yaml syntax error
*/
private boolean disabled;
@Getter private boolean pluginDisabled;
/** Minecraft version the server is running on, always using the latest on proxies */
private final ProtocolVersion serverVersion;
@Getter private final ProtocolVersion serverVersion;
/** Boolean checking floodgate plugin presence for hook */
private boolean floodgate;
@Getter private final boolean floodgateInstalled = ReflectionUtils.classExists("org.geysermc.floodgate.api.FloodgateApi");
/** Version string defined by the server */
private final String serverVersionString;
@Getter private final String serverVersionString;
/** TAB's data folder */
private final File dataFolder;
@Getter private final File dataFolder;
/** Plugin's console logger provided by platform */
private final Object logger;
@Getter private final Object logger;
/** File with YAML syntax error, which prevented plugin from loading */
private String brokenFile;
@Getter @Setter private String brokenFile;
/**
* Constructs new instance with given parameters and sets this
@@ -114,12 +113,6 @@ public class TAB extends TabAPI {
this.serverVersionString = serverVersionString;
this.dataFolder = dataFolder;
this.logger = logger;
try {
Class.forName("org.geysermc.floodgate.api.FloodgateApi");
floodgate = true;
} catch (ClassNotFoundException | IllegalStateException e) {
//plugin not installed
}
try {
eventBus = new EventBusImpl();
} catch (NoSuchMethodError e) {
@@ -154,13 +147,15 @@ public class TAB extends TabAPI {
featureManager = new FeatureManagerImpl();
featureManager.registerFeature(TabConstants.Feature.PLACEHOLDER_MANAGER, new PlaceholderManagerImpl());
featureManager.registerFeature(TabConstants.Feature.GROUP_MANAGER, new GroupManager(platform.detectPermissionPlugin()));
platform.registerPlaceholders();
platform.loadFeatures();
platform.loadPlayers();
command = new TabCommand();
featureManager.load();
for (TabPlayer p : players) ((ITabPlayer)p).markAsLoaded(false);
for (TabPlayer p : onlinePlayers) ((ITabPlayer)p).markAsLoaded(false);
cpu.enable();
if (eventBus != null) eventBus.fire(TabLoadEventImpl.getInstance());
disabled = false;
pluginDisabled = false;
sendConsoleMessage("&aEnabled in " + (System.currentTimeMillis()-time) + "ms", true);
return configuration.getMessages().getReloadSuccess();
} catch (YAMLException e) {
@@ -180,7 +175,7 @@ public class TAB extends TabAPI {
* and cancels all tasks.
*/
public void unload() {
if (disabled) return;
if (pluginDisabled) return;
try {
long time = System.currentTimeMillis();
if (configuration.getMysql() != null) configuration.getMysql().closeConnection();
@@ -196,48 +191,13 @@ public class TAB extends TabAPI {
* Clears online player maps and arrays and cancels all tasks
*/
private void kill() {
disabled = true;
pluginDisabled = true;
data.clear();
playersByTabListId.clear();
players = new TabPlayer[0];
onlinePlayers = new TabPlayer[0];
cpu.cancelAllTasks();
}
/**
* Loads universal features present on all platforms with the same configuration
*/
public void loadUniversalFeatures() {
if (configuration.getConfig().getBoolean("header-footer.enabled", true))
featureManager.registerFeature(TabConstants.Feature.HEADER_FOOTER, new HeaderFooter());
if (configuration.isRemoveGhostPlayers())
featureManager.registerFeature(TabConstants.Feature.GHOST_PLAYER_FIX, new GhostPlayerFix());
if (serverVersion.getMinorVersion() >= 8 && configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true)) {
if (configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false)) {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new AlignedPlayerList());
} else {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new PlayerList());
}
}
if (configuration.getConfig().getBoolean("ping-spoof.enabled", false))
featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true))
featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (configuration.getConfig().getBoolean("belowname-objective.enabled", true))
featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
if (configuration.getConfig().getBoolean("scoreboard.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (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());
}
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManager());
}
featureManager.registerFeature(TabConstants.Feature.NICK_COMPATIBILITY, new NickCompatibility());
}
/**
* Adds specified player to online players
*
@@ -247,7 +207,7 @@ public class TAB extends TabAPI {
public void addPlayer(TabPlayer player) {
data.put(player.getUniqueId(), player);
playersByTabListId.put(player.getTablistUUID(), player);
players = data.values().toArray(new TabPlayer[0]);
onlinePlayers = data.values().toArray(new TabPlayer[0]);
}
/**
@@ -259,35 +219,7 @@ public class TAB extends TabAPI {
public void removePlayer(TabPlayer player) {
data.remove(player.getUniqueId());
playersByTabListId.remove(player.getTablistUUID());
players = data.values().toArray(new TabPlayer[0]);
}
/**
* Returns instance of this class
*
* @return instance of this class
*/
public static TAB getInstance() {
return instance;
}
/**
* Changes instance of this class to new value
*
* @param instance
* Instance to set variable to
*/
public static void setInstance(TAB instance) {
TAB.instance = instance;
}
/**
* Returns {@code true} if floodgate plugin is installed, {@code false} if not
*
* @return {@code true} if floodgate plugin is installed, {@code false} if not
*/
public boolean isFloodgateInstalled() {
return floodgate;
onlinePlayers = data.values().toArray(new TabPlayer[0]);
}
/**
@@ -299,15 +231,6 @@ public class TAB extends TabAPI {
return (GroupManager) featureManager.getFeature(TabConstants.Feature.GROUP_MANAGER);
}
/**
* Returns {@link #platform}
*
* @return {@link #platform}
*/
public Platform getPlatform() {
return platform;
}
/**
* Returns {@link #cpu}
*
@@ -317,79 +240,6 @@ public class TAB extends TabAPI {
return cpu;
}
/**
* Returns {@link #errorManager}
*
* @return {@link #errorManager}
*/
public ErrorManager getErrorManager() {
return errorManager;
}
/**
* Returns {@link #configuration}
*
* @return {@link #configuration}
*/
public Configs getConfiguration() {
return configuration;
}
/**
* Returns {@link #command}
*
* @return {@link #command}
*/
public TabCommand getCommand() {
return command;
}
/**
* Returns {@link #disabledCommand}
*
* @return {@link #disabledCommand}
*/
public DisabledCommand getDisabledCommand() {
return disabledCommand;
}
/**
* Returns {@link #serverVersionString}
*
* @return {@link #serverVersionString}
*/
public String getServerVersionString() {
return serverVersionString;
}
/**
* Returns {@link #dataFolder}
*
* @return {@link #dataFolder}
*/
public File getDataFolder() {
return dataFolder;
}
/**
* Returns {@link #logger}
*
* @return {@link #logger}
*/
public Object getLogger() {
return logger;
}
@Override
public EventBusImpl getEventBus() {
return eventBus;
}
@Override
public ProtocolVersion getServerVersion() {
return serverVersion;
}
@Override
public BossBarManager getBossBarManager() {
return (BossBarManager) featureManager.getFeature(TabConstants.Feature.BOSS_BAR);
@@ -464,33 +314,13 @@ public class TAB extends TabAPI {
errorManager.printError(message, t);
}
@Override
public void setBrokenFile(String file) {
this.brokenFile = file;
}
@Override
public TablistFormatManager getTablistFormatManager() {
return (TablistFormatManager) featureManager.getFeature(TabConstants.Feature.PLAYER_LIST);
}
@Override
public TabPlayer[] getOnlinePlayers(){
return players;
}
@Override
public FeatureManagerImpl getFeatureManager() {
return featureManager;
}
@Override
public void debug(String message) {
if (configuration != null && configuration.isDebugMode()) sendConsoleMessage("&9[DEBUG] " + message, true);
}
@Override
public boolean isPluginDisabled() {
return disabled;
}
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.shared.backend;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.protocol.PacketBuilder;
import me.neznamy.tab.shared.Platform;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import org.jetbrains.annotations.Nullable;
public abstract class BackendPlatform extends Platform {
public BackendPlatform(PacketBuilder packetBuilder) {
super(packetBuilder);
}
@Override
public @Nullable TabFeature getGlobalPlayerlist() {
return null;
}
@Override
public @Nullable RedisSupport getRedisSupport() {
return null;
}
}
@@ -0,0 +1,227 @@
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.UUID;
public abstract class BackendArmorStand implements ArmorStand {
/** Entity id counter to pick unique entity ID for each armor stand */
private static int idCounter = 2000000000;
/** NameTag feature */
protected final BackendNameTagX manager;
/** Armor stand manager which this armor stand belongs to */
protected final BackendArmorStandManager asm;
/** Owner of the armor stand */
protected final TabPlayer owner;
/** Offset in blocks, 0 for original height */
@Getter protected double offset;
/** If offset is static, or dynamic based on other armor stands */
@Getter private final boolean staticOffset;
/** Entity ID of this armor stand */
@Getter protected final int entityId = idCounter++;
/** Unique ID of this armor stand */
protected final UUID uuid = UUID.randomUUID();
/** Sneaking flag of armor stands */
protected boolean sneaking;
/** Armor stand visibility to use in metadata */
protected boolean visible;
/** Visibility flag to compare against for previous value, including invisibility potion compensation */
private boolean visibleWPotion;
/** Refresh property dedicated to this armor stand */
@Getter protected final Property property;
/**
* 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 BackendArmorStand(BackendNameTagX feature, BackendArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
this.manager = feature;
this.asm = asm;
this.owner = owner;
this.staticOffset = staticOffset;
this.offset = yOffset;
this.property = owner.getProperty(propertyName);
visible = calculateVisibility();
visibleWPotion = visible && !owner.hasInvisibilityPotion();
}
@Override
public void setOffset(double offset) {
if (this.offset == offset) return;
this.offset = offset;
for (TabPlayer all : asm.getNearbyPlayers()) {
sendTeleportPacket(all);
}
}
@Override
public void refresh() {
visible = calculateVisibility();
visibleWPotion = visible && !owner.hasInvisibilityPotion();
updateMetadata();
}
@Override
public void updateVisibility(boolean force) {
boolean visibility = calculateVisibility() && !owner.hasInvisibilityPotion(); //trigger packet send but don't save, so it can be reverted if viewer is spectator
if (visibleWPotion != visibility || force) {
refresh();
}
}
@Override
public void sneak(boolean sneaking) {
if (this.sneaking == sneaking) return; //idk
this.sneaking = sneaking;
for (TabPlayer viewer : asm.getNearbyPlayers()) {
if (viewer.getVersion().getMinorVersion() == 14 && !manager.isArmorStandsAlwaysVisible()) {
//1.14.x client sided bug, de-spawning completely
if (sneaking) {
destroy(viewer);
} else {
spawn(viewer);
}
} else {
//respawning so there's no animation and it's instant
respawn(viewer);
}
}
}
@Override
public void destroy() {
for (TabPlayer all : asm.getNearbyPlayers())
destroy(all);
}
@Override
public void teleport() {
for (TabPlayer all : asm.getNearbyPlayers()) {
sendTeleportPacket(all);
}
}
@Override
public void teleport(TabPlayer viewer) {
if (!asm.isNearby(viewer) && viewer != owner) {
asm.spawn(viewer);
} else {
sendTeleportPacket(viewer);
}
}
/**
* Returns general visibility rule for everyone with limited info
*
* @return {@code true} if armor stand should be visible, {@code false} if not
*/
public boolean calculateVisibility() {
if (manager.isArmorStandsAlwaysVisible()) return true;
if (owner.isDisguised() || manager.isOnBoat(owner)) return false;
return owner.getGamemode() != 3 && !manager.hasHiddenNametag(owner) && property.get().length() > 0;
}
/**
* 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 {@code true} if it's empty, {@code false} if not
*/
protected boolean isNameVisiblyEmpty(String displayName) {
if (displayName.length() == 0) return true;
if (!displayName.startsWith(EnumChatFormat.COLOR_STRING) && !displayName.startsWith("&") && !displayName.startsWith("#")) return false;
String text = IChatBaseComponent.fromColoredText(displayName).toRawText();
if (text.contains(" ")) text = text.replace(" ", "");
return text.length() == 0;
}
/**
* Gets Y to add based on player's pose, armor stand offset and viewer
*
* @param sleeping
* Whether player is sleeping or not
* @param sneaking
* Whether player is sneakinig or not
* @param viewer
* Viewer of the armor stand
* @return Y to add to player's location
*/
public double getYAdd(boolean sleeping, boolean sneaking, TabPlayer viewer) {
double y = getOffset();
if (!sleeping) {
if (sneaking) {
if (viewer.getVersion().getMinorVersion() >= 15) {
y += 1.37;
} else if (viewer.getVersion().getMinorVersion() >= 9) {
y += 1.52;
} else {
y += 1.7;
}
} else {
y += viewer.getVersion().getMinorVersion() >= 9 ? 1.8 : 1.84; // Normal
}
} else {
y += viewer.getVersion().getMinorVersion() >= 9 ? 0.2 : 0.26; // Sleeping
}
if (viewer.getVersion().getMinorVersion() == 8 && !manager.isMarkerFor18x()) y -= 2;
return y;
}
public void respawn(TabPlayer viewer) {
destroy(viewer);
// 1.8.0 will not see entity that respawned in the same tick
// creating new delayed task every time someone sneaks can be abused and cause OOM
spawn(viewer);
}
/**
* Updates armor stand's metadata for everyone
*/
public void updateMetadata() {
for (TabPlayer viewer : asm.getNearbyPlayers()) {
updateMetadata(viewer);
}
}
public boolean shouldBeInvisibleFor(TabPlayer viewer, String displayName) {
return isNameVisiblyEmpty(displayName) || !manager.canSee(viewer, owner) ||
manager.hasHiddenNametag(owner, viewer) || manager.hasHiddenNameTagVisibilityView(viewer) ||
(owner.hasInvisibilityPotion() && viewer.getGamemode() != 3);
}
/**
* Updates armor stand's metadata for specified viewer
*/
public abstract void updateMetadata(TabPlayer viewer);
public abstract void sendTeleportPacket(TabPlayer viewer);
}
@@ -1,9 +1,10 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.ArmorStandManager;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
import java.util.ArrayList;
@@ -11,22 +12,20 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A helper class for easy management of armor stands of a player
*/
public class BukkitArmorStandManager implements ArmorStandManager {
public class BackendArmorStandManager implements ArmorStandManager {
/** Map of registered armor stands by name */
private final Map<String, ArmorStand> armorStands = new LinkedHashMap<>();
private final Map<String, BackendArmorStand> armorStands = new LinkedHashMap<>();
/** Armor stands in an array for speed while iterating */
private ArmorStand[] armorStandArray = new ArmorStand[0];
private BackendArmorStand[] armorStandArray = new BackendArmorStand[0];
/** Players in entity tracking range of owner */
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
private final List<TabPlayer> nearbyPlayerList = new ArrayList<>();
/** Nearby players in an array for speed while iterating */
private TabPlayer[] nearbyPlayerArray = new TabPlayer[0];
@Getter
private TabPlayer[] nearbyPlayers = new TabPlayer[0];
/**
* Constructs new instance with given parameters and loads armor stands.
@@ -36,17 +35,17 @@ public class BukkitArmorStandManager implements ArmorStandManager {
* @param owner
* Owner of this armor stand manager
*/
public BukkitArmorStandManager(NameTagX nameTagX, TabPlayer owner) {
public BackendArmorStandManager(NameTagX nameTagX, TabPlayer owner) {
owner.setProperty(nameTagX, TabConstants.Property.NAMETAG, owner.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue()
+ owner.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue()
+ owner.getProperty(TabConstants.Property.TAGSUFFIX).getCurrentRawValue());
double height = 0;
for (String line : nameTagX.getDynamicLines()) {
addArmorStand(line, new BukkitArmorStand(this, owner, line, height, false));
addArmorStand(line, ((BackendNameTagX)nameTagX).createArmorStand(this, owner, line, height, false));
height += 0.26;
}
for (Map.Entry<String, Object> line : nameTagX.getStaticLines().entrySet()) {
addArmorStand(line.getKey(), new BukkitArmorStand(this, owner, line.getKey(), Double.parseDouble(line.getValue().toString()), true));
addArmorStand(line.getKey(), ((BackendNameTagX)nameTagX).createArmorStand(this, owner, line.getKey(), Double.parseDouble(line.getValue().toString()), true));
}
fixArmorStandHeights();
}
@@ -68,14 +67,6 @@ public class BukkitArmorStandManager implements ArmorStandManager {
for (ArmorStand a : armorStandArray) a.teleport();
}
/**
* Returns array of nearby players
* @return array of nearby players
*/
public TabPlayer[] getNearbyPlayers(){
return nearbyPlayerArray;
}
/**
* Returns {@code true} if requested player is nearby, {@code false} if not
*
@@ -84,7 +75,7 @@ public class BukkitArmorStandManager implements ArmorStandManager {
* @return {@code true} if player nearby, {@code false} if not
*/
public boolean isNearby(TabPlayer viewer) {
return nearbyPlayers.contains(viewer);
return nearbyPlayerList.contains(viewer);
}
/**
@@ -118,7 +109,7 @@ public class BukkitArmorStandManager implements ArmorStandManager {
*/
public void respawn() {
for (ArmorStand a : armorStandArray) {
for (TabPlayer viewer : nearbyPlayerArray) {
for (TabPlayer viewer : nearbyPlayers) {
a.respawn(viewer);
}
}
@@ -131,8 +122,8 @@ public class BukkitArmorStandManager implements ArmorStandManager {
* player to spawn armor stands for
*/
public void spawn(TabPlayer viewer) {
nearbyPlayers.add(viewer);
nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
nearbyPlayerList.add(viewer);
nearbyPlayers = nearbyPlayerList.toArray(new TabPlayer[0]);
if (viewer.getVersion().getMinorVersion() < 8) return;
for (ArmorStand a : armorStandArray) a.spawn(viewer);
}
@@ -143,7 +134,7 @@ public class BukkitArmorStandManager implements ArmorStandManager {
public void fixArmorStandHeights() {
double currentY = -0.26;
for (ArmorStand as : armorStandArray) {
if (as.hasStaticOffset()) continue;
if (as.isStaticOffset()) continue;
if (as.getProperty().get().length() != 0) {
currentY += 0.26;
as.setOffset(currentY);
@@ -159,10 +150,10 @@ public class BukkitArmorStandManager implements ArmorStandManager {
* @param as
* Armor stand to add
*/
public void addArmorStand(String name, ArmorStand as) {
public void addArmorStand(String name, BackendArmorStand as) {
armorStands.put(name, as);
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
for (TabPlayer p : nearbyPlayerArray) as.spawn(p);
armorStandArray = armorStands.values().toArray(new BackendArmorStand[0]);
for (TabPlayer p : nearbyPlayers) as.spawn(p);
}
/**
@@ -172,7 +163,7 @@ public class BukkitArmorStandManager implements ArmorStandManager {
* player to remove
*/
public void unregisterPlayer(TabPlayer viewer) {
if (nearbyPlayers.remove(viewer)) nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
if (nearbyPlayerList.remove(viewer)) nearbyPlayers = nearbyPlayerList.toArray(new TabPlayer[0]);
}
public void updateVisibility(boolean force) {
@@ -193,8 +184,8 @@ public class BukkitArmorStandManager implements ArmorStandManager {
@Override
public void destroy() {
for (ArmorStand a : armorStandArray) a.destroy();
nearbyPlayers.clear();
nearbyPlayerArray = new TabPlayer[0];
nearbyPlayerList.clear();
nearbyPlayers = new TabPlayer[0];
}
@Override
@@ -208,4 +199,8 @@ public class BukkitArmorStandManager implements ArmorStandManager {
}
if (fix) fixArmorStandHeights();
}
}
public void updateMetadata(TabPlayer viewer) {
for (BackendArmorStand a : armorStandArray) a.updateMetadata(viewer);
}
}
@@ -0,0 +1,198 @@
package me.neznamy.tab.shared.backend.features.unlimitedtags;
import lombok.Getter;
import me.neznamy.tab.api.*;
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
import java.util.Collections;
import java.util.List;
public abstract class BackendNameTagX extends NameTagX {
/** Vehicle manager reference */
@Getter private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
/** Packet Listener reference */
protected final PacketListener packetListener = new PacketListener(this);
public BackendNameTagX() {
super(BackendArmorStandManager::new);
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER, vehicleManager);
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_PACKET_LISTENER, packetListener);
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder("%gamemode%", 500, TabPlayer::getGamemode);
TabFeature gamemode = new TabFeature("Unlimited NameTags", "Gamemode listener") {
{
addUsedPlaceholders(Collections.singletonList("%gamemode%"));
}
@Override
public void refresh(TabPlayer viewer, boolean force) {
for (TabPlayer target : TabAPI.getInstance().getOnlinePlayers()) {
getArmorStandManager(target).updateMetadata(viewer);
}
}
};
TabAPI.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_GAMEMODE_LISTENER, gamemode);
}
/**
* 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, () -> {
for (TabPlayer p : TabAPI.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(p)) continue;
getArmorStandManager(p).updateVisibility(false);
}
});
}
@Override
public BackendArmorStandManager getArmorStandManager(TabPlayer player) {
return (BackendArmorStandManager) armorStandManagerMap.get(player);
}
@Override
public void load() {
super.load();
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(all)) continue;
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, all);
}
}
startVisibilityRefreshTask();
}
@Override
public void unload() {
super.unload();
unregisterListener();
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
super.onJoin(connectedPlayer);
if (isPlayerDisabled(connectedPlayer)) return;
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, connectedPlayer);
spawnArmorStands(connectedPlayer, viewer);
}
}
@Override
public boolean isOnBoat(TabPlayer player) {
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;
if (!areInSameWorld(viewer, target)) return;
if (getDistance(viewer, target) <= 48 && canSee(viewer, target) && !target.isVanished())
getArmorStandManager(target).spawn(viewer);
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
super.onQuit(disconnectedPlayer);
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
getArmorStandManager(all).unregisterPlayer(disconnectedPlayer);
}
armorStandManagerMap.get(disconnectedPlayer).destroy();
armorStandManagerMap.remove(disconnectedPlayer); // WeakHashMap doesn't clear this due to value referencing the key
}
@Override
public void resumeArmorStands(TabPlayer player) {
if (isPlayerDisabled(player)) return;
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, player);
}
}
@Override
public void setNameTagPreview(TabPlayer player, boolean status) {
if (status) {
getArmorStandManager(player).spawn(player);
} else {
getArmorStandManager(player).destroy(player);
}
}
@Override
public void pauseArmorStands(TabPlayer player) {
getArmorStandManager(player).destroy();
}
@Override
public void updateNameTagVisibilityView(TabPlayer player) {
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
getArmorStandManager(all).updateVisibility(true);
}
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
super.onWorldChange(p, from, to);
if (isUnlimitedDisabled(p.getServer(), to)) {
getDisabledUnlimitedPlayers().add(p);
updateTeamData(p);
} else if (getDisabledUnlimitedPlayers().remove(p)) {
updateTeamData(p);
}
if (isPreviewingNametag(p)) {
getArmorStandManager(p).spawn(p);
}
//for some reason this is needed for some users
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
if (viewer.getWorld().equals(from)) {
getArmorStandManager(p).destroy(viewer);
}
}
}
public int getEntityId(TabPlayer player) {
return getEntityId(player.getPlayer());
}
/**
* Returns flat distance between two players ignoring Y value
*
* @param player1
* first player
* @param player2
* second player
* @return flat distance in blocks
*/
public abstract double getDistance(TabPlayer player1, TabPlayer player2);
public abstract boolean areInSameWorld(TabPlayer player1, TabPlayer player2);
public abstract boolean canSee(TabPlayer viewer, TabPlayer target);
public abstract void unregisterListener();
public abstract List<Integer> getPassengers(Object vehicle);
public abstract void registerVehiclePlaceholder();
public abstract Object getVehicle(TabPlayer player);
public abstract int getEntityId(Object entity);
public abstract String getEntityType(Object entity);
public abstract BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset);
}
@@ -1,20 +1,18 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
package me.neznamy.tab.shared.backend.features.unlimitedtags;
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.platforms.bukkit.nms.storage.NMSStorage;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* The packet listening part for securing proper functionality of armor stands.
* Bukkit events are too unreliable and delayed/ahead which causes de-sync
* 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.
@@ -22,13 +20,10 @@ import java.util.concurrent.ConcurrentHashMap;
public class PacketListener extends TabFeature {
/** Reference to the main feature */
private final BukkitNameTagX nameTagX;
protected final BackendNameTagX nameTagX;
/** A player map by entity id, used for better performance */
private final Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/**
* Constructs new instance with given parameter
@@ -36,7 +31,7 @@ public class PacketListener extends TabFeature {
* @param nameTagX
* main feature
*/
public PacketListener(BukkitNameTagX nameTagX) {
public PacketListener(BackendNameTagX nameTagX) {
super(nameTagX.getFeatureName(), null);
this.nameTagX = nameTagX;
}
@@ -44,61 +39,34 @@ public class PacketListener extends TabFeature {
@Override
public void load() {
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
entityIdMap.put(((Player) all.getPlayer()).getEntityId(), all);
entityIdMap.put(nameTagX.getEntityId(all), all);
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
entityIdMap.put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
entityIdMap.put(nameTagX.getEntityId(connectedPlayer), connectedPlayer);
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
entityIdMap.remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
entityIdMap.remove(nameTagX.getEntityId(disconnectedPlayer));
}
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && nameTagX.getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.setField(packet, nms.PacketPlayInUseEntity_ENTITY, ((Player) attacked.getPlayer()).getEntityId());
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || nameTagX.isDisabledPlayer(receiver) || nameTagX.getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutEntityTeleport.isInstance(packet)) {
onEntityMove(receiver, nms.PacketPlayOutEntityTeleport_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutEntityDestroy.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet);
if (entities instanceof List) {
onEntityDestroy(receiver, (List<Integer>) entities);
} else {
//1.17.0
onEntityDestroy(receiver, (int) entities);
}
} else {
onEntityDestroy(receiver, (int[]) nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet));
}
/**
* 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
*/
public void onEntitySpawn(TabPlayer receiver, int entityId) {
TabPlayer spawnedPlayer = entityIdMap.get(entityId);
if (spawnedPlayer != null && spawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(spawnedPlayer)) {
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_SPAWN,
() -> nameTagX.getArmorStandManager(spawnedPlayer).spawn(receiver));
}
}
@@ -113,18 +81,17 @@ public class PacketListener extends TabFeature {
* @param entityId
* entity that moved
*/
private void onEntityMove(TabPlayer receiver, int entityId) {
public void onEntityMove(TabPlayer receiver, int entityId) {
TabPlayer pl = entityIdMap.get(entityId);
List<Entity> vehicleList;
if (pl != null) {
//player moved
if (nameTagX.isPlayerDisabled(pl) || !pl.isLoaded()) return;
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE,
() -> nameTagX.getArmorStandManager(pl).teleport(receiver));
} else if ((vehicleList = nameTagX.getVehicleManager().getVehicles().get(entityId)) != null){
} else {
//a vehicle carrying something moved
for (Entity entity : vehicleList) {
TabPlayer passenger = entityIdMap.get(entity.getEntityId());
for (Integer entity : nameTagX.getVehicleManager().getVehicles().getOrDefault(entityId, Collections.emptyList())) {
TabPlayer passenger = entityIdMap.get(entity);
if (passenger != null && nameTagX.getArmorStandManager(passenger) != null) {
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE_PASSENGER,
() -> nameTagX.getArmorStandManager(passenger).teleport(receiver));
@@ -133,23 +100,6 @@ 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)) {
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_SPAWN,
() -> nameTagX.getArmorStandManager(spawnedPlayer).spawn(receiver));
}
}
/**
* Processes entity destroy packet and destroys armor stands if
* entity ID belongs to an online player.
@@ -159,7 +109,7 @@ public class PacketListener extends TabFeature {
* @param entities
* de-spawned entities
*/
private void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
public void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
@@ -174,7 +124,7 @@ public class PacketListener extends TabFeature {
* @param entities
* de-spawned entities
*/
private void onEntityDestroy(TabPlayer receiver, int... entities) {
public void onEntityDestroy(TabPlayer receiver, int... entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
@@ -189,12 +139,12 @@ public class PacketListener extends TabFeature {
* @param entity
* de-spawned entity
*/
private void onEntityDestroy(TabPlayer receiver, int entity) {
public void onEntityDestroy(TabPlayer receiver, int entity) {
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer)) {
BukkitArmorStandManager asm = nameTagX.getArmorStandManager(deSpawnedPlayer);
BackendArmorStandManager asm = nameTagX.getArmorStandManager(deSpawnedPlayer);
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_DESTROY,
() -> asm.destroy(receiver));
}
}
}
}
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff. Show more