Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51635a7322 | ||
|
|
481b33ba55 | ||
|
|
22c785fc99 | ||
|
|
42d4159b69 | ||
|
|
6d95a2f06d | ||
|
|
7f0b1d8a2e | ||
|
|
a3fecd0fea | ||
|
|
f3d6a54a2a | ||
|
|
1d434f2557 | ||
|
|
b3500086e4 | ||
|
|
bf9202af38 | ||
|
|
bd2c683568 | ||
|
|
c03feaab23 | ||
|
|
9cb9ccbd2a | ||
|
|
4606ebd588 | ||
|
|
679be83c5a | ||
|
|
afe868bb95 | ||
|
|
c9ce3aa23e | ||
|
|
11a49c4874 | ||
|
|
65d2a0fba9 | ||
|
|
c1219195c7 | ||
|
|
fca0dfc91b | ||
|
|
7f1cc3063c | ||
|
|
e444c25287 | ||
|
|
476354e407 | ||
|
|
9f84d3f22f | ||
|
|
4608ff360c | ||
|
|
68b12e199d | ||
|
|
4b1151f769 | ||
|
|
6687214f2a | ||
|
|
a57757258e | ||
|
|
3a6060c45d | ||
|
|
ee375c1b60 | ||
|
|
c9d870cca0 | ||
|
|
3ad8015cc1 | ||
|
|
33609c322f | ||
|
|
431b89c487 | ||
|
|
35a146992c | ||
|
|
dedee114c4 | ||
|
|
9d42af0388 | ||
|
|
8f5d81bc33 | ||
|
|
b17f5563dd | ||
|
|
0ab8bb363a | ||
|
|
ac5fca86bd | ||
|
|
28c2c2e193 | ||
|
|
904c0b6a17 | ||
|
|
f3c31bc92c | ||
|
|
6016b84889 | ||
|
|
cfb5cb3a9f | ||
|
|
1746f6b1d0 | ||
|
|
9c3979a0d9 | ||
|
|
810676c69f | ||
|
|
d0892a245a | ||
|
|
f539b1b93b | ||
|
|
adfa64a29f | ||
|
|
d4f86ee79d | ||
|
|
6ebe31b737 | ||
|
|
dcbd5e62d4 | ||
|
|
a7e6e49363 | ||
|
|
d115257ff7 | ||
|
|
d578be276c | ||
|
|
e338f57441 | ||
|
|
c87321b5b0 | ||
|
|
84bb9b449b | ||
|
|
9d613fc24d | ||
|
|
3f2ae3131e | ||
|
|
0e9f53bd58 | ||
|
|
3981815b63 | ||
|
|
3ef99e4e7c | ||
|
|
76767a0958 | ||
|
|
1683943d58 | ||
|
|
cc0dbe8623 | ||
|
|
b056251e07 | ||
|
|
3b352c66e2 | ||
|
|
b4f0ea73c1 | ||
|
|
1c683788b5 | ||
|
|
250597df2d | ||
|
|
0d2d1d5aaa | ||
|
|
061375d1e7 | ||
|
|
5b05614305 | ||
|
|
69db2e0e53 | ||
|
|
00ef364324 | ||
|
|
de68312b6b | ||
|
|
ddac08bcbc | ||
|
|
517e285bb3 | ||
|
|
81a08a6b51 | ||
|
|
4dfef1d032 | ||
|
|
5d38e28cae | ||
|
|
66a12d10c4 | ||
|
|
1a4027c8d5 | ||
|
|
2a0243c51a | ||
|
|
4851d09e70 | ||
|
|
3cdf3accfd | ||
|
|
fd1f094216 | ||
|
|
25ece1ecb8 | ||
|
|
802e65c280 | ||
|
|
882688f056 | ||
|
|
97d60d0ab3 | ||
|
|
9ef99daed1 | ||
|
|
71c85d9d39 | ||
|
|
b981ab46be | ||
|
|
016633e494 | ||
|
|
1db862ab56 | ||
|
|
3563786310 | ||
|
|
ac8851dd6a | ||
|
|
53ab2d9793 | ||
|
|
2a58c86ffc | ||
|
|
fa46d527ae | ||
|
|
667bd38305 | ||
|
|
faba439466 | ||
|
|
c301157758 | ||
|
|
8d09d425a4 | ||
|
|
11f03d5a10 | ||
|
|
83625202a1 | ||
|
|
eb4b715a88 | ||
|
|
89dce941cf | ||
|
|
6371b67e9e | ||
|
|
966e27418a | ||
|
|
4adf7d1734 | ||
|
|
1a4c4a6fe8 | ||
|
|
7ff36592ae | ||
|
|
1b513412d2 | ||
|
|
f2bb43fbcd | ||
|
|
df5d07d885 | ||
|
|
0ad2f33c4c | ||
|
|
dee48fccb3 | ||
|
|
9ca372ab13 | ||
|
|
58a849a902 | ||
|
|
895072ec35 | ||
|
|
2b2c4d2c39 | ||
|
|
d534b6e2e8 | ||
|
|
783e9d9931 | ||
|
|
a109d9b55b | ||
|
|
5ce59ce44f | ||
|
|
1e9195a9bc | ||
|
|
3099d16468 | ||
|
|
c9c6af346c | ||
|
|
af80a43628 | ||
|
|
dc3c98ec91 | ||
|
|
dffa2c83f6 | ||
|
|
dc66f253a7 | ||
|
|
17498cbfc2 | ||
|
|
96e4d30168 | ||
|
|
c9d1934bb9 | ||
|
|
8bec083194 | ||
|
|
abf047be23 | ||
|
|
4a95399d56 | ||
|
|
885bf5154e | ||
|
|
3e146fc496 | ||
|
|
95710bcaf5 | ||
|
|
edd620f17d | ||
|
|
9c466d8b1e | ||
|
|
b385ab70fb | ||
|
|
140224840b | ||
|
|
2078cd5a5f | ||
|
|
27bae7fdbd | ||
|
|
174b4214de | ||
|
|
4ac524840c | ||
|
|
dd4ce4e2c3 | ||
|
|
2a03d69628 | ||
|
|
c28252e6ce | ||
|
|
da1768d833 | ||
|
|
b58c3e6b47 | ||
|
|
c7575bdc8f | ||
|
|
e275d22061 | ||
|
|
b5600ef22a | ||
|
|
b8e14c9256 | ||
|
|
1af5b9a308 | ||
|
|
2dd719c3a5 | ||
|
|
45d94694cd | ||
|
|
3ce8c6a192 | ||
|
|
7deb86f105 | ||
|
|
d9b4f6a43f | ||
|
|
54a7fb8721 | ||
|
|
d1dcb2f437 | ||
|
|
a92bab617a | ||
|
|
57e34bc595 | ||
|
|
953baa273c | ||
|
|
c403cb3187 | ||
|
|
ffd6dc5bb9 | ||
|
|
0f6504a3dc | ||
|
|
f376fea598 | ||
|
|
4896cdff91 | ||
|
|
b4c87431bc | ||
|
|
1cffbab97d | ||
|
|
9b0711075a | ||
|
|
17a8e902eb | ||
|
|
4bdb5ba245 | ||
|
|
fd7748d280 | ||
|
|
317b0292ff | ||
|
|
b87be649bc | ||
|
|
e2dda54011 | ||
|
|
d17518c39b | ||
|
|
48541dd0bd | ||
|
|
b83b023fee | ||
|
|
60e0c78033 | ||
|
|
a395f7f01f | ||
|
|
09c36ef69e | ||
|
|
9d5fa685a0 | ||
|
|
6794f593fa | ||
|
|
5e9965276f | ||
|
|
1473038955 | ||
|
|
2041ca57f3 | ||
|
|
098782d820 | ||
|
|
87e9a6defd | ||
|
|
3d896ff828 | ||
|
|
bf93240888 | ||
|
|
0b0e30c3c9 | ||
|
|
84d3816bd1 | ||
|
|
04ff606947 | ||
|
|
1cb8e6e238 | ||
|
|
4a2226170d | ||
|
|
1469124cfc | ||
|
|
0826095e73 | ||
|
|
1bd698adde | ||
|
|
1058d269ff | ||
|
|
bacd87b78e | ||
|
|
5befbe8780 | ||
|
|
e159b2adae | ||
|
|
52f7418853 | ||
|
|
76061f7ccd | ||
|
|
7d39e21eae | ||
|
|
fee3723474 | ||
|
|
2f0e4c6888 | ||
|
|
b04cb6d3c5 | ||
|
|
a9228a698a | ||
|
|
27102495d8 | ||
|
|
d1bf96ab33 | ||
|
|
3360dbb468 | ||
|
|
d224e7584e | ||
|
|
6373540076 | ||
|
|
5f78aca997 | ||
|
|
145d944d33 | ||
|
|
ac91940e3b | ||
|
|
ef8452be73 | ||
|
|
47e0810697 | ||
|
|
1f30f225c7 | ||
|
|
fcbfd60ddb | ||
|
|
46f9033713 | ||
|
|
7e65018a0e | ||
|
|
9ef3485968 | ||
|
|
257f8f1145 | ||
|
|
8d65cbb0e5 | ||
|
|
20d221e3ca | ||
|
|
94072acf10 | ||
|
|
0422cc2597 | ||
|
|
58f61e645b | ||
|
|
5f416bd9e5 | ||
|
|
64528c913c | ||
|
|
357e7c6a49 | ||
|
|
81dd202053 | ||
|
|
189797aa73 | ||
|
|
1ae2ef7cad | ||
|
|
de14e14d33 | ||
|
|
19892c14c1 | ||
|
|
2e00c4c002 | ||
|
|
97aa367629 | ||
|
|
87cc9a030b | ||
|
|
8ba83d1cb1 | ||
|
|
9f0e60bd47 | ||
|
|
93b2a25b2e | ||
|
|
4242d7a839 | ||
|
|
0ddb99bd46 | ||
|
|
969672e3ad | ||
|
|
2423fbb150 | ||
|
|
a99928dbf5 | ||
|
|
12f5dd52b4 | ||
|
|
2bd105446e | ||
|
|
6d85875fde | ||
|
|
42810aa490 |
No files matched your search
@@ -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,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:
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Memory leak
|
||||
description: Memory leak caused by TAB.
|
||||
labels: ["Type: Memory leak"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Heap dump
|
||||
description: Link to uploaded memory dump which I can look into.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Uncleared object(s)
|
||||
description: Objects you believe should have been removed from the memory but are not
|
||||
placeholder: |
|
||||
There is only 1 player online, but heap dump has 100 instances of TabPlayer
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included download link of my heap dump
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
- label: I have explained what I believe is wrong with the heap dump
|
||||
required: true
|
||||
@@ -0,0 +1,55 @@
|
||||
name: Performance issue
|
||||
description: Performance issue in TAB or in server caused by TAB
|
||||
labels: ["Type: Performance"]
|
||||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Server version
|
||||
description: Version and type of used server software where TAB is installed.
|
||||
placeholder: Spigot 1.18.1
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: TAB version
|
||||
description: Full version in numbers, "latest" is not a version.
|
||||
placeholder: 3.0.2
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Evidence
|
||||
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce (if known)
|
||||
description: What to do in order to reproduce this issue
|
||||
placeholder: |
|
||||
Step 1: ..
|
||||
Step 2: ..
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Any other info you want to provide.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: I am running latest version of the plugin
|
||||
required: true
|
||||
- label: I have included sufficient evidence of the issue
|
||||
required: true
|
||||
- label: I ticked all of the boxes without actually reading them
|
||||
required: false
|
||||
@@ -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:
|
||||
|
||||
@@ -3,7 +3,6 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- v3
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
jobs:
|
||||
@@ -11,22 +10,22 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 11
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: ~/.sonar/cache
|
||||
key: ${{ runner.os }}-sonar
|
||||
restore-keys: ${{ runner.os }}-sonar
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -39,7 +38,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
|
||||
run: ./gradlew clean build
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Jar
|
||||
path: jar/build/libs/TAB-**.jar
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -7,18 +7,70 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
|
||||
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
|
||||
version anymore. Only one version of the plugin exists now, which contains all implemented features
|
||||
and is available to everyone for free. However, customer support is only provided to
|
||||
customers who have purchased the plugin.
|
||||
customers who have purchased the plugin (see [Customer support](#customer-support)).
|
||||
|
||||
# Compiling
|
||||
Compilation requires JDK 8 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
|
||||
# Links
|
||||
Download the plugin for free: [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
|
||||
[BuiltByBit](https://builtbybit.com/resources/20631/), [Modrinth](https://modrinth.com/plugin/tab-was-taken)
|
||||
Support my work & receive customer support: [BuiltByBit](https://builtbybit.com/resources/14009/), [Polymart](https://polymart.org/resource/484)
|
||||
Other links: [Discord support for customers](https://discord.gg/EaSvdk6), [Wiki](https://github.com/NEZNAMY/TAB/wiki)
|
||||
# Customer support
|
||||
Unlike with other projects where requirements and level of support provided are unclear,
|
||||
we provide a clear answer to what kind of service you will or will not receive. Some don't
|
||||
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
|
||||
a free download link in description, which can be confusing.
|
||||
|
||||
Free plugins typically do have a support discord, however you will rarely receive
|
||||
official support from the developers, especially for large projects with tons of users, where
|
||||
helping everyone in the world (theoretically) would consume too much time, leaving authors
|
||||
no time left to actually work on the project.
|
||||
In the end, they are free, made by people in their free time, and they are allowed to choose
|
||||
what they do or don't want to do in their free time. They often leave support to other people,
|
||||
possibly staff members, who are, however, in the end just more volunteers who may not even know
|
||||
the answer.
|
||||
|
||||
With paid plugins you are more likely to receive support, but unless explicitly said, nothing is
|
||||
guaranteed. There are lots of paid plugins where authors don't provide any support or redirect
|
||||
it to helpers, where again they are unpaid volunteers who might not even know the answer.
|
||||
|
||||
In TAB, everything is clearly defined. The plugin is free to download for everyone.
|
||||
Customer support, however, is only provided to customers who have formally purchased a copy
|
||||
of the plugin on any of the websites the plugin is being sold on:
|
||||
* [BuiltByBit](https://builtbybit.com/resources/14009/) (accepts any payment method)
|
||||
* [Polymart](https://polymart.org/resource/484) (only PayPal)
|
||||
|
||||
After purchasing, you can request assistance via either private messages on the website where you
|
||||
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
|
||||
preferred, since the communication will be much faster thanks to notifications and more).
|
||||
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
|
||||
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
|
||||
after user confirmed all of their questions were answered and then receive a transcript of the ticket
|
||||
from the bot after ticket is closed into private messages (unless user is blocking them).
|
||||
* We do not rely on the community helping, which often makes things even worse. We know people have
|
||||
better things to do than helping random people with a random plugin, especially when most people only
|
||||
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
|
||||
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
|
||||
most mods on most servers who only check it when they are really bored, give a wiki link and
|
||||
disappear again.
|
||||
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
|
||||
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
|
||||
whenever I am available without any intentional delays.
|
||||
|
||||
The wiki contains everything about the plugin in one way or another, so support should not
|
||||
even be needed. It also contains a few examples, but obviously cannot contain a pre-made config
|
||||
for every possible idea anyone can come up with.
|
||||
Support is provided to people who decided paying for support is more time efficient than reading the wiki,
|
||||
especially for serious servers, where price of the plugin is nothing compared to other server costs.
|
||||
|
||||
# Other links
|
||||
Download the plugin for free:
|
||||
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
|
||||
* [BuiltByBit](https://builtbybit.com/resources/20631/)
|
||||
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
|
||||
|
||||
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
|
||||
[Donate](https://paypal.me/neznamy1) if you are feeling extra generous
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
|
||||
@@ -26,15 +78,9 @@ In other words, they **are not available for**:
|
||||
* **Support** - support is provided on discord for buyers, where roles are given to verified users
|
||||
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
|
||||
* **Feature requests** - closed indefinitely.
|
||||
|
||||
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
|
||||
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)
|
||||
@@ -0,0 +1,2 @@
|
||||
# Security Policy
|
||||
If you found a security issue, send me a private message anywhere.
|
||||
@@ -9,14 +9,19 @@ dependencies {
|
||||
}
|
||||
compileOnlyApi(libs.snakeyaml)
|
||||
api(libs.yamlAssist)
|
||||
compileOnlyApi(libs.gson)
|
||||
compileOnlyApi(libs.adventureMiniMessage)
|
||||
compileOnlyApi(libs.adventureApi)
|
||||
compileOnlyApi(libs.adventureLegacy)
|
||||
compileOnlyApi(libs.via)
|
||||
}
|
||||
|
||||
blossom {
|
||||
replaceToken("@plugin_version@", project.version)
|
||||
replaceToken("@name@", rootProject.name)
|
||||
replaceToken("@id@", rootProject.ext.get("id")!!.toString())
|
||||
replaceToken("@version@", project.version)
|
||||
replaceToken("@description@", project.description)
|
||||
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
|
||||
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
|
||||
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ public interface ArmorStand {
|
||||
*
|
||||
* @return true if static, false if dynamic
|
||||
*/
|
||||
boolean hasStaticOffset();
|
||||
boolean isStaticOffset();
|
||||
|
||||
/**
|
||||
* Changes offset of the armor stand
|
||||
@@ -34,41 +34,7 @@ public interface ArmorStand {
|
||||
* @return property for armor stand's name
|
||||
*/
|
||||
Property getProperty();
|
||||
|
||||
/**
|
||||
* Teleports armor stand to owner's current location for everyone in range
|
||||
*/
|
||||
void teleport();
|
||||
|
||||
/**
|
||||
* Teleports armor stand to owner's current location for specified player
|
||||
*
|
||||
* @param viewer
|
||||
* player to send packet to
|
||||
*/
|
||||
void teleport(TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Changes sneaking flag and sends packets to everyone in range
|
||||
*
|
||||
* @param sneaking
|
||||
* new sneaking status
|
||||
*/
|
||||
void sneak(boolean sneaking);
|
||||
|
||||
/**
|
||||
* DeSpawns armor stand for everyone
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* DeSpawns armor stand for specified player
|
||||
*
|
||||
* @param viewer
|
||||
* player to send packet to
|
||||
*/
|
||||
void destroy(TabPlayer viewer);
|
||||
|
||||
|
||||
/**
|
||||
* Updates armor stand's name if needed
|
||||
*/
|
||||
@@ -88,21 +54,4 @@ public interface ArmorStand {
|
||||
* @return entity ID of this armor stand
|
||||
*/
|
||||
int getEntityId();
|
||||
|
||||
/**
|
||||
* Spawns armor stand for specified player
|
||||
*
|
||||
* @param viewer
|
||||
* player to spawn for
|
||||
*/
|
||||
void spawn(TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Sends respawn packets to specified player to make position
|
||||
* change instant instead of teleport animation
|
||||
*
|
||||
* @param viewer
|
||||
* Player to send armor stand respawn packets to
|
||||
*/
|
||||
void respawn(TabPlayer viewer);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface BossBarHandler {
|
||||
|
||||
/**
|
||||
* Creates boss bar and sends it to the player
|
||||
*
|
||||
* @param id
|
||||
* Unique identifier to match the bossbar with other functions
|
||||
* @param title
|
||||
* BossBar title
|
||||
* @param progress
|
||||
* BossBar progress (0-1)
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param style
|
||||
* BossBar style
|
||||
*/
|
||||
void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Updates title
|
||||
*
|
||||
* @param id
|
||||
* Unique identifier to match the bossbar with other functions
|
||||
* @param title
|
||||
* New title
|
||||
*/
|
||||
void update(@NonNull UUID id, @NonNull String title);
|
||||
|
||||
/**
|
||||
* Updates progress
|
||||
*
|
||||
* @param id
|
||||
* Unique identifier to match the bossbar with other functions
|
||||
* @param progress
|
||||
* New progress (0-1)
|
||||
*/
|
||||
void update(@NonNull UUID id, float progress);
|
||||
|
||||
/**
|
||||
* Updates style
|
||||
*
|
||||
* @param id
|
||||
* Unique identifier to match the bossbar with other functions
|
||||
* @param style
|
||||
* New style
|
||||
*/
|
||||
void update(@NonNull UUID id, @NonNull BarStyle style);
|
||||
|
||||
/**
|
||||
* Updates color
|
||||
*
|
||||
* @param id
|
||||
* Unique identifier to match the bossbar with other functions
|
||||
* @param color
|
||||
* New color
|
||||
*/
|
||||
void update(@NonNull UUID id, @NonNull BarColor color);
|
||||
|
||||
/**
|
||||
* Removes boss bar
|
||||
*
|
||||
* @param id
|
||||
* Unique identifier to match the bossbar with other functions
|
||||
*/
|
||||
void remove(@NonNull UUID id);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import me.neznamy.tab.api.feature.TabFeature;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -91,22 +93,6 @@ public interface FeatureManager {
|
||||
*/
|
||||
boolean onCommand(TabPlayer sender, String command);
|
||||
|
||||
/**
|
||||
* Internal function that marks objective packet deserialization
|
||||
* when at least 1 feature uses it.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*/
|
||||
void markObjective();
|
||||
|
||||
/**
|
||||
* Internal function that marks display objective packet deserialization
|
||||
* when at least 1 feature uses it.
|
||||
* <p>
|
||||
* Internal use only.
|
||||
*/
|
||||
void markDisplayObjective();
|
||||
|
||||
/**
|
||||
* Returns array of all currently enabled features
|
||||
*
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Enum class representing all possibly used protocol versions
|
||||
*/
|
||||
@@ -8,6 +14,7 @@ public enum ProtocolVersion {
|
||||
UNKNOWN_SERVER_VERSION ("Unknown"),
|
||||
UNKNOWN_CLIENT_VERSION ("Unknown"),
|
||||
PROXY ("Proxy"),
|
||||
V1_19_4 (762),
|
||||
V1_19_3 (761),
|
||||
V1_19_2 (760),
|
||||
V1_19_1 (760),
|
||||
@@ -70,13 +77,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
|
||||
@@ -84,7 +94,7 @@ public enum ProtocolVersion {
|
||||
* @param networkId
|
||||
* network id of this version
|
||||
*/
|
||||
ProtocolVersion(int networkId){
|
||||
ProtocolVersion(int networkId) {
|
||||
this.networkId = networkId;
|
||||
this.minorVersion = Integer.parseInt(toString().split("_")[1]);
|
||||
this.friendlyName = toString().substring(1).replace("_", ".");
|
||||
@@ -96,51 +106,12 @@ public enum ProtocolVersion {
|
||||
* @param friendlyName
|
||||
* friendly name to display
|
||||
*/
|
||||
ProtocolVersion(String friendlyName){
|
||||
ProtocolVersion(String friendlyName) {
|
||||
this.networkId = 999;
|
||||
this.minorVersion = 18;
|
||||
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
|
||||
*
|
||||
@@ -170,4 +141,19 @@ public enum ProtocolVersion {
|
||||
}
|
||||
return UNKNOWN_CLIENT_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets player's network version using ViaVersion API
|
||||
*
|
||||
* @param player
|
||||
* Player's UUID
|
||||
* @param playerName
|
||||
* Player's name for debug messages
|
||||
* @return Player's network version
|
||||
*/
|
||||
public static int getPlayerVersionVia(UUID player, String playerName) {
|
||||
int version = Via.getAPI().getPlayerVersion(player);
|
||||
TabAPI.getInstance().debug("ViaVersion returned protocol version " + version + " for " + playerName);
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
* Sets objective display slot of specified objective
|
||||
*
|
||||
* @param slot
|
||||
* Display slot
|
||||
* @param objective
|
||||
* Objective name
|
||||
*/
|
||||
void setDisplaySlot(DisplaySlot slot, @NonNull String objective);
|
||||
|
||||
/**
|
||||
* Sets scoreboard score
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param player
|
||||
* Affected player
|
||||
* @param score
|
||||
* New score value
|
||||
*/
|
||||
void setScore(@NonNull String objective, @NonNull String player, int score);
|
||||
|
||||
/**
|
||||
* Removes scoreboard score
|
||||
*
|
||||
* @param objective
|
||||
* Objective to remove from
|
||||
* @param player
|
||||
* Player to remove from sidebar
|
||||
*/
|
||||
void removeScore(@NonNull String objective, @NonNull String player);
|
||||
|
||||
void registerObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
|
||||
|
||||
void unregisterObjective(@NonNull String objectiveName);
|
||||
|
||||
void updateObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
|
||||
|
||||
void registerTeam(@NonNull String name, String prefix, String suffix, String visibility,
|
||||
String collision, Collection<String> players, int options);
|
||||
|
||||
void unregisterTeam(@NonNull String name);
|
||||
|
||||
void updateTeam(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options);
|
||||
|
||||
enum DisplaySlot {
|
||||
|
||||
PLAYER_LIST, SIDEBAR, BELOW_NAME
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import me.neznamy.tab.api.config.ConfigurationFile;
|
||||
import me.neznamy.tab.api.event.EventBus;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.api.task.ThreadManager;
|
||||
import me.neznamy.tab.api.team.TeamManager;
|
||||
|
||||
/**
|
||||
@@ -36,8 +35,12 @@ public abstract class TabAPI {
|
||||
* If instance is {@code null}
|
||||
*/
|
||||
public static TabAPI getInstance() {
|
||||
if (instance == null) throw new IllegalStateException("API instance is null. This likely means you shaded TAB's API into your project" +
|
||||
" instead of only using it, which is not allowed.");
|
||||
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
|
||||
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
|
||||
"plugin loads after TAB, add it as a depend or softdepend of your plugin.\n" +
|
||||
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
|
||||
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
|
||||
"to not include them, such as scope provided for maven compilation.");
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -139,13 +142,6 @@ public abstract class TabAPI {
|
||||
*/
|
||||
public abstract void sendConsoleMessage(String message, boolean translateColors);
|
||||
|
||||
/**
|
||||
* Returns TAB's Thread manager, which allows task submitting
|
||||
*
|
||||
* @return ThreadManager instance
|
||||
*/
|
||||
public abstract ThreadManager getThreadManager();
|
||||
|
||||
/**
|
||||
* Returns TAB's cache file used to store player toggle data
|
||||
*
|
||||
|
||||
@@ -1,26 +1,33 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* A class containing various constants used everywhere in the plugin
|
||||
* to allow easier overview and modification to prevent inconsistencies.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class TabConstants {
|
||||
|
||||
public static final String PLUGIN_VERSION = "@plugin_version@";
|
||||
public static final String PLUGIN_NAME = "@name@";
|
||||
public static final String PLUGIN_ID = "@id@";
|
||||
public static final String PLUGIN_VERSION = "@version@";
|
||||
public static final String PLUGIN_DESCRIPTION = "@description@";
|
||||
public static final String PLUGIN_WEBSITE = "@website@";
|
||||
public static final String PLUGIN_AUTHOR = "@author@";
|
||||
|
||||
public static final String NO_GROUP = "NONE";
|
||||
public static final String DEFAULT_GROUP = "_DEFAULT_";
|
||||
public static final String PLUGIN_MESSAGE_CHANNEL_NAME = "tab:bridge-2";
|
||||
public static final String REDIS_CHANNEL_NAME = "TAB";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent the class from being instantiated
|
||||
*/
|
||||
private TabConstants() {}
|
||||
public static final String REDIS_CHANNEL_NAME = PLUGIN_NAME;
|
||||
public static final String PIPELINE_HANDLER_NAME = PLUGIN_NAME;
|
||||
|
||||
/**
|
||||
* Feature sub-category explaining why / when a certain feature
|
||||
* needed CPU time to process tasks.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class CpuUsageCategory {
|
||||
|
||||
public static final String PLAYER_JOIN = "Player Join";
|
||||
@@ -54,56 +61,12 @@ public class TabConstants {
|
||||
public static final String SCOREBOARD_PACKET_CHECK = "Checking for other plugins";
|
||||
public static final String PROCESSING_PLAYER_MOVEMENT = "Processing player movement";
|
||||
public static final String TELEPORTING_WITHER = "Teleporting wither";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent the class from being instantiated
|
||||
*/
|
||||
private CpuUsageCategory() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information why a specific feature sent a packet
|
||||
* and what the packet sent is supposed to do.
|
||||
*/
|
||||
public static class PacketCategory {
|
||||
|
||||
public static final String BOSSBAR_COLOR_STYLE = "BossBar (Color and style)";
|
||||
public static final String BOSSBAR_PROGRESS = "BossBar (Progress)";
|
||||
public static final String BOSSBAR_TEXT = "BossBar (Text)";
|
||||
public static final String BOSSBAR_WITHER_TELEPORT = "BossBar (Teleporting Wither)";
|
||||
|
||||
public static final String GLOBAL_PLAYERLIST_LATENCY = "Global PlayerList (Updating latency)";
|
||||
public static final String GLOBAL_PLAYERLIST_VANISH = "Global PlayerList (Vanishing)";
|
||||
|
||||
public static final String LAYOUT_FIXED_SLOTS = "Layout (Fixed slots)";
|
||||
public static final String LAYOUT_PLAYER_SLOTS = "Layout (Player slots)";
|
||||
public static final String LAYOUT_LATENCY = "Layout (Updating latency)";
|
||||
|
||||
public static final String NAMETAGS_TEAM_REGISTER = "NameTags (Team register)";
|
||||
public static final String NAMETAGS_TEAM_UNREGISTER = "NameTags (Team unregister)";
|
||||
public static final String NAMETAGS_TEAM_UPDATE = "NameTags (Team update)";
|
||||
|
||||
public static final String SORTING_CHANGING_TEAM_NAME = "Sorting (Changing team name)";
|
||||
|
||||
public static final String SCOREBOARD_TITLE = "Scoreboard (Title)";
|
||||
public static final String SCOREBOARD_LINES = "Scoreboard (Lines)";
|
||||
|
||||
public static final String UNLIMITED_NAMETAGS_OFFSET_CHANGE = "Unlimited NameTags (Changing offset)";
|
||||
public static final String UNLIMITED_NAMETAGS_SPAWN = "Unlimited NameTags (Spawning)";
|
||||
public static final String UNLIMITED_NAMETAGS_DESPAWN = "Unlimited NameTags (Despawning)";
|
||||
public static final String UNLIMITED_NAMETAGS_TELEPORT = "Unlimited NameTags (Teleporting)";
|
||||
public static final String UNLIMITED_NAMETAGS_SNEAK = "Unlimited NameTags (Sneaking)";
|
||||
public static final String UNLIMITED_NAMETAGS_METADATA = "Unlimited NameTags (Metadata)";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent the class from being instantiated
|
||||
*/
|
||||
private PacketCategory() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission nodes used by the plugin
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class Permission {
|
||||
|
||||
public static final String COMMAND_ALL = "tab.admin";
|
||||
@@ -144,17 +107,13 @@ public class TabConstants {
|
||||
public static final String TEST_PERMISSION = "tab.testpermission";
|
||||
|
||||
public static final String GROUP_PREFIX = "tab.group.";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent the class from being instantiated
|
||||
*/
|
||||
private Permission(){}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal property names used to store text
|
||||
* under a specific key
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class Property {
|
||||
|
||||
public static final String HEADER = "header";
|
||||
@@ -179,11 +138,6 @@ public class TabConstants {
|
||||
|
||||
public static final String YELLOW_NUMBER = "yellow-number";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent the class from being instantiated
|
||||
*/
|
||||
private Property() {}
|
||||
|
||||
public static String bossbarTitle(String name) {
|
||||
return "bossbar-title-" + name;
|
||||
}
|
||||
@@ -217,6 +171,7 @@ public class TabConstants {
|
||||
* Feature names used to register features under in the
|
||||
* feature manager which they can be retrieved by.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class Feature {
|
||||
|
||||
//universal features
|
||||
@@ -244,6 +199,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";
|
||||
@@ -257,11 +213,6 @@ public class TabConstants {
|
||||
public static final String PACKET_SERIALIZING = "Packet serializing";
|
||||
public static final String PACKET_DESERIALIZING = "Packet deserializing";
|
||||
|
||||
/**
|
||||
* Private constructor to prevent the class from being instantiated
|
||||
*/
|
||||
private Feature(){}
|
||||
|
||||
public static String scoreboardLine(String line) {
|
||||
return "scoreboard-" + line;
|
||||
}
|
||||
@@ -286,14 +237,19 @@ public class TabConstants {
|
||||
return "BossBar-progress-" + name;
|
||||
}
|
||||
|
||||
public static String bossBarColorStyle(String name) {
|
||||
return "BossBar-color-style-" + name;
|
||||
public static String bossBarColor(String color) {
|
||||
return "BossBar-color-" + color;
|
||||
}
|
||||
|
||||
public static String bossBarStyle(String style) {
|
||||
return "BossBar-style-" + style;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All internal placeholders offered by the plugin
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class Placeholder {
|
||||
|
||||
// Universal
|
||||
@@ -335,24 +291,8 @@ public class TabConstants {
|
||||
public static final String VAULT_SUFFIX = "%vault-suffix%";
|
||||
public static final String HEALTH = "%health%";
|
||||
|
||||
/**
|
||||
* Minimum refresh interval of a placeholder in milliseconds, used to set refresh interval of the main task
|
||||
*/
|
||||
public static final int MINIMUM_REFRESH_INTERVAL = 50;
|
||||
|
||||
/**
|
||||
* Timeout in milliseconds for a placeholder to return value
|
||||
* to prevent SQL tasks by PlaceholderAPI placeholders from freezing TAB
|
||||
*/
|
||||
public static final int PLACEHOLDER_RETRIEVE_TIMEOUT = 50;
|
||||
|
||||
/**
|
||||
* Internal constant used to detect if placeholder threw an error.
|
||||
* If so, placeholder's last known value is displayed instead.
|
||||
*/
|
||||
public static final String ERROR_VALUE = "ERROR";
|
||||
|
||||
|
||||
public static String condition(String name) {
|
||||
return "%condition:" + name + "%";
|
||||
}
|
||||
@@ -369,12 +309,14 @@ public class TabConstants {
|
||||
/**
|
||||
* All plugins TAB hooks into
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class Plugin {
|
||||
|
||||
public static final String LUCKPERMS = "LuckPerms";
|
||||
public static final String VAULT = "Vault";
|
||||
public static final String PLACEHOLDER_API = "PlaceholderAPI";
|
||||
public static final String LIBS_DISGUISES = "LibsDisguises";
|
||||
public static final String VIAREWIND = "ViaRewind";
|
||||
public static final String VIAVERSION = "ViaVersion";
|
||||
public static final String PROTOCOL_SUPPORT = "ProtocolSupport";
|
||||
public static final String ESSENTIALS = "Essentials";
|
||||
@@ -385,6 +327,7 @@ public class TabConstants {
|
||||
/**
|
||||
* TAB's custom metrics charts
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static class MetricsChart {
|
||||
|
||||
public static final String PERMISSION_SYSTEM = "permission_system";
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
|
||||
|
||||
/**
|
||||
* Abstract class representing a core feature of the plugin.
|
||||
* <p>
|
||||
* It receives all kinds of events and can react to them.
|
||||
*/
|
||||
public abstract class TabFeature {
|
||||
|
||||
/** Feature's name displayed in /tab cpu */
|
||||
private final String featureName;
|
||||
|
||||
/** Feature's function name displayed in place of refreshing in /tab cpu */
|
||||
private final String refreshDisplayName;
|
||||
|
||||
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
|
||||
protected String[] disabledServers = new String[0];
|
||||
|
||||
/** Flag tracking whether disabled server list is a whitelist or blacklist */
|
||||
private boolean serverWhitelistMode = false;
|
||||
|
||||
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
|
||||
protected String[] disabledWorlds = new String[0];
|
||||
|
||||
/** Flag tracking whether disabled world list is a whitelist or blacklist */
|
||||
private boolean worldWhitelistMode = false;
|
||||
|
||||
/** Players located in currently disabled worlds / servers */
|
||||
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
/**
|
||||
* List of methods overridden (implemented) by each feature.
|
||||
* If a method is not overridden, it is not called at all.
|
||||
* This avoids a massive spam for every single method
|
||||
* in every single feature in /tab cpu output
|
||||
*/
|
||||
private final List<String> methodOverrides = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads method overrides
|
||||
*
|
||||
* @param featureName
|
||||
* Feature's name in /tab cpu
|
||||
* @param refreshDisplayName
|
||||
* "refreshing" cpu display type name of the feature
|
||||
*/
|
||||
protected TabFeature(String featureName, String refreshDisplayName) {
|
||||
this.featureName = featureName;
|
||||
this.refreshDisplayName = refreshDisplayName;
|
||||
try {
|
||||
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onCommand");
|
||||
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onJoin");
|
||||
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onQuit");
|
||||
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onWorldChange");
|
||||
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onServerChange");
|
||||
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class) {
|
||||
methodOverrides.add("onDisplayObjective");
|
||||
TabAPI.getInstance().getFeatureManager().markDisplayObjective();
|
||||
}
|
||||
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onLoginPacket");
|
||||
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class) {
|
||||
methodOverrides.add("onObjective");
|
||||
TabAPI.getInstance().getFeatureManager().markObjective();
|
||||
}
|
||||
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPlayerInfo");
|
||||
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPacketReceive");
|
||||
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onPacketSend");
|
||||
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("refresh");
|
||||
if (getClass().getMethod("onVanishStatusChange", TabPlayer.class).getDeclaringClass() != TabFeature.class)
|
||||
methodOverrides.add("onVanishStatusChange");
|
||||
} catch (NoSuchMethodException e) {
|
||||
//this will never happen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads method overrides.
|
||||
* Also loads lists of disabled worlds and servers of this feature with config
|
||||
* section path specified with {@code configSection} parameter.
|
||||
*
|
||||
* @param featureName
|
||||
* Feature's name in /tab cpu
|
||||
* @param refreshDisplayName
|
||||
* "refreshing" cpu display type name of the feature
|
||||
* @param configSection
|
||||
* Configuration section of the feature to load disabled
|
||||
* servers / worlds from
|
||||
*/
|
||||
protected TabFeature(String featureName, String refreshDisplayName, String configSection) {
|
||||
this(featureName, refreshDisplayName);
|
||||
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
|
||||
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
|
||||
if (disabledServers != null) {
|
||||
this.disabledServers = disabledServers.toArray(new String[0]);
|
||||
serverWhitelistMode = disabledServers.contains("WHITELIST");
|
||||
}
|
||||
if (disabledWorlds != null) {
|
||||
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
|
||||
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all online players and sends packets
|
||||
*/
|
||||
public void load() {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads all players and sends clear packets
|
||||
*/
|
||||
public void unload() {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes command from player. This is typically a toggle command.
|
||||
*
|
||||
* @param sender
|
||||
* command sender
|
||||
* @param message
|
||||
* command line
|
||||
* @return {@code true} if event should be cancelled, {@code true} if not
|
||||
*/
|
||||
public boolean onCommand(TabPlayer sender, String message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes join event
|
||||
*
|
||||
* @param connectedPlayer
|
||||
* player who connected
|
||||
*/
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes quit event
|
||||
*
|
||||
* @param disconnectedPlayer
|
||||
* player who disconnected
|
||||
*/
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes world switch
|
||||
*
|
||||
* @param changed
|
||||
* player who switched world
|
||||
* @param from
|
||||
* world player changed from
|
||||
* @param to
|
||||
* world player changed to
|
||||
*/
|
||||
public void onWorldChange(TabPlayer changed, String from, String to) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes server switch
|
||||
*
|
||||
* @param changed
|
||||
* player who switched server
|
||||
* @param from
|
||||
* server player changed from
|
||||
* @param to
|
||||
* server player changed to
|
||||
*/
|
||||
public void onServerChange(TabPlayer changed, String from, String to) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send and returns true if packet should be cancelled
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving packet
|
||||
* @param packet
|
||||
* received packet
|
||||
*/
|
||||
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes login packet, only available on BungeeCord
|
||||
*
|
||||
* @param packetReceiver
|
||||
* player receiving client reset packet
|
||||
*/
|
||||
public void onLoginPacket(TabPlayer packetReceiver) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving packet
|
||||
* @param packet
|
||||
* received packet
|
||||
*/
|
||||
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the packet send and possibly modifies it
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving packet
|
||||
* @param info
|
||||
* received packet
|
||||
*/
|
||||
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes raw packet sent by client
|
||||
*
|
||||
* @param sender
|
||||
* packet sender
|
||||
* @param packet
|
||||
* packet received
|
||||
* @return true if false should be cancelled, false if not
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes raw packet sent to client
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param packet
|
||||
* the packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs refresh of specified player
|
||||
*
|
||||
* @param refreshed
|
||||
* player to refresh
|
||||
* @param force
|
||||
* if refresh should be forced despite refresh seemingly not needed
|
||||
*/
|
||||
public void refresh(TabPlayer refreshed, boolean force) {
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes vanish status change of player
|
||||
*
|
||||
* @param player
|
||||
* Player who changed vanish status
|
||||
*/
|
||||
public void onVanishStatusChange(TabPlayer player){
|
||||
//empty by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this feature as one using specified placeholders
|
||||
*
|
||||
* @param placeholders
|
||||
* placeholders to add as used in this feature
|
||||
*/
|
||||
public void addUsedPlaceholders(Collection<String> placeholders) {
|
||||
if (placeholders.isEmpty()) return;
|
||||
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if world or server is disabled, {@code false} if not
|
||||
*
|
||||
* @param server
|
||||
* server to check
|
||||
* @param world
|
||||
* world to check
|
||||
* @return {@code true} if feature should be disabled, {@code false} if not
|
||||
*/
|
||||
public boolean isDisabled(String server, String world) {
|
||||
boolean contains = contains(disabledWorlds, world);
|
||||
if (worldWhitelistMode) contains = !contains;
|
||||
if (contains) return true;
|
||||
contains = contains(disabledServers, server);
|
||||
if (serverWhitelistMode) contains = !contains;
|
||||
return contains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if list contains the specified element or element
|
||||
* ends with {@code "*"} and element meeting that requirement is present,
|
||||
* {@code false} otherwise.
|
||||
*
|
||||
* @param list
|
||||
* List to check
|
||||
* @param element
|
||||
* Element to find
|
||||
* @return {@code true} if element was found, {@code false} if not
|
||||
*/
|
||||
protected boolean contains(String[] list, String element) {
|
||||
if (element == null) return false;
|
||||
for (String s : list) {
|
||||
if (s.endsWith("*")) {
|
||||
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
|
||||
} else {
|
||||
if (element.equalsIgnoreCase(s)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 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
|
||||
*
|
||||
* @param method
|
||||
* Method to check
|
||||
* @return {@code true} if overridden, {@code false} if not
|
||||
*/
|
||||
public boolean overridesMethod(String method) {
|
||||
return methodOverrides.contains(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player is currently located in a server
|
||||
* or world, which is marked as disabled, {@code false} if not
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
* @return {@code true} if player is in disabled server / world,
|
||||
* {@code false} if not
|
||||
*/
|
||||
public boolean isDisabledPlayer(TabPlayer p) {
|
||||
return disabledPlayers.contains(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds specified player into list of players in disabled
|
||||
* servers / worlds.
|
||||
*
|
||||
* @param p
|
||||
* Player to add
|
||||
*/
|
||||
public void addDisabledPlayer(TabPlayer p) {
|
||||
if (disabledPlayers.contains(p)) return;
|
||||
disabledPlayers.add(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified player from list of players in disabled
|
||||
* servers / worlds
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
*/
|
||||
public void removeDisabledPlayer(TabPlayer p) {
|
||||
disabledPlayers.remove(p);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,11 @@ package me.neznamy.tab.api;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.api.protocol.TabPacket;
|
||||
import me.neznamy.tab.api.feature.Refreshable;
|
||||
import me.neznamy.tab.api.tablist.Skin;
|
||||
import me.neznamy.tab.api.tablist.TabList;
|
||||
|
||||
/**
|
||||
* An interface representing a player
|
||||
@@ -33,11 +34,11 @@ public interface TabPlayer {
|
||||
|
||||
/**
|
||||
* Returns player's uuid used in TabList. This may only be different from real uuid if
|
||||
* TAB is installed on velocity with some specific velocity setup
|
||||
* TAB is installed on a proxy which does not rewrite UUIDs.
|
||||
*
|
||||
* @return player's uuid in TabList
|
||||
*/
|
||||
UUID getTablistUUID();
|
||||
UUID getTablistId();
|
||||
|
||||
/**
|
||||
* Returns player's protocol version
|
||||
@@ -76,36 +77,6 @@ public interface TabPlayer {
|
||||
*/
|
||||
boolean hasPermission(String permission);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet
|
||||
*
|
||||
* @param packet
|
||||
* packet to send
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
*
|
||||
* @param packet
|
||||
* packet to send
|
||||
* @param feature
|
||||
* feature to increment sent packet counter of
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet, TabFeature feature);
|
||||
|
||||
/**
|
||||
* Sends the player a custom universal packet and adds that packet into counter that
|
||||
* is displayed in /tab cpu
|
||||
*
|
||||
* @param packet
|
||||
* packet to send
|
||||
* @param feature
|
||||
* feature to increment sent packet counter of
|
||||
*/
|
||||
void sendCustomPacket(TabPacket packet, String feature);
|
||||
|
||||
/**
|
||||
* Sends the player a platform-specific packet
|
||||
*
|
||||
@@ -141,13 +112,6 @@ public interface TabPlayer {
|
||||
*/
|
||||
void sendMessage(IChatBaseComponent message);
|
||||
|
||||
/**
|
||||
* Returns player's netty channel. On servers running version 1.7 or older returns {@code null}.
|
||||
*
|
||||
* @return player's channel
|
||||
*/
|
||||
Channel getChannel();
|
||||
|
||||
/**
|
||||
* Returns player's ping calculated by server
|
||||
*
|
||||
@@ -182,7 +146,7 @@ public interface TabPlayer {
|
||||
* new raw value
|
||||
* @return {@code true} if value changed / did not exist, {@code false} if value did not change
|
||||
*/
|
||||
boolean setProperty(TabFeature feature, String identifier, String rawValue);
|
||||
boolean setProperty(Refreshable feature, String identifier, String rawValue);
|
||||
|
||||
/**
|
||||
* Loads property from config using standard property loading algorithm
|
||||
@@ -191,7 +155,7 @@ public interface TabPlayer {
|
||||
* property name to load
|
||||
* @return {@code true} if value did not exist or changed, {@code false} otherwise
|
||||
*/
|
||||
boolean loadPropertyFromConfig(TabFeature feature, String property);
|
||||
boolean loadPropertyFromConfig(Refreshable feature, String property);
|
||||
|
||||
/**
|
||||
* Loads property from config using standard property loading algorithm. If the property is
|
||||
@@ -203,7 +167,7 @@ public interface TabPlayer {
|
||||
* value to use if property is not defined in config
|
||||
* @return {@code true} if value did not exist or changed, {@code false} otherwise
|
||||
*/
|
||||
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
|
||||
boolean loadPropertyFromConfig(Refreshable feature, String property, String ifNotSet);
|
||||
|
||||
/**
|
||||
* Returns name of player's scoreboard team or {@code null} if NameTag feature is disabled
|
||||
@@ -305,16 +269,31 @@ public interface TabPlayer {
|
||||
String getNickname();
|
||||
|
||||
/**
|
||||
* Returns player's chat message signing key. Returned object is a direct
|
||||
* object from the server, which is different per platform.
|
||||
* Sets header and footer to specified values
|
||||
*
|
||||
* @return Player's direct chat message signing key
|
||||
* @param header
|
||||
* Header to use
|
||||
* @param footer
|
||||
* Footer to use
|
||||
*/
|
||||
Object getProfilePublicKey();
|
||||
void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer);
|
||||
|
||||
/**
|
||||
* Return player's chat session ID
|
||||
* @return player's chat session ID
|
||||
* Returns scoreboard interface for calling scoreboard-related methods
|
||||
*
|
||||
* @return scoreboard interface for calling scoreboard-related methods
|
||||
*/
|
||||
UUID getChatSessionId();
|
||||
Scoreboard getScoreboard();
|
||||
|
||||
/**
|
||||
* Returns TabList interface for calling tablist-related methods
|
||||
* @return TabList interface for calling tablist-related methods
|
||||
*/
|
||||
TabList getTabList();
|
||||
|
||||
/**
|
||||
* Returns handler for calling bossbar-related methods
|
||||
* @return handler for calling bossbar-related methods
|
||||
*/
|
||||
BossBarHandler getBossBarHandler();
|
||||
}
|
||||
@@ -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,60 +38,13 @@ 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
|
||||
*
|
||||
* @return true if bold is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isBold(){
|
||||
public boolean isBold() {
|
||||
return Boolean.TRUE.equals(bold);
|
||||
}
|
||||
|
||||
@@ -106,7 +53,7 @@ public class ChatModifier {
|
||||
*
|
||||
* @return true if italic is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isItalic(){
|
||||
public boolean isItalic() {
|
||||
return Boolean.TRUE.equals(italic);
|
||||
}
|
||||
|
||||
@@ -115,7 +62,7 @@ public class ChatModifier {
|
||||
*
|
||||
* @return true if underlined is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isUnderlined(){
|
||||
public boolean isUnderlined() {
|
||||
return Boolean.TRUE.equals(underlined);
|
||||
}
|
||||
|
||||
@@ -124,7 +71,7 @@ public class ChatModifier {
|
||||
*
|
||||
* @return true if strikethrough is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isStrikethrough(){
|
||||
public boolean isStrikethrough() {
|
||||
return Boolean.TRUE.equals(strikethrough);
|
||||
}
|
||||
|
||||
@@ -133,34 +80,17 @@ public class ChatModifier {
|
||||
*
|
||||
* @return true if obfuscation is defined and set to true, false otherwise
|
||||
*/
|
||||
public boolean isObfuscated(){
|
||||
public boolean isObfuscated() {
|
||||
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();
|
||||
@@ -65,7 +57,7 @@ public class DeserializedChatComponent extends IChatBaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChatBaseComponent> getExtra(){
|
||||
public List<IChatBaseComponent> getExtra() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getExtra();
|
||||
}
|
||||
@@ -77,27 +69,27 @@ public class DeserializedChatComponent extends IChatBaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModifier getModifier() {
|
||||
public @NotNull ChatModifier getModifier() {
|
||||
if (!deserialized) deserialize();
|
||||
return super.getModifier();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
|
||||
if (!deserialized) deserialize();
|
||||
modified = true;
|
||||
return super.setExtra(components);
|
||||
}
|
||||
|
||||
@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
|
||||
*
|
||||
@@ -190,7 +137,7 @@ public enum EnumChatFormat {
|
||||
* exact blue value
|
||||
* @return enum value or null if no such combination exists
|
||||
*/
|
||||
public static EnumChatFormat fromRGBExact(int red, int green, int blue){
|
||||
public static EnumChatFormat fromRGBExact(int red, int green, int blue) {
|
||||
for (EnumChatFormat format : VALUES) {
|
||||
if (format.red == red && format.green == green && format.blue == blue) return format;
|
||||
}
|
||||
@@ -205,12 +152,12 @@ public enum EnumChatFormat {
|
||||
* text to replace color symbol in
|
||||
* @return colorized string from provided text
|
||||
*/
|
||||
public static String color(String textToTranslate){
|
||||
public static String color(String textToTranslate) {
|
||||
if (textToTranslate == null) return null;
|
||||
if (!textToTranslate.contains("&")) return textToTranslate;
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for (int i = 0; i < b.length - 1; i++) {
|
||||
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx".indexOf(b[(i + 1)]) > -1)){
|
||||
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx".indexOf(b[(i + 1)]) > -1)) {
|
||||
b[i] = COLOR_CHAR;
|
||||
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
|
||||
}
|
||||
@@ -242,9 +189,9 @@ public enum EnumChatFormat {
|
||||
if (input == null) return "";
|
||||
StringBuilder result = new StringBuilder();
|
||||
int length = input.length();
|
||||
for (int index = length - 1; index > -1; index--){
|
||||
for (int index = length - 1; index > -1; index--) {
|
||||
char section = input.charAt(index);
|
||||
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)){
|
||||
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)) {
|
||||
char c = input.charAt(index + 1);
|
||||
if ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".contains(String.valueOf(c))) {
|
||||
result.insert(0, COLOR_CHAR);
|
||||
|
||||
@@ -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()) {
|
||||
@@ -67,7 +69,7 @@ public class IChatBaseComponent {
|
||||
*
|
||||
* @return list of extra components
|
||||
*/
|
||||
public List<IChatBaseComponent> getExtra(){
|
||||
public List<IChatBaseComponent> getExtra() {
|
||||
if (extra == null) return Collections.emptyList();
|
||||
return extra;
|
||||
}
|
||||
@@ -81,7 +83,7 @@ public class IChatBaseComponent {
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code components} is an empty list
|
||||
*/
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
|
||||
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
|
||||
this.extra = components;
|
||||
return this;
|
||||
@@ -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,15 +177,14 @@ 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();
|
||||
IChatBaseComponent component = new IChatBaseComponent();
|
||||
for (int i = 0; i < text.length(); i++){
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == EnumChatFormat.COLOR_CHAR){
|
||||
if (c == EnumChatFormat.COLOR_CHAR) {
|
||||
i++;
|
||||
if (i >= text.length()) {
|
||||
break;
|
||||
@@ -247,7 +194,7 @@ public class IChatBaseComponent {
|
||||
c = (char)(c + ' ');
|
||||
}
|
||||
EnumChatFormat format = EnumChatFormat.getByChar(c);
|
||||
if (format != null){
|
||||
if (format != null) {
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
@@ -255,7 +202,7 @@ public class IChatBaseComponent {
|
||||
component.text = null;
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
switch (format){
|
||||
switch (format) {
|
||||
case BOLD:
|
||||
component.modifier.setBold(true);
|
||||
break;
|
||||
@@ -281,7 +228,7 @@ public class IChatBaseComponent {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (c == '#' && text.length() > i+6){
|
||||
} else if (c == '#' && text.length() > i+6) {
|
||||
String hex = text.substring(i+1, i+7);
|
||||
if (RGBUtils.getInstance().isHexCode(hex)) {
|
||||
TextColor color;
|
||||
@@ -292,7 +239,7 @@ public class IChatBaseComponent {
|
||||
color = new TextColor(hex);
|
||||
i += 6;
|
||||
}
|
||||
if (builder.length() > 0){
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
builder = new StringBuilder();
|
||||
@@ -420,19 +367,7 @@ public class IChatBaseComponent {
|
||||
* text to create component from
|
||||
* @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;
|
||||
public static IChatBaseComponent optimizedComponent(String text) {
|
||||
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,10 @@
|
||||
package me.neznamy.tab.api.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -10,28 +14,11 @@ 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
|
||||
*/
|
||||
public Object get() {
|
||||
return originalComponent;
|
||||
}
|
||||
@Getter private final Object originalComponent;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -44,7 +31,7 @@ public class WrappedChatComponent extends IChatBaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IChatBaseComponent> getExtra(){
|
||||
public List<IChatBaseComponent> getExtra() {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@@ -54,22 +41,22 @@ public class WrappedChatComponent extends IChatBaseComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModifier getModifier() {
|
||||
public @NotNull ChatModifier getModifier() {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
|
||||
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
|
||||
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@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,18 +1,20 @@
|
||||
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.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.chat.rgb.format.*;
|
||||
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);
|
||||
@@ -142,7 +132,9 @@ public class RGBUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all hex codes in given string to legacy codes
|
||||
* Converts all hex codes in given string to legacy codes.
|
||||
* Also removes redundant color codes caused by this operation
|
||||
* to properly fit in limits.
|
||||
*
|
||||
* @param text
|
||||
* text to convert
|
||||
@@ -150,29 +142,7 @@ public class RGBUtils {
|
||||
*/
|
||||
public String convertRGBtoLegacy(String text) {
|
||||
if (text == null) return null;
|
||||
if (!text.contains("#")) return EnumChatFormat.color(text);
|
||||
String applied = applyFormats(text);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < applied.length(); i++){
|
||||
char c = applied.charAt(i);
|
||||
if (c == '#' && applied.length() > i+6) {
|
||||
String hexCode = applied.substring(i+1, i+7);
|
||||
if (isHexCode(hexCode)) {
|
||||
if (containsLegacyCode(applied, i)) {
|
||||
sb.append(new TextColor(hexCode, EnumChatFormat.getByChar(applied.charAt(i+8))).getLegacyColor().getFormat());
|
||||
i += 8;
|
||||
} else {
|
||||
sb.append(new TextColor(hexCode).getLegacyColor().getFormat());
|
||||
i += 6;
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
return IChatBaseComponent.fromColoredText(text).toLegacyText();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,8 +153,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);
|
||||
@@ -192,18 +161,4 @@ public class RGBUtils {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if text contains legacy color request at defined RGB index start
|
||||
*
|
||||
* @param text
|
||||
* text to check
|
||||
* @param i
|
||||
* current index start
|
||||
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
|
||||
*/
|
||||
private static boolean containsLegacyCode(String text, int i) {
|
||||
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
|
||||
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to command preprocess event
|
||||
* possibly cancelling it.
|
||||
*/
|
||||
public interface CommandListener {
|
||||
|
||||
/**
|
||||
* Called when player is about to run a command
|
||||
*
|
||||
* @param sender
|
||||
* Command sender
|
||||
* @param message
|
||||
* Command line including /
|
||||
* @return Whether the command should be cancelled or not
|
||||
*/
|
||||
boolean onCommand(TabPlayer sender, String message);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Interface for features listening to display name change
|
||||
*/
|
||||
public interface DisplayNameListener {
|
||||
|
||||
/**
|
||||
* Called when player is about to receive a packet for changing display name
|
||||
*
|
||||
* @param packetReceiver
|
||||
* Player who is about to receive the packet
|
||||
* @param id
|
||||
* Affected entry
|
||||
* @param displayName
|
||||
* Display name in the packet
|
||||
* @return New display name to write to the packet
|
||||
*/
|
||||
IChatBaseComponent onDisplayNameChange(TabPlayer packetReceiver, UUID id, IChatBaseComponent displayName);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Listener for features listening to display objective packet.
|
||||
*/
|
||||
public interface DisplayObjectiveListener {
|
||||
|
||||
/**
|
||||
* Called when player is about to receive display objective packet
|
||||
*
|
||||
* @param receiver
|
||||
* Packet receiver
|
||||
* @param slot
|
||||
* Objective slot
|
||||
* @param objective
|
||||
* Objective name
|
||||
*/
|
||||
void onDisplayObjective(TabPlayer receiver, int slot, String objective);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Interface for features listening to tablist entry add
|
||||
*/
|
||||
public interface EntryAddListener {
|
||||
|
||||
/**
|
||||
* Called when player is about to receive packet for adding a tablist entry
|
||||
*
|
||||
* @param packetReceiver
|
||||
* Player who received the packet
|
||||
* @param id
|
||||
* UUID of added player
|
||||
* @param name
|
||||
* Username of added player
|
||||
*/
|
||||
void onEntryAdd(TabPlayer packetReceiver, UUID id, String name);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Interface for features listening to gamemode change packet
|
||||
*/
|
||||
public interface GameModeListener {
|
||||
|
||||
/**
|
||||
* Called when player receives packet to change gamemode
|
||||
*
|
||||
* @param packetReceiver
|
||||
* Player who received the packet
|
||||
* @param id
|
||||
* UUID of affected player
|
||||
* @param gameMode
|
||||
* gamemode in the packet
|
||||
* @return New gamemode to write to the packet
|
||||
*/
|
||||
int onGameModeChange(TabPlayer packetReceiver, UUID id, int gameMode);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to player join event
|
||||
*/
|
||||
public interface JoinListener {
|
||||
|
||||
/**
|
||||
* Called when player connects to the server. The player is
|
||||
* already added to online player list.
|
||||
*
|
||||
* @param connectedPlayer
|
||||
* Player who connected
|
||||
*/
|
||||
void onJoin(TabPlayer connectedPlayer);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Called when a packet to change player's latency is sent.
|
||||
*/
|
||||
public interface LatencyListener {
|
||||
|
||||
/**
|
||||
* Called when a player receives a packet for latency change.
|
||||
*
|
||||
* @param packetReceiver
|
||||
* Player who received the packet
|
||||
* @param id
|
||||
* UUID of affected player
|
||||
* @param latency
|
||||
* Latency in the packet
|
||||
* @return New latency
|
||||
*/
|
||||
int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
/**
|
||||
* Interface for features looking to load players on plugin enable
|
||||
*/
|
||||
public interface Loadable {
|
||||
|
||||
/**
|
||||
* Called on plugin (re)load
|
||||
*/
|
||||
void load();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to Login packet
|
||||
* usually sent by proxy on server switch
|
||||
*/
|
||||
public interface LoginPacketListener {
|
||||
|
||||
/**
|
||||
* Called when player receives Login packet
|
||||
*
|
||||
* @param packetReceiver
|
||||
* Player who received the packet
|
||||
*/
|
||||
void onLoginPacket(TabPlayer packetReceiver);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Listener for features listening to objective packet.
|
||||
*/
|
||||
public interface ObjectiveListener {
|
||||
|
||||
/**
|
||||
* Called when player is about to receive display objective packet
|
||||
*
|
||||
* @param receiver
|
||||
* Packet receiver
|
||||
* @param action
|
||||
* Packet action (0 = register, 1 = unregister, 2 = update)
|
||||
* @param objective
|
||||
* Objective name
|
||||
*/
|
||||
void onObjective(TabPlayer receiver, int action, String objective);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to packets sent by players
|
||||
*/
|
||||
public interface PacketReceiveListener {
|
||||
|
||||
/**
|
||||
* Called when a player is about to send a packet to the server
|
||||
*
|
||||
* @param sender
|
||||
* Packet sender
|
||||
* @param packet
|
||||
* The packet
|
||||
* @return Whether the packet should be cancelled or not
|
||||
* @throws ReflectiveOperationException
|
||||
* If thrown by reflective operation
|
||||
*/
|
||||
boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to packets sent to players
|
||||
*/
|
||||
public interface PacketSendListener {
|
||||
|
||||
/**
|
||||
* Called when a player is about to receive a packet from the server
|
||||
*
|
||||
* @param receiver
|
||||
* Player who is about to receive a packet
|
||||
* @param packet
|
||||
* The packet
|
||||
* @throws ReflectiveOperationException
|
||||
* If thrown by reflective operation
|
||||
*/
|
||||
void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to player quit event
|
||||
*/
|
||||
public interface QuitListener {
|
||||
|
||||
/**
|
||||
* Called when player disconnected from the server. The player is
|
||||
* still present in online player list.
|
||||
*
|
||||
* @param disconnectedPlayer
|
||||
* Player who disconnected
|
||||
*/
|
||||
void onQuit(TabPlayer disconnectedPlayer);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Interface for features periodically refreshing visuals
|
||||
*/
|
||||
public interface Refreshable {
|
||||
|
||||
/**
|
||||
* Called when a placeholder used by this feature changes value
|
||||
*
|
||||
* @param refreshed
|
||||
* Player which a placeholder changed value for
|
||||
* @param force
|
||||
* Whether refresh should be forced
|
||||
*/
|
||||
void refresh(TabPlayer refreshed, boolean force);
|
||||
|
||||
/**
|
||||
* Returns display name of {@link #refresh(TabPlayer, boolean)}
|
||||
* called for this feature in /tab cpu
|
||||
*
|
||||
* @return Display name of refresh function in this feature
|
||||
*/
|
||||
String getRefreshDisplayName();
|
||||
|
||||
/**
|
||||
* Registers this feature as one using specified placeholders
|
||||
*
|
||||
* @param placeholders
|
||||
* placeholders to add as used in this feature
|
||||
*/
|
||||
default void addUsedPlaceholders(Collection<String> placeholders) {
|
||||
if (placeholders.isEmpty()) return;
|
||||
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of this feature displayed in /tab cpu
|
||||
*
|
||||
* @return name of this feature display in /tab cpu
|
||||
*/
|
||||
String getFeatureName();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to players switching servers
|
||||
*/
|
||||
public interface ServerSwitchListener {
|
||||
|
||||
/**
|
||||
* Called when player switched server
|
||||
*
|
||||
* @param changed
|
||||
* Player who changed server
|
||||
* @param from
|
||||
* Name of previous server
|
||||
* @param to
|
||||
* Name of new server
|
||||
*/
|
||||
void onServerChange(TabPlayer changed, String from, String to);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Abstract class representing a core feature of the plugin.
|
||||
*/
|
||||
public abstract class TabFeature {
|
||||
|
||||
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
|
||||
protected String[] disabledServers = new String[0];
|
||||
|
||||
/** Flag tracking whether disabled server list is a whitelist or blacklist */
|
||||
private boolean serverWhitelistMode = false;
|
||||
|
||||
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
|
||||
protected String[] disabledWorlds = new String[0];
|
||||
|
||||
/** Flag tracking whether disabled world list is a whitelist or blacklist */
|
||||
private boolean worldWhitelistMode = false;
|
||||
|
||||
/** Players located in currently disabled worlds / servers */
|
||||
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
protected TabFeature() {}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads method overrides.
|
||||
* Also loads lists of disabled worlds and servers of this feature with config
|
||||
* section path specified with {@code configSection} parameter.
|
||||
*
|
||||
* @param configSection
|
||||
* Configuration section of the feature to load disabled
|
||||
* servers / worlds from
|
||||
*/
|
||||
protected TabFeature(@NonNull String configSection) {
|
||||
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
|
||||
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
|
||||
if (disabledServers != null) {
|
||||
this.disabledServers = disabledServers.toArray(new String[0]);
|
||||
serverWhitelistMode = disabledServers.contains("WHITELIST");
|
||||
}
|
||||
if (disabledWorlds != null) {
|
||||
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
|
||||
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if world or server is disabled, {@code false} if not
|
||||
*
|
||||
* @param server
|
||||
* server to check
|
||||
* @param world
|
||||
* world to check
|
||||
* @return {@code true} if feature should be disabled, {@code false} if not
|
||||
*/
|
||||
public boolean isDisabled(String server, String world) {
|
||||
boolean contains = contains(disabledWorlds, world);
|
||||
if (worldWhitelistMode) contains = !contains;
|
||||
if (contains) return true;
|
||||
contains = contains(disabledServers, server);
|
||||
if (serverWhitelistMode) contains = !contains;
|
||||
return contains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if list contains the specified element or element
|
||||
* ends with {@code "*"} and element meeting that requirement is present,
|
||||
* {@code false} otherwise.
|
||||
*
|
||||
* @param list
|
||||
* List to check
|
||||
* @param element
|
||||
* Element to find
|
||||
* @return {@code true} if element was found, {@code false} if not
|
||||
*/
|
||||
protected boolean contains(String[] list, String element) {
|
||||
if (element == null) return false;
|
||||
for (String s : list) {
|
||||
if (s.endsWith("*")) {
|
||||
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
|
||||
} else {
|
||||
if (element.equalsIgnoreCase(s)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player is currently located in a server
|
||||
* or world, which is marked as disabled, {@code false} if not
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
* @return {@code true} if player is in disabled server / world,
|
||||
* {@code false} if not
|
||||
*/
|
||||
public boolean isDisabledPlayer(TabPlayer p) {
|
||||
return disabledPlayers.contains(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds specified player into list of players in disabled
|
||||
* servers / worlds.
|
||||
*
|
||||
* @param p
|
||||
* Player to add
|
||||
*/
|
||||
public void addDisabledPlayer(TabPlayer p) {
|
||||
if (disabledPlayers.contains(p)) return;
|
||||
disabledPlayers.add(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes specified player from list of players in disabled
|
||||
* servers / worlds
|
||||
*
|
||||
* @param p
|
||||
* Player to check
|
||||
*/
|
||||
public void removeDisabledPlayer(TabPlayer p) {
|
||||
disabledPlayers.remove(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of this feature displayed in /tab cpu
|
||||
*
|
||||
* @return name of this feature display in /tab cpu
|
||||
*/
|
||||
public abstract String getFeatureName();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
/**
|
||||
* Interface for features looking to unload players on plugin disable
|
||||
*/
|
||||
public interface UnLoadable {
|
||||
|
||||
/**
|
||||
* Called on plugin unload
|
||||
*/
|
||||
void unload();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to vanish status change
|
||||
*/
|
||||
public interface VanishListener {
|
||||
|
||||
/**
|
||||
* Called when player's vanish status changes. New status
|
||||
* is available in {@link TabPlayer#isVanished()}.
|
||||
*
|
||||
* @param player
|
||||
* Player who changed vanish status
|
||||
*/
|
||||
void onVanishStatusChange(TabPlayer player);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neznamy.tab.api.feature;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Interface for features listening to players switching worlds
|
||||
*/
|
||||
public interface WorldSwitchListener {
|
||||
|
||||
/**
|
||||
* Called when player switched world
|
||||
*
|
||||
* @param changed
|
||||
* Player who changed world
|
||||
* @param from
|
||||
* Name of previous world
|
||||
* @param to
|
||||
* Name of new world
|
||||
*/
|
||||
void onWorldChange(TabPlayer changed, String from, String to);
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.feature.Refreshable;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
public interface PlaceholderManager {
|
||||
public interface PlaceholderManager extends Refreshable {
|
||||
|
||||
/**
|
||||
* Registers a server placeholder (placeholder with same output for all players)
|
||||
@@ -43,7 +43,7 @@ public interface PlaceholderManager {
|
||||
* @param feature
|
||||
* feature using the placeholder
|
||||
*/
|
||||
void addUsedPlaceholder(String identifier, TabFeature feature);
|
||||
void addUsedPlaceholder(String identifier, Refreshable feature);
|
||||
|
||||
String findReplacement(String placeholder, String output);
|
||||
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* A class for packet building, methods are overridden in
|
||||
* platform-specific builders which extend this class to build actual packets.
|
||||
* Packets which don't have a method override are forwarded into
|
||||
* sendPacket method, where they are handled using platform's API instead.
|
||||
*/
|
||||
public class PacketBuilder {
|
||||
|
||||
/** Function map turning custom packet class into platform-specific packets */
|
||||
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance and fills {@link #buildMap} with abstract build methods
|
||||
* of packets present on all platforms.
|
||||
*/
|
||||
public PacketBuilder() {
|
||||
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
|
||||
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
|
||||
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
|
||||
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
|
||||
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts custom packet into platform-specific packet by calling a function from
|
||||
* {@link #buildMap}.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to build
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if reflection fails
|
||||
*/
|
||||
public Object build(TabPacket packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
Preconditions.checkNotNull(packet, "packet");
|
||||
Preconditions.checkNotNull(clientVersion, "clientVersion");
|
||||
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Custom packet to be built
|
||||
* @param clientVersion
|
||||
* Protocol version of player to build the packet for
|
||||
* @return Platform-specific packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts given string to specified character length (or length-1 if last character is a color character)
|
||||
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
|
||||
* RGB codes are converted into legacy, since cutting is only needed for <1.13.
|
||||
* If {@code string} is {@code null}, empty string is returned.
|
||||
*
|
||||
* @param string
|
||||
* String to cut
|
||||
* @param length
|
||||
* Length to cut to
|
||||
* @return string cut to {@code length} characters
|
||||
*/
|
||||
public String cutTo(String string, int length) {
|
||||
if (string == null) return "";
|
||||
String legacyText = string;
|
||||
if (string.contains("#")) {
|
||||
//converting RGB to legacy colors
|
||||
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
|
||||
}
|
||||
if (legacyText.length() <= length) return legacyText;
|
||||
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
|
||||
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
|
||||
} else {
|
||||
return legacyText.substring(0, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
|
||||
* it as a serialized component, which BungeeCord uses.
|
||||
* <p>
|
||||
* If {@code clientVersion} is < 1.12, the text is cut to {@code length} characters if
|
||||
* needed and returned.
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @param clientVersion
|
||||
* Version of player to convert text for
|
||||
* @return serialized component for 1.13+ clients, cut string for 1.12-
|
||||
*/
|
||||
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
|
||||
if (text == null) return null;
|
||||
if (clientVersion.getMinorVersion() >= 13) {
|
||||
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
|
||||
} else {
|
||||
return cutTo(text, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of player info packet into
|
||||
* {@link PacketPlayOutPlayerInfo} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific info packet
|
||||
* @param clientVersion
|
||||
* Version of client receiving the packet
|
||||
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of objective packet into
|
||||
* {@link PacketPlayOutScoreboardObjective} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific objective packet
|
||||
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts platform-specific instance of display objective packet into
|
||||
* {@link PacketPlayOutScoreboardDisplayObjective} object.
|
||||
*
|
||||
* @param packet
|
||||
* platform-specific display objective packet
|
||||
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutBoss implements TabPacket {
|
||||
|
||||
/** UUID of the BossBar */
|
||||
private final UUID id;
|
||||
|
||||
/** Action of this packet */
|
||||
private final Action action;
|
||||
|
||||
/** BossBar title */
|
||||
private String name;
|
||||
|
||||
/** BossBar progress (0-1)*/
|
||||
private float pct;
|
||||
|
||||
/** BossBar color */
|
||||
private BarColor color;
|
||||
|
||||
/** BossBar style */
|
||||
private BarStyle overlay;
|
||||
|
||||
/** Darken screen flag */
|
||||
private boolean darkenScreen;
|
||||
|
||||
/** Play music flag */
|
||||
private boolean playMusic;
|
||||
|
||||
/** Create fog flag */
|
||||
private boolean createWorldFog;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param name
|
||||
* BossBar title
|
||||
* @param pct
|
||||
* BossBar progress
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param overlay
|
||||
* BossBar style
|
||||
*/
|
||||
public PacketPlayOutBoss(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");
|
||||
this.action = Action.ADD;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.pct = pct;
|
||||
this.color = color;
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.REMOVE action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
this.action = Action.REMOVE;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PCT action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param pct
|
||||
* BossBar progress
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, float pct) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
this.action = Action.UPDATE_PCT;
|
||||
this.id = id;
|
||||
this.pct = pct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_NAME action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param name
|
||||
* BossBar title
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, String name) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
Preconditions.checkNotNull(name, "title");
|
||||
this.action = Action.UPDATE_NAME;
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_STYLE action
|
||||
*
|
||||
* @param id
|
||||
* BossBar uuid
|
||||
* @param color
|
||||
* BossBar color
|
||||
* @param overlay
|
||||
* BossBar style
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, BarColor color, BarStyle overlay) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
Preconditions.checkNotNull(color, "color");
|
||||
Preconditions.checkNotNull(overlay, "style");
|
||||
this.action = Action.UPDATE_STYLE;
|
||||
this.id = id;
|
||||
this.color = color;
|
||||
this.overlay = overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and
|
||||
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PROPERTIES action
|
||||
*
|
||||
* @param darkenScreen
|
||||
* Darken screen flag
|
||||
* @param playMusic
|
||||
* Play music flag
|
||||
* @param createWorldFog
|
||||
* Create fog flag
|
||||
*/
|
||||
public PacketPlayOutBoss(UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
|
||||
Preconditions.checkNotNull(id, "uuid");
|
||||
this.action = Action.UPDATE_PROPERTIES;
|
||||
this.id = id;
|
||||
this.darkenScreen = darkenScreen;
|
||||
this.playMusic = playMusic;
|
||||
this.createWorldFog = createWorldFog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
|
||||
* <p>
|
||||
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
|
||||
* to the final value.
|
||||
*
|
||||
* @return the bitmask
|
||||
*/
|
||||
public byte getFlags(){
|
||||
byte value = 0;
|
||||
if (darkenScreen) value += 1;
|
||||
if (playMusic) value += 2;
|
||||
if (createWorldFog) value += 4;
|
||||
return value;
|
||||
}
|
||||
|
||||
@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.
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
ADD,
|
||||
REMOVE,
|
||||
UPDATE_PCT,
|
||||
UPDATE_NAME,
|
||||
UPDATE_STYLE,
|
||||
UPDATE_PROPERTIES
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutChat implements TabPacket {
|
||||
|
||||
/** Message to be sent */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing positions of a chat message
|
||||
* Calling ordinal() will return type's network ID.
|
||||
*/
|
||||
public enum ChatMessageType {
|
||||
|
||||
CHAT,
|
||||
SYSTEM,
|
||||
GAME_INFO
|
||||
}
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
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
|
||||
*/
|
||||
public class PacketPlayOutPlayerInfo implements TabPacket {
|
||||
|
||||
/** Packet actions */
|
||||
private final EnumSet<EnumPlayerInfoAction> actions;
|
||||
|
||||
/** List of affected entries */
|
||||
private final List<PlayerInfoData> entries;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
|
||||
this(action, Arrays.asList(entries));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
|
||||
Preconditions.checkNotNull(action, "action");
|
||||
Preconditions.checkNotNull(entries, "entries");
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
actions = EnumSet.of(EnumPlayerInfoAction.ADD_PLAYER,
|
||||
EnumPlayerInfoAction.INITIALIZE_CHAT,
|
||||
EnumPlayerInfoAction.UPDATE_GAME_MODE,
|
||||
EnumPlayerInfoAction.UPDATE_LISTED,
|
||||
EnumPlayerInfoAction.UPDATE_LATENCY,
|
||||
EnumPlayerInfoAction.UPDATE_DISPLAY_NAME);
|
||||
} else {
|
||||
this.actions = EnumSet.of(action);
|
||||
}
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param actions
|
||||
* Packet actions
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumSet<EnumPlayerInfoAction> actions, PlayerInfoData... entries) {
|
||||
Preconditions.checkNotNull(actions, "action");
|
||||
Preconditions.checkNotNull(entries, "entries");
|
||||
this.actions = actions;
|
||||
this.entries = Arrays.asList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param actions
|
||||
* Packet actions
|
||||
* @param entries
|
||||
* Affected entries
|
||||
*/
|
||||
public PacketPlayOutPlayerInfo(EnumSet<EnumPlayerInfoAction> actions, List<PlayerInfoData> entries) {
|
||||
Preconditions.checkNotNull(actions, "action");
|
||||
Preconditions.checkNotNull(entries, "entries");
|
||||
this.actions = actions;
|
||||
this.entries = entries;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("PacketPlayOutPlayerInfo{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
|
||||
*/
|
||||
public static class PlayerInfoData {
|
||||
|
||||
/** Latency */
|
||||
private int latency;
|
||||
|
||||
/** I have no idea what this is */
|
||||
private boolean listed;
|
||||
|
||||
/** GameMode */
|
||||
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
|
||||
|
||||
/**
|
||||
* Display name displayed in TabList. Using {@code null} results in no display name
|
||||
* and scoreboard team prefix/suffix being visible in TabList instead.
|
||||
*/
|
||||
private IChatBaseComponent displayName;
|
||||
|
||||
/** 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
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param gameMode
|
||||
* Player's GameMode
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId, EnumGamemode gameMode) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.uniqueId = uniqueId;
|
||||
this.gameMode = gameMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param latency
|
||||
* Player's ping
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId, int latency) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.uniqueId = uniqueId;
|
||||
this.latency = latency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Suitable for
|
||||
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
* @param displayName
|
||||
* Player's display name
|
||||
*/
|
||||
public PlayerInfoData(UUID uniqueId, IChatBaseComponent displayName) {
|
||||
Preconditions.checkNotNull(uniqueId, "uuid");
|
||||
this.uniqueId = uniqueId;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter. Suitable for
|
||||
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
|
||||
*
|
||||
* @param uniqueId
|
||||
* Player's uuid
|
||||
*/
|
||||
public PlayerInfoData(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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* En enum representing packet action
|
||||
*/
|
||||
public enum EnumPlayerInfoAction {
|
||||
|
||||
ADD_PLAYER,
|
||||
INITIALIZE_CHAT,
|
||||
UPDATE_GAME_MODE,
|
||||
UPDATE_LISTED,
|
||||
UPDATE_LATENCY,
|
||||
UPDATE_DISPLAY_NAME,
|
||||
REMOVE_PLAYER
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum representing GameMode
|
||||
*/
|
||||
public enum EnumGamemode {
|
||||
|
||||
NOT_SET,
|
||||
SURVIVAL,
|
||||
CREATIVE,
|
||||
ADVENTURE,
|
||||
SPECTATOR;
|
||||
|
||||
/** Value array to iterate over to avoid array creations on each call */
|
||||
public static final EnumGamemode[] VALUES = values();
|
||||
}
|
||||
}
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
|
||||
|
||||
/** TabList header */
|
||||
private final IChatBaseComponent header;
|
||||
|
||||
/** TabList footer */
|
||||
private final IChatBaseComponent footer;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
|
||||
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
|
||||
*
|
||||
* @param header
|
||||
* TabList header
|
||||
* @param footer
|
||||
* TabList footer
|
||||
*/
|
||||
public PacketPlayOutPlayerListHeaderFooter(String header, String footer) {
|
||||
Preconditions.checkNotNull(header, "header");
|
||||
Preconditions.checkNotNull(footer, "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;
|
||||
}
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
|
||||
|
||||
/**
|
||||
* Display slot.
|
||||
* 0 = PlayerList,
|
||||
* 1 = SideBar,
|
||||
* 2 = BelowName.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
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,
|
||||
* 1 = unregister,
|
||||
* 2 = update title
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new packet with given objective name and 1 (unregister) action.
|
||||
*
|
||||
* @param objectiveName
|
||||
* objective name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code objectiveName} is null or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardObjective(String objectiveName) {
|
||||
Preconditions.checkNotNull(objectiveName, "objective name");
|
||||
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
|
||||
this.objectiveName = objectiveName;
|
||||
this.displayName = ""; //avoiding NPE on <1.7
|
||||
this.action = 1;
|
||||
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.
|
||||
*/
|
||||
public enum EnumScoreboardHealthDisplay {
|
||||
|
||||
INTEGER, HEARTS
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* A class representing platform specific packet class
|
||||
*/
|
||||
public class PacketPlayOutScoreboardScore implements TabPacket {
|
||||
|
||||
/** Packet action */
|
||||
private final Action action;
|
||||
|
||||
/** Objective name */
|
||||
private final String objectiveName;
|
||||
|
||||
/** Affected player */
|
||||
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
|
||||
*/
|
||||
public enum Action {
|
||||
|
||||
CHANGE,
|
||||
REMOVE
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
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
|
||||
*/
|
||||
public class PacketPlayOutScoreboardTeam implements TabPacket {
|
||||
|
||||
/**
|
||||
* Up to 16 characters long team name.
|
||||
* It is used to sort players alphabetically in TabList by team names.
|
||||
* Team-less players appear on top, sorted by usernames.
|
||||
* Players with teams are below them, players of the same team are
|
||||
* sorted alphabetically by usernames.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/** Team prefix, limited to 16 characters, on <1.13 */
|
||||
private String playerPrefix;
|
||||
|
||||
/** Team suffix, limited to 16 characters, on <1.13 */
|
||||
private String playerSuffix;
|
||||
|
||||
/**
|
||||
* NameTag visibility rule since 1.8. Possible options are:
|
||||
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
|
||||
*/
|
||||
private String nameTagVisibility;
|
||||
|
||||
/**
|
||||
* Collision rule added in 1.9. Possible options are:
|
||||
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
|
||||
*/
|
||||
private String collisionRule;
|
||||
|
||||
/**
|
||||
* Team color field added in 1.13. It determines name color,
|
||||
* prefix and suffix color start.
|
||||
*/
|
||||
private EnumChatFormat color;
|
||||
|
||||
/**
|
||||
* Affected entities. For players, it is their name, for other entities their UUID
|
||||
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
|
||||
* Entity UUID option was added in 1.8.
|
||||
*/
|
||||
private Collection<String> players = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Packet action.
|
||||
* 0 = create team,
|
||||
* 1 = remove team,
|
||||
* 2 = update team info,
|
||||
* 3 = add entries,
|
||||
* 4 = remove entries
|
||||
*/
|
||||
private final int action;
|
||||
|
||||
/**
|
||||
* Bit mask.
|
||||
* 0x01: Allow friendly fire,
|
||||
* 0x02: Can see invisible players on the same team.
|
||||
*/
|
||||
private int options;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters. Private constructor used
|
||||
* internally to validate team name and set action.
|
||||
*
|
||||
* @param action
|
||||
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
|
||||
* 3 = add entries, 4 = remove entries)
|
||||
* @param name
|
||||
* Team name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
private PacketPlayOutScoreboardTeam(int action, String name) {
|
||||
Preconditions.checkNotNull(name, "team name");
|
||||
Preconditions.checkMaxLength(name, 16, "team name");
|
||||
this.action = action;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 0 (CREATE) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param prefix
|
||||
* Prefix of players in team
|
||||
* @param suffix
|
||||
* Suffix of players in team
|
||||
* @param visibility
|
||||
* NameTag visibility rule
|
||||
* @param collision
|
||||
* Collision rule
|
||||
* @param players
|
||||
* Affected entities
|
||||
* @param options
|
||||
* bitmask of team options
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, 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;
|
||||
this.collisionRule = collision;
|
||||
this.players = players;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 1 (REMOVE) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team) {
|
||||
this(1, team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param prefix
|
||||
* Prefix of players in team
|
||||
* @param suffix
|
||||
* Suffix of players in team
|
||||
* @param visibility
|
||||
* NameTag visibility rule
|
||||
* @param collision
|
||||
* Collision rule
|
||||
* @param options
|
||||
* bitmask of team options
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, String prefix, 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;
|
||||
this.collisionRule = collision;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
|
||||
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
|
||||
*
|
||||
* @param team
|
||||
* Team name, up to 16 characters long
|
||||
* @param players
|
||||
* Affected entities
|
||||
* @param add
|
||||
* {@code true} if players should be added, {@code false} if removed from team
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code name} is null, empty or longer than 16 characters
|
||||
*/
|
||||
public PacketPlayOutScoreboardTeam(String team, 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 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
|
||||
/**
|
||||
* Class representing a minecraft skin as a value - signature pair.
|
||||
*/
|
||||
public class Skin {
|
||||
|
||||
/** Skin value */
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.neznamy.tab.api.protocol;
|
||||
|
||||
/**
|
||||
* Interface representing a packet that can be sent to client independently of platform.
|
||||
* It is technically empty, existence is only present to restrict type access
|
||||
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
|
||||
* be called with an actual packet.
|
||||
*/
|
||||
public interface TabPacket {
|
||||
|
||||
/**
|
||||
* Returns user-friendly string representation of the packet
|
||||
*
|
||||
* @return user-friendly string representation of the packet
|
||||
*/
|
||||
String toString();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Class representing a minecraft skin as a value - signature pair.
|
||||
*/
|
||||
@Data @AllArgsConstructor
|
||||
public class Skin {
|
||||
|
||||
/** Skin value */
|
||||
@NonNull private final String value;
|
||||
|
||||
/** Skin signature */
|
||||
@Nullable private final String signature;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TabList {
|
||||
|
||||
String TEXTURES_PROPERTY = "textures";
|
||||
|
||||
void removeEntry(@NonNull UUID entry);
|
||||
|
||||
void removeEntries(@NonNull Collection<UUID> entries);
|
||||
|
||||
void updateDisplayName(@NonNull UUID entry, @Nullable IChatBaseComponent displayName);
|
||||
|
||||
void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries);
|
||||
|
||||
void updateLatency(@NonNull UUID entry, int latency);
|
||||
|
||||
void updateLatencies(@NonNull Map<UUID, Integer> entries);
|
||||
|
||||
void updateGameMode(@NonNull UUID entry, int gameMode);
|
||||
|
||||
void updateGameModes(@NonNull Map<UUID, Integer> entries);
|
||||
|
||||
void addEntry(@NonNull TabListEntry entry);
|
||||
|
||||
void addEntries(@NonNull Collection<TabListEntry> entries);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A subclass representing player list entry
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class TabListEntry {
|
||||
|
||||
/** Player UUID */
|
||||
@NonNull private UUID uniqueId;
|
||||
|
||||
/** Real name of affected player */
|
||||
@Nullable private String name;
|
||||
|
||||
/** Player's skin, null for empty skin */
|
||||
@Nullable private Skin skin;
|
||||
|
||||
/** If player should appear in tablist or not */
|
||||
private boolean listed;
|
||||
|
||||
/** Latency */
|
||||
private int latency;
|
||||
|
||||
/** GameMode */
|
||||
private int gameMode;
|
||||
|
||||
/**
|
||||
* Display name displayed in TabList. Using {@code null} results in no display name
|
||||
* and scoreboard team prefix/suffix being visible in TabList instead.
|
||||
*/
|
||||
@Nullable private IChatBaseComponent displayName;
|
||||
|
||||
/** Player's chat signing key (1.19+), remote chat session (1.19.3+) */
|
||||
@Nullable private Object chatSession;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public static class Builder {
|
||||
|
||||
@NonNull private UUID uniqueId;
|
||||
@Nullable private String name;
|
||||
@Nullable private Skin skin;
|
||||
private boolean listed;
|
||||
private int latency;
|
||||
private int gameMode;
|
||||
@Nullable private IChatBaseComponent displayName;
|
||||
@Nullable private Object chatSession;
|
||||
|
||||
public Builder name(String name) { this.name = name; return this; }
|
||||
public Builder skin(Skin skin) { this.skin = skin; return this; }
|
||||
public Builder listed(boolean listed) { this.listed = listed; return this; }
|
||||
public Builder latency(int latency) { this.latency = latency; return this; }
|
||||
public Builder gameMode(int gameMode) { this.gameMode = gameMode; return this; }
|
||||
public Builder displayName(IChatBaseComponent displayName) { this.displayName = displayName; return this; }
|
||||
public Builder chatSession(Object chatSession) { this.chatSession = chatSession; return this; }
|
||||
|
||||
public TabListEntry build() {
|
||||
return new TabListEntry(uniqueId, name, skin, listed, latency, gameMode, displayName, chatSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package me.neznamy.tab.api.task;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
|
||||
/**
|
||||
* Manager for scheduling and executing tasks in TAB's
|
||||
* main thread to avoid concurrency issues.
|
||||
*/
|
||||
public interface ThreadManager {
|
||||
|
||||
/**
|
||||
* Starts a task in new thread and measures how long it took to process
|
||||
*
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
*/
|
||||
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a task in new thread and measures how long it took to process
|
||||
*
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
*/
|
||||
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task in a new thread
|
||||
*
|
||||
* @param task
|
||||
* the task
|
||||
*/
|
||||
Future<Void> runTask(Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a new task with defined repeat interval that measures cpu usage
|
||||
*
|
||||
* @param intervalMilliseconds
|
||||
* task interval
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Starts a new task with defined repeat interval
|
||||
*
|
||||
* @param intervalMilliseconds
|
||||
* task interval
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> startRepeatingTask(int intervalMilliseconds, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task with a delay and measures how long it took to process
|
||||
*
|
||||
* @param delayMilliseconds
|
||||
* how long to run the task after
|
||||
* @param feature
|
||||
* feature to add cpu usage to
|
||||
* @param type
|
||||
* usage type to add cpu usage to
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
|
||||
|
||||
/**
|
||||
* Runs task with a delay
|
||||
*
|
||||
* @param delayMilliseconds
|
||||
* how long to run the task after
|
||||
* @param task
|
||||
* the task
|
||||
* @return Future representing the submitted task
|
||||
*/
|
||||
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.neznamy.tab.api.team;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface TeamManager {
|
||||
|
||||
@@ -12,7 +14,7 @@ public interface TeamManager {
|
||||
* @see #showNametag(TabPlayer)
|
||||
* @see #hasHiddenNametag(TabPlayer)
|
||||
*/
|
||||
void hideNametag(TabPlayer player);
|
||||
void hideNametag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Hides player's NameTag for specified player until it's shown again
|
||||
@@ -22,7 +24,7 @@ public interface TeamManager {
|
||||
* @param viewer
|
||||
* player to hide NameTag for
|
||||
*/
|
||||
void hideNametag(TabPlayer player, TabPlayer viewer);
|
||||
void hideNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Makes player's NameTag visible again
|
||||
@@ -32,7 +34,7 @@ public interface TeamManager {
|
||||
* @see #hideNametag(TabPlayer)
|
||||
* @see #hasHiddenNametag(TabPlayer)
|
||||
*/
|
||||
void showNametag(TabPlayer player);
|
||||
void showNametag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Shows player's NameTag for specified viewer if it was hidden before
|
||||
@@ -42,7 +44,7 @@ public interface TeamManager {
|
||||
* @param viewer
|
||||
* player to show NameTag back for
|
||||
*/
|
||||
void showNametag(TabPlayer player, TabPlayer viewer);
|
||||
void showNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Return whether player has hidden NameTag or not
|
||||
@@ -53,7 +55,7 @@ public interface TeamManager {
|
||||
* @see #hideNametag(TabPlayer)
|
||||
* @see #showNametag(TabPlayer)
|
||||
*/
|
||||
boolean hasHiddenNametag(TabPlayer player);
|
||||
boolean hasHiddenNametag(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if NameTag is hidden for specified viewer, false if not
|
||||
@@ -64,7 +66,7 @@ public interface TeamManager {
|
||||
* player to check visibility status for
|
||||
* @return true if hidden, false if not
|
||||
*/
|
||||
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
|
||||
boolean hasHiddenNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
|
||||
|
||||
/**
|
||||
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
|
||||
@@ -73,7 +75,7 @@ public interface TeamManager {
|
||||
* @param player
|
||||
* player to pause team handling of
|
||||
*/
|
||||
void pauseTeamHandling(TabPlayer player);
|
||||
void pauseTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
|
||||
@@ -81,7 +83,7 @@ public interface TeamManager {
|
||||
* @param player
|
||||
* player to resume team handling of
|
||||
*/
|
||||
void resumeTeamHandling(TabPlayer player);
|
||||
void resumeTeamHandling(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
|
||||
@@ -91,7 +93,7 @@ public interface TeamManager {
|
||||
* player to check handling status of
|
||||
* @return true if paused, false if not
|
||||
*/
|
||||
boolean hasTeamHandlingPaused(TabPlayer player);
|
||||
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Forces new team name for the player until this method is called again with null argument and
|
||||
@@ -102,7 +104,7 @@ public interface TeamManager {
|
||||
* @param teamName
|
||||
* forced team name
|
||||
*/
|
||||
void forceTeamName(TabPlayer player, String teamName);
|
||||
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
|
||||
|
||||
/**
|
||||
* Returns forced team name of player or null if not forced
|
||||
@@ -111,7 +113,7 @@ public interface TeamManager {
|
||||
* player to check forced team name of
|
||||
* @return forced team name of player or null if not forced
|
||||
*/
|
||||
String getForcedTeamName(TabPlayer player);
|
||||
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
|
||||
|
||||
|
||||
/**
|
||||
@@ -122,7 +124,7 @@ public interface TeamManager {
|
||||
* @param collision
|
||||
* forced collision rule
|
||||
*/
|
||||
void setCollisionRule(TabPlayer player, Boolean collision);
|
||||
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
|
||||
|
||||
/**
|
||||
* Returns forced collision rule or null if collision is not forced using setCollisionRule
|
||||
@@ -131,7 +133,7 @@ public interface TeamManager {
|
||||
* player to get forced collision of
|
||||
* @return forced value or null if not forced
|
||||
*/
|
||||
Boolean getCollisionRule(TabPlayer player);
|
||||
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Sends update team's properties packet of player's team to everyone
|
||||
@@ -139,23 +141,23 @@ public interface TeamManager {
|
||||
* @param player
|
||||
* player to update team data of
|
||||
*/
|
||||
void updateTeamData(TabPlayer player);
|
||||
void updateTeamData(@NonNull TabPlayer player);
|
||||
|
||||
void setPrefix(TabPlayer player, String prefix);
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
void setSuffix(TabPlayer player, String suffix);
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
void resetPrefix(TabPlayer player);
|
||||
void resetPrefix(@NonNull TabPlayer player);
|
||||
|
||||
void resetSuffix(TabPlayer player);
|
||||
void resetSuffix(@NonNull TabPlayer player);
|
||||
|
||||
String getCustomPrefix(TabPlayer player);
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
String getCustomSuffix(TabPlayer player);
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
String getOriginalPrefix(TabPlayer player);
|
||||
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
String getOriginalSuffix(TabPlayer player);
|
||||
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles nametag visibility view on all players for specified player.
|
||||
@@ -167,7 +169,7 @@ public interface TeamManager {
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
|
||||
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden nametags by either calling
|
||||
@@ -178,5 +180,5 @@ public interface TeamManager {
|
||||
* player to check
|
||||
* @return {@code true} if hidden, {@code false} if not
|
||||
*/
|
||||
boolean hasHiddenNameTagVisibilityView(TabPlayer player);
|
||||
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
|
||||
}
|
||||
@@ -2,9 +2,6 @@ package me.neznamy.tab.api.util;
|
||||
|
||||
/**
|
||||
* An interface for BiFunction which can throw an exception.
|
||||
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
|
||||
* call build method for packet classes, which may throw an
|
||||
* exception due to reflection.
|
||||
* @param <A>
|
||||
* First function argument
|
||||
* @param <B>
|
||||
|
||||
@@ -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,31 +1,16 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
|
||||
/**
|
||||
* Class with utility methods used in the plugin
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class Preconditions {
|
||||
|
||||
/**
|
||||
* Private constructor preventing the class from being instantiated
|
||||
*/
|
||||
private Preconditions() {}
|
||||
|
||||
/**
|
||||
* Checks 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 +24,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,12 +0,0 @@
|
||||
package me.neznamy.tab.api.util;
|
||||
|
||||
/**
|
||||
* Supplier that can throw an exception
|
||||
*
|
||||
* @param <T>
|
||||
* Return class type
|
||||
*/
|
||||
public interface SupplierWithException<T> {
|
||||
|
||||
T get() throws Exception;
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -18,7 +10,9 @@ tasks {
|
||||
filter<ReplaceTokens>("tokens" to mapOf(
|
||||
"name" to rootProject.name,
|
||||
"version" to project.version,
|
||||
"description" to project.description
|
||||
"description" to project.description,
|
||||
"website" to rootProject.ext.get("website")!!.toString(),
|
||||
"author" to rootProject.ext.get("author")!!.toString()
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
@@ -27,4 +21,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))
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,6 @@ tasks {
|
||||
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
|
||||
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
|
||||
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
|
||||
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.yamlassist")
|
||||
}
|
||||
}
|
||||
@@ -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"])
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -4,15 +4,21 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = property("projectVersion") as String // from gradle.properties
|
||||
version = "3.3.2"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
ext.set("website", "https://github.com/NEZNAMY/TAB")
|
||||
ext.set("author", "NEZNAMY")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bungeecord,
|
||||
// projects.krypton,
|
||||
projects.velocity
|
||||
projects.krypton,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
@@ -4,9 +4,5 @@ dependencies {
|
||||
compileOnly(libs.bukkit)
|
||||
compileOnly(libs.placeholderapi)
|
||||
compileOnly(libs.vault)
|
||||
compileOnly(libs.via)
|
||||
compileOnly(libs.authlib)
|
||||
compileOnly(libs.essentials) {
|
||||
exclude("org.spigotmc", "spigot-api")
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -12,20 +14,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
|
||||
@@ -34,9 +27,9 @@ public class BukkitEventListener implements Listener {
|
||||
* quit event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e){
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
if (TAB.getInstance().isPluginDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,9 +40,9 @@ public class BukkitEventListener implements Listener {
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() ->
|
||||
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
|
||||
if (TAB.getInstance().isPluginDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() ->
|
||||
TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,10 +52,10 @@ public class BukkitEventListener implements Listener {
|
||||
* world changed event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e){
|
||||
if (TabAPI.getInstance().isPluginDisabled()) return;
|
||||
TabAPI.getInstance().getThreadManager().runTask(() ->
|
||||
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
if (TAB.getInstance().isPluginDisabled()) return;
|
||||
TAB.getInstance().getCPUManager().runTask(() ->
|
||||
TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,557 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.chat.WrappedChatComponent;
|
||||
import me.neznamy.tab.api.protocol.*;
|
||||
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.platforms.bukkit.nms.*;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitPacketBuilder extends PacketBuilder {
|
||||
|
||||
/** NMS data storage */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
|
||||
/** Map of entity type ids for entities used by the plugin */
|
||||
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<>();
|
||||
|
||||
/** Component cache for better performance (1.15- players) */
|
||||
private final Map<IChatBaseComponent, Object> componentCacheLegacy = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPacketBuilder() {
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 2);
|
||||
} else if (nms.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (nms.getMinorVersion() >= 8){
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> build((PacketPlayOutEntityMetadata)packet));
|
||||
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> build((PacketPlayOutEntityTeleport)packet));
|
||||
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> build((PacketPlayOutEntityDestroy)packet));
|
||||
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> build((PacketPlayOutSpawnEntityLiving)packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
|
||||
//1.9+ server or client, handled by bukkit api or ViaVersion
|
||||
return packet;
|
||||
}
|
||||
//<1.9 client and server
|
||||
return buildBossPacketEntity(packet, clientVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
Object component = toNMSComponent(packet.getMessage(), clientVersion);
|
||||
if (nms.getMinorVersion() >= 19)
|
||||
try {
|
||||
return nms.newPacketPlayOutChat.newInstance(component, packet.getType() == PacketPlayOutChat.ChatMessageType.GAME_INFO);
|
||||
} catch (Exception e) {
|
||||
//1.19.0
|
||||
return nms.newPacketPlayOutChat.newInstance(component, packet.getType().ordinal());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 16)
|
||||
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf(nms.ChatMessageType, packet.getType().toString()), UUID.randomUUID());
|
||||
if (nms.getMinorVersion() >= 12)
|
||||
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf(nms.ChatMessageType, packet.getType().toString()));
|
||||
if (nms.getMinorVersion() >= 8)
|
||||
return nms.newPacketPlayOutChat.newInstance(component, (byte) packet.getType().ordinal());
|
||||
if (nms.getMinorVersion() == 7)
|
||||
return nms.newPacketPlayOutChat.newInstance(component);
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
if (nms.ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
if (packet.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
|
||||
return nms.newClientboundPlayerInfoRemovePacket.newInstance(
|
||||
packet.getEntries().stream().map(PlayerInfoData::getUniqueId).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
Enum[] array = packet.getActions().stream().map(action -> Enum.valueOf(nms.EnumPlayerInfoAction, action.toString())).toArray(Enum[]::new);
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(array[0], nms.getHandle.invoke(Bukkit.getOnlinePlayers().iterator().next()));
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (PlayerInfoData data : packet.getEntries()) {
|
||||
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
|
||||
if (data.getSkin() != null) profile.getProperties().put("textures",
|
||||
new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
|
||||
Object obj = nms.newPlayerInfoData.newInstance(
|
||||
data.getUniqueId(),
|
||||
profile,
|
||||
data.isListed(),
|
||||
data.getLatency(),
|
||||
data.getGameMode() == null ? null : Enum.valueOf(nms.EnumGamemode, data.getGameMode().toString()),
|
||||
data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion),
|
||||
data.getProfilePublicKey() == null ? null : nms.newRemoteChatSession$Data.newInstance(data.getChatSessionId(), data.getProfilePublicKey()));
|
||||
items.add(obj);
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_ACTION, EnumSet.of(array[0], array));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
|
||||
return nmsPacket;
|
||||
} else {
|
||||
//1.19.2-
|
||||
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
|
||||
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(nms.EnumPlayerInfoAction, action.toString()), Array.newInstance(nms.EntityPlayer, 0));
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (PlayerInfoData data : packet.getEntries()) {
|
||||
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
|
||||
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (nms.newPlayerInfoData.getParameterTypes()[0] == nms.PacketPlayOutPlayerInfo) {
|
||||
parameters.add(nmsPacket);
|
||||
}
|
||||
parameters.add(profile);
|
||||
parameters.add(data.getLatency());
|
||||
parameters.add(data.getGameMode() == null ? null : Enum.valueOf(nms.EnumGamemode, data.getGameMode().toString()));
|
||||
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
|
||||
if (nms.getMinorVersion() >= 19) parameters.add(data.getProfilePublicKey());
|
||||
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
|
||||
return nmsPacket;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
if (nms.newPacketPlayOutPlayerListHeaderFooter.getParameterCount() == 2) {
|
||||
return nms.newPacketPlayOutPlayerListHeaderFooter.newInstance(toNMSComponent(packet.getHeader(), clientVersion), toNMSComponent(packet.getFooter(), clientVersion));
|
||||
}
|
||||
Object nmsPacket = nms.newPacketPlayOutPlayerListHeaderFooter.newInstance();
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_HEADER, toNMSComponent(packet.getHeader(), clientVersion));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_FOOTER, toNMSComponent(packet.getFooter(), clientVersion));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
return nms.newPacketPlayOutScoreboardDisplayObjective.newInstance(packet.getSlot(), newScoreboardObjective(packet.getObjectiveName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
String displayName = clientVersion.getMinorVersion() < 13 ? cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.newPacketPlayOutScoreboardObjective.newInstance(nms.newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
|
||||
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
|
||||
packet.getRenderType() == null ? null : Enum.valueOf(nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString())),
|
||||
packet.getAction());
|
||||
}
|
||||
|
||||
Object nmsPacket = nms.newPacketPlayOutScoreboardObjective.newInstance();
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME, packet.getObjectiveName());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_DISPLAYNAME, displayName);
|
||||
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_RENDERTYPE, Enum.valueOf(nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_METHOD, packet.getAction());
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(Enum.valueOf(nms.EnumScoreboardAction, packet.getAction().toString()), packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
|
||||
}
|
||||
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
|
||||
return nms.newPacketPlayOutScoreboardScore_String.newInstance(packet.getPlayer());
|
||||
}
|
||||
Object score = nms.newScoreboardScore.newInstance(nms.emptyScoreboard, newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
|
||||
nms.ScoreboardScore_setScore.invoke(score, packet.getScore());
|
||||
if (nms.getMinorVersion() >= 8) {
|
||||
return nms.newPacketPlayOutScoreboardScore.newInstance(score);
|
||||
}
|
||||
return nms.newPacketPlayOutScoreboardScore.newInstance(score, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.PacketPlayOutScoreboardTeam == null) return null; //fabric
|
||||
Object team = nms.newScoreboardTeam.newInstance(nms.emptyScoreboard, packet.getName());
|
||||
String prefix = packet.getPlayerPrefix();
|
||||
String suffix = packet.getPlayerSuffix();
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
prefix = cutTo(prefix, 16);
|
||||
suffix = cutTo(suffix, 16);
|
||||
}
|
||||
((Collection<String>)nms.ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(packet.getPlayers());
|
||||
nms.ScoreboardTeam_setAllowFriendlyFire.invoke(team, (packet.getOptions() & 0x1) > 0);
|
||||
nms.ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (packet.getOptions() & 0x2) > 0);
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
createTeamModern(packet, clientVersion, team, prefix, suffix);
|
||||
} else {
|
||||
createTeamLegacy(packet, team, prefix, suffix);
|
||||
}
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
switch (packet.getAction()) {
|
||||
case 0:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, true);
|
||||
case 1:
|
||||
return nms.PacketPlayOutScoreboardTeam_of.invoke(null, team);
|
||||
case 2:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, false);
|
||||
case 3:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(nms.PacketPlayOutScoreboardTeam_PlayerAction, "ADD"));
|
||||
case 4:
|
||||
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(nms.PacketPlayOutScoreboardTeam_PlayerAction, "REMOVE"));
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid action: " + packet.getAction());
|
||||
}
|
||||
}
|
||||
return nms.newPacketPlayOutScoreboardTeam.newInstance(team, packet.getAction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity destroy packet from custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Destroy packet
|
||||
* @return NMS destroy packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutEntityDestroy packet) throws ReflectiveOperationException {
|
||||
try {
|
||||
return nms.newPacketPlayOutEntityDestroy.newInstance(new Object[]{packet.getEntities()});
|
||||
} catch (IllegalArgumentException e) {
|
||||
//1.17.0
|
||||
return nms.newPacketPlayOutEntityDestroy.newInstance(packet.getEntities()[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity metadata packet from custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Metadata packet
|
||||
* @return NMS metadata packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
|
||||
if (nms.newPacketPlayOutEntityMetadata.getParameterCount() == 2) {
|
||||
//1.19.3+
|
||||
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), nms.DataWatcher_b.invoke(packet.getDataWatcher().toNMS()));
|
||||
} else {
|
||||
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), packet.getDataWatcher().toNMS(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity spawn packet from custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Spawn packet
|
||||
* @return NMS spawn packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutSpawnEntityLiving packet) throws ReflectiveOperationException {
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance(nms.dummyEntity);
|
||||
} else {
|
||||
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance();
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYID, packet.getEntityId());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_YAW, (byte)(packet.getLocation().getYaw() * 256.0f / 360.0f));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_PITCH, (byte)(packet.getLocation().getPitch() * 256.0f / 360.0f));
|
||||
if (nms.getMinorVersion() <= 14) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, packet.getDataWatcher().toNMS());
|
||||
}
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_UUID, packet.getUniqueId());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, packet.getLocation().getX());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, packet.getLocation().getY());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, packet.getLocation().getZ());
|
||||
} else {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, floor(packet.getLocation().getX()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, floor(packet.getLocation().getY()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, floor(packet.getLocation().getZ()*32));
|
||||
}
|
||||
int id = entityIds.get(packet.getEntityType());
|
||||
if (nms.getMinorVersion() >= 19) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, nms.EntityTypes_ARMOR_STAND); // :(
|
||||
} else {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, id);
|
||||
}
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity teleport packet from custom packet class
|
||||
*
|
||||
* @param packet
|
||||
* Teleport packet
|
||||
* @return NMS teleport packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
public Object build(PacketPlayOutEntityTeleport packet) throws ReflectiveOperationException {
|
||||
Object nmsPacket;
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance(nms.dummyEntity);
|
||||
} else {
|
||||
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance();
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_ENTITYID, packet.getEntityId());
|
||||
if (nms.getMinorVersion() >= 9) {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, packet.getLocation().getX());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, packet.getLocation().getY());
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, packet.getLocation().getZ());
|
||||
} else {
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, floor(packet.getLocation().getX()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, floor(packet.getLocation().getY()*32));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, floor(packet.getLocation().getZ()*32));
|
||||
}
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_YAW, (byte) (packet.getLocation().getYaw()/360*256));
|
||||
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_PITCH, (byte) (packet.getLocation().getPitch()/360*256));
|
||||
return nmsPacket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data into NMS team from custom team packet. Used on 1.13+ servers.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to read from
|
||||
* @param clientVersion
|
||||
* Version of player
|
||||
* @param team
|
||||
* Team to write to
|
||||
* @param prefix
|
||||
* Prefix to use
|
||||
* @param suffix
|
||||
* Suffix to use
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private void createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion, Object team, String prefix, String suffix) throws ReflectiveOperationException {
|
||||
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
|
||||
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
|
||||
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
|
||||
nms.ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, format.toString()));
|
||||
nms.ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(nms.EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
|
||||
nms.ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(nms.EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes data into NMS team from custom team packet. Used on 1.12- servers.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to read from
|
||||
* @param team
|
||||
* Team to write to
|
||||
* @param prefix
|
||||
* Prefix to use
|
||||
* @param suffix
|
||||
* Suffix to use
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix) throws ReflectiveOperationException {
|
||||
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
if (nms.getMinorVersion() >= 8) nms.ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(nms.EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
|
||||
if (nms.getMinorVersion() >= 9) nms.ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(nms.EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() < 8) return null;
|
||||
if (nms.ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
if (nms.ClientboundPlayerInfoRemovePacket.isInstance(nmsPacket)) {
|
||||
List<UUID> entries = (List<UUID>) nms.ClientboundPlayerInfoRemovePacket_getEntries.invoke(nmsPacket);
|
||||
return new PacketPlayOutPlayerInfo(
|
||||
EnumPlayerInfoAction.REMOVE_PLAYER,
|
||||
entries.stream().map(PlayerInfoData::new).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
EnumSet<?> set = (EnumSet<?>) nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket);
|
||||
EnumPlayerInfoAction[] array = set.stream().map(action -> EnumPlayerInfoAction.valueOf(action.toString())).toArray(EnumPlayerInfoAction[]::new);
|
||||
EnumSet<EnumPlayerInfoAction> actions = EnumSet.of(array[0], array);
|
||||
List<PlayerInfoData> listData = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
|
||||
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
|
||||
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
|
||||
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
|
||||
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
|
||||
Skin skin = null;
|
||||
if (!profile.getProperties().get("textures").isEmpty()) {
|
||||
Property pr = profile.getProperties().get("textures").iterator().next();
|
||||
skin = new Skin(pr.getValue(), pr.getSignature());
|
||||
}
|
||||
Object remoteChatSession = nms.PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData);
|
||||
listData.add(
|
||||
new PlayerInfoData(
|
||||
profile.getName(),
|
||||
profile.getId(),
|
||||
skin,
|
||||
(boolean) nms.PlayerInfoData_isListed.invoke(nmsData),
|
||||
(int) nms.PlayerInfoData_getLatency.invoke(nmsData),
|
||||
gameMode,
|
||||
nmsComponent == null ? null : new WrappedChatComponent(nmsComponent),
|
||||
remoteChatSession == null ? null : (UUID) nms.RemoteChatSession$Data_getSessionId.invoke(remoteChatSession),
|
||||
remoteChatSession == null ? null : nms.RemoteChatSession$Data_getProfilePublicKey.invoke(remoteChatSession)));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(actions, listData);
|
||||
} else {
|
||||
//1.19.2-
|
||||
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
|
||||
List<PlayerInfoData> listData = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
|
||||
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
|
||||
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
|
||||
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
|
||||
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
|
||||
IChatBaseComponent listName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
|
||||
Skin skin = null;
|
||||
if (!profile.getProperties().get("textures").isEmpty()) {
|
||||
Property pr = profile.getProperties().get("textures").iterator().next();
|
||||
skin = new Skin(pr.getValue(), pr.getSignature());
|
||||
}
|
||||
Object profilePublicKey = nms.getMinorVersion() >= 19 ? nms.PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData) : null;
|
||||
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), skin, true,
|
||||
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName, null, profilePublicKey));
|
||||
}
|
||||
return new PacketPlayOutPlayerInfo(action, listData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
|
||||
return new PacketPlayOutScoreboardObjective(nms.PacketPlayOutScoreboardObjective_METHOD.getInt(nmsPacket),
|
||||
(String) nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.get(nmsPacket), null,
|
||||
PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
|
||||
return new PacketPlayOutScoreboardDisplayObjective(
|
||||
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.getInt(nmsPacket),
|
||||
(String) nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.get(nmsPacket)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
|
||||
*
|
||||
* @param packet
|
||||
* packet to build
|
||||
* @param clientVersion
|
||||
* client version
|
||||
* @return entity BossBar packet
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
|
||||
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
|
||||
|
||||
int entityId = packet.getId().hashCode();
|
||||
if (packet.getAction() == Action.REMOVE) {
|
||||
return build(new PacketPlayOutEntityDestroy(entityId));
|
||||
}
|
||||
DataWatcher w = new DataWatcher();
|
||||
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
|
||||
float health = 300*packet.getPct();
|
||||
if (health == 0) health = 1;
|
||||
w.helper().setHealth(health);
|
||||
}
|
||||
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
|
||||
w.helper().setCustomName(packet.getName(), clientVersion);
|
||||
}
|
||||
if (packet.getAction() == Action.ADD) {
|
||||
w.helper().setEntityFlags((byte) 32);
|
||||
w.helper().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));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A method taken from minecraft code used to convert double to int
|
||||
*
|
||||
* @param paramDouble
|
||||
* double value
|
||||
* @return int value
|
||||
*/
|
||||
private int floor(double paramDouble){
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
|
||||
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
|
||||
* If {@code component} is {@code null}, returns {@code null}
|
||||
*
|
||||
* @param component
|
||||
* component to convert
|
||||
* @param clientVersion
|
||||
* client version used to decide RGB conversion
|
||||
* @return converted component or {@code null} if {@code component} is {@code null}
|
||||
* @throws ReflectiveOperationException
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given name.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @return NMS Objective
|
||||
* @throws ReflectiveOperationException
|
||||
* if thrown by reflective operation
|
||||
*/
|
||||
private Object newScoreboardObjective(String objectiveName) throws ReflectiveOperationException {
|
||||
if (nms.getMinorVersion() >= 13) {
|
||||
return nms.newScoreboardObjective.newInstance(null, objectiveName, null, toNMSComponent(new IChatBaseComponent(""), TabAPI.getInstance().getServerVersion()), null);
|
||||
}
|
||||
return nms.newScoreboardObjective.newInstance(null, objectiveName, nms.IScoreboardCriteria_self.get(null));
|
||||
}
|
||||
}
|
||||
+137
-119
@@ -1,149 +1,167 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.feature.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.chat.WrappedChatComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
*/
|
||||
public class BukkitPipelineInjector extends PipelineInjector {
|
||||
|
||||
/** NMS data storage */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BukkitPipelineInjector(){
|
||||
public BukkitPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
return BukkitChannelDuplexHandler::new;
|
||||
protected Channel getChannel(TabPlayer player) {
|
||||
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
try {
|
||||
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
|
||||
} catch (final IllegalAccessException exception) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom channel duplex handler override
|
||||
*/
|
||||
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
|
||||
@Override
|
||||
public void onDisplayObjective(TabPlayer player, Object packet) throws IllegalAccessException {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
PacketPlayOutScoreboardDisplayObjectiveStorage.POSITION.getInt(packet),
|
||||
(String) PacketPlayOutScoreboardDisplayObjectiveStorage.OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
/** Injected player */
|
||||
private final TabPlayer player;
|
||||
@Override
|
||||
public void onObjective(TabPlayer player, Object packet) throws IllegalAccessException {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
PacketPlayOutScoreboardObjectiveStorage.METHOD.getInt(packet),
|
||||
(String) PacketPlayOutScoreboardObjectiveStorage.OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
*
|
||||
* @param player
|
||||
* player to inject
|
||||
*/
|
||||
public BukkitChannelDuplexHandler(TabPlayer player) {
|
||||
this.player = player;
|
||||
@Override
|
||||
public boolean isDisplayObjective(Object packet) {
|
||||
return PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(Object packet) {
|
||||
return PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(Object packet) {
|
||||
return PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(Object packet) {
|
||||
return PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogin(Object packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
NMSStorage nms = NMSStorage.getInstance();
|
||||
List<String> actions;
|
||||
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
actions = ((EnumSet<?>)PacketPlayOutPlayerInfoStorage.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
|
||||
} else {
|
||||
//1.19.2-
|
||||
actions = Collections.singletonList(PacketPlayOutPlayerInfoStorage.ACTION.get(packet).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
|
||||
try {
|
||||
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
|
||||
super.channelRead(context, packet);
|
||||
} catch (Exception e){
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) PacketPlayOutPlayerInfoStorage.PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoDataStorage.PlayerInfoData_getProfile.invoke(nmsData);
|
||||
int gameMode = 0;
|
||||
int latency = 0;
|
||||
IChatBaseComponent displayName = null;
|
||||
if (actions.contains("UPDATE_GAME_MODE") || actions.contains("ADD_PLAYER")) {
|
||||
gameMode = PacketPlayOutPlayerInfoStorage.gameMode2Int(PlayerInfoDataStorage.PlayerInfoData_GameMode.get(nmsData));
|
||||
gameMode = TAB.getInstance().getFeatureManager().onGameModeChange(receiver, profile.getId(), gameMode);
|
||||
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_GameMode.set(nmsData, PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode));
|
||||
}
|
||||
if (actions.contains("UPDATE_LATENCY") || actions.contains("ADD_PLAYER")) {
|
||||
latency = PlayerInfoDataStorage.PlayerInfoData_Latency.getInt(nmsData);
|
||||
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latency);
|
||||
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_Latency.set(nmsData, latency);
|
||||
}
|
||||
if (actions.contains("UPDATE_DISPLAY_NAME") || actions.contains("ADD_PLAYER")) {
|
||||
Object nmsComponent = PlayerInfoDataStorage.PlayerInfoData_DisplayName.get(nmsData);
|
||||
displayName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
|
||||
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId(), displayName);
|
||||
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_DisplayName.set(nmsData, nms.toNMSComponent(displayName, receiver.getVersion()));
|
||||
}
|
||||
if (actions.contains("ADD_PLAYER")) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
|
||||
boolean listed = PlayerInfoDataStorage.PlayerInfoData_Listed.getBoolean(nmsData);
|
||||
Object chatSession = PlayerInfoDataStorage.PlayerInfoData_RemoteChatSession.get(nmsData);
|
||||
updatedList.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
|
||||
profile.getId(),
|
||||
profile,
|
||||
listed,
|
||||
latency,
|
||||
PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode),
|
||||
nms.toNMSComponent(displayName, receiver.getVersion()),
|
||||
chatSession));
|
||||
}
|
||||
}
|
||||
if (nms.is1_19_3Plus()) {
|
||||
PacketPlayOutPlayerInfoStorage.PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
|
||||
try {
|
||||
if (nms.PacketPlayOutPlayerInfo.isInstance(packet) ||
|
||||
(nms.ClientboundPlayerInfoRemovePacket != null && nms.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
|
||||
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
|
||||
return;
|
||||
}
|
||||
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam != null && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
|
||||
long time = System.nanoTime();
|
||||
modifyPlayers(packet);
|
||||
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
|
||||
super.write(context, packet, channelPromise);
|
||||
return;
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardDisplayObjective.isInstance(packet)){
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
|
||||
}
|
||||
if (nms.PacketPlayOutScoreboardObjective.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, packet);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
|
||||
} catch (Exception e){
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
|
||||
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
super.write(context, packet, channelPromise);
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
|
||||
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
|
||||
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all real players from team if packet does not come from TAB and reports this to override log
|
||||
*
|
||||
* @param packetPlayOutScoreboardTeam
|
||||
* team packet
|
||||
* @throws ReflectiveOperationException
|
||||
* nmsGameMode
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
|
||||
int action = nms.PacketPlayOutScoreboardTeam_ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) nms.PacketPlayOutScoreboardTeam_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) nms.PacketPlayOutScoreboardTeam_NAME.get(packetPlayOutScoreboardTeam);
|
||||
if (players == null) return;
|
||||
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
for (String entry : players) {
|
||||
TabPlayer p = getPlayer(entry);
|
||||
if (p == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
|
||||
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
nms.setField(packetPlayOutScoreboardTeam, nms.PacketPlayOutScoreboardTeam_PLAYERS, newList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player with matching game profile name. This is different from
|
||||
* real name when a nick plugin changing names of players is used. If no
|
||||
* player was found, returns {@code null}.
|
||||
*
|
||||
* @param name
|
||||
* Game profile name
|
||||
* @return Player with matching game profile name
|
||||
*/
|
||||
private TabPlayer getPlayer(String name) {
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getNickname().equals(name)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
}
|
||||
}
|
||||
+19
-61
@@ -1,41 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
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.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.ess3.api.events.NickChangeEvent;
|
||||
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.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.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Bukkit registry to register bukkit-only and universal placeholders
|
||||
*/
|
||||
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,14 +39,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;
|
||||
numberFormat.setMaximumFractionDigits(2);
|
||||
public BukkitPlaceholderRegistry() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) chat = rspChat.getProvider();
|
||||
@@ -96,32 +75,22 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
|
||||
}
|
||||
if (paperMspt != null) {
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> numberFormat.format(Bukkit.getAverageTickTime()));
|
||||
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
|
||||
}
|
||||
Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.AFK, 500, p -> {
|
||||
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
|
||||
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()));
|
||||
@@ -133,15 +102,4 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
|
||||
super.registerPlaceholders(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats TPS using number formatter with 2 decimal places.
|
||||
*
|
||||
* @param tps
|
||||
* TPS to format
|
||||
* @return Formatted TPS as a String
|
||||
*/
|
||||
private String formatTPS(double tps) {
|
||||
return numberFormat.format(Math.min(20, tps));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,30 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.api.ProtocolVersion;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.feature.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.nms.storage.nms.NMSStorage;
|
||||
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.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.permission.LuckPerms;
|
||||
import me.neznamy.tab.shared.permission.None;
|
||||
import me.neznamy.tab.shared.permission.PermissionPlugin;
|
||||
@@ -27,34 +35,28 @@ import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
*/
|
||||
public class BukkitPlatform extends Platform {
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitPlatform extends BackendPlatform {
|
||||
|
||||
@Getter private final BukkitPipelineInjector pipelineInjector = NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
|
||||
|
||||
/** 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);
|
||||
private Plugin viaVersion;
|
||||
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
|
||||
private final boolean viaVersion = ReflectionUtils.classExists("com.viaversion.viaversion.api.Via");
|
||||
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin parameter
|
||||
*
|
||||
* @param plugin
|
||||
* plugin instance
|
||||
*/
|
||||
public BukkitPlatform(JavaPlugin plugin) {
|
||||
super(new BukkitPacketBuilder());
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PermissionPlugin detectPermissionPlugin() {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LUCKPERMS)) {
|
||||
@@ -68,50 +70,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
|
||||
@@ -120,6 +80,43 @@ 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 NameTag getUnlimitedNametags() {
|
||||
return new BukkitNameTagX(plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull TabExpansion getTabExpansion() {
|
||||
if (placeholderAPI) {
|
||||
BukkitTabExpansion expansion = new BukkitTabExpansion();
|
||||
expansion.register();
|
||||
return expansion;
|
||||
}
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabFeature getPetFix() {
|
||||
return new PetFix();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable TabFeature getPerWorldPlayerlist() {
|
||||
return new PerWorldPlayerList(plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API
|
||||
*
|
||||
@@ -180,27 +177,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.
|
||||
*
|
||||
@@ -209,13 +185,13 @@ public class BukkitPlatform extends Platform {
|
||||
* @return protocol version of the player
|
||||
*/
|
||||
public int getProtocolVersion(Player player) {
|
||||
if (protocolSupport){
|
||||
if (protocolSupport) {
|
||||
int version = getProtocolVersionPS(player);
|
||||
//some PS versions return -1 on unsupported server versions instead of throwing exception
|
||||
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
|
||||
}
|
||||
if (viaVersion != null) {
|
||||
return getProtocolVersionVia(player, 0);
|
||||
if (viaVersion) {
|
||||
return ProtocolVersion.getPlayerVersionVia(player.getUniqueId(), player.getName());
|
||||
}
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
@@ -227,7 +203,7 @@ public class BukkitPlatform extends Platform {
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player using ProtocolSupport
|
||||
*/
|
||||
private int getProtocolVersionPS(Player player){
|
||||
private int getProtocolVersionPS(Player player) {
|
||||
try {
|
||||
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
|
||||
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
|
||||
@@ -239,39 +215,10 @@ public class BukkitPlatform extends Platform {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns protocol version of requested player using ViaVersion
|
||||
*
|
||||
* @param player
|
||||
* Player to get protocol version of
|
||||
* @return protocol version of the player using ViaVersion
|
||||
*/
|
||||
private int getProtocolVersionVia(Player player, int retryLevel){
|
||||
try {
|
||||
if (retryLevel == 10) {
|
||||
TAB.getInstance().debug("Failed to get protocol version of " + player.getName() + " after 10 retries");
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
int version = Via.getAPI().getPlayerVersion(player.getUniqueId());
|
||||
if (version == -1) {
|
||||
if (!player.isOnline()) return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
Thread.sleep(5);
|
||||
return getProtocolVersionVia(player, retryLevel + 1);
|
||||
}
|
||||
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + player.getName() + " (online=" + player.isOnline() + ")");
|
||||
return version;
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return -1;
|
||||
} catch (Exception | LinkageError e) {
|
||||
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ViaVersion v%s", player.getName(), viaVersion.getDescription().getVersion()), e);
|
||||
return TAB.getInstance().getServerVersion().getNetworkId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends console message using ConsoleCommandSender, due to
|
||||
* Paper not translating colors correctly in Logger messages
|
||||
* and to allow RGB (at least on Paper, doesn't work on spigot)
|
||||
*
|
||||
* @param message
|
||||
* Message to send
|
||||
@@ -280,6 +227,9 @@ public class BukkitPlatform extends Platform {
|
||||
*/
|
||||
@Override
|
||||
public void sendConsoleMessage(String message, boolean translateColors) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + (translateColors ? EnumChatFormat.color(message) : message));
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + (translateColors ?
|
||||
EnumChatFormat.color(RGBUtils.getInstance().convertToBukkitFormat(message,
|
||||
TAB.getInstance().getServerVersion().getMinorVersion() >= 16))
|
||||
: message));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardScoreStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
|
||||
import me.neznamy.tab.shared.TabScoreboard;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Bukkit, which uses packets
|
||||
* to send scoreboards. While md_5 keeps saying that you don't
|
||||
* need packets for scoreboards in every single spigot update,
|
||||
* this is not true. It may seem that way at first, but after
|
||||
* investigating, fatal problems are found:<p>
|
||||
* #1 - Limitations on legacy versions are forced in the API.
|
||||
* While this may not seem like a problem, it enforces those
|
||||
* limits even for 1.13+ players (if using ViaVersion).<p>
|
||||
* #2 - Modern versions no longer have any limits, but md_5
|
||||
* decided to add some random limits for absolutely no reason
|
||||
* at all. Scoreboard title received a random 128 characters
|
||||
* limit including color codes. Together with the almighty bukkit
|
||||
* RGB format using 14 characters for 1 color code, this makes
|
||||
* gradients just impossible to use. Team prefix/suffix also
|
||||
* received a 64 characters limit (excluding color codes at least),
|
||||
* however that might not be enough for displaying a line of text
|
||||
* in sidebar, which would require splitting the text into prefix
|
||||
* and suffix, which is just begging for bugs to be introduced.<p>
|
||||
* #3 - Other plugins can decide to put players into their own
|
||||
* scoreboard, automatically destroying all visuals made by the
|
||||
* plugin. They might also put all players into the same scoreboard,
|
||||
* making per-player view of teams, especially sidebar not working.<p>
|
||||
* #4 - API made scoreboards get saved into the world folder and then
|
||||
* resent on join with everything that was ever registered to it,
|
||||
* which is very poor performance-wise, especially since it does
|
||||
* that in the main server thread. It might also send conflicting
|
||||
* information, such as putting a player into team they were on
|
||||
* previously, but are not supposed to be anymore. Removing the plugin
|
||||
* would cause all visuals to still be present on the server,
|
||||
* requiring owners to properly clear scoreboards, which is not something
|
||||
* everyone knows how to do.
|
||||
*/
|
||||
public class BukkitScoreboard extends TabScoreboard {
|
||||
|
||||
public BukkitScoreboard(TabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(DisplaySlot slot, @NonNull String objective) {
|
||||
player.sendPacket(PacketPlayOutScoreboardDisplayObjectiveStorage.buildSilent(slot.ordinal(), objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
|
||||
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(0, objectiveName, title, hearts, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NonNull String objectiveName) {
|
||||
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(1, objectiveName, "", false, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
|
||||
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(2, objectiveName, title, hearts, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
|
||||
player.sendPacket(PacketPlayOutScoreboardTeamStorage.register(name, prefix, suffix, visibility, collision, players, options, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NonNull String name) {
|
||||
player.sendPacket(PacketPlayOutScoreboardTeamStorage.unregister(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options) {
|
||||
player.sendPacket(PacketPlayOutScoreboardTeamStorage.update(name, prefix, suffix, visibility, collision, options, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NonNull String objective, @NonNull String playerName, int score) {
|
||||
player.sendPacket(PacketPlayOutScoreboardScoreStorage.change(objective, playerName, score));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NonNull String objective, @NonNull String playerName) {
|
||||
player.sendPacket(PacketPlayOutScoreboardScoreStorage.remove(objective, playerName));
|
||||
}
|
||||
}
|
||||
+14
-14
@@ -5,8 +5,7 @@ import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.api.util.SupplierWithException;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.*;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
@@ -19,18 +18,19 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit platform
|
||||
*/
|
||||
public class Main extends JavaPlugin {
|
||||
public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable(){
|
||||
public void onEnable() {
|
||||
String version = Bukkit.getBukkitVersion().split("-")[0];
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
|
||||
if (!isVersionSupported()){
|
||||
if (!isVersionSupported()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
@@ -66,44 +66,44 @@ public class Main extends JavaPlugin {
|
||||
*
|
||||
* @return {@code true} if server version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported(){
|
||||
private boolean isVersionSupported() {
|
||||
List<String> supportedVersions = Arrays.asList(
|
||||
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
|
||||
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
|
||||
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
|
||||
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1",
|
||||
"v1_18_R2", "v1_19_R1", "v1_19_R2");
|
||||
"v1_18_R2", "v1_19_R1", "v1_19_R2", "v1_19_R3");
|
||||
String supportedVersionRange = "1.5 - 1.19.4";
|
||||
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
try {
|
||||
long time = System.currentTimeMillis();
|
||||
int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
NMSStorage.setInstance(getNMSLoader());
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
|
||||
return true;
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.2 - 1.19.3. Disabling just to stay safe."));
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on " + supportedVersionRange + ". Disabling just to stay safe."));
|
||||
}
|
||||
} catch (IllegalStateException ex) {
|
||||
if (supportedVersions.contains(serverPackage)) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report this issue (include your server version & fork too)"));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.2 - 1.19.3. Disabling."));
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports " + supportedVersionRange + ". Disabling."));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private NMSStorage getNMSLoader() {
|
||||
List<SupplierWithException<NMSStorage>> loaders = new ArrayList<>();
|
||||
List<Callable<NMSStorage>> loaders = new ArrayList<>();
|
||||
loaders.add(BukkitLegacyNMSStorage::new);
|
||||
loaders.add(BukkitModernNMSStorage::new);
|
||||
loaders.add(MojangModernNMSStorage::new);
|
||||
loaders.add(ThermosNMSStorage::new);
|
||||
for (SupplierWithException<NMSStorage> loader : loaders) {
|
||||
for (Callable<NMSStorage> loader : loaders) {
|
||||
try {
|
||||
return loader.get();
|
||||
} catch (Exception ignored){}
|
||||
return loader.call();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
throw new IllegalStateException("Unsupported server version");
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.tablist.TabListEntry;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
|
||||
import me.neznamy.tab.shared.tablist.BulkUpdateTabList;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* TabList which support modifying many entries at once
|
||||
* for significantly better performance. For 1.7 players,
|
||||
* ViaVersion properly splits the packet into multiple, so
|
||||
* we don't need to worry about that here.
|
||||
* <p>
|
||||
* This class does not support server versions of 1.7 and
|
||||
* below, because of the massive differences in tablist
|
||||
* and packet fields.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitTabList extends BulkUpdateTabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void removeEntries(@NonNull Collection<UUID> entries) {
|
||||
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
try {
|
||||
player.sendPacket(PacketPlayOutPlayerInfoStorage.newClientboundPlayerInfoRemovePacket.newInstance(new ArrayList<>(entries)));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
//1.19.2-
|
||||
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("REMOVE_PLAYER",
|
||||
entries.stream().map(id ->
|
||||
new TabListEntry.Builder(id).build()).collect(Collectors.toList()),
|
||||
player.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries) {
|
||||
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_DISPLAY_NAME",
|
||||
entries.entrySet().stream().map(entry ->
|
||||
new TabListEntry.Builder(entry.getKey()).displayName(entry.getValue()).build()).collect(Collectors.toList()),
|
||||
player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatencies(@NonNull Map<UUID, Integer> entries) {
|
||||
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_LATENCY",
|
||||
entries.entrySet().stream().map(entry ->
|
||||
new TabListEntry.Builder(entry.getKey()).latency(entry.getValue()).build()).collect(Collectors.toList()),
|
||||
player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameModes(@NonNull Map<UUID, Integer> entries) {
|
||||
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_GAME_MODE",
|
||||
entries.entrySet().stream().map(entry ->
|
||||
new TabListEntry.Builder(entry.getKey()).gameMode(entry.getValue()).build()).collect(Collectors.toList()),
|
||||
player.getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntries(@NonNull Collection<TabListEntry> entries) {
|
||||
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("ADD_PLAYER",
|
||||
entries.stream().map(entry ->
|
||||
new TabListEntry(entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
|
||||
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getChatSession())).collect(Collectors.toList()),
|
||||
player.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,59 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
|
||||
import me.neznamy.tab.api.protocol.Skin;
|
||||
import me.neznamy.tab.api.util.Preconditions;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BarColor;
|
||||
import org.bukkit.boss.BarFlag;
|
||||
import org.bukkit.boss.BarStyle;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.BossBarHandler;
|
||||
import me.neznamy.tab.api.Scoreboard;
|
||||
import me.neznamy.tab.api.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.api.tablist.Skin;
|
||||
import me.neznamy.tab.api.tablist.TabList;
|
||||
import me.neznamy.tab.api.util.ComponentCache;
|
||||
import me.neznamy.tab.api.util.ReflectionUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_8;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_9;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBarVia;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import 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.UUID;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Bukkit platform
|
||||
*/
|
||||
public class BukkitTabPlayer extends ITabPlayer {
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/** Spigot check */
|
||||
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
|
||||
|
||||
/** Component cache to save CPU when creating components */
|
||||
private static final ComponentCache<IChatBaseComponent, BaseComponent[]> componentCache = new ComponentCache<>(10000,
|
||||
(component, clientVersion) -> ComponentSerializer.parse(component.toString(clientVersion)));
|
||||
|
||||
/** Player's NMS handle (EntityPlayer), preloading for speed */
|
||||
private Object handle;
|
||||
|
||||
/** Player's connection for sending packets, preloading for speed */
|
||||
private Object playerConnection;
|
||||
|
||||
/** Bukkit BossBars the player can currently see */
|
||||
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
||||
@Getter private Object playerConnection;
|
||||
|
||||
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
|
||||
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
|
||||
@Getter private final Scoreboard scoreboard = new BukkitScoreboard(this);
|
||||
@Getter private final TabList tabList = new BukkitTabList(this);
|
||||
@Getter private final BossBarHandler bossBarHandler = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
|
||||
new BukkitBossBar1_9(this) : getVersion().getMinorVersion() >= 9 ? new BukkitBossBarVia(this) : new BukkitBossBar1_8(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player and protocol version
|
||||
@@ -54,8 +63,8 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
* @param protocolVersion
|
||||
* Player's protocol network id
|
||||
*/
|
||||
public BukkitTabPlayer(Player p, int protocolVersion){
|
||||
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion, true);
|
||||
public BukkitTabPlayer(Player p, int protocolVersion) {
|
||||
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion);
|
||||
try {
|
||||
handle = NMSStorage.getInstance().getHandle.invoke(player);
|
||||
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
|
||||
@@ -65,12 +74,8 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
Preconditions.checkNotNull(permission, "permission");
|
||||
long time = System.nanoTime();
|
||||
boolean value = getPlayer().hasPermission(permission);
|
||||
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
|
||||
return value;
|
||||
public boolean hasPermission(@NonNull String permission) {
|
||||
return getPlayer().hasPermission(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,150 +92,20 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
@Override
|
||||
public void sendPacket(Object nmsPacket) {
|
||||
if (nmsPacket == null || !getPlayer().isOnline()) return;
|
||||
long time = System.nanoTime();
|
||||
try {
|
||||
if (nmsPacket instanceof PacketPlayOutBoss) {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
|
||||
handle((PacketPlayOutBoss) nmsPacket);
|
||||
} else {
|
||||
handleVia((PacketPlayOutBoss) nmsPacket);
|
||||
}
|
||||
} else if (nmsPacket instanceof PacketPlayOutChat) {
|
||||
getPlayer().sendMessage(((PacketPlayOutChat) nmsPacket).getMessage().toLegacyText());
|
||||
} else {
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
}
|
||||
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
|
||||
}
|
||||
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PacketPlayOutBoss packet send request using Bukkit API,
|
||||
* since the API offers everything we need and makes us not need to
|
||||
* deal with NMS code at all.
|
||||
*
|
||||
* @param packet
|
||||
* packet request to handle using Bukkit API
|
||||
*/
|
||||
private void handle(PacketPlayOutBoss packet) {
|
||||
BossBar bar = bossBars.get(packet.getId());
|
||||
if (packet.getAction() == PacketPlayOutBoss.Action.ADD) {
|
||||
if (bossBars.containsKey(packet.getId())) return;
|
||||
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
|
||||
BarColor.valueOf(packet.getColor().name()),
|
||||
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
|
||||
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
|
||||
bar.setProgress(packet.getPct());
|
||||
bossBars.put(packet.getId(), bar);
|
||||
bar.addPlayer(getPlayer());
|
||||
return;
|
||||
@Override
|
||||
public void sendMessage(IChatBaseComponent message) {
|
||||
if (spigot) {
|
||||
getPlayer().spigot().sendMessage(componentCache.get(message, version));
|
||||
} else {
|
||||
getPlayer().sendMessage(message.toLegacyText());
|
||||
}
|
||||
if (bar == null) return; //no idea how
|
||||
switch (packet.getAction()) {
|
||||
case REMOVE:
|
||||
bar.removePlayer(getPlayer());
|
||||
bossBars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
bar.setProgress(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
bar.setColor(BarColor.valueOf(packet.getColor().name()));
|
||||
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = bossBars.get(packet.getId());
|
||||
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
|
||||
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
|
||||
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
|
||||
* 1.8 servers using ViaVersion API instead of using Wither.
|
||||
*
|
||||
* @param packet
|
||||
* packet request to handle using ViaVersion API
|
||||
*/
|
||||
private void handleVia(PacketPlayOutBoss packet) {
|
||||
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
|
||||
switch (packet.getAction()) {
|
||||
case ADD:
|
||||
if (viaBossBars.containsKey(packet.getId())) return;
|
||||
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
|
||||
packet.getPct(),
|
||||
BossColor.valueOf(packet.getColor().name()),
|
||||
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
//fog missing from via API
|
||||
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
|
||||
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
|
||||
viaBossBars.put(packet.getId(), bar);
|
||||
bar.addPlayer(getPlayer().getUniqueId());
|
||||
break;
|
||||
case REMOVE:
|
||||
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
|
||||
viaBossBars.remove(packet.getId());
|
||||
break;
|
||||
case UPDATE_PCT:
|
||||
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
|
||||
break;
|
||||
case UPDATE_NAME:
|
||||
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
|
||||
break;
|
||||
case UPDATE_STYLE:
|
||||
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
|
||||
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
|
||||
break;
|
||||
case UPDATE_PROPERTIES:
|
||||
bar = viaBossBars.get(packet.getId());
|
||||
//fog missing from via API
|
||||
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
|
||||
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets BossBar flag to requested target value.
|
||||
*
|
||||
* @param bar
|
||||
* BossBar to set flag of
|
||||
* @param targetValue
|
||||
* Target value of the flag
|
||||
* @param flag
|
||||
* Flag to set value of
|
||||
*/
|
||||
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
|
||||
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
|
||||
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets BossBar flag to requested target value.
|
||||
*
|
||||
* @param bar
|
||||
* BossBar to set flag of
|
||||
* @param targetValue
|
||||
* Target value of the flag
|
||||
* @param flag
|
||||
* Flag to set value of
|
||||
*/
|
||||
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
|
||||
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
|
||||
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -254,7 +129,7 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
@Override
|
||||
public Skin getSkin() {
|
||||
try {
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get("textures");
|
||||
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
@@ -279,54 +154,81 @@ public class BukkitTabPlayer extends ITabPlayer {
|
||||
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().getGameMode().getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProfilePublicKey() {
|
||||
if (NMSStorage.getInstance().getMinorVersion() < 19) return null;
|
||||
public void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer) {
|
||||
// Method was added to Bukkit API in 1.13.1, however despite that it's just a String one
|
||||
// Using it would cause high CPU usage and massive memory allocations on RGB & animations
|
||||
// Send packet instead for performance & older server version support
|
||||
|
||||
/*if (TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_13_1.getNetworkId()) {
|
||||
String bukkitHeader = RGBUtils.getInstance().convertToBukkitFormat(header.toFlatText(),
|
||||
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
|
||||
String bukkitFooter = RGBUtils.getInstance().convertToBukkitFormat(footer.toFlatText(),
|
||||
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
|
||||
getPlayer().setPlayerListHeaderFooter(bukkitHeader, bukkitFooter);
|
||||
return;
|
||||
}*/
|
||||
|
||||
try {
|
||||
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);
|
||||
sendPacket(PacketPlayOutPlayerListHeaderFooterStorage.build(header, footer, version));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(int entityId, UUID id, Object entityType, Location location, EntityData data) {
|
||||
try {
|
||||
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get profile key of " + getName(), e);
|
||||
return null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getChatSessionId() {
|
||||
if (NMSStorage.getInstance().RemoteChatSession != null) return null;
|
||||
public void updateEntityMetadata(int entityId, EntityData data) {
|
||||
try {
|
||||
Object session = NMSStorage.getInstance().EntityPlayer_RemoteChatSession.get(handle);
|
||||
if (session == null) return null;
|
||||
return (UUID) NMSStorage.getInstance().RemoteChatSession_getSessionId.invoke(session);
|
||||
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
|
||||
//1.19.3+
|
||||
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
|
||||
} else {
|
||||
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get chat session of " + getName(), e);
|
||||
return null;
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Channel getChannel() {
|
||||
public void teleportEntity(int entityId, Location location) {
|
||||
try {
|
||||
if (NMSStorage.getInstance().CHANNEL != null)
|
||||
return (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
|
||||
} catch (IllegalAccessException e) {
|
||||
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + getName(), e);
|
||||
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
try {
|
||||
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
|
||||
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
|
||||
} else {
|
||||
//1.17.0 Mojank
|
||||
for (int entity : entities) {
|
||||
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.BossBarHandler;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar using wither entity for <1.9 players on <1.9 servers.
|
||||
* Additional logic, such as teleporting the entity must be done
|
||||
* separately, as this class does not handle it.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBar1_8 implements BossBarHandler {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
w.getHelper().setCustomName(title, player.getVersion());
|
||||
w.getHelper().setEntityFlags((byte) 32);
|
||||
w.getHelper().setWitherInvulnerableTime(880); // Magic number
|
||||
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull String title) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
w.getHelper().setCustomName(title, player.getVersion());
|
||||
player.updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, float progress) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.getHelper().setHealth(health);
|
||||
player.updateEntityMetadata(id.hashCode(), w);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull BarStyle style) {} // Added in 1.9
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull BarColor color) {} // Added in 1.9
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull UUID id) {
|
||||
player.destroyEntities(id.hashCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.BossBarHandler;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BossBar;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Bossbar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
|
||||
* to allow 1.8 players, it will handle the entity and teleporting by itself.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBar1_9 implements BossBarHandler {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/** Bukkit BossBars the player can currently see */
|
||||
private final Map<UUID, BossBar> bossBars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NonNull UUID id, @NonNull String title, float progress, me.neznamy.tab.api.bossbar.@NonNull BarColor color, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
|
||||
if (bossBars.containsKey(id)) return;
|
||||
BossBar bar = Bukkit.createBossBar(
|
||||
RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
bar.setProgress(progress);
|
||||
bar.addPlayer(player.getPlayer());
|
||||
bossBars.put(id, bar);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull String title) {
|
||||
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, float progress) {
|
||||
bossBars.get(id).setProgress(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
|
||||
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarColor color) {
|
||||
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull UUID id) {
|
||||
bossBars.remove(id).removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossBar;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.BossBarHandler;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.api.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBarVia implements BossBarHandler {
|
||||
|
||||
/** Player this handler belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
|
||||
private final Map<UUID, BossBar> viaBossBars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
|
||||
if (viaBossBars.containsKey(id)) return;
|
||||
BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
|
||||
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
|
||||
progress,
|
||||
BossColor.valueOf(color.name()),
|
||||
BossStyle.valueOf(style.getBukkitName()));
|
||||
viaBossBars.put(id, bar);
|
||||
bar.addPlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull String title) {
|
||||
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
|
||||
player.getVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, float progress) {
|
||||
viaBossBars.get(id).setHealth(progress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull BarStyle style) {
|
||||
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NonNull UUID id, @NonNull BarColor color) {
|
||||
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull UUID id) {
|
||||
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
+12
-26
@@ -1,13 +1,13 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.TabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -21,30 +21,19 @@ import java.util.WeakHashMap;
|
||||
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
|
||||
|
||||
/** Map holding all values for all players for easy and high-performance access */
|
||||
private final Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
|
||||
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
|
||||
|
||||
@Getter private final String author = TabConstants.PLUGIN_AUTHOR;
|
||||
@Getter private final String identifier = TabConstants.PLUGIN_ID;
|
||||
@Getter private final String version = TabConstants.PLUGIN_VERSION;
|
||||
|
||||
@Override
|
||||
public boolean persist(){
|
||||
public boolean persist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getAuthor(){
|
||||
return "NEZNAMY";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getIdentifier(){
|
||||
return "tab";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getVersion() {
|
||||
return TabConstants.PLUGIN_VERSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onPlaceholderRequest(Player player, @NotNull String identifier){
|
||||
public String onPlaceholderRequest(Player player, @NotNull String identifier) {
|
||||
if (identifier.startsWith("replace_")) {
|
||||
String text = "%" + identifier.substring(8) + "%";
|
||||
String textBefore;
|
||||
@@ -58,16 +47,13 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
|
||||
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TabAPI.getInstance().getPlaceholderManager());
|
||||
}
|
||||
if (player == null) return "<Player cannot be null>";
|
||||
TabPlayer p = TabAPI.getInstance().getPlayer(player.getUniqueId());
|
||||
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
|
||||
return values.get(p).get(identifier);
|
||||
return values.get(player).get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(TabPlayer player, String key, String value) {
|
||||
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
|
||||
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
|
||||
}
|
||||
}
|
||||
+17
-22
@@ -5,7 +5,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.feature.Loadable;
|
||||
import me.neznamy.tab.api.feature.UnLoadable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -15,7 +19,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import me.neznamy.tab.api.feature.TabFeature;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
|
||||
@@ -23,36 +27,27 @@ import me.neznamy.tab.shared.TAB;
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
|
||||
/** Reference to main class */
|
||||
private final JavaPlugin plugin;
|
||||
@RequiredArgsConstructor
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
/** Config options */
|
||||
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads config options
|
||||
*
|
||||
* @param plugin
|
||||
* plugin instance
|
||||
*/
|
||||
public PerWorldPlayerList(JavaPlugin plugin) {
|
||||
super("Per world PlayerList", null);
|
||||
this.plugin = plugin;
|
||||
/** Plugin reference*/
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
@Getter private final String featureName = "Per world PlayerList";
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(){
|
||||
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getOnlinePlayers().forEach(this::checkPlayer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload(){
|
||||
public void unload() {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
for (Player pl : Bukkit.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
@@ -84,7 +79,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(Player p) {
|
||||
for (Player all : Bukkit.getOnlinePlayers()){
|
||||
for (Player all : Bukkit.getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabFeature;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.api.feature.PacketReceiveListener;
|
||||
import me.neznamy.tab.api.feature.PacketSendListener;
|
||||
import me.neznamy.tab.api.feature.TabFeature;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.List;
|
||||
@@ -20,7 +25,7 @@ import java.util.WeakHashMap;
|
||||
* Since 1.16 this results in client sending entity use packet twice,
|
||||
* so we must cancel the 2nd one to prevent double toggle.
|
||||
*/
|
||||
public class PetFix extends TabFeature {
|
||||
public class PetFix extends TabFeature implements PacketReceiveListener, PacketSendListener {
|
||||
|
||||
/** NMS Storage reference for quick access */
|
||||
private final NMSStorage nms = NMSStorage.getInstance();
|
||||
@@ -43,13 +48,7 @@ public class PetFix extends TabFeature {
|
||||
*/
|
||||
private static final int INTERACT_COOLDOWN = 160;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*/
|
||||
public PetFix() {
|
||||
super("Pet name fix", null);
|
||||
TabAPI.getInstance().debug("Loaded PetFix feature");
|
||||
}
|
||||
@Getter private final String featureName = "Pet name fix";
|
||||
|
||||
/**
|
||||
* Returns position of pet owner field based on server version
|
||||
@@ -118,9 +117,9 @@ public class PetFix extends TabFeature {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
|
||||
if (nms.PacketPlayOutEntityMetadata.isInstance(packet)) {
|
||||
if (PacketPlayOutEntityMetadataStorage.CLASS.isInstance(packet)) {
|
||||
Object removedEntry = null;
|
||||
List<Object> items = (List<Object>) nms.PacketPlayOutEntityMetadata_LIST.get(packet);
|
||||
List<Object> items = (List<Object>) PacketPlayOutEntityMetadataStorage.LIST.get(packet);
|
||||
if (items == null) return;
|
||||
try {
|
||||
for (Object item : items) {
|
||||
@@ -128,11 +127,11 @@ public class PetFix extends TabFeature {
|
||||
int slot;
|
||||
Object value;
|
||||
if (nms.is1_19_3Plus()) {
|
||||
slot = nms.DataWatcher$DataValue_POSITION.getInt(item);
|
||||
value = nms.DataWatcher$DataValue_VALUE.get(item);
|
||||
slot = DataWatcher.DataValue_POSITION.getInt(item);
|
||||
value = DataWatcher.DataValue_VALUE.get(item);
|
||||
} else {
|
||||
slot = nms.DataWatcherObject_SLOT.getInt(nms.DataWatcherItem_TYPE.get(item));
|
||||
value = nms.DataWatcherItem_VALUE.get(item);
|
||||
slot = DataWatcherObject.SLOT.getInt(DataWatcherItem.TYPE.get(item));
|
||||
value = DataWatcherItem.VALUE.get(item);
|
||||
}
|
||||
if (slot == petOwnerPosition) {
|
||||
if (value instanceof Optional || value instanceof com.google.common.base.Optional) {
|
||||
@@ -140,18 +139,18 @@ public class PetFix extends TabFeature {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removedEntry != null) items.remove(removedEntry);
|
||||
} catch (ConcurrentModificationException e) {
|
||||
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
|
||||
onPacketSend(receiver, packet);
|
||||
}
|
||||
if (removedEntry != null) items.remove(removedEntry);
|
||||
} else if (nms.PacketPlayOutSpawnEntityLiving.isInstance(packet) && nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER != null) {
|
||||
} else if (PacketPlayOutSpawnEntityLivingStorage.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER != null) {
|
||||
//<1.15
|
||||
DataWatcher watcher = DataWatcher.fromNMS(nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.get(packet));
|
||||
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.get(packet));
|
||||
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
|
||||
if (petOwner != null && (petOwner.getValue() instanceof Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
|
||||
watcher.removeValue(petOwnerPosition);
|
||||
nms.setField(packet, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, watcher.toNMS());
|
||||
PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.set(packet, watcher.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -9,37 +16,25 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerRespawnEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
|
||||
/**
|
||||
* An additional class with additional code for <1.9 servers due to an entity being required
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener {
|
||||
|
||||
/** Distance of the Wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the class and registers events and task
|
||||
*
|
||||
* @param plugin
|
||||
* plugin instance
|
||||
*/
|
||||
public WitherBossBar(JavaPlugin plugin) {
|
||||
|
||||
/** Reference to plugin for registering listener */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
|
||||
//when in a small window, it's about 100ms
|
||||
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
|
||||
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
@@ -54,7 +49,7 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
|
||||
if (!line.containsPlayer(p)) continue;
|
||||
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
|
||||
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-370
@@ -1,370 +0,0 @@
|
||||
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.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 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;
|
||||
|
||||
/** 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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param asm
|
||||
* Armor stand manager which this armor stand belongs to
|
||||
* @param owner
|
||||
* Owner of the armor stand
|
||||
* @param propertyName
|
||||
* Name of refresh property to use
|
||||
* @param yOffset
|
||||
* Offset in blocks
|
||||
* @param staticOffset
|
||||
* {@code true} if offset is static, {@code false} if not
|
||||
*/
|
||||
public BukkitArmorStand(BukkitArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
|
||||
this.asm = asm;
|
||||
this.owner = owner;
|
||||
this.staticOffset = 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)) {
|
||||
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
* viewer's game version.
|
||||
*
|
||||
* @param viewer
|
||||
* Player looking at the armor stand
|
||||
* @return Location where armor stand should be for specified viewer
|
||||
*/
|
||||
public Location getLocation(TabPlayer viewer) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("DONKEY")) { //1.11+
|
||||
return vehicle.getLocation().getY() + 0.525;
|
||||
}
|
||||
if (vehicle.getType() == EntityType.PIG) {
|
||||
return vehicle.getLocation().getY() + 0.325;
|
||||
}
|
||||
if (vehicle.getType().toString().equals("STRIDER")) { //1.16+
|
||||
return vehicle.getLocation().getY() + 1.15;
|
||||
}
|
||||
}
|
||||
//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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if owner is swimming, {@code false} if not
|
||||
* @return {@code true} if owner is swimming, {@code false} if not
|
||||
*/
|
||||
private boolean isSwimming() {
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
|
||||
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates data watcher with specified display name for viewer
|
||||
*
|
||||
* @param displayName
|
||||
* armor stand name
|
||||
* @param viewer
|
||||
* player to apply checks against
|
||||
* @return DataWatcher for viewer
|
||||
*/
|
||||
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
+148
-149
@@ -1,188 +1,187 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
|
||||
import me.neznamy.tab.api.feature.PacketSendListener;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Pose;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
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 {
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitNameTagX extends BackendNameTagX implements PacketSendListener {
|
||||
|
||||
/** 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.
|
||||
*
|
||||
* @param plugin
|
||||
* 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);
|
||||
}
|
||||
/** Plugin reference */
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
|
||||
super.load();
|
||||
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
|
||||
if (isPlayerDisabled(all)) continue;
|
||||
for (TabPlayer viewer : TabAPI.getInstance().getOnlinePlayers()) {
|
||||
spawnArmorStands(viewer, all);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
|
||||
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
|
||||
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
|
||||
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
|
||||
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
|
||||
if (nms.getMinorVersion() >= 17) {
|
||||
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
|
||||
if (entities instanceof List) {
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
|
||||
} else {
|
||||
//1.17.0
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
|
||||
}
|
||||
} else {
|
||||
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().toLowerCase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSneaking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSwimming(TabPlayer player) {
|
||||
Player p = (Player) player.getPlayer();
|
||||
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
|
||||
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGliding(TabPlayer player) {
|
||||
return TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSleeping(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).isSleeping();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getArmorStandType() {
|
||||
return EntityType.ARMOR_STAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getX(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getX();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getY(Object entity) {
|
||||
return ((Entity)entity).getLocation().getY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getZ(TabPlayer player) {
|
||||
return ((Player)player.getPlayer()).getLocation().getZ();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
|
||||
DataWatcher datawatcher = new DataWatcher();
|
||||
datawatcher.getHelper().setEntityFlags(flags);
|
||||
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.getHelper().setCustomNameVisible(nameVisible);
|
||||
datawatcher.getHelper().setArmorStandFlags((byte)16);
|
||||
return datawatcher;
|
||||
}
|
||||
}
|
||||
+5
-12
@@ -1,8 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -13,21 +15,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
|
||||
*
|
||||
@@ -38,7 +31,7 @@ public class EventListener implements Listener {
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
TabPlayer p = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (p == null || feature.isPlayerDisabled(p)) return;
|
||||
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,7 +42,7 @@ public class EventListener implements Listener {
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
|
||||
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (feature.isPlayerDisabled(respawned)) return;
|
||||
feature.getArmorStandManager(respawned).teleport();
|
||||
|
||||
-200
@@ -1,200 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
|
||||
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabConstants;
|
||||
import me.neznamy.tab.api.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.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
|
||||
* if trying to listen to move event.
|
||||
* For entering/leaving tracking range there are no events and
|
||||
* periodic / move-triggered distance checks would cause high CPU usage.
|
||||
*/
|
||||
public class PacketListener extends TabFeature {
|
||||
|
||||
/** Reference to the main feature */
|
||||
private final BukkitNameTagX 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
|
||||
*
|
||||
* @param nameTagX
|
||||
* main feature
|
||||
*/
|
||||
public PacketListener(BukkitNameTagX nameTagX) {
|
||||
super(nameTagX.getFeatureName(), null);
|
||||
this.nameTagX = nameTagX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
|
||||
entityIdMap.put(((Player) all.getPlayer()).getEntityId(), all);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoin(TabPlayer connectedPlayer) {
|
||||
entityIdMap.put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuit(TabPlayer disconnectedPlayer) {
|
||||
entityIdMap.remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
|
||||
}
|
||||
|
||||
@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 entity move packet. If entity ID belongs to a player,
|
||||
* armor stands of that player are teleported to player who received the packet.
|
||||
* If it belongs to a vehicle carrying a player, that player's armor stands are
|
||||
* teleported as well.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entityId
|
||||
* entity that moved
|
||||
*/
|
||||
private 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){
|
||||
//a vehicle carrying something moved
|
||||
for (Entity entity : vehicleList) {
|
||||
TabPlayer passenger = entityIdMap.get(entity.getEntityId());
|
||||
if (passenger != null && nameTagX.getArmorStandManager(passenger) != null) {
|
||||
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE_PASSENGER,
|
||||
() -> nameTagX.getArmorStandManager(passenger).teleport(receiver));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entities
|
||||
* de-spawned entities
|
||||
*/
|
||||
private void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
|
||||
for (int entity : entities) {
|
||||
onEntityDestroy(receiver, entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes entity destroy packet and destroys armor stands if
|
||||
* entity ID belongs to an online player.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entities
|
||||
* de-spawned entities
|
||||
*/
|
||||
private void onEntityDestroy(TabPlayer receiver, int... entities) {
|
||||
for (int entity : entities) {
|
||||
onEntityDestroy(receiver, entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes entity destroy packet and destroys armor stands if
|
||||
* entity ID belongs to an online player.
|
||||
*
|
||||
* @param receiver
|
||||
* packet receiver
|
||||
* @param entity
|
||||
* de-spawned entity
|
||||
*/
|
||||
private void onEntityDestroy(TabPlayer receiver, int entity) {
|
||||
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
|
||||
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer)) {
|
||||
BukkitArmorStandManager asm = nameTagX.getArmorStandManager(deSpawnedPlayer);
|
||||
TabAPI.getInstance().getThreadManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_DESTROY,
|
||||
() -> asm.destroy(receiver));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 313 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user