mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9070a6aadc | ||
|
|
c68e135c66 | ||
|
|
8693b4fe8f | ||
|
|
cb394007a4 | ||
|
|
cd92910281 | ||
|
|
5f77dd0adb | ||
|
|
4ef94d3c60 | ||
|
|
8e9b575c37 | ||
|
|
beab7d5a4f | ||
|
|
d2f5fc4d69 | ||
|
|
fb9a7f693b | ||
|
|
1d2d636270 | ||
|
|
37ec5ee442 | ||
|
|
5ed67cf5f8 | ||
|
|
76fc47d5bd | ||
|
|
aa856beee1 | ||
|
|
e6f2cc3eda | ||
|
|
02fd45587b | ||
|
|
7cef6eeb61 | ||
|
|
da919bc43b | ||
|
|
2b661d6c5b | ||
|
|
ee0ced63ef | ||
|
|
736dad5819 | ||
|
|
11f36e8a22 | ||
|
|
2cddf0b313 | ||
|
|
36a2d449b7 | ||
|
|
b6786456a2 | ||
|
|
76e8e1af6f | ||
|
|
ff55838a54 | ||
|
|
b14b6828cc | ||
|
|
be7fb67988 | ||
|
|
ae0be45d7d | ||
|
|
ba237d5dc4 | ||
|
|
bdf5cd8751 | ||
|
|
9a6d309840 | ||
|
|
9a6e92a58c | ||
|
|
1bc018918b | ||
|
|
f32f8f5cdd | ||
|
|
39585de3f0 | ||
|
|
9ce81fc68e | ||
|
|
0d56f0da98 | ||
|
|
14a3d8da12 | ||
|
|
e4fa99b865 | ||
|
|
96af2cb573 | ||
|
|
f53ec58b2f | ||
|
|
2ff30dda83 | ||
|
|
c0972f8faf | ||
|
|
9663e1aa7e | ||
|
|
d83a61b30a | ||
|
|
96d83da9b7 | ||
|
|
f1d881e0ba | ||
|
|
d1285c7e04 | ||
|
|
d4476e2d4c | ||
|
|
3124d51381 | ||
|
|
b6ae1a79d4 | ||
|
|
42d7b598bf | ||
|
|
052e828d5a | ||
|
|
55d4f8da6b | ||
|
|
1c5a4bbacc | ||
|
|
565d4bdeec | ||
|
|
3d9ef7d252 | ||
|
|
161bcbb236 | ||
|
|
c8af0ab5bc | ||
|
|
98e9c167c5 | ||
|
|
ed815c5767 | ||
|
|
f6e53ac91f | ||
|
|
3e5df50f9e | ||
|
|
1e28a93c5f | ||
|
|
717bdd3550 | ||
|
|
3bee514bb9 | ||
|
|
678d4b8db7 | ||
|
|
d76e5e7f9b | ||
|
|
9629192305 | ||
|
|
eef7debdba | ||
|
|
138d9f7f46 | ||
|
|
e4abd9ec2f | ||
|
|
8efce72e26 | ||
|
|
15ebca2d11 | ||
|
|
6583489523 | ||
|
|
5610d52813 | ||
|
|
618b61dd60 | ||
|
|
2de58f7a3b | ||
|
|
2bd94158f9 | ||
|
|
4e7ab43ac9 | ||
|
|
33529d0cfd | ||
|
|
a5b929bdf5 | ||
|
|
d10bd3358d | ||
|
|
ffe928fbe8 | ||
|
|
fd61739d96 | ||
|
|
e1602a5655 | ||
|
|
a936302fd2 | ||
|
|
7f0d4df6ca | ||
|
|
35ab0c67c5 | ||
|
|
090633140f | ||
|
|
8efe02c2ef | ||
|
|
2dbcaf029c | ||
|
|
ce758534bd | ||
|
|
15caf70170 | ||
|
|
c07c3a3a4f | ||
|
|
b9000af55e | ||
|
|
ed15ba29bb | ||
|
|
2fb8da022a | ||
|
|
ed65088cb8 | ||
|
|
f367542096 | ||
|
|
00e457542d | ||
|
|
b0c423b2dd | ||
|
|
eb5e3d637c | ||
|
|
4acd24b829 | ||
|
|
f8576834fe | ||
|
|
1e9888a8b5 | ||
|
|
d99b6643e9 | ||
|
|
0b98d98867 | ||
|
|
d040b0faf5 | ||
|
|
d53c20da56 | ||
|
|
fad035699f | ||
|
|
c7ab2b93a3 | ||
|
|
4b75144276 | ||
|
|
99edcf68c1 | ||
|
|
9b6f1ce955 | ||
|
|
b570020944 | ||
|
|
e91ba4ef79 | ||
|
|
a1fbefb13f | ||
|
|
340592d951 | ||
|
|
a1c0bf7dec | ||
|
|
a62f20469e | ||
|
|
3f1f78315b | ||
|
|
12d8bc75cd | ||
|
|
c1cf02ee9f | ||
|
|
7ac240af76 | ||
|
|
82c34a33b5 | ||
|
|
d2330337ef | ||
|
|
af38bd0383 | ||
|
|
c0a9ede377 | ||
|
|
555ba3eb73 | ||
|
|
6f0e757ad6 | ||
|
|
077b8fee96 | ||
|
|
643df47437 | ||
|
|
9e1ea4678d | ||
|
|
f3576b678b | ||
|
|
2b49bce998 | ||
|
|
896048ffff | ||
|
|
04cb14f790 | ||
|
|
bd22b0c08b | ||
|
|
95ce52bbeb | ||
|
|
6b411f5cce | ||
|
|
c15b0d178b | ||
|
|
f5ffb69675 | ||
|
|
24e59d970f | ||
|
|
4ece4b66cd | ||
|
|
7852d244bb | ||
|
|
d1c2487ba4 | ||
|
|
4ea789efcf | ||
|
|
43fc97c671 | ||
|
|
a20378d5cb | ||
|
|
7c0384c96b | ||
|
|
7d4419b300 | ||
|
|
393af9b5c5 | ||
|
|
559e423a1d | ||
|
|
a2d68e14e6 | ||
|
|
580b67d9ed | ||
|
|
080654f703 | ||
|
|
002a002749 | ||
|
|
7e888e46f9 | ||
|
|
199ca94acd | ||
|
|
67455bd47d | ||
|
|
fbb0e252c5 | ||
|
|
2566972ac7 | ||
|
|
9455294060 | ||
|
|
6870b42a1a | ||
|
|
32cd414a51 | ||
|
|
35f63b7075 | ||
|
|
f9097b11b6 | ||
|
|
dc5acdf729 | ||
|
|
e5fb25daee | ||
|
|
33b6867c02 | ||
|
|
d572973b60 | ||
|
|
3d240f0f7d | ||
|
|
3a4973d760 | ||
|
|
811c9716a2 | ||
|
|
771f6db89a | ||
|
|
97f515f573 | ||
|
|
c589dfb281 | ||
|
|
82efe5b4d1 | ||
|
|
765f593b88 | ||
|
|
21e5de25cb | ||
|
|
4333e44f6d | ||
|
|
7070fd886b | ||
|
|
89480a9018 | ||
|
|
19ce873037 | ||
|
|
977007b427 | ||
|
|
218c1c9d9a | ||
|
|
f358ac9d9c | ||
|
|
58fe2b4582 | ||
|
|
211624b1a9 | ||
|
|
6874369a48 | ||
|
|
f8c52d3577 | ||
|
|
376bf5314d | ||
|
|
2346df67a0 | ||
|
|
e63c7b43e1 | ||
|
|
578691fe8c | ||
|
|
189d8a82d5 | ||
|
|
9412d9cc28 | ||
|
|
75b2bfa14c | ||
|
|
7fcb5c8aa3 | ||
|
|
9827711111 | ||
|
|
16aecd65e6 | ||
|
|
c143ecc6ae | ||
|
|
2935f9e205 | ||
|
|
a6a33a79d4 | ||
|
|
82f9d8ddee | ||
|
|
daa6012b36 | ||
|
|
5001c82f23 | ||
|
|
59dbad9abf | ||
|
|
945c7cc7b8 | ||
|
|
3391f7646c | ||
|
|
88070aa2b2 | ||
|
|
c792296746 | ||
|
|
18a13f777d | ||
|
|
469529b324 | ||
|
|
4715a5ab9d | ||
|
|
be305162a6 | ||
|
|
7a1bc16e9d | ||
|
|
eb2c80deb8 | ||
|
|
0863b77bf0 | ||
|
|
769a698509 | ||
|
|
53870b2c81 | ||
|
|
e198a5df88 | ||
|
|
ac947f698b | ||
|
|
ecabd62b6b | ||
|
|
f350d200ac | ||
|
|
97d5a3f7ef | ||
|
|
f15f3efc3f | ||
|
|
7563b6b946 | ||
|
|
421c53e6be | ||
|
|
3db8666951 | ||
|
|
ecc6e01cf9 | ||
|
|
becf81db00 | ||
|
|
92f5b337c9 | ||
|
|
3ddf884c23 | ||
|
|
116117af55 | ||
|
|
0980bcfca5 | ||
|
|
20e2e8ded6 | ||
|
|
13af0b7b2b | ||
|
|
cbcbfdcbf1 | ||
|
|
38eb294667 | ||
|
|
71d16ff906 | ||
|
|
395058d771 | ||
|
|
d8af7c454c | ||
|
|
2f10084e7a | ||
|
|
5af387b32d | ||
|
|
4b7144f705 | ||
|
|
0fbbbb6485 | ||
|
|
f033d78785 | ||
|
|
2ea79eb57d | ||
|
|
226ee0db9d | ||
|
|
8a62f21e84 | ||
|
|
c353c39086 | ||
|
|
2b2c877ab2 | ||
|
|
016af4bfee | ||
|
|
b3d26d91de | ||
|
|
a7a7f409eb | ||
|
|
f44ccec712 | ||
|
|
1c0887733c | ||
|
|
7ee3c35785 | ||
|
|
01b725cf38 | ||
|
|
1ac6d90633 | ||
|
|
9704193237 | ||
|
|
69b150b391 | ||
|
|
7ffb3edda5 | ||
|
|
046eb68ce6 | ||
|
|
816d80a27c | ||
|
|
957037e5f4 | ||
|
|
9ca179a71f | ||
|
|
cb4d96f1ba | ||
|
|
7d57522775 | ||
|
|
902565f1b1 | ||
|
|
2f8de436af | ||
|
|
38bd035d99 | ||
|
|
88b6d1a3ca | ||
|
|
a74878e7bb | ||
|
|
2b6eba7c4d | ||
|
|
0de1b86ef5 | ||
|
|
ae0ab6471b | ||
|
|
c24cdaa8a4 | ||
|
|
df1442dd8a | ||
|
|
b17d5306db | ||
|
|
88d5294b8e | ||
|
|
8c3405d01f | ||
|
|
9f83ba700c | ||
|
|
ae147211ca | ||
|
|
01d305eba0 | ||
|
|
08c30574a9 | ||
|
|
39c4725a2f | ||
|
|
7002aeefaf | ||
|
|
f1201cc5bc | ||
|
|
0b99d4ac52 | ||
|
|
642cb701dd | ||
|
|
66d6aee115 | ||
|
|
38d8eb1108 | ||
|
|
f9941c5dff | ||
|
|
855a6f447a | ||
|
|
fa0a1f8553 | ||
|
|
0d1fee1636 | ||
|
|
ec8269332f | ||
|
|
00fe9400e6 | ||
|
|
8389b1e151 | ||
|
|
2289d10d6c | ||
|
|
b30af4f144 | ||
|
|
041322c53f | ||
|
|
548968bcfc | ||
|
|
6d405b70e7 | ||
|
|
1b4d4acc63 | ||
|
|
6f498089fb | ||
|
|
8e27cba0f6 | ||
|
|
ae48041a96 | ||
|
|
bad16a4cdc | ||
|
|
07baf769dd | ||
|
|
ab409643f6 | ||
|
|
c745a61601 | ||
|
|
c4e8e83d1e | ||
|
|
9133f3d792 | ||
|
|
b779d584a5 | ||
|
|
05bb61b3ab | ||
|
|
5d2885e662 | ||
|
|
ef645c1058 | ||
|
|
52a476fd0a | ||
|
|
7386c45f06 | ||
|
|
1a508ddedf | ||
|
|
a8675f254a | ||
|
|
0f70cd789e | ||
|
|
b8f594f42c | ||
|
|
7857527ce1 | ||
|
|
05c41a5469 | ||
|
|
f11f8afdc4 | ||
|
|
fa499aa46d | ||
|
|
e8a7cc026f | ||
|
|
433ef6a8a7 | ||
|
|
fc4b5a7bcd | ||
|
|
bd43134d58 | ||
|
|
557970ba4e | ||
|
|
d3418e0067 | ||
|
|
b8d1845d4e | ||
|
|
32e360409d | ||
|
|
c54a778af9 | ||
|
|
ce25698f87 | ||
|
|
4d95cc838a | ||
|
|
67fa0a231e | ||
|
|
3c2e8c81c4 |
No files matched your search
@@ -1 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Support
|
||||
url: https://discord.gg/YPqXt63YQj
|
||||
about: Fresh new community discord providing support with all plugins, including TAB
|
||||
@@ -8,14 +8,14 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
|
||||
- uses: actions/checkout@v4 #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 17
|
||||
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4 #https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 17
|
||||
java-version: 21
|
||||
- name: Cache SonarCloud packages
|
||||
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
|
||||
with:
|
||||
|
||||
@@ -6,16 +6,16 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
|
||||
# Download
|
||||
### Releases
|
||||
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/) or [Modrinth](https://modrinth.com/plugin/tab-was-taken).
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
|
||||
|
||||
### Dev builds
|
||||
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
|
||||
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
|
||||
If you want to benefit from a recent change in the code before official release, you can download development builds
|
||||
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions).
|
||||
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions) (you must be logged into GitHub to be able to download it).
|
||||
|
||||
# Compiling
|
||||
Compilation requires JDK 17 and up.
|
||||
Compilation requires JDK 21 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
@@ -46,12 +46,13 @@ Here is the full list of potential services and their status:
|
||||
| Wiki updates<sup>1</sup> | ✔ |
|
||||
| Feature requests | ❌ |
|
||||
| Customer support<sup>2</sup> | ✔ |
|
||||
| Free user support<sup>3</sup> | ❌ |
|
||||
| Free user support<sup>3</sup> | ❌* |
|
||||
| Optimization<sup>4</sup> | ✔ |
|
||||
|
||||
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
|
||||
<sup>2</sup> - Assistance with the plugin on discord, including answering any questions and helping with
|
||||
configuration to those, who bought a copy of the plugin when it was purchasable.
|
||||
<sup>3</sup> - Like <sup>2</sup>, but for anyone in the world who asks for it.
|
||||
<sup>2</sup> - Unrestricted assistance with the plugin for those, who bought a copy of the plugin when it was purchasable.
|
||||
<sup>3</sup> - Unlimited access to my free time for everyone in the world.
|
||||
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
|
||||
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
|
||||
|
||||
*Instead, you can use [this fresh new community discord](https://discord.gg/YPqXt63YQj) made by a nice person who will provide this service.
|
||||
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:24.0.1")
|
||||
compileOnlyApi("org.jetbrains:annotations:24.1.0")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.neznamy.tab.api;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -8,6 +9,7 @@ import java.util.UUID;
|
||||
/**
|
||||
* An interface representing a player
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabPlayer {
|
||||
|
||||
/**
|
||||
@@ -27,7 +29,7 @@ public interface TabPlayer {
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return an instance of bukkit/bungee player depending on platform
|
||||
* @return platform's player object
|
||||
*/
|
||||
@NotNull Object getPlayer();
|
||||
|
||||
@@ -66,4 +68,27 @@ public interface TabPlayer {
|
||||
* @see #setTemporaryGroup(String)
|
||||
*/
|
||||
boolean hasTemporaryGroup();
|
||||
|
||||
/**
|
||||
* Changes expected profile name of the player. This adapts all name-bound features
|
||||
* to use this new profile name if another plugin changed profile name of the player. <p>
|
||||
* Automatic profile name change detection is available on Bukkit, BungeeCord and Fabric,
|
||||
* therefore using this is redundant there. This function is only needed on Sponge and Velocity,
|
||||
* where the detection is not available. <p>
|
||||
* Warning: This function does NOT change player's profile name, it only updates the tracked
|
||||
* name inside the plugin.
|
||||
*
|
||||
* @param profileName
|
||||
* New expected profile name
|
||||
*/
|
||||
void setExpectedProfileName(@NonNull String profileName);
|
||||
|
||||
/**
|
||||
* Returns player's expected profile name. This defaults to player's username, but may
|
||||
* get changed by other plugins and then detected by TAB.
|
||||
*
|
||||
* @return Player's expected profile name
|
||||
*/
|
||||
@NotNull
|
||||
String getExpectedProfileName();
|
||||
}
|
||||
@@ -4,13 +4,27 @@ package me.neznamy.tab.api.bossbar;
|
||||
* An enum representing all available BossBar colors.
|
||||
* Calling ordinal() will return color's network ID.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarColor {
|
||||
|
||||
/** Pink color */
|
||||
PINK,
|
||||
|
||||
/** Blue color */
|
||||
BLUE,
|
||||
|
||||
/** Red color */
|
||||
RED,
|
||||
|
||||
/** Green color */
|
||||
GREEN,
|
||||
|
||||
/** Yellow color */
|
||||
YELLOW,
|
||||
|
||||
/** Purple color */
|
||||
PURPLE,
|
||||
|
||||
/** White color */
|
||||
WHITE
|
||||
}
|
||||
@@ -4,11 +4,21 @@ package me.neznamy.tab.api.bossbar;
|
||||
* An enum representing all available BossBar styles.
|
||||
* Calling ordinal() will return style's network ID.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public enum BarStyle {
|
||||
|
||||
/** Fully solid line */
|
||||
PROGRESS,
|
||||
|
||||
/** Line segmented into 6 parts */
|
||||
NOTCHED_6,
|
||||
|
||||
/** Line segmented into 10 parts */
|
||||
NOTCHED_10,
|
||||
|
||||
/** Line segmented into 12 parts */
|
||||
NOTCHED_12,
|
||||
|
||||
/** Line segmented into 20 parts */
|
||||
NOTCHED_20
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the BossBar feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface BossBarManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ import lombok.NonNull;
|
||||
* The event bus. This is used to register event listeners/handlers for
|
||||
* handling events that may be fired by TAB.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface EventBus {
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the Team feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface NameTagManager {
|
||||
|
||||
/**
|
||||
@@ -150,13 +151,51 @@ public interface NameTagManager {
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns custom prefix assigned using {@link #setPrefix(TabPlayer, String)}.
|
||||
* If no custom prefix is set, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom prefix of
|
||||
* @return Custom prefix assigned using the API.
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable
|
||||
String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns custom suffix assigned using {@link #setSuffix(TabPlayer, String)}.
|
||||
* If no custom suffix is set, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom suffix of
|
||||
* @return Custom suffix assigned using the API.
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
*/
|
||||
@Nullable
|
||||
String getCustomSuffix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns original prefix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original prefix of
|
||||
* @return Original prefix assigned by the plugin's internal logic.
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NonNull
|
||||
String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original suffix of
|
||||
* @return Original suffix assigned by the plugin's internal logic.
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NonNull
|
||||
String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Toggles name tag visibility view on all players for specified player.
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
|
||||
* return {@code null} or not be an instance of this class.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface UnlimitedNameTagManager extends NameTagManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.function.Supplier;
|
||||
* An interface allowing placeholder registration. Instance can be
|
||||
* obtained using {@link TabAPI#getPlaceholderManager()}
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface PlaceholderManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for player placeholders (placeholders returning different value for each player)
|
||||
@@ -18,7 +19,7 @@ public interface PlayerPlaceholder extends Placeholder {
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
|
||||
void updateValue(@NonNull TabPlayer player, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
|
||||
@@ -2,10 +2,12 @@ package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for relational placeholders (returning different value for each player duo)
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface RelationalPlaceholder extends Placeholder {
|
||||
|
||||
/**
|
||||
@@ -20,7 +22,7 @@ public interface RelationalPlaceholder extends Placeholder {
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
|
||||
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.neznamy.tab.api.placeholder;
|
||||
|
||||
import lombok.NonNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for server placeholders (returning same value for all players)
|
||||
@@ -15,7 +15,7 @@ public interface ServerPlaceholder extends Placeholder {
|
||||
* @param value
|
||||
* New value
|
||||
*/
|
||||
void updateValue(@NonNull Object value);
|
||||
void updateValue(@Nullable Object value);
|
||||
|
||||
/**
|
||||
* Force-updates placeholder value using previously provided update function.
|
||||
|
||||
@@ -11,6 +11,7 @@ import java.util.List;
|
||||
* {@link ScoreboardManager#createScoreboard(String, String, List)},
|
||||
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Scoreboard {
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the scoreboard feature to be enabled in config,
|
||||
* the method will return {@code null} otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface ScoreboardManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the Header/footer feature to be enabled in config, otherwise
|
||||
* the method will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface HeaderFooterManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package me.neznamy.tab.api.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for managing player sorting by directly configuring
|
||||
* team names of players. Instance can be obtained using
|
||||
* {@link TabAPI#getSortingManager()}. If the feature is disabled,
|
||||
* it will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface SortingManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* This requires the feature to be enabled in config, otherwise the method will
|
||||
* return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface TabListFormatManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
/**
|
||||
* Interface representing a layout.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface Layout {
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
|
||||
* config, it will return {@code null}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface LayoutManager {
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,6 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.0.1")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.1-SNAPSHOT")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.6-SNAPSHOT")
|
||||
}
|
||||
@@ -31,6 +31,6 @@ tasks {
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(17))
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
+2
-10
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.1.2"
|
||||
version = "4.1.3"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -19,15 +19,7 @@ val platforms = setOf(
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric,
|
||||
projects.fabric.v1152,
|
||||
projects.fabric.v1165,
|
||||
projects.fabric.v1171,
|
||||
projects.fabric.v1182,
|
||||
projects.fabric.v1192,
|
||||
projects.fabric.v1193,
|
||||
projects.fabric.v1201,
|
||||
projects.fabric.v1202,
|
||||
projects.fabric.v1204
|
||||
projects.fabric.v1144
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
@@ -17,23 +17,47 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* The core for bukkit forwarding events into all enabled features
|
||||
*/
|
||||
public class BukkitEventListener extends EventListener<Player> implements Listener {
|
||||
public class BukkitEventListener implements EventListener<Player>, Listener {
|
||||
|
||||
/**
|
||||
* Listens to player quit event.
|
||||
*
|
||||
* @param e
|
||||
* Disconnect event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onQuit(PlayerQuitEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to player join event.
|
||||
*
|
||||
* @param e
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to world change event.
|
||||
*
|
||||
* @param e
|
||||
* World change event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to command event to potentially cancel it.
|
||||
*
|
||||
* @param e
|
||||
* Command event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
|
||||
|
||||
@@ -1,42 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Pipeline injection for bukkit
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
private static Method getHandle;
|
||||
private static Field PLAYER_CONNECTION;
|
||||
private static Field NETWORK_MANAGER;
|
||||
private static Field CHANNEL;
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
/** Function for getting player's channel */
|
||||
private static FunctionWithException<TabPlayer, Channel> getChannel;
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
@@ -45,185 +28,48 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
available = true;
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"network channel injection due to a compatibility error. This will make the following features not work: " +
|
||||
"Unlimited nametag mode, anti-override for tablist formatting & nametags, detecting nickname change for compatibility " +
|
||||
"with nick plugins and compatibility with other scoreboard plugins. " +
|
||||
"Please update the plugin a to version with native support for your server version to unlock the features.");
|
||||
BukkitUtils.compatibilityError(e, "network channel injection", null,
|
||||
"Unlimited nametag mode not working and being replaced with regular nametags",
|
||||
"Anti-override for tablist & nametags not working",
|
||||
"Compatibility with nickname plugins changing player names will not work",
|
||||
"Scoreboard will not be checking for other plugins");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if pipeline injection is available, {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if pipeline injection is available, {@code false} if not
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return getChannel != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
if (!PacketScoreboard.isAvailable()) return;
|
||||
int position;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
position = ((Enum<?>)PacketScoreboard.displayPacketData.DisplayObjective_POSITION.get(packet)).ordinal();
|
||||
} else {
|
||||
position = PacketScoreboard.displayPacketData.DisplayObjective_POSITION.getInt(packet);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
|
||||
(String) PacketScoreboard.displayPacketData.DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
if (!PacketScoreboard.isAvailable()) return;
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
PacketScoreboard.Objective_METHOD.getInt(packet),
|
||||
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
if (!PacketScoreboard.isAvailable()) return false;
|
||||
return PacketScoreboard.displayPacketData.DisplayObjectiveClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
if (!PacketScoreboard.isAvailable()) return false;
|
||||
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
if (!PacketScoreboard.isAvailable()) return false;
|
||||
return PacketScoreboard.teamPacketData.TeamPacketClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return BukkitTabList.PlayerInfoClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
onPlayerInfo1_19_3(receiver, packet);
|
||||
} else {
|
||||
onPlayerInfo1_19_2(receiver, packet);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void onPlayerInfo1_19_3(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
List<String> actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id;
|
||||
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
|
||||
Object displayName = null;
|
||||
int latency = 0;
|
||||
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name())) {
|
||||
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
|
||||
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
|
||||
}
|
||||
if (actions.contains(TabList.Action.UPDATE_LATENCY.name())) {
|
||||
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
|
||||
}
|
||||
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
|
||||
}
|
||||
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
|
||||
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
|
||||
id,
|
||||
profile,
|
||||
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
|
||||
latency,
|
||||
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
|
||||
}
|
||||
BukkitTabList.PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void onPlayerInfo1_19_2(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
String action = BukkitTabList.ACTION.get(packet).toString();
|
||||
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(TabList.Action.UPDATE_DISPLAY_NAME.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
|
||||
Object displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
|
||||
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
|
||||
BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
|
||||
}
|
||||
if (action.equals(TabList.Action.UPDATE_LATENCY.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
|
||||
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
|
||||
BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
|
||||
}
|
||||
if (action.equals(TabList.Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLogin(@NotNull Object packet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
|
||||
if (!PacketScoreboard.isAvailable()) return;
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
int action = PacketScoreboard.teamPacketData.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
|
||||
if (action == 1 || action == 2 || action == 4) return;
|
||||
Collection<String> players = (Collection<String>) PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
|
||||
String teamName = (String) PacketScoreboard.teamPacketData.TeamPacket_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 = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam == null) {
|
||||
newList.add(entry);
|
||||
continue;
|
||||
}
|
||||
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
|
||||
logTeamOverride(teamName, p.getName(), expectedTeam);
|
||||
} else {
|
||||
newList.add(entry);
|
||||
}
|
||||
}
|
||||
PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
|
||||
return getChannel.apply(player);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.*;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
@@ -17,44 +13,12 @@ public class BukkitTAB extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!isVersionSupported()) {
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
|
||||
new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
//null check due to compatibility check making instance not get set on unsupported versions
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all used NMS classes, constructors, fields and methods.
|
||||
* Returns {@code true} if everything went successfully,
|
||||
* {@code false} if anything went wrong.
|
||||
*
|
||||
* @return {@code true} if server version is compatible, {@code false} if not
|
||||
*/
|
||||
private boolean isVersionSupported() {
|
||||
try {
|
||||
DataWatcher.load();
|
||||
PacketEntityView.load();
|
||||
BukkitTabList.load();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
|
||||
Bukkit.getConsoleSender().sendMessage(String.format(
|
||||
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
|
||||
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5.getFriendlyName(), ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName()
|
||||
));
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] A compatibility issue " +
|
||||
"with your server was found. Unless you are running some really weird server software, this is a bug.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 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 tab list
|
||||
* and packet fields.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class BukkitTabList implements TabList {
|
||||
|
||||
public static Class<?> PlayerInfoClass;
|
||||
private static Constructor<?> newPlayerInfo;
|
||||
public static Field ACTION;
|
||||
public static Field PLAYERS;
|
||||
private static Class<Enum> ActionClass;
|
||||
public static Class<?> ClientboundPlayerInfoRemovePacket;
|
||||
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
|
||||
private static Class<?> EntityPlayer;
|
||||
|
||||
public static Constructor<?> newPlayerInfoData;
|
||||
public static Field PlayerInfoData_UUID;
|
||||
public static Field PlayerInfoData_Profile;
|
||||
public static Field PlayerInfoData_Latency;
|
||||
public static Field PlayerInfoData_GameMode;
|
||||
public static Field PlayerInfoData_DisplayName;
|
||||
public static Field PlayerInfoData_Listed;
|
||||
public static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
private static Object[] gameModes;
|
||||
|
||||
@Nullable
|
||||
private static SkinData skinData;
|
||||
|
||||
private static Method ChatSerializer_DESERIALIZE;
|
||||
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
public static void load() throws NoSuchMethodException, ClassNotFoundException {
|
||||
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported
|
||||
|
||||
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
|
||||
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
|
||||
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang 1.19.3+
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a", // Bukkit 1.19.3+
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumPlayerInfoAction" // Bukkit 1.8.0
|
||||
);
|
||||
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
|
||||
Class<?> playerInfoDataClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang 1.19.3
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b", // Bukkit 1.19.3+
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
|
||||
"PlayerInfoData" // Bukkit 1.8.0
|
||||
);
|
||||
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
|
||||
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
ClientboundPlayerInfoRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket");
|
||||
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
|
||||
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
|
||||
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
|
||||
} else {
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
|
||||
}
|
||||
gameModes = new Object[] {
|
||||
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
|
||||
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
|
||||
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
|
||||
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
|
||||
};
|
||||
|
||||
try {
|
||||
skinData = new SkinData();
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"getting player's game profile due to a compatibility error. This will " +
|
||||
"result in player skins not properly working in layout feature. " +
|
||||
"Please update the plugin a to version with native support for your server version for optimal experience.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
if (PlayerInfoClass == null) return;
|
||||
if (ClientboundPlayerInfoRemovePacket != null) {
|
||||
//1.19.3+
|
||||
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
} else {
|
||||
//1.19.2-
|
||||
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
if (PlayerInfoClass == null) return;
|
||||
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
if (PlayerInfoClass == null) return;
|
||||
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
if (PlayerInfoClass == null) return;
|
||||
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
if (PlayerInfoClass == null) return;
|
||||
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
|
||||
Object packet;
|
||||
List<Object> players = new ArrayList<>();
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
EnumSet<?> actions;
|
||||
if (action == Action.ADD_PLAYER) {
|
||||
actions = EnumSet.allOf(ActionClass);
|
||||
} else {
|
||||
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
|
||||
}
|
||||
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
|
||||
players.add(newPlayerInfoData.newInstance(
|
||||
entry.getUniqueId(),
|
||||
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
|
||||
true,
|
||||
entry.getLatency(),
|
||||
gameModes[entry.getGameMode()],
|
||||
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
|
||||
null
|
||||
));
|
||||
} else {
|
||||
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
|
||||
parameters.add(entry.getLatency());
|
||||
parameters.add(gameModes[entry.getGameMode()]);
|
||||
parameters.add(entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()));
|
||||
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
|
||||
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
|
||||
}
|
||||
PLAYERS.set(packet, players);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public Object toComponent(IChatBaseComponent component) {
|
||||
return componentCache.get(component, player.getVersion());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name == null ? "" : name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
if (skinData == null) return null;
|
||||
return skinData.getSkin(player);
|
||||
}
|
||||
|
||||
private static class SkinData {
|
||||
|
||||
private final Method getHandle;
|
||||
private final Method getProfile;
|
||||
|
||||
@SneakyThrows
|
||||
public SkinData() {
|
||||
Class<?> EntityHuman = BukkitReflection.getClass("world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman");
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
// There is only supposed to be one, however there are exceptions:
|
||||
// #1 - CatServer adds another method
|
||||
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
|
||||
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
|
||||
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
public Skin getSkin(@NotNull BukkitTabPlayer player) {
|
||||
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player.getPlayer()))).getProperties().get(TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return new Skin(
|
||||
(String) property.getClass().getMethod("value").invoke(property),
|
||||
(String) property.getClass().getMethod("signature").invoke(property)
|
||||
);
|
||||
} else {
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,20 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BossBarLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.PacketEntityView;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.backend.entityview.DummyEntityView;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
@@ -33,20 +28,17 @@ import org.jetbrains.annotations.Nullable;
|
||||
@Getter
|
||||
public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
|
||||
|
||||
@NotNull
|
||||
private final Scoreboard<BukkitTabPlayer> scoreboard = ScoreboardLoader.getInstance().apply(this);
|
||||
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
|
||||
|
||||
@NotNull
|
||||
private final BukkitTabList tabList = new BukkitTabList(this);
|
||||
private final BossBar bossBar = BossBarLoader.findInstance(this);
|
||||
|
||||
@NotNull
|
||||
private final BossBar bossBar = BukkitReflection.getMinorVersion() >= 9 ? new BukkitBossBar(this) :
|
||||
getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
|
||||
|
||||
@NotNull
|
||||
private final EntityView entityView = new PacketEntityView(this);
|
||||
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given bukkit player
|
||||
@@ -57,7 +49,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
* bukkit player
|
||||
*/
|
||||
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,23 +58,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getPing() {
|
||||
return PingRetriever.getPing(getPlayer());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void sendPacket(@NotNull Object nmsPacket) {
|
||||
PacketSender.sendPacket(getPlayer(), nmsPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
if (spigot) {
|
||||
getPlayer().spigot().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
|
||||
} else {
|
||||
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
|
||||
}
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,11 +89,6 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
return (Player) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isOnline();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BukkitPlatform getPlatform() {
|
||||
return (BukkitPlatform) platform;
|
||||
@@ -119,7 +96,10 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
|
||||
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
|
||||
if (v.asBoolean()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3,12 +3,12 @@ package me.neznamy.tab.platforms.bukkit;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -18,6 +18,11 @@ import java.util.Collection;
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BukkitUtils {
|
||||
|
||||
/** Whether compatibility exceptions should be printed or not, enabling when adding support for new versions */
|
||||
public static final boolean PRINT_EXCEPTIONS = false;
|
||||
|
||||
private static boolean compatibilityIssue;
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
|
||||
* and we want to avoid errors.
|
||||
@@ -39,34 +44,45 @@ public class BukkitUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
|
||||
* If not, closest legacy color is used instead.
|
||||
* Prints a console warn that some compatibility issue was found.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param rgbClient
|
||||
* Whether client accepts RGB colors or not.
|
||||
* @return Converted string using bukkit color format
|
||||
* @param exception
|
||||
* Exception thrown when initializing fields, if applicable
|
||||
* @param failedCheck
|
||||
* Check that failed
|
||||
* @param fallback
|
||||
* Fallback implementation, if available
|
||||
* @param missingFeatures
|
||||
* Features that will be broken because of the incompatibility
|
||||
*/
|
||||
@NotNull
|
||||
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
|
||||
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
|
||||
@Nullable String fallback, @NotNull String... missingFeatures) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (BukkitReflection.getMinorVersion() >= 16 && rgbClient) {
|
||||
String hexCode = component.getModifier().getColor().getHexCode();
|
||||
char c = EnumChatFormat.COLOR_CHAR;
|
||||
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
|
||||
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
|
||||
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
|
||||
} else {
|
||||
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
|
||||
}
|
||||
sb.append(EnumChatFormat.RED);
|
||||
sb.append("[TAB] Failed to initialize minecraft fields for ");
|
||||
sb.append(failedCheck);
|
||||
sb.append(" due to a compatibility error. ");
|
||||
if (fallback != null) {
|
||||
sb.append("Using fallback solution using ").append(fallback).append(". ");
|
||||
} else {
|
||||
sb.append("No fallback solution was found. ");
|
||||
}
|
||||
sb.append(component.getModifier().getMagicCodes());
|
||||
if (component.getText() != null) sb.append(component.getText());
|
||||
for (IChatBaseComponent extra : component.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
if (missingFeatures.length > 0) sb.append("This will result in: ");
|
||||
for (int i=0; i<missingFeatures.length; i++) {
|
||||
sb.append("\n").append("#").append(i + 1).append(": ").append(missingFeatures[i]);
|
||||
}
|
||||
return sb.toString();
|
||||
Bukkit.getConsoleSender().sendMessage(sb.toString());
|
||||
if (PRINT_EXCEPTIONS) exception.printStackTrace();
|
||||
compatibilityIssue = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message asking user to update the plugin if some compatibility issue was found.
|
||||
*/
|
||||
public static void sendCompatibilityMessage() {
|
||||
if (!compatibilityIssue) return;
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
|
||||
"a version with native support for your server version for optimal experience. This plugin version " +
|
||||
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class for finding BossBar implementation based on server and client version.
|
||||
*/
|
||||
public class BossBarLoader {
|
||||
|
||||
/**
|
||||
* Finds best available instance for given player.
|
||||
*
|
||||
* @param player
|
||||
* Player to find instance for
|
||||
* @return BossBar instance for player
|
||||
*/
|
||||
@NotNull
|
||||
public static BossBar findInstance(@NotNull BukkitTabPlayer player) {
|
||||
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
|
||||
if (BukkitReflection.getMinorVersion() >= 9) return new BukkitBossBar(player);
|
||||
|
||||
// 1.9+ player on 1.8 server, handle using ViaVersion API
|
||||
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar(player);
|
||||
|
||||
// 1.8- server and player, use wither
|
||||
return new EntityBossBar(player);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@ package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -36,7 +35,7 @@ public class BukkitBossBar implements BossBar {
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (bossBars.containsKey(id)) return;
|
||||
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
|
||||
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
|
||||
player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
styles[style.ordinal()]
|
||||
);
|
||||
@@ -47,7 +46,7 @@ public class BukkitBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bossBars.get(id).setTitle(BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16));
|
||||
bossBars.get(id).setTitle(player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,7 @@ import me.neznamy.tab.shared.platform.BossBar;
|
||||
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;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -20,6 +20,15 @@ import java.util.UUID;
|
||||
@RequiredArgsConstructor
|
||||
public class EntityBossBar implements BossBar {
|
||||
|
||||
/** Max health of wither */
|
||||
private static final int WITHER_MAX_HEALTH = 300;
|
||||
|
||||
/** Ideal value for invulnerable time for minimal wither size */
|
||||
private static final int IDEAL_INVULNERABLE_TIME = 880;
|
||||
|
||||
/** Flag making entity invisible */
|
||||
private static final byte INVISIBLE_FLAG = 1 << 5;
|
||||
|
||||
/** Player this handler belongs to */
|
||||
@NotNull
|
||||
private final BukkitTabPlayer player;
|
||||
@@ -27,12 +36,12 @@ public class EntityBossBar implements BossBar {
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
float health = WITHER_MAX_HEALTH*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.setHealth(health);
|
||||
w.setCustomName(title, player.getVersion());
|
||||
w.setEntityFlags((byte) 32);
|
||||
w.setWitherInvulnerableTime(880); // Magic number
|
||||
w.setEntityFlags(INVISIBLE_FLAG);
|
||||
w.setWitherInvulnerableTime(IDEAL_INVULNERABLE_TIME);
|
||||
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
|
||||
}
|
||||
|
||||
@@ -46,7 +55,7 @@ public class EntityBossBar implements BossBar {
|
||||
@Override
|
||||
public void update(@NotNull UUID id, float progress) {
|
||||
DataWatcher w = new DataWatcher();
|
||||
float health = 300*progress;
|
||||
float health = WITHER_MAX_HEALTH*progress;
|
||||
if (health == 0) health = 1;
|
||||
w.setHealth(health);
|
||||
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
|
||||
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
@@ -36,7 +36,7 @@ public class ViaBossBar implements BossBar {
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
if (viaBossBars.containsKey(id)) return;
|
||||
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
|
||||
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
|
||||
TabComponent.optimized(title).toLegacyText(),
|
||||
progress,
|
||||
BossColor.valueOf(color.name()),
|
||||
styles[style.ordinal()]
|
||||
@@ -47,7 +47,7 @@ public class ViaBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
viaBossBars.get(id).setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
|
||||
viaBossBars.get(id).setTitle(TabComponent.optimized(title).toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
package me.neznamy.tab.platforms.bukkit.entity;
|
||||
|
||||
import lombok.*;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
@ToString
|
||||
public class DataWatcher implements EntityData {
|
||||
|
||||
private static Object DataWatcherSerializer_BYTE;
|
||||
private static Object DataWatcherSerializer_FLOAT;
|
||||
private static Object DataWatcherSerializer_STRING;
|
||||
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
|
||||
private static Object DataWatcherSerializer_BOOLEAN;
|
||||
|
||||
private static final int armorStandFlagsPosition = EntityData.getArmorStandFlagsPosition(BukkitReflection.getMinorVersion());
|
||||
|
||||
/** 1.19.3+ */
|
||||
private static Constructor<?> newDataWatcher$Item;
|
||||
|
||||
/** 1.19.2- */
|
||||
public static Class<?> DataWatcher;
|
||||
private static Constructor<?> newDataWatcher;
|
||||
private static Method DataWatcher_register;
|
||||
private static Constructor<?> newDataWatcherObject;
|
||||
|
||||
/** Watched data */
|
||||
private final Map<Integer, Item> dataValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
if (minorVersion >= 9) {
|
||||
loadSerializers();
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
Class<?> dataWatcher$Item = BukkitReflection.getClass("network.syncher.SynchedEntityData$DataValue", "network.syncher.DataWatcher$c");
|
||||
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
|
||||
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
|
||||
newDataWatcher$Item = dataWatcher$Item.getConstructor(int.class, dataWatcherSerializer, Object.class);
|
||||
} else {
|
||||
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
|
||||
if (minorVersion >= 7) {
|
||||
ComponentConverter.ensureAvailable();
|
||||
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
|
||||
} else {
|
||||
newDataWatcher = DataWatcher.getConstructor();
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
Class<?> dataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
|
||||
"network.syncher.DataWatcherObject", "DataWatcherObject");
|
||||
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
|
||||
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
|
||||
dataWatcherObject, Object.class
|
||||
);
|
||||
newDataWatcherObject = dataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
|
||||
} else {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
|
||||
Object.class
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadSerializers() throws ReflectiveOperationException {
|
||||
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
|
||||
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
|
||||
DataWatcherSerializer_BYTE = ReflectionUtils.getField(dataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(dataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_STRING = ReflectionUtils.getField(dataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
|
||||
}
|
||||
} else {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
|
||||
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
|
||||
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = dataWatcherRegistry.getDeclaredField("h").get(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
*
|
||||
* @param position
|
||||
* data position
|
||||
* @param serializer
|
||||
* Object serializer (1.9+)
|
||||
* @param value
|
||||
* value
|
||||
*/
|
||||
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
|
||||
dataValues.put(position, new Item(position, serializer, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
setValue(0, DataWatcherSerializer_BYTE, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
|
||||
*
|
||||
* @param customName
|
||||
* target custom name
|
||||
* @param clientVersion
|
||||
* client version
|
||||
*/
|
||||
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT, Optional.of(TabComponent.optimized(customName).convert(clientVersion)));
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
setValue(2, DataWatcherSerializer_STRING, customName);
|
||||
} else {
|
||||
//name length is limited to 64 characters on <1.8
|
||||
String cutName = (customName.length() > Limitations.BOSSBAR_NAME_LENGTH_1_7 ?
|
||||
customName.substring(0, Limitations.BOSSBAR_NAME_LENGTH_1_7) : customName);
|
||||
if (BukkitReflection.getMinorVersion() >= 6) {
|
||||
setValue(10, null, cutName);
|
||||
} else {
|
||||
setValue(5, null, cutName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
*
|
||||
* @param visible
|
||||
* if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
|
||||
} else {
|
||||
setValue(3, null, (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
*
|
||||
* @param health
|
||||
* health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (BukkitReflection.getMinorVersion() >= 6) {
|
||||
setValue(6, DataWatcherSerializer_FLOAT, health);
|
||||
} else {
|
||||
setValue(16, null, (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes wither invulnerable time
|
||||
* @param time
|
||||
* Time, apparently
|
||||
*/
|
||||
public void setWitherInvulnerableTime(int time) {
|
||||
if (BukkitReflection.getMinorVersion() > 8)
|
||||
throw new UnsupportedOperationException("Not supported on 1.9+");
|
||||
setValue(20, null, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
*
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object build() {
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
List<Object> items = new ArrayList<>();
|
||||
for (Item item : dataValues.values()) {
|
||||
items.add(newDataWatcher$Item.newInstance(item.position, item.serializer, item.value));
|
||||
}
|
||||
return items;
|
||||
} else {
|
||||
Object nmsWatcher;
|
||||
if (newDataWatcher.getParameterCount() == 1) { //1.7+
|
||||
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = newDataWatcher.newInstance();
|
||||
}
|
||||
for (Item item : dataValues.values()) {
|
||||
Object nmsObject = item.createObject();
|
||||
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for specific data watcher item.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class Item {
|
||||
|
||||
/** Item position */
|
||||
private final int position;
|
||||
|
||||
/** Item serializer (1.9+) */
|
||||
@Nullable
|
||||
private final Object serializer;
|
||||
|
||||
/** Item value */
|
||||
@NonNull
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates data watcher object with position and serializer
|
||||
*
|
||||
* @return NMS data watcher object
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object createObject() {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
return newDataWatcherObject.newInstance(position, serializer);
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
package me.neznamy.tab.platforms.bukkit.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.util.*;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* EntityView implementation for Bukkit using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PacketEntityView implements EntityView {
|
||||
|
||||
/** Version with entity metadata being split from spawn packet */
|
||||
private static final int SPLIT_METADATA_VERSION = 15;
|
||||
|
||||
private static PacketSender packetSender;
|
||||
|
||||
/** PacketPlayOutEntityDestroy */
|
||||
private static Class<?> EntityDestroyClass;
|
||||
private static BiConsumerWithException<BukkitTabPlayer, int[]> destroyEntities;
|
||||
private static FunctionWithException<Object, int[]> getDestroyedEntities;
|
||||
|
||||
/** PacketPlayOutEntityMetadata */
|
||||
private static BiFunctionWithException<Integer, EntityData, Object> newEntityMetadata;
|
||||
|
||||
/** PacketPlayOutEntityTeleport */
|
||||
private static BiFunctionWithException<Integer, Location, Object> newEntityTeleport;
|
||||
private static Class<?> EntityTeleportClass;
|
||||
private static Field EntityTeleport_EntityId;
|
||||
|
||||
/** PacketPlayOutSpawnEntityLiving */
|
||||
private static QuintFunction<Integer, UUID, Object, Location, EntityData, Object> newSpawnEntity;
|
||||
|
||||
/** Other entity packets */
|
||||
private static Class<?> PacketPlayOutEntity;
|
||||
private static Field PacketPlayOutEntity_ENTITYID;
|
||||
private static Field PacketPlayOutEntity_X;
|
||||
private static Field PacketPlayOutEntity_Y;
|
||||
private static Field PacketPlayOutEntity_Z;
|
||||
private static Class<?> PacketPlayOutEntityLook;
|
||||
private static QuadFunction<Integer, Long, Long, Long, Object> newMovePacket;
|
||||
private static Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
private static Constructor<?> newClientboundBundlePacket;
|
||||
private static Field ClientboundBundlePacket_packets;
|
||||
private static Function<Object, Boolean> isBundlePacket = packet -> false;
|
||||
private static BiConsumerWithException<BukkitTabPlayer, Iterable<?>> sendAsBundle = (player, packets) -> {
|
||||
for (Object packet : packets) {
|
||||
packetSender.sendPacket(player.getPlayer(), packet);
|
||||
}
|
||||
};
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
/** Player this view belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/**
|
||||
* Loads all required classes and fields and marks class as available.
|
||||
* If something goes wrong, a warning is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
DataWatcher.load();
|
||||
loadEntityMetadata();
|
||||
loadEntityDestroy();
|
||||
loadEntityTeleport();
|
||||
loadEntityMove();
|
||||
loadEntitySpawn();
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
Class<?> ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
newClientboundBundlePacket = ClientboundBundlePacket.getConstructor(Iterable.class);
|
||||
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
|
||||
isBundlePacket = ClientboundBundlePacket::isInstance;
|
||||
sendAsBundle = (player, packets) -> packetSender.sendPacket(player.getPlayer(), newClientboundBundlePacket.newInstance(packets));
|
||||
}
|
||||
packetSender = new PacketSender();
|
||||
available = true;
|
||||
} catch (Exception e) {
|
||||
List<String> missingFeatures = new ArrayList<>();
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
missingFeatures.add("Unlimited nametag mode not working and being replaced with regular nametags");
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() <= 8) {
|
||||
missingFeatures.add("BossBar feature not working");
|
||||
}
|
||||
BukkitUtils.compatibilityError(e, "sending entity packets", null, missingFeatures.toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("JavaReflectionInvocation")
|
||||
private static void loadEntityMetadata() throws ReflectiveOperationException {
|
||||
Class<?> entityMetadataClass = BukkitReflection.getClass("network.protocol.game.ClientboundSetEntityDataPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, List.class);
|
||||
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build());
|
||||
} else {
|
||||
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
|
||||
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build(), true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityDestroy() throws ReflectiveOperationException {
|
||||
EntityDestroyClass = BukkitReflection.getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
Field entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
|
||||
try {
|
||||
Constructor<?> constructor = EntityDestroyClass.getConstructor(int[].class);
|
||||
destroyEntities = (player, ids) -> packetSender.sendPacket(player.getPlayer(), constructor.newInstance(new Object[]{ids}));
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
getDestroyedEntities = packet -> ((List<Integer>) entities.get(packet)).stream().mapToInt(i -> i).toArray();
|
||||
} else {
|
||||
getDestroyedEntities = packet -> (int[]) entities.get(packet);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0 Mojank
|
||||
Constructor<?> constructor = EntityDestroyClass.getConstructor(int.class);
|
||||
destroyEntities = (player, ids) -> { for (int entity : ids) packetSender.sendPacket(player.getPlayer(), constructor.newInstance(entity));};
|
||||
getDestroyedEntities = packet -> new int[]{entities.getInt(packet)};
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityTeleport() throws ReflectiveOperationException {
|
||||
EntityTeleportClass = BukkitReflection.getClass("network.protocol.game.ClientboundTeleportEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
Callable<Object> newPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
// Dummy armor stand for constructor
|
||||
Class<?> world = BukkitReflection.getClass("world.level.Level", "world.level.World", "World");
|
||||
Class<?> entityArmorStand = BukkitReflection.getClass("world.entity.decoration.ArmorStand",
|
||||
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
|
||||
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
|
||||
Method World_getHandle = BukkitReflection.getBukkitClass("CraftWorld").getMethod("getHandle");
|
||||
Object dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
|
||||
Constructor<?> constructor = EntityTeleportClass.getConstructor(BukkitReflection.getClass("world.entity.Entity"));
|
||||
newPacket = () -> constructor.newInstance(dummyEntity);
|
||||
} else {
|
||||
Constructor<?> constructor = EntityTeleportClass.getConstructor();
|
||||
newPacket = constructor::newInstance;
|
||||
}
|
||||
|
||||
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
|
||||
Field EntityTeleport_X;
|
||||
Field EntityTeleport_Y;
|
||||
Field EntityTeleport_Z;
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
|
||||
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
|
||||
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
|
||||
} else {
|
||||
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
|
||||
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
|
||||
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
|
||||
}
|
||||
newEntityTeleport = (entityId, location) -> {
|
||||
Object packet = newPacket.call();
|
||||
EntityTeleport_EntityId.set(packet, entityId);
|
||||
EntityTeleport_X.set(packet, toPosition(location.getX()));
|
||||
EntityTeleport_Y.set(packet, toPosition(location.getY()));
|
||||
EntityTeleport_Z.set(packet, toPosition(location.getZ()));
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
|
||||
private static void loadEntityMove() throws ReflectiveOperationException {
|
||||
// Classes
|
||||
PacketPlayOutEntity = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityLook = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
|
||||
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
|
||||
"PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
Class<?> packetPlayOutRelEntityMove = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Pos",
|
||||
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMove", "PacketPlayOutEntity$PacketPlayOutRelEntityMove",
|
||||
"PacketPlayOutRelEntityMove", "Packet31RelEntityMove");
|
||||
|
||||
|
||||
// Fields
|
||||
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
|
||||
if (BukkitReflection.getMinorVersion() >= 14) {
|
||||
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, short.class);
|
||||
PacketPlayOutEntity_X = fields.get(0);
|
||||
PacketPlayOutEntity_Y = fields.get(1);
|
||||
PacketPlayOutEntity_Z = fields.get(2);
|
||||
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, short.class, short.class, short.class, boolean.class);
|
||||
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.shortValue(), y.shortValue(), z.shortValue(), false);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, int.class);
|
||||
PacketPlayOutEntity_X = fields.get(1);
|
||||
PacketPlayOutEntity_Y = fields.get(2);
|
||||
PacketPlayOutEntity_Z = fields.get(3);
|
||||
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, long.class, long.class, long.class, boolean.class);
|
||||
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x, y, z, false);
|
||||
} else if (BukkitReflection.getMinorVersion() == 8) {
|
||||
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, byte.class);
|
||||
PacketPlayOutEntity_X = fields.get(0);
|
||||
PacketPlayOutEntity_Y = fields.get(1);
|
||||
PacketPlayOutEntity_Z = fields.get(2);
|
||||
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, byte.class, byte.class, byte.class, boolean.class);
|
||||
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.byteValue(), y.byteValue(), z.byteValue(), false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads required NMS classes, fields and methods. If it fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If anything fails
|
||||
*/
|
||||
private static void loadEntitySpawn() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
|
||||
// Entity ids
|
||||
EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
if (minorVersion >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (minorVersion >= 8) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> SpawnEntityClass = BukkitReflection.getClass("network.protocol.game.ClientboundAddEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
Field SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
PacketPlayOutNamedEntitySpawn = SpawnEntityClass;
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = SpawnEntity_EntityId;
|
||||
} else {
|
||||
PacketPlayOutNamedEntitySpawn = BukkitReflection.getClass("network.protocol.game.ClientboundAddPlayerPacket",
|
||||
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
|
||||
}
|
||||
Constructor<?> constructor;
|
||||
|
||||
// 1.17+ constructor using Spawn entity packet
|
||||
if (minorVersion >= 17) {
|
||||
Class<?> Vec3D = BukkitReflection.getClass("world.phys.Vec3", "world.phys.Vec3D");
|
||||
Object Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
|
||||
Class<?> EntityTypes = BukkitReflection.getClass("world.entity.EntityType", "world.entity.EntityTypes");
|
||||
if (minorVersion >= 19) {
|
||||
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
|
||||
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
|
||||
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0.0d);
|
||||
} else {
|
||||
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
|
||||
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
|
||||
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 1.16- constructor using spawn entity living packet
|
||||
constructor = SpawnEntityClass.getConstructor();
|
||||
@Nullable Field SpawnEntity_UUID;
|
||||
Field SpawnEntity_X;
|
||||
Field SpawnEntity_Y;
|
||||
Field SpawnEntity_Z;
|
||||
@Nullable Field SpawnEntity_DataWatcher;
|
||||
Field SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
|
||||
if (minorVersion >= 9) {
|
||||
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
|
||||
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
|
||||
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
|
||||
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
|
||||
} else {
|
||||
SpawnEntity_UUID = null;
|
||||
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
|
||||
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
|
||||
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
|
||||
}
|
||||
if (minorVersion < SPLIT_METADATA_VERSION) {
|
||||
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
|
||||
} else {
|
||||
SpawnEntity_DataWatcher = null;
|
||||
}
|
||||
newSpawnEntity = (id, uuid, type, l, data) -> {
|
||||
Object nmsPacket = constructor.newInstance();
|
||||
SpawnEntity_EntityId.set(nmsPacket, id);
|
||||
if (minorVersion < SPLIT_METADATA_VERSION) SpawnEntity_DataWatcher.set(nmsPacket, data.build());
|
||||
if (minorVersion >= 9) SpawnEntity_UUID.set(nmsPacket, uuid);
|
||||
SpawnEntity_X.set(nmsPacket, toPosition(l.getX()));
|
||||
SpawnEntity_Y.set(nmsPacket, toPosition(l.getY()));
|
||||
SpawnEntity_Z.set(nmsPacket, toPosition(l.getZ()));
|
||||
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) type));
|
||||
return nmsPacket;
|
||||
};
|
||||
}
|
||||
|
||||
private static Object toPosition(double paramDouble) {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
return paramDouble;
|
||||
} else {
|
||||
int i = (int) (paramDouble*32);
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
|
||||
if (BukkitReflection.getMinorVersion() >= SPLIT_METADATA_VERSION) {
|
||||
sendAsBundle.accept(player, Arrays.asList(
|
||||
newSpawnEntity.apply(entityId, id, entityType, l, null),
|
||||
newEntityMetadata.apply(entityId, data)
|
||||
));
|
||||
} else {
|
||||
packetSender.sendPacket(player.getPlayer(), newSpawnEntity.apply(entityId, id, entityType, l, data));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
packetSender.sendPacket(player.getPlayer(), newEntityMetadata.apply(entityId, data));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
packetSender.sendPacket(player.getPlayer(), newEntityTeleport.apply(entityId, location));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
destroyEntities.accept(player, entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyPacket(@NotNull Object packet) {
|
||||
return EntityDestroyClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeleportPacket(@NotNull Object packet) {
|
||||
return EntityTeleportClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
|
||||
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMovePacket(@NotNull Object packet) {
|
||||
return PacketPlayOutEntity.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLookPacket(@NotNull Object packet) {
|
||||
return PacketPlayOutEntityLook.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getTeleportEntityId(@NotNull Object teleportPacket) {
|
||||
return EntityTeleport_EntityId.getInt(teleportPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getMoveEntityId(@NotNull Object movePacket) {
|
||||
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
|
||||
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
return getDestroyedEntities.apply(destroyPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return isBundlePacket.apply(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Location getMoveDiff(@NotNull Object movePacket) {
|
||||
return new Location(
|
||||
((Number)PacketPlayOutEntity_X.get(movePacket)).intValue(),
|
||||
((Number)PacketPlayOutEntity_Y.get(movePacket)).intValue(),
|
||||
((Number)PacketPlayOutEntity_Z.get(movePacket)).intValue()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void moveEntity(int entityId, @NotNull Location moveDiff) {
|
||||
packetSender.sendPacket(player.getPlayer(), newMovePacket.apply(
|
||||
entityId, (long) moveDiff.getX(), (long) moveDiff.getY(), (long) moveDiff.getZ()));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -33,15 +34,36 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
|
||||
/** Version in which multiple passenger option was added */
|
||||
private static final int MULTI_PASSENGER_VERSION = 11;
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers the class listener.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to use for registering listener
|
||||
*/
|
||||
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to player sneak event to sneak armor stands.
|
||||
*
|
||||
* @param e
|
||||
* Sneak event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onSneak(PlayerToggleSneakEvent e) {
|
||||
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to respawn event to resend armor stands.
|
||||
*
|
||||
* @param e
|
||||
* Respawn event
|
||||
*/
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
respawn(e.getPlayer().getUniqueId());
|
||||
@@ -74,7 +96,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
@NotNull
|
||||
public List<Integer> getPassengers(@NotNull Object entity) {
|
||||
Entity vehicle = (Entity) entity;
|
||||
if (BukkitReflection.getMinorVersion() >= 11) {
|
||||
if (BukkitReflection.getMinorVersion() >= MULTI_PASSENGER_VERSION) {
|
||||
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
|
||||
} else {
|
||||
if (vehicle.getPassenger() != null) {
|
||||
@@ -99,7 +121,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
@Override
|
||||
@NotNull
|
||||
public String getEntityType(@NotNull Object entity) {
|
||||
return ((Entity) entity).getType().toString().toLowerCase();
|
||||
return ((Entity) entity).getType().toString().toLowerCase(Locale.US);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,7 +174,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
datawatcher.setEntityFlags(flags);
|
||||
datawatcher.setCustomName(displayName, viewer.getVersion());
|
||||
datawatcher.setCustomNameVisible(nameVisible);
|
||||
datawatcher.setArmorStandFlags((byte)16);
|
||||
datawatcher.setArmorStandFlags(datawatcher.MARKER_FLAG);
|
||||
return datawatcher;
|
||||
}
|
||||
|
||||
@@ -165,4 +187,9 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
|
||||
public boolean isDead(@NotNull TabPlayer player) {
|
||||
return ((BukkitTabPlayer)player).getPlayer().isDead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlying(@NotNull TabPlayer player) {
|
||||
return ((BukkitTabPlayer)player).getPlayer().isFlying();
|
||||
}
|
||||
}
|
||||
+4
-8
@@ -12,11 +12,7 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TAB's expansion for PlaceholderAPI
|
||||
@@ -30,7 +26,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
|
||||
/** List of all placeholders offered by the plugin for command suggestions */
|
||||
@NotNull
|
||||
private final List<String> placeholders = Arrays.asList(
|
||||
private final List<String> placeholders = Collections.unmodifiableList(Arrays.asList(
|
||||
"%tab_tabprefix%",
|
||||
"%tab_tabsuffix%",
|
||||
"%tab_tagprefix%",
|
||||
@@ -54,7 +50,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
"%tab_nametag_visibility%",
|
||||
"%tab_replace_<placeholder>%",
|
||||
"%tab_placeholder_<placeholder>%"
|
||||
);
|
||||
));
|
||||
|
||||
@Override
|
||||
public boolean persist() {
|
||||
@@ -101,7 +97,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
return text;
|
||||
}
|
||||
if (identifier.startsWith("placeholder_")) {
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring("placeholder_".length()) + "%", TAB.getInstance().getPlaceholderManager());
|
||||
}
|
||||
if (player == null) return "<Player cannot be null>";
|
||||
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
|
||||
|
||||
+23
-8
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.UnLoadable;
|
||||
@@ -29,12 +28,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
@Getter private final String featureName = "Per world PlayerList";
|
||||
|
||||
/** Config options */
|
||||
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
private final boolean allowBypass = config().getBoolean("per-world-playerlist.allow-bypass-permission", false);
|
||||
private final List<String> ignoredWorlds = config().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
|
||||
private final Map<String, List<String>> sharedWorlds = config().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
@@ -63,18 +60,30 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
HandlerList.unregisterAll(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join event listener to synchronously perform hiding.
|
||||
*
|
||||
* @param e
|
||||
* Join event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
* World change listener to synchronously perform player showing/hiding.
|
||||
*
|
||||
* @param e
|
||||
* World change event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onWorldChange(PlayerChangedWorldEvent e) {
|
||||
long time = System.nanoTime();
|
||||
checkPlayer(e.getPlayer());
|
||||
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,4 +125,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
}
|
||||
return viewerWorldGroup.equals(targetWorldGroup);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFeatureName() {
|
||||
return "Per world PlayerList";
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
@RequiredArgsConstructor
|
||||
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
|
||||
|
||||
/** Distance of the wither in blocks */
|
||||
private static final int WITHER_DISTANCE = 60;
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events
|
||||
*
|
||||
@@ -39,7 +42,7 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
|
||||
//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,
|
||||
featureName, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
getFeatureName(), TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
|
||||
super.load();
|
||||
teleport();
|
||||
}
|
||||
@@ -51,9 +54,9 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
|
||||
for (BossBar line : getRegisteredBossBars().values()) {
|
||||
if (!line.getPlayers().contains(p)) continue;
|
||||
if (!line.containsPlayer(p)) continue;
|
||||
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
|
||||
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(60)); // Wither distance
|
||||
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(WITHER_DISTANCE));
|
||||
if (loc.getY() < 1) loc.setY(1);
|
||||
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
|
||||
}
|
||||
@@ -67,13 +70,16 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
|
||||
}
|
||||
|
||||
/**
|
||||
* Respawning wither as respawn screen destroys all entities in client
|
||||
* Respawning wither as respawn screen destroys all entities in client.
|
||||
*
|
||||
* @param e
|
||||
* Respawn event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onRespawn(PlayerRespawnEvent e) {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) return;
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
TAB.getInstance().getCPUManager().runMeasuredTask(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
|
||||
() -> detectBossBarsAndSend(player));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Header/footer sender using Bukkit API. Despite this feature getting
|
||||
* added into the game in 1.8, Bukkit team took 4 entire years to add
|
||||
* it to the API, which eventually made it there in 1.13. <p>
|
||||
* The input is just a String, meaning Bukkit will have to deserialize it
|
||||
* into components, which is a massive performance loss. It also does not
|
||||
* seem to support fonts.
|
||||
*/
|
||||
public class BukkitHeaderFooter extends HeaderFooter {
|
||||
|
||||
/** Flag tracking whether this class can be used on current server */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().setPlayerListHeaderFooter(
|
||||
BukkitUtils.toBukkitFormat(header, player.getVersion().getMinorVersion() >= 16),
|
||||
BukkitUtils.toBukkitFormat(footer, player.getVersion().getMinorVersion() >= 16)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A class responsible for sending header/footer to players on request.
|
||||
*/
|
||||
public abstract class HeaderFooter {
|
||||
|
||||
/** Instance of the class, null if no implementation is available */
|
||||
@Getter
|
||||
@Nullable
|
||||
private static HeaderFooter instance;
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
if (PaperHeaderFooter.isAvailable()) {
|
||||
instance = new PaperHeaderFooter();
|
||||
} else if (PacketHeaderFooter.isAvailable() && PacketSender.isAvailable()) {
|
||||
instance = new PacketHeaderFooter();
|
||||
} else if (BukkitHeaderFooter.isAvailable()) {
|
||||
instance = new BukkitHeaderFooter();
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
|
||||
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
|
||||
"Please update the plugin to version with proper support for your server version.");
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
|
||||
"result in the feature not working. " +
|
||||
"Please update the plugin to version with proper support for your server version.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends header/footer to player.
|
||||
*
|
||||
* @param player
|
||||
* Player to send header/footer to.
|
||||
* @param header
|
||||
* Header to use.
|
||||
* @param footer
|
||||
* Footer to use.
|
||||
*/
|
||||
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Header/footer sender that uses NMS to send packets. Available
|
||||
* on all versions since 1.8 when the feature was added into the game.
|
||||
*/
|
||||
public class PacketHeaderFooter extends HeaderFooter {
|
||||
|
||||
private static Constructor<?> newHeaderFooter;
|
||||
private static Field HEADER;
|
||||
private static Field FOOTER;
|
||||
private static Method ChatSerializer_DESERIALIZE;
|
||||
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
static {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component",
|
||||
"network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
|
||||
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
|
||||
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
|
||||
} else {
|
||||
newHeaderFooter = HeaderFooterClass.getConstructor();
|
||||
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
|
||||
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
|
||||
}
|
||||
available = true;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Print exception to find out what went wrong
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
player.sendPacket(newHeaderFooter.newInstance(convert(header, player), convert(footer, player)));
|
||||
} else {
|
||||
Object packet = newHeaderFooter.newInstance();
|
||||
HEADER.set(packet, convert(header, player));
|
||||
FOOTER.set(packet, convert(footer, player));
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private Object convert(IChatBaseComponent component, TabPlayer player) {
|
||||
return componentCache.get(component, player.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.hook.AdventureHook;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Header/footer sender using Paper's API added in 1.16.5.
|
||||
* Thanks to both cache and direct component conversion, this
|
||||
* implementation is even faster than using NMS components
|
||||
* and is therefore preferred if available.
|
||||
*/
|
||||
public class PaperHeaderFooter extends HeaderFooter {
|
||||
|
||||
/** Flag tracking whether this class can be used on current server */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
|
||||
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class);
|
||||
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.getPlayer().sendPlayerListHeaderAndFooter(
|
||||
AdventureHook.toAdventureComponent(header, player.getVersion()),
|
||||
AdventureHook.toAdventureComponent(footer, player.getVersion())
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.tab.platforms.bukkit.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.VanishAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* PremiumVanish hook for Bukkit.
|
||||
*/
|
||||
public class BukkitPremiumVanishHook extends PremiumVanishHook {
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return VanishAPI.canSee(((BukkitTabPlayer)viewer).getPlayer(), ((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished(@NotNull TabPlayer player) {
|
||||
return VanishAPI.isInvisible(((BukkitTabPlayer)player).getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Class containing main NMS-related information and methods.
|
||||
*/
|
||||
public class BukkitReflection {
|
||||
|
||||
/** Server's NMS/CraftBukkit package */
|
||||
@Getter
|
||||
private static final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
/** CraftBukkit package */
|
||||
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
|
||||
|
||||
/** Server's minor version */
|
||||
@Getter
|
||||
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
/** Server version data */
|
||||
private static final ServerVersion serverVersion = detectServerVersion();
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.3 or not */
|
||||
@Getter
|
||||
@@ -23,7 +27,7 @@ public class BukkitReflection {
|
||||
|
||||
/** Flag determining whether the server version is at least 1.19.4 or not */
|
||||
@Getter
|
||||
private static final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
|
||||
private static final boolean is1_19_4Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.2 or not */
|
||||
@Getter
|
||||
@@ -33,6 +37,34 @@ public class BukkitReflection {
|
||||
@Getter
|
||||
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
|
||||
|
||||
private static ServerVersion detectServerVersion() {
|
||||
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
|
||||
String[] array = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
|
||||
int minorVersion;
|
||||
if (array.length > 3) {
|
||||
// Normal packaging
|
||||
String serverPackage = array[3];
|
||||
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
if (minorVersion < 17) {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
} else {
|
||||
// Paper without CB relocation
|
||||
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server's minor version, such as 20 for 1.20.4
|
||||
*
|
||||
* @return server's minor version
|
||||
*/
|
||||
public static int getMinorVersion() {
|
||||
return serverVersion.getMinorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
|
||||
* without "net.minecraft." prefix, for <1.17 it takes class name only.
|
||||
@@ -43,19 +75,39 @@ public class BukkitReflection {
|
||||
* @throws ClassNotFoundException
|
||||
* if class does not exist
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
for (String name : names) {
|
||||
try {
|
||||
if (minorVersion >= 17) {
|
||||
return Class.forName("net.minecraft." + name);
|
||||
} else {
|
||||
return loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
return serverVersion.getClass.apply(name);
|
||||
} catch (ClassNotFoundException | NullPointerException ignored) {
|
||||
// not the first class name in array
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns CraftBukkit class with given package and name.
|
||||
*
|
||||
* @param name
|
||||
* Package and name of the class
|
||||
* @return CraftBukkit class
|
||||
* @throws ClassNotFoundException
|
||||
* If class does not exist
|
||||
*/
|
||||
public static Class<?> getBukkitClass(@NotNull String name) throws ClassNotFoundException {
|
||||
return Class.forName(CRAFTBUKKIT_PACKAGE + "." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Class with server version information.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class ServerVersion {
|
||||
|
||||
private final FunctionWithException<String, Class<?>> getClass;
|
||||
private final int minorVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Class for converting TAB component into NMS components (1.7+).
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class ComponentConverter {
|
||||
|
||||
/** Instance of the class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
|
||||
private final FunctionWithException<String, Object> newTextComponent;
|
||||
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
|
||||
|
||||
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.ChatModifier", "ChatModifier");
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("EnumChatFormat");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling;
|
||||
private final Field Component_modifier;
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
|
||||
|
||||
// 1.15-
|
||||
private Method ChatModifier_setColor;
|
||||
|
||||
// 1.16+
|
||||
private Method ChatHexColor_fromRGB;
|
||||
private Constructor<?> newMinecraftKey;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
private ComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
|
||||
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.IChatMutableComponent", "IChatMutableComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
} else {
|
||||
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.ChatComponentText", "ChatComponentText");
|
||||
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
|
||||
newTextComponent = newChatComponentText::newInstance;
|
||||
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.ChatHexColor", "ChatHexColor");
|
||||
Class<?> MinecraftKey = BukkitReflection.getClass("resources.MinecraftKey", "MinecraftKey");
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ChatClickable", "ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.ChatHoverable", "ChatHoverable");
|
||||
newMinecraftKey = MinecraftKey.getConstructor(String.class);
|
||||
ChatHexColor_fromRGB = ReflectionUtils.getOnlyMethod(chatHexColor, chatHexColor, int.class);
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, MinecraftKey));
|
||||
convertModifier = this::createModifierModern;
|
||||
} else {
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
|
||||
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB component to NMS component.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param modern
|
||||
* Whether client supports RGB or not
|
||||
* @return Converted component
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object convert(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
|
||||
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Object nmsComponent = newTextComponent.apply(component1.getText());
|
||||
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
|
||||
Object color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
|
||||
} else {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : newMinecraftKey.newInstance(modifier.getFont())
|
||||
);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (modifier.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
|
||||
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
|
||||
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
|
||||
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
|
||||
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
|
||||
return nmsModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
INSTANCE = new ComponentConverter();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the component converter is available and throws an exception if not.
|
||||
*/
|
||||
public static void ensureAvailable() {
|
||||
if (INSTANCE == null) throw new IllegalStateException("Component converter is not available");
|
||||
}
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.*;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A class representing the n.m.s.DataWatcher class to make work with it much easier
|
||||
*/
|
||||
@ToString
|
||||
public class DataWatcher implements EntityData {
|
||||
|
||||
/** NMS Fields */
|
||||
public static Class<?> DataWatcher;
|
||||
private static Constructor<?> newDataWatcher;
|
||||
private static Method DataWatcher_register;
|
||||
private static Method DataWatcher_markDirty;
|
||||
public static Method DataWatcher_packDirty;
|
||||
|
||||
private static Class<?> DataWatcherObject;
|
||||
private static Constructor<?> newDataWatcherObject;
|
||||
|
||||
private static Object DataWatcherSerializer_BYTE;
|
||||
private static Object DataWatcherSerializer_FLOAT;
|
||||
private static Object DataWatcherSerializer_STRING;
|
||||
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
|
||||
private static Object DataWatcherSerializer_BOOLEAN;
|
||||
|
||||
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
|
||||
|
||||
private static Method ChatSerializer_DESERIALIZE;
|
||||
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
/** Watched data */
|
||||
private final Map<Integer, Item> dataValues = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Returns armor stand flags position based on server version
|
||||
*
|
||||
* @return armor stand flags position based on server version
|
||||
*/
|
||||
private static int getArmorStandFlagsPosition() {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
//1.17.x, 1.18.x, 1.19.x, 1.20.x
|
||||
return 15;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 15) {
|
||||
//1.15.x, 1.16.x
|
||||
return 14;
|
||||
} else if (BukkitReflection.getMinorVersion() == 14) {
|
||||
//1.14.x
|
||||
return 13;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 10) {
|
||||
//1.10.x - 1.13.x
|
||||
return 11;
|
||||
} else {
|
||||
//1.8.x - 1.9.x
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all required Fields and throws Exception if something went wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
|
||||
if (minorVersion >= 7) {
|
||||
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
|
||||
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
|
||||
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
|
||||
} else {
|
||||
newDataWatcher = DataWatcher.getConstructor();
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
DataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
|
||||
"network.syncher.DataWatcherObject", "DataWatcherObject");
|
||||
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
|
||||
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
|
||||
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
|
||||
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
|
||||
DataWatcherObject, Object.class
|
||||
);
|
||||
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
|
||||
DataWatcherSerializer_BYTE = ReflectionUtils.getField(dataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(dataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_STRING = ReflectionUtils.getField(dataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
|
||||
}
|
||||
} else {
|
||||
if (minorVersion >= 13) {
|
||||
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
|
||||
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
|
||||
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
|
||||
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
|
||||
} else {
|
||||
DataWatcherSerializer_BOOLEAN = dataWatcherRegistry.getDeclaredField("h").get(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DataWatcher_register = ReflectionUtils.getMethod(
|
||||
DataWatcher,
|
||||
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
|
||||
Object.class
|
||||
);
|
||||
}
|
||||
if (minorVersion >= 19) {
|
||||
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang, Bukkit}
|
||||
}
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value into data values
|
||||
*
|
||||
* @param position
|
||||
* data position
|
||||
* @param serializer
|
||||
* Object serializer (1.9+)
|
||||
* @param value
|
||||
* value
|
||||
*/
|
||||
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
|
||||
dataValues.put(position, new Item(position, serializer, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity byte flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setEntityFlags(byte flags) {
|
||||
setValue(0, DataWatcherSerializer_BYTE, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
|
||||
*
|
||||
* @param customName
|
||||
* target custom name
|
||||
* @param clientVersion
|
||||
* client version
|
||||
*/
|
||||
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
|
||||
Optional.of(componentCache.get(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
setValue(2, DataWatcherSerializer_STRING, customName);
|
||||
} else {
|
||||
//name length is limited to 64 characters on <1.8
|
||||
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
|
||||
if (BukkitReflection.getMinorVersion() >= 6) {
|
||||
setValue(10, null, cutName);
|
||||
} else {
|
||||
setValue(5, null, cutName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes custom name visibility boolean
|
||||
*
|
||||
* @param visible
|
||||
* if visible or not
|
||||
*/
|
||||
public void setCustomNameVisible(boolean visible) {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
|
||||
} else {
|
||||
setValue(3, null, (byte)(visible?1:0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes entity health
|
||||
*
|
||||
* @param health
|
||||
* health of entity
|
||||
*/
|
||||
public void setHealth(float health) {
|
||||
if (BukkitReflection.getMinorVersion() >= 6) {
|
||||
setValue(6, DataWatcherSerializer_FLOAT, health);
|
||||
} else {
|
||||
setValue(16, null, (int)health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes armor stand flags
|
||||
*
|
||||
* @param flags
|
||||
* flags to write
|
||||
*/
|
||||
public void setArmorStandFlags(byte flags) {
|
||||
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes wither invulnerable time
|
||||
* @param time
|
||||
* Time, apparently
|
||||
*/
|
||||
public void setWitherInvulnerableTime(int time) {
|
||||
if (BukkitReflection.getMinorVersion() > 8)
|
||||
throw new UnsupportedOperationException("Not supported on 1.9+");
|
||||
setValue(20, null, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the class into an instance of NMS.DataWatcher
|
||||
*
|
||||
* @return an instance of NMS.DataWatcher with same data
|
||||
*/
|
||||
@SneakyThrows
|
||||
public @NotNull Object build() {
|
||||
Object nmsWatcher;
|
||||
if (newDataWatcher.getParameterCount() == 1) { //1.7+
|
||||
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
|
||||
} else {
|
||||
nmsWatcher = newDataWatcher.newInstance();
|
||||
}
|
||||
for (Item item : dataValues.values()) {
|
||||
Object nmsObject = item.createObject();
|
||||
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
|
||||
if (BukkitReflection.is1_19_3Plus()) DataWatcher_markDirty.invoke(nmsWatcher, nmsObject);
|
||||
}
|
||||
return nmsWatcher;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public static class Item {
|
||||
|
||||
/** Item position */
|
||||
private final int position;
|
||||
|
||||
/** Item serializer (1.9+) */
|
||||
@Nullable
|
||||
private final Object serializer;
|
||||
|
||||
/** Item value */
|
||||
@NotNull
|
||||
private final Object value;
|
||||
|
||||
/**
|
||||
* Creates data watcher object with position and serializer
|
||||
*
|
||||
* @return NMS data watcher object
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object createObject() {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
return newDataWatcherObject.newInstance(position, serializer);
|
||||
} else {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PacketEntityView implements EntityView {
|
||||
|
||||
/** PacketPlayOutEntityDestroy */
|
||||
private static Class<?> EntityDestroyClass;
|
||||
private static Constructor<?> newEntityDestroy;
|
||||
private static Field EntityDestroy_Entities;
|
||||
|
||||
/** PacketPlayOutEntityMetadata */
|
||||
private static Constructor<?> newEntityMetadata;
|
||||
|
||||
/** PacketPlayOutEntityTeleport */
|
||||
private static Class<?> EntityTeleportClass;
|
||||
private static Constructor<?> newEntityTeleport;
|
||||
private static Field EntityTeleport_EntityId;
|
||||
private static Field EntityTeleport_X;
|
||||
private static Field EntityTeleport_Y;
|
||||
private static Field EntityTeleport_Z;
|
||||
|
||||
/** PacketPlayOutSpawnEntityLiving */
|
||||
private static Class<?> SpawnEntityClass;
|
||||
private static Constructor<?> newSpawnEntity;
|
||||
|
||||
/** 1.17+ */
|
||||
private static Object Vec3D_Empty;
|
||||
private static Object EntityTypes_ARMOR_STAND;
|
||||
|
||||
/** 1.16.5- */
|
||||
private static Field SpawnEntity_EntityId;
|
||||
private static Field SpawnEntity_EntityType;
|
||||
private static Field SpawnEntity_UUID;
|
||||
private static Field SpawnEntity_X;
|
||||
private static Field SpawnEntity_Y;
|
||||
private static Field SpawnEntity_Z;
|
||||
private static Field SpawnEntity_DataWatcher;
|
||||
private static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
|
||||
|
||||
/** Other entity packets */
|
||||
private static Class<?> PacketPlayOutEntity;
|
||||
private static Field PacketPlayOutEntity_ENTITYID;
|
||||
private static Class<?> PacketPlayOutEntityLook;
|
||||
private static Class<?> PacketPlayOutNamedEntitySpawn;
|
||||
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
|
||||
|
||||
private static Class<?> ClientboundBundlePacket;
|
||||
private static Field ClientboundBundlePacket_packets;
|
||||
|
||||
private static Object dummyEntity;
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
/** Player this view belongs to */
|
||||
private final BukkitTabPlayer player;
|
||||
|
||||
/**
|
||||
* Loads all required classes and fields and throws Exception if something went wrong
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
loadEntityMetadata();
|
||||
loadEntityDestroy();
|
||||
loadEntityTeleport();
|
||||
loadEntityMove();
|
||||
loadEntitySpawn();
|
||||
if (BukkitReflection.is1_19_4Plus()) {
|
||||
ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
|
||||
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
|
||||
}
|
||||
available = true;
|
||||
}
|
||||
|
||||
private static void loadEntityMetadata() throws ReflectiveOperationException {
|
||||
// Class
|
||||
Class<?> entityMetadataClass = getClass("network.protocol.game.ClientboundSetEntityDataPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
|
||||
|
||||
// Constructor
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
newEntityMetadata = entityMetadataClass.getConstructor(int.class, List.class);
|
||||
} else {
|
||||
newEntityMetadata = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityDestroy() throws ReflectiveOperationException {
|
||||
// Class
|
||||
EntityDestroyClass = getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
|
||||
|
||||
// Constructor
|
||||
try {
|
||||
newEntityDestroy = EntityDestroyClass.getConstructor(int[].class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
//1.17.0
|
||||
newEntityDestroy = EntityDestroyClass.getConstructor(int.class);
|
||||
}
|
||||
|
||||
// Field
|
||||
EntityDestroy_Entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
|
||||
}
|
||||
|
||||
private static void loadEntityTeleport() throws ReflectiveOperationException {
|
||||
// Class
|
||||
EntityTeleportClass = getClass("network.protocol.game.ClientboundTeleportEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
|
||||
|
||||
// Constructor
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
newEntityTeleport = EntityTeleportClass.getConstructor(getClass("world.entity.Entity"));
|
||||
} else {
|
||||
newEntityTeleport = EntityTeleportClass.getConstructor();
|
||||
}
|
||||
|
||||
// Fields
|
||||
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
|
||||
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
|
||||
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
|
||||
} else {
|
||||
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
|
||||
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
|
||||
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
|
||||
}
|
||||
|
||||
// Dummy armor stand for constructor
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Class<?> world = getClass("world.level.Level", "world.level.World", "World");
|
||||
Class<?> entityArmorStand = getClass("world.entity.decoration.ArmorStand",
|
||||
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
|
||||
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
|
||||
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".CraftWorld").getMethod("getHandle");
|
||||
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadEntityMove() throws ReflectiveOperationException {
|
||||
// Classes
|
||||
PacketPlayOutEntity = getClass("network.protocol.game.ClientboundMoveEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
|
||||
PacketPlayOutEntityLook = getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
|
||||
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
|
||||
"PacketPlayOutEntityLook", "Packet32EntityLook");
|
||||
|
||||
// Field
|
||||
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
|
||||
}
|
||||
|
||||
private static void loadEntitySpawn() throws ReflectiveOperationException {
|
||||
SpawnEntityClass = getClass("network.protocol.game.ClientboundAddEntityPacket",
|
||||
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
|
||||
SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Class<?> Vec3D = getClass("world.phys.Vec3", "world.phys.Vec3D");
|
||||
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
|
||||
Class<?> EntityTypes = getClass("world.entity.EntityType", "world.entity.EntityTypes");
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
|
||||
} else {
|
||||
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
|
||||
} else {
|
||||
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
|
||||
}
|
||||
} else {
|
||||
newSpawnEntity = SpawnEntityClass.getConstructor();
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
|
||||
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
|
||||
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
|
||||
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
|
||||
} else {
|
||||
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
|
||||
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
|
||||
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
|
||||
}
|
||||
SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
|
||||
if (BukkitReflection.getMinorVersion() <= 14) {
|
||||
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
|
||||
}
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 1);
|
||||
} else {
|
||||
entityIds.put(EntityType.WITHER, 64);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
entityIds.put(EntityType.ARMOR_STAND, 30);
|
||||
}
|
||||
}
|
||||
if (!BukkitReflection.is1_20_2Plus()) {
|
||||
PacketPlayOutNamedEntitySpawn = getClass("network.protocol.game.ClientboundAddPlayerPacket",
|
||||
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
|
||||
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static int floor(double paramDouble) {
|
||||
int i = (int)paramDouble;
|
||||
return paramDouble < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
if (minorVersion >= 19) {
|
||||
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d));
|
||||
} else if (minorVersion >= 17) {
|
||||
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty));
|
||||
} else {
|
||||
Object nmsPacket = newSpawnEntity.newInstance();
|
||||
SpawnEntity_EntityId.set(nmsPacket, entityId);
|
||||
if (minorVersion <= 14) {
|
||||
SpawnEntity_DataWatcher.set(nmsPacket, data.build());
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
SpawnEntity_UUID.set(nmsPacket, id);
|
||||
SpawnEntity_X.set(nmsPacket, l.getX());
|
||||
SpawnEntity_Y.set(nmsPacket, l.getY());
|
||||
SpawnEntity_Z.set(nmsPacket, l.getZ());
|
||||
} else {
|
||||
SpawnEntity_X.set(nmsPacket, floor(l.getX()*32));
|
||||
SpawnEntity_Y.set(nmsPacket, floor(l.getY()*32));
|
||||
SpawnEntity_Z.set(nmsPacket, floor(l.getZ()*32));
|
||||
}
|
||||
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
|
||||
player.sendPacket(nmsPacket);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 15) {
|
||||
updateEntityMetadata(entityId, data);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
if (newEntityMetadata.getParameterCount() == 2) {
|
||||
//1.19.3+
|
||||
player.sendPacket(newEntityMetadata.newInstance(entityId, DataWatcher.DataWatcher_packDirty.invoke(data.build())));
|
||||
} else {
|
||||
player.sendPacket(newEntityMetadata.newInstance(entityId, data.build(), true));
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void teleportEntity(int entityId, @NotNull Location location) {
|
||||
Object nmsPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
nmsPacket = newEntityTeleport.newInstance(dummyEntity);
|
||||
} else {
|
||||
nmsPacket = newEntityTeleport.newInstance();
|
||||
}
|
||||
EntityTeleport_EntityId.set(nmsPacket, entityId);
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
EntityTeleport_X.set(nmsPacket, location.getX());
|
||||
EntityTeleport_Y.set(nmsPacket, location.getY());
|
||||
EntityTeleport_Z.set(nmsPacket, location.getZ());
|
||||
} else {
|
||||
EntityTeleport_X.set(nmsPacket, floor(location.getX()*32));
|
||||
EntityTeleport_Y.set(nmsPacket, floor(location.getY()*32));
|
||||
EntityTeleport_Z.set(nmsPacket, floor(location.getZ()*32));
|
||||
}
|
||||
player.sendPacket(nmsPacket);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
if (newEntityDestroy.getParameterTypes()[0] != int.class) {
|
||||
player.sendPacket(newEntityDestroy.newInstance(new Object[]{entities}));
|
||||
} else {
|
||||
//1.17.0 Mojank
|
||||
for (int entity : entities) {
|
||||
player.sendPacket(newEntityDestroy.newInstance(entity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyPacket(@NotNull Object packet) {
|
||||
return EntityDestroyClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeleportPacket(@NotNull Object packet) {
|
||||
return EntityTeleportClass.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return SpawnEntityClass.isInstance(packet);
|
||||
} else {
|
||||
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMovePacket(@NotNull Object packet) {
|
||||
return PacketPlayOutEntity.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLookPacket(@NotNull Object packet) {
|
||||
return PacketPlayOutEntityLook.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getTeleportEntityId(@NotNull Object teleportPacket) {
|
||||
return EntityTeleport_EntityId.getInt(teleportPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getMoveEntityId(@NotNull Object movePacket) {
|
||||
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return SpawnEntity_EntityId.getInt(playerSpawnPacket);
|
||||
} else {
|
||||
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
Object entities = PacketEntityView.EntityDestroy_Entities.get(destroyPacket);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
if (entities instanceof List) {
|
||||
return ((List<Integer>)entities).stream().mapToInt(i -> i).toArray();
|
||||
} else {
|
||||
//1.17.0
|
||||
return new int[]{(int) entities};
|
||||
}
|
||||
} else {
|
||||
return (int[]) entities;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return BukkitReflection.is1_19_4Plus() && ClientboundBundlePacket.isInstance(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class from given potential names. Supports modded servers, such as Thermos.
|
||||
*
|
||||
* @param names
|
||||
* Potential class names
|
||||
* @return class from given name
|
||||
* @throws ClassNotFoundException
|
||||
* if class was not found
|
||||
*/
|
||||
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
|
||||
for (String name : names) {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft." + name);
|
||||
} else {
|
||||
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft.server." + BukkitReflection.getServerPackage() + "." + name);
|
||||
}
|
||||
} catch (ClassNotFoundException | NullPointerException e) {
|
||||
// Wrong class name
|
||||
}
|
||||
}
|
||||
throw new ClassNotFoundException("No class found with potential names " + Arrays.toString(names));
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,56 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for sending packets to players.
|
||||
*/
|
||||
public class PacketSender {
|
||||
|
||||
private static Method getHandle;
|
||||
private static Field PLAYER_CONNECTION;
|
||||
private static Method sendPacket;
|
||||
@Getter private static boolean available;
|
||||
/** Function for sending packet */
|
||||
@NotNull
|
||||
private final BiConsumerWithException<Player, Object> send;
|
||||
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
if (BukkitReflection.getMinorVersion() >= 7) {
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
available = true;
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"sending packets due to a compatibility error. Majority of the features will not work / be very limited. " +
|
||||
"Please update the plugin to a version with proper support for your server version.");
|
||||
/**
|
||||
* Constructs new instance and attempts to load required classes, fields and methods.
|
||||
* If something fails, error is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public PacketSender() throws ReflectiveOperationException {
|
||||
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection", "PlayerConnection");
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Method sendPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 7) {
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
send = (player, packet) -> sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends packet to specified player.
|
||||
* If something goes wrong, throws an exception.
|
||||
*
|
||||
* @param player
|
||||
* Player to send packet to
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static void sendPacket(@NotNull Player player, @NotNull Object packet) {
|
||||
if (!player.isOnline() || sendPacket == null) return;
|
||||
sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
|
||||
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
|
||||
send.accept(player, packet);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,54 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for retrieving ping of players.
|
||||
*/
|
||||
public class PingRetriever {
|
||||
|
||||
private static Method getHandle;
|
||||
private static Field PING;
|
||||
/** Ping getter function */
|
||||
private static FunctionWithException<Player, Integer> getPing;
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() < 17) {
|
||||
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
getPing = Player::getPing;
|
||||
} else {
|
||||
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
|
||||
Field PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
|
||||
getPing = player -> PING.getInt(getHandle.invoke(player));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"getting player's ping due to a compatibility error. This will " +
|
||||
"result in ping showing \"-1\". " +
|
||||
"Please update the plugin a to version with native support for your server version to properly show ping.");
|
||||
getPing = p -> -1;
|
||||
BukkitUtils.compatibilityError(e, "getting player's ping", null, "%ping% returning -1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's ping. If ping getter is not available and fields
|
||||
* failed to load, returns {@code -1}. If an exception was throws by
|
||||
* reflective operation, it is re-thrown.
|
||||
*
|
||||
* @param player
|
||||
* Player to get ping of
|
||||
* @return Player's ping or {@code -1} if it failed
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static int getPing(@NotNull Player player) {
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return player.getPing();
|
||||
}
|
||||
if (PING == null) return -1;
|
||||
return PING.getInt(getHandle.invoke(player));
|
||||
return getPing.apply(player);
|
||||
}
|
||||
}
|
||||
+120
-55
@@ -1,20 +1,23 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import de.myzelyam.api.vanish.VanishAPI;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.*;
|
||||
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
|
||||
import me.neznamy.tab.platforms.bukkit.entity.PacketEntityView;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
@@ -27,7 +30,8 @@ import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.hook.LuckPermsHook;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -54,63 +58,66 @@ import java.lang.reflect.Field;
|
||||
@Getter
|
||||
public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
/** Flag tracking plugin presence */
|
||||
private final boolean premiumVanish = Bukkit.getPluginManager().isPluginEnabled("PremiumVanish");
|
||||
|
||||
/**
|
||||
* Plugin instance for registering tasks and events
|
||||
*/
|
||||
/** Plugin instance for registering tasks and events */
|
||||
@NotNull
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/**
|
||||
* Variables checking presence of other plugins to hook into
|
||||
*/
|
||||
/** Server version */
|
||||
@Getter
|
||||
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
|
||||
|
||||
/**
|
||||
* NMS server to get TPS from on spigot
|
||||
*/
|
||||
/** NMS server to get TPS from on spigot */
|
||||
@Nullable
|
||||
private Object server;
|
||||
|
||||
/**
|
||||
* TPS field
|
||||
*/
|
||||
/** TPS field */
|
||||
@Nullable
|
||||
private Field spigotTps;
|
||||
|
||||
/**
|
||||
* Detection for presence of Paper's TPS getter
|
||||
*/
|
||||
/** Detection for presence of Paper's TPS getter */
|
||||
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
|
||||
|
||||
/**
|
||||
* Detection for presence of Paper's MSPT getter
|
||||
*/
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
long time = System.currentTimeMillis();
|
||||
try {
|
||||
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
spigotTps = server.getClass().getField("recentTps");
|
||||
} catch (ReflectiveOperationException e) {
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
//not spigot
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
|
||||
PremiumVanishHook.setInstance(new BukkitPremiumVanishHook());
|
||||
}
|
||||
ComponentConverter.tryLoad();
|
||||
PacketEntityView.tryLoad();
|
||||
PingRetriever.tryLoad();
|
||||
PacketSender.tryLoad();
|
||||
ScoreboardLoader.findInstance();
|
||||
TabListBase.findInstance();
|
||||
ScoreboardLoader.tryLoad();
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
BukkitPipelineInjector.tryLoad();
|
||||
HeaderFooter.findInstance();
|
||||
}
|
||||
BukkitUtils.sendCompatibilityMessage();
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + EnumChatFormat.GRAY + "Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBarManagerImpl getLegacyBossBar() {
|
||||
return new WitherBossBar(plugin);
|
||||
public BossBarManagerImpl getBossBar() {
|
||||
if (BukkitReflection.getMinorVersion() <= 8) return new WitherBossBar(plugin);
|
||||
return new BossBarManagerImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -129,8 +136,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) {
|
||||
Chat chat = rspChat.getProvider();
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
int refresh = TAB.getInstance().getConfiguration().getPermissionRefreshInterval();
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", refresh, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", refresh, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
}
|
||||
}
|
||||
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
|
||||
@@ -148,7 +156,10 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public NameTag getUnlimitedNameTags() {
|
||||
return BukkitReflection.getMinorVersion() >= 8 ? new BukkitNameTagX(plugin) : new NameTag();
|
||||
return BukkitReflection.getMinorVersion() >= 8 &&
|
||||
PacketEntityView.isAvailable() &&
|
||||
BukkitPipelineInjector.isAvailable() ?
|
||||
new BukkitNameTagX(plugin) : new NameTag();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,6 +202,14 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a sync placeholder with given identifier and refresh.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Placeholder refresh
|
||||
*/
|
||||
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6);
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
@@ -205,21 +224,19 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull IChatBaseComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + BukkitUtils.toBukkitFormat(message, true));
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull IChatBaseComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] [WARN] " + BukkitUtils.toBukkitFormat(message, true));
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] [WARN] " + toBukkitFormat(message, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " +
|
||||
Bukkit.getBukkitVersion().split("-")[0] +
|
||||
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,13 +252,13 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
command.setExecutor(cmd);
|
||||
command.setTabCompleter(cmd);
|
||||
} else {
|
||||
logWarn(new IChatBaseComponent("Failed to register command, is it defined in plugin.yml?"));
|
||||
logWarn(new SimpleComponent("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
Metrics metrics = new Metrics(plugin, 5304);
|
||||
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED,
|
||||
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
|
||||
@@ -252,14 +269,17 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProtocolVersion getServerVersion() {
|
||||
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (ComponentConverter.INSTANCE != null) {
|
||||
return ComponentConverter.INSTANCE.convert(component, modern);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,22 +315,67 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task in the main thread for given entity.
|
||||
*
|
||||
* @param entity
|
||||
* Entity's main thread
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
Bukkit.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an entity task that may or may not need to use its main thread.
|
||||
*
|
||||
* @param entity
|
||||
* Entity to work with
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
task.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
Player bViewer = ((BukkitTabPlayer)viewer).getPlayer();
|
||||
Player bTarget = ((BukkitTabPlayer)target).getPlayer();
|
||||
if (BackendPlatform.super.canSee(viewer, target)) return true;
|
||||
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (premiumVanish && VanishAPI.canSee(bViewer, bTarget)) return true;
|
||||
if (bViewer.canSee(bTarget)) return true;
|
||||
return BackendPlatform.super.canSee(viewer, target);
|
||||
/**
|
||||
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
|
||||
* If not, closest legacy color is used instead.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param rgbClient
|
||||
* Whether client accepts RGB colors or not.
|
||||
* @return Converted string using bukkit color format
|
||||
*/
|
||||
@NotNull
|
||||
public String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) {
|
||||
if (component instanceof SimpleComponent) return component.toLegacyText();
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (iComponent.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB() && rgbClient) {
|
||||
String hexCode = iComponent.getModifier().getColor().getHexCode();
|
||||
char c = EnumChatFormat.COLOR_CHAR;
|
||||
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
|
||||
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
|
||||
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
|
||||
} else {
|
||||
sb.append(iComponent.getModifier().getColor().getLegacyColor());
|
||||
}
|
||||
}
|
||||
sb.append(iComponent.getModifier().getMagicCodes());
|
||||
sb.append(iComponent.getText());
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,8 @@ import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -18,10 +18,16 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Platform override for Folia
|
||||
* Platform override for Folia.
|
||||
*/
|
||||
public class FoliaPlatform extends BukkitPlatform {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
public FoliaPlatform(@NotNull JavaPlugin plugin) {
|
||||
super(plugin);
|
||||
}
|
||||
@@ -31,7 +37,7 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
super.loadPlayers();
|
||||
|
||||
// Values are never updated in the API, warn users
|
||||
logWarn(new IChatBaseComponent("Folia never updates MSPT and TPS values in the API, making " +
|
||||
logWarn(new SimpleComponent("Folia never updates MSPT and TPS values in the API, making " +
|
||||
"%mspt% and %tps% return the default values (0 and 20)."));
|
||||
|
||||
// Folia never calls PlayerChangedWorldEvent, this is a workaround
|
||||
|
||||
-268
@@ -1,268 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.scoreboard.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
|
||||
* #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>
|
||||
*/
|
||||
// Throw the NPE if something is not as expected, parent class should ensure it anyway
|
||||
@SuppressWarnings({"ConstantConditions", "deprecation"})
|
||||
public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
/** Flag tracking whether this implementation is available for use */
|
||||
@Getter
|
||||
private static final boolean available;
|
||||
|
||||
/** Array of display slots, because the Bukkit order does not match network ordinals */
|
||||
private static org.bukkit.scoreboard.DisplaySlot[] slots;
|
||||
|
||||
/** Server's minor version */
|
||||
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
|
||||
|
||||
protected org.bukkit.scoreboard.Scoreboard scoreboard;
|
||||
|
||||
static {
|
||||
available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
|
||||
if (available) {
|
||||
slots = new org.bukkit.scoreboard.DisplaySlot[]{
|
||||
org.bukkit.scoreboard.DisplaySlot.PLAYER_LIST,
|
||||
org.bukkit.scoreboard.DisplaySlot.SIDEBAR,
|
||||
org.bukkit.scoreboard.DisplaySlot.BELOW_NAME
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
|
||||
// Put player into a different scoreboard for per-player view
|
||||
runSync(() -> {
|
||||
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
|
||||
player.getPlayer().setScoreboard(scoreboard);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot0(int slot, @NotNull String objective) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objective).setDisplaySlot(slots[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
|
||||
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
|
||||
checkPlayerScoreboard();
|
||||
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
scoreboard.getObjective(objective).getScore(scoreHolder).setScore(score);
|
||||
} else {
|
||||
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(scoreHolder)).setScore(score);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
|
||||
checkPlayerScoreboard();
|
||||
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
scoreboard.resetScores(scoreHolder);
|
||||
} else {
|
||||
scoreboard.resetScores(Bukkit.getOfflinePlayer(scoreHolder));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
checkPlayerScoreboard();
|
||||
newObjective(objectiveName, "dummy", title, display);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getObjective(objectiveName).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
checkPlayerScoreboard();
|
||||
Objective obj = scoreboard.getObjective(objectiveName);
|
||||
setDisplayName(obj, title);
|
||||
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.values()[display]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
checkPlayerScoreboard();
|
||||
Team team = scoreboard.registerNewTeam(name);
|
||||
setPrefix(team, prefix);
|
||||
setSuffix(team, suffix);
|
||||
if (serverMinorVersion >= 8)
|
||||
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
|
||||
if (serverMinorVersion >= 9)
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
if (serverMinorVersion >= 13)
|
||||
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
players.forEach(team::addEntry);
|
||||
} else {
|
||||
players.forEach(player -> team.addPlayer(Bukkit.getOfflinePlayer(player)));
|
||||
}
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
checkPlayerScoreboard();
|
||||
scoreboard.getTeam(name).unregister();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
checkPlayerScoreboard();
|
||||
Team team = scoreboard.getTeam(name);
|
||||
setPrefix(team, prefix);
|
||||
setSuffix(team, suffix);
|
||||
if (serverMinorVersion >= 8)
|
||||
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
|
||||
if (serverMinorVersion >= 9)
|
||||
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
|
||||
if (serverMinorVersion >= 13)
|
||||
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
}
|
||||
|
||||
public void newObjective(String objectiveName, String criteria, String title, int display) {
|
||||
if (serverMinorVersion >= 14) {
|
||||
scoreboard.registerNewObjective(
|
||||
objectiveName,
|
||||
criteria,
|
||||
transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13),
|
||||
RenderType.values()[display]
|
||||
);
|
||||
} else {
|
||||
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
|
||||
}
|
||||
}
|
||||
|
||||
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
|
||||
objective.setDisplayName(transform(displayName, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
}
|
||||
|
||||
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
|
||||
team.setPrefix(transform(prefix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
|
||||
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms text into format using bukkit RGB syntax if server version is 1.16+
|
||||
*
|
||||
* @param text
|
||||
* Text to transform
|
||||
* @param maxLengthModern
|
||||
* Maximum text length defined by bukkit API on 1.13+
|
||||
* @param maxLengthLegacy
|
||||
* Maximum text length defined by bukkit API on 1.12-
|
||||
* @return Converted text
|
||||
*/
|
||||
@NotNull
|
||||
private String transform(@NotNull String text, int maxLengthModern, int maxLengthLegacy) {
|
||||
String transformed = BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(text), player.getVersion().getMinorVersion() >= 16);
|
||||
if (serverMinorVersion >= 16 && maxLengthModern < 128) { // Scoreboard title is not stripping colors
|
||||
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
|
||||
transformed = transformed.substring(0, transformed.length()-1);
|
||||
} else if (serverMinorVersion >= 13) {
|
||||
if (transformed.length() > maxLengthModern)
|
||||
transformed = transformed.substring(0, maxLengthModern);
|
||||
} else {
|
||||
if (transformed.length() > maxLengthLegacy)
|
||||
transformed = transformed.substring(0, maxLengthLegacy);
|
||||
}
|
||||
return transformed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure player is in the correct scoreboard.
|
||||
* If not, puts the player into correct scoreboard and prints
|
||||
* a warning.
|
||||
* The only possible reason for this to happen is another plugin
|
||||
* putting a player into a different scoreboard.
|
||||
* Sadly there is no efficient solution to this, which md_5 fails
|
||||
* to understand and keeps saying you don't need packets for scoreboards.
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void checkPlayerScoreboard() {
|
||||
if (player.getPlayer().getScoreboard() != scoreboard) {
|
||||
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
|
||||
"than expected. This means another plugin changed player's scoreboard.");
|
||||
runSync(() -> player.getPlayer().setScoreboard(scoreboard));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the task if in main thread. If not, submits the task and waits for the result.
|
||||
*
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void runSync(@NotNull Runnable task) {
|
||||
if (Bukkit.isPrimaryThread()) {
|
||||
// Server thread (plugin reload)
|
||||
task.run();
|
||||
} else {
|
||||
// Plugin thread (player join)
|
||||
CountDownLatch c = new CountDownLatch(1);
|
||||
player.getPlatform().runSync(player.getPlayer(), () -> {
|
||||
task.run();
|
||||
c.countDown();
|
||||
});
|
||||
c.await();
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
-19
@@ -1,67 +1,77 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class NullScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
/**
|
||||
* Dummy implementation if no scoreboard implementation is available.
|
||||
*/
|
||||
public class NullScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
|
||||
|
||||
public NullScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NullScoreboard(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDisplaySlot0(int slot, @NotNull String objective) {
|
||||
protected void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
|
||||
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
|
||||
protected void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
|
||||
protected void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
protected void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterObjective0(@NotNull String objectiveName) {
|
||||
protected void unregisterObjective0(@NonNull String objectiveName) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
protected void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
protected void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unregisterTeam0(@NotNull String name) {
|
||||
protected void unregisterTeam0(@NonNull String name) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
protected void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
-517
@@ -1,517 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Bukkit, which uses packets
|
||||
* to send scoreboards to use the full potential on all versions
|
||||
* and server software without any artificial limits.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
private static Class<?> Component;
|
||||
private static Class<?> Scoreboard;
|
||||
private static Class<?> ScoreboardObjective;
|
||||
private static Object emptyScoreboard;
|
||||
private static Class<?> NumberFormat;
|
||||
private static Constructor<?> newFixedFormat;
|
||||
|
||||
// Objective packet
|
||||
public static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
public static Field Objective_OBJECTIVE_NAME;
|
||||
public static Field Objective_METHOD;
|
||||
private static Field Objective_RENDER_TYPE;
|
||||
private static Constructor<?> newScoreboardObjective;
|
||||
private static Method ScoreboardObjective_setDisplayName;
|
||||
private static Enum<?>[] healthDisplays;
|
||||
private static Object IScoreboardCriteria_dummy;
|
||||
|
||||
private static ScorePacketData scorePacketData;
|
||||
public static TeamPacketData teamPacketData;
|
||||
public static DisplayPacketData displayPacketData;
|
||||
|
||||
private static Method ChatSerializer_DESERIALIZE;
|
||||
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
|
||||
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
|
||||
|
||||
static {
|
||||
try {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
|
||||
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
|
||||
"IScoreboardCriteria", // 1.5.1 - 1.16.5
|
||||
"IObjective" // 1.5.0
|
||||
);
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
|
||||
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
|
||||
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
|
||||
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"EnumScoreboardHealthDisplay");
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (minorVersion < 13) {
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
}
|
||||
}
|
||||
if (minorVersion < 13) {
|
||||
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
|
||||
}
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
scorePacketData = new ScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
displayPacketData = new DisplayPacketData();
|
||||
available = true;
|
||||
} catch (Exception ignored) {
|
||||
// Print exception to find out what went wrong
|
||||
}
|
||||
}
|
||||
|
||||
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot0(int slot, @NotNull String objective) {
|
||||
player.sendPacket(displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
player.sendPacket(newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
player.sendPacket(newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
player.sendPacket(newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjectivePacket(int action, @NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
|
||||
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
|
||||
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
player.sendPacket(teamPacketData.registerTeam(name, prefix, toComponent(prefix), suffix,
|
||||
toComponent(suffix), visibility, collision, players, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
player.sendPacket(teamPacketData.unregisterTeam(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
player.sendPacket(teamPacketData.updateTeam(name, prefix, toComponent(prefix), suffix,
|
||||
toComponent(suffix), visibility, collision, options));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
|
||||
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
|
||||
player.sendPacket(scorePacketData.setScore(objective, scoreHolder, score, toComponent(displayName), toFixedFormat(numberFormat)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
|
||||
player.sendPacket(scorePacketData.removeScore(objective, scoreHolder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given parameters.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @param title
|
||||
* Objective title
|
||||
* @param renderType
|
||||
* Render type
|
||||
* @param numberFormat
|
||||
* Default number format (1.20.3+)
|
||||
* @return Created objective
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object newObjective(@NotNull String objectiveName, @NotNull String title, int renderType,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
// 1.20.3+
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objectiveName,
|
||||
null, // Criteria
|
||||
toComponent(title),
|
||||
healthDisplays[renderType],
|
||||
false, // Auto update
|
||||
toFixedFormat(numberFormat)
|
||||
);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
// 1.13 - 1.20.2
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objectiveName,
|
||||
null, // Criteria
|
||||
toComponent(title),
|
||||
healthDisplays[renderType]
|
||||
);
|
||||
}
|
||||
// 1.5 - 1.12.2
|
||||
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
|
||||
ScoreboardObjective_setDisplayName.invoke(objective, title);
|
||||
return objective;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object toComponent(@NotNull String text) {
|
||||
return toComponent(IChatBaseComponent.optimizedComponent(text));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object toComponent(@Nullable IChatBaseComponent component) {
|
||||
if (component == null || BukkitReflection.getMinorVersion() < 8) return null;
|
||||
return componentCache.get(component, player.getVersion());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private Object toFixedFormat(@Nullable IChatBaseComponent numberFormat) {
|
||||
if (numberFormat == null || newFixedFormat == null) return null;
|
||||
return newFixedFormat.newInstance(toComponent(numberFormat));
|
||||
}
|
||||
|
||||
private static class ScorePacketData {
|
||||
|
||||
private final Constructor<?> newSetScorePacket;
|
||||
private Constructor<?> newResetScorePacket; // 1.20.3+
|
||||
private Constructor<?> newSetScorePacket_String; // 1.12-
|
||||
private Constructor<?> newScoreboardScore; // 1.12-
|
||||
private Field SetScorePacket_SCORE; // 1.12-
|
||||
private Enum<?>[] scoreboardActions; // 1.20.2-
|
||||
|
||||
@SneakyThrows
|
||||
public ScorePacketData() {
|
||||
Class<?> SetScorePacket = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
|
||||
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
newResetScorePacket = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
|
||||
newSetScorePacket = SetScorePacket.getConstructor(String.class, String.class, int.class, Component, NumberFormat);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> EnumScoreboardAction = BukkitReflection.getClass("server.ServerScoreboard$Method",
|
||||
"server.ScoreboardServer$Action", "ScoreboardServer$Action");
|
||||
newSetScorePacket = SetScorePacket.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
|
||||
scoreboardActions = (Enum<?>[]) EnumScoreboardAction.getMethod("values").invoke(null);
|
||||
} else {
|
||||
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
|
||||
newSetScorePacket_String = SetScorePacket.getConstructor(String.class);
|
||||
SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
|
||||
newScoreboardScore = ScoreboardScore.getConstructor(Scoreboard, ScoreboardObjective, String.class);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore);
|
||||
} else {
|
||||
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore, int.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Object setScore(@NotNull String objective, @NotNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
return newSetScorePacket.newInstance(scoreHolder, objective, score, displayName, numberFormat);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
return newSetScorePacket.newInstance(scoreboardActions[0], objective, scoreHolder, score);
|
||||
} else {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(
|
||||
emptyScoreboard,
|
||||
newScoreboardObjective.newInstance(emptyScoreboard, objective, IScoreboardCriteria_dummy),
|
||||
scoreHolder
|
||||
);
|
||||
Object packet;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
packet = newSetScorePacket.newInstance(scoreboardScore);
|
||||
} else {
|
||||
packet = newSetScorePacket.newInstance(scoreboardScore, ScoreAction.CHANGE);
|
||||
}
|
||||
SetScorePacket_SCORE.set(packet, score);
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Object removeScore(@NotNull String objective, @NotNull String scoreHolder) {
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
return newResetScorePacket.newInstance(scoreHolder, objective);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
return newSetScorePacket.newInstance(scoreboardActions[1], objective, scoreHolder, 0);
|
||||
} else {
|
||||
return newSetScorePacket_String.newInstance(scoreHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class TeamPacketData {
|
||||
|
||||
public Class<?> TeamPacketClass;
|
||||
private Constructor<?> newTeamPacket;
|
||||
private final Constructor<?> newScoreboardTeam;
|
||||
private Method TeamPacketConstructor_of;
|
||||
private Method TeamPacketConstructor_ofBoolean;
|
||||
public Field TeamPacket_NAME;
|
||||
public Field TeamPacket_ACTION;
|
||||
public Field TeamPacket_PLAYERS;
|
||||
private final Method ScoreboardTeam_getPlayerNameSet;
|
||||
private Method ScoreboardTeam_setNameTagVisibility;
|
||||
private Method ScoreboardTeam_setCollisionRule;
|
||||
private final Method ScoreboardTeam_setPrefix;
|
||||
private final Method ScoreboardTeam_setSuffix;
|
||||
private Method ScoreboardTeam_setColor;
|
||||
private final Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
private final Enum<?>[] chatFormats;
|
||||
private Enum<?>[] nameVisibilities;
|
||||
private Enum<?>[] collisionRules;
|
||||
|
||||
@SneakyThrows
|
||||
public TeamPacketData() {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
|
||||
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
|
||||
TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
|
||||
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
|
||||
);
|
||||
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
|
||||
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
|
||||
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
|
||||
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
|
||||
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
|
||||
boolean.class
|
||||
);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
|
||||
boolean.class
|
||||
);
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
|
||||
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumNameTagVisibility" // Bukkit 1.8.0
|
||||
);
|
||||
nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
|
||||
enumNameTagVisibility
|
||||
);
|
||||
}
|
||||
if (minorVersion >= 9) {
|
||||
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
|
||||
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
|
||||
collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
}
|
||||
if (minorVersion >= 13) {
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
Component
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
Component
|
||||
);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[] {"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
}
|
||||
if (minorVersion >= 17) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Object registerTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
|
||||
@NotNull String suffix, @Nullable Object suffixComponent,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
|
||||
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Object unregisterTeam(@NotNull String name) {
|
||||
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return TeamPacketConstructor_of.invoke(null, team);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Object updateTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
|
||||
@NotNull String suffix, @Nullable Object suffixComponent,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @Nullable Object prefixComponent,
|
||||
@NotNull String suffix, @Nullable Object suffixComponent,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
|
||||
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 8)
|
||||
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
|
||||
if (BukkitReflection.getMinorVersion() >= 9)
|
||||
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
|
||||
return team;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DisplayPacketData {
|
||||
|
||||
public final Class<?> DisplayObjectiveClass;
|
||||
private final Constructor<?> newDisplayObjective;
|
||||
public final Field DisplayObjective_POSITION;
|
||||
public final Field DisplayObjective_OBJECTIVE_NAME;
|
||||
private final Object[] displaySlots;
|
||||
|
||||
@SneakyThrows
|
||||
public DisplayPacketData() {
|
||||
DisplayObjectiveClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
|
||||
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
|
||||
);
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
|
||||
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
|
||||
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
|
||||
} else {
|
||||
displaySlots = new Object[]{0, 1, 2};
|
||||
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public Object setDisplaySlot(int slot, @NotNull Object objective) {
|
||||
return newDisplayObjective.newInstance(displaySlots[slot], objective);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.hook.AdventureHook;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.scoreboard.Objective;
|
||||
import org.bukkit.scoreboard.RenderType;
|
||||
import org.bukkit.scoreboard.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Scoreboard handler using Paper API, which got added
|
||||
* in early 1.16.5 builds. Unlike the Bukkit API, the new
|
||||
* methods using adventure components do not have any
|
||||
* pointless artificial limits added in.
|
||||
* <p>
|
||||
* However, it still inherits the issue with compatibility
|
||||
* with other plugins that create scoreboards.
|
||||
*/
|
||||
public class PaperScoreboard extends BukkitScoreboard {
|
||||
|
||||
/** Flag tracking whether this implementation is available for use */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
|
||||
ReflectionUtils.methodExists(Team.class, "prefix", Component.class);
|
||||
|
||||
public PaperScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newObjective(String objectiveName, String criteria, String title, int display) {
|
||||
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.values()[display]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
|
||||
objective.displayName(toAdventure(displayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
|
||||
team.prefix(toAdventure(prefix));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
|
||||
team.suffix(toAdventure(suffix));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts raw text into adventure component
|
||||
*
|
||||
* @param text
|
||||
* Text to convert
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
private Component toAdventure(@NotNull String text) {
|
||||
return AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
|
||||
}
|
||||
}
|
||||
+16
-25
@@ -2,44 +2,35 @@ package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Loader finding the best available Scoreboard implementation.
|
||||
*/
|
||||
public class ScoreboardLoader {
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer>> instance;
|
||||
@NotNull
|
||||
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer, ?>> instance = NullScoreboard::new;
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
* Tries to load Scoreboard packets.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
if (PacketScoreboard.isAvailable() && PacketSender.isAvailable()) {
|
||||
public static void tryLoad() {
|
||||
if (PacketScoreboard.isAvailable()) {
|
||||
instance = PacketScoreboard::new;
|
||||
} else if (PaperScoreboard.isAvailable()) {
|
||||
instance = PaperScoreboard::new;
|
||||
sendMessage("Paper");
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
instance = BukkitScoreboard::new;
|
||||
sendMessage("Bukkit");
|
||||
} else {
|
||||
instance = NullScoreboard::new;
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"Scoreboards due to a compatibility error. No fallback solution was found either. This will " +
|
||||
"result in the features not working. " +
|
||||
"Please update the plugin a to version with native support for your server version to unlock the features.");
|
||||
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendMessage(String implementationName) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
|
||||
"Scoreboards due to a compatibility error. Using fallback solution using " + implementationName + " API. " +
|
||||
"This may result in features not working properly, mainly compatibility with other plugins. " +
|
||||
"Please update the plugin to a version with native support for your server version for optimal experience.");
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Class storing NMS fields and methods of DisplayObjective packet.
|
||||
*/
|
||||
public class DisplayPacketData {
|
||||
|
||||
private final Class<?> DisplayObjectiveClass;
|
||||
private final Constructor<?> newDisplayObjective;
|
||||
private final Field DisplayObjective_OBJECTIVE_NAME;
|
||||
private final Object[] displaySlots;
|
||||
private final FunctionWithException<Object, Integer> packetToSlot;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS fields.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public DisplayPacketData() throws ReflectiveOperationException {
|
||||
Class<?> ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
DisplayObjectiveClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
|
||||
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
|
||||
);
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
|
||||
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
|
||||
packetToSlot = packet -> ((Enum<?>)DisplayObjective_POSITION.get(packet)).ordinal();
|
||||
} else {
|
||||
displaySlots = new Object[]{0, 1, 2};
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
packetToSlot = DisplayObjective_POSITION::getInt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet for setting display slot.
|
||||
*
|
||||
* @param slot
|
||||
* Objective slot
|
||||
* @param objective
|
||||
* Objective to display
|
||||
* @return Packet for setting display slot
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object setDisplaySlot(int slot, @NonNull Object objective) {
|
||||
return newDisplayObjective.newInstance(displaySlots[slot], objective);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packet is display objective packet and forwards it to enabled features.
|
||||
*
|
||||
* @param player
|
||||
* Player who received packet
|
||||
* @param packet
|
||||
* Received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (DisplayObjectiveClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
|
||||
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation which uses packets
|
||||
* to send scoreboards to use the full potential on all versions
|
||||
* and server software without any artificial limits.
|
||||
*/
|
||||
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
@Getter
|
||||
private static Exception exception;
|
||||
|
||||
static Class<?> Component;
|
||||
static Class<?> Scoreboard;
|
||||
static Class<?> ScoreboardObjective;
|
||||
static Object emptyScoreboard;
|
||||
static Class<?> NumberFormat;
|
||||
private static Constructor<?> newFixedFormat;
|
||||
|
||||
// Objective packet
|
||||
private static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
private static Field Objective_OBJECTIVE_NAME;
|
||||
private static Field Objective_METHOD;
|
||||
private static Field Objective_RENDER_TYPE;
|
||||
static Constructor<?> newScoreboardObjective;
|
||||
private static Method ScoreboardObjective_setDisplayName;
|
||||
private static Enum<?>[] healthDisplays;
|
||||
static Object IScoreboardCriteria_dummy;
|
||||
|
||||
private static ScorePacketData scorePacketData;
|
||||
@Getter private static TeamPacketData teamPacketData;
|
||||
@Getter private static DisplayPacketData displayPacketData;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
private final Map<String, Object> teams = new HashMap<>();
|
||||
|
||||
static {
|
||||
try {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
|
||||
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
|
||||
"IScoreboardCriteria", // 1.5.1 - 1.16.5
|
||||
"IObjective" // 1.5.0
|
||||
);
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
|
||||
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
ComponentConverter.ensureAvailable();
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"EnumScoreboardHealthDisplay");
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (minorVersion < 13) {
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
}
|
||||
}
|
||||
if (minorVersion < 13) {
|
||||
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
|
||||
}
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
scorePacketData = new ScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
displayPacketData = new DisplayPacketData();
|
||||
packetSender = new PacketSender();
|
||||
available = true;
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PacketScoreboard(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
packetSender.sendPacket(player.getPlayer(), displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NonNull String objectiveName) {
|
||||
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjectivePacket(int action, @NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Object numberFormat) {
|
||||
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
|
||||
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
|
||||
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
Object team = teamPacketData.createTeam(name);
|
||||
teams.put(name, team);
|
||||
packetSender.sendPacket(player.getPlayer(), teamPacketData.registerTeam(team, prefix, toComponent(prefix), suffix,
|
||||
toComponent(suffix), visibility, collision, players, options, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NonNull String name) {
|
||||
packetSender.sendPacket(player.getPlayer(), teamPacketData.unregisterTeam(teams.remove(name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
packetSender.sendPacket(player.getPlayer(), teamPacketData.updateTeam(teams.get(name), prefix, toComponent(prefix), suffix,
|
||||
toComponent(suffix), visibility, collision, options, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
packetSender.sendPacket(player.getPlayer(), scorePacketData.setScore(objective, scoreHolder, score, displayName, toFixedFormat(numberFormat)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
packetSender.sendPacket(player.getPlayer(), scorePacketData.removeScore(objective, scoreHolder));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
displayPacketData.onPacketSend(player, packet);
|
||||
if (ObjectivePacketClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
if (isAntiOverrideTeams()) teamPacketData.onPacketSend(player, packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Scoreboard Objective with given parameters.
|
||||
*
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @param title
|
||||
* Objective title
|
||||
* @param renderType
|
||||
* Render type
|
||||
* @param numberFormat
|
||||
* Default number format (1.20.3+)
|
||||
* @return Created objective
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object newObjective(@NonNull String objectiveName, @NonNull String title, int renderType,
|
||||
@Nullable Object numberFormat) {
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
// 1.20.3+
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objectiveName,
|
||||
null, // Criteria
|
||||
toComponent(title),
|
||||
healthDisplays[renderType],
|
||||
false, // Auto update
|
||||
toFixedFormat(numberFormat)
|
||||
);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
// 1.13 - 1.20.2
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objectiveName,
|
||||
null, // Criteria
|
||||
toComponent(title),
|
||||
healthDisplays[renderType]
|
||||
);
|
||||
}
|
||||
// 1.5 - 1.12.2
|
||||
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
|
||||
ScoreboardObjective_setDisplayName.invoke(objective, title);
|
||||
return objective;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Object toComponent(@NonNull String text) {
|
||||
return TabComponent.optimized(text).convert(player.getVersion());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private Object toFixedFormat(@Nullable Object numberFormat) {
|
||||
if (numberFormat == null || newFixedFormat == null) return null;
|
||||
return newFixedFormat.newInstance(numberFormat);
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.QuintFunction;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Class containing data for scoreboard score set/reset packets.
|
||||
*/
|
||||
public class ScorePacketData {
|
||||
|
||||
private final QuintFunction<String, String, Integer, Object, Object, Object> setScore;
|
||||
private final BiFunctionWithException<String, String, Object> removeScore;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all fields. Throws exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something went wrong
|
||||
*/
|
||||
public ScorePacketData() throws ReflectiveOperationException {
|
||||
Class<?> SetScorePacket = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
|
||||
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
QuintFunction<String, String, Integer, Object, Object, Object> setScore0;
|
||||
try {
|
||||
// 1.20.5+
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, Optional.class, Optional.class);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, Optional.ofNullable(displayName), Optional.ofNullable(numberFormat));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// 1.20.3 - 1.20.4
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, PacketScoreboard.Component, PacketScoreboard.NumberFormat);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, displayName, numberFormat);
|
||||
}
|
||||
setScore = setScore0;
|
||||
Constructor<?> newResetScore = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder, objective);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> actionClass = BukkitReflection.getClass("server.ServerScoreboard$Method", "server.ScoreboardServer$Action", "ScoreboardServer$Action");
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(actionClass, String.class, String.class, int.class);
|
||||
Enum<?>[] scoreboardActions = (Enum<?>[]) actionClass.getMethod("values").invoke(null);
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> newSetScore.newInstance(scoreboardActions[0], objective, holder, score);
|
||||
removeScore = (objective, holder) -> newSetScore.newInstance(scoreboardActions[1], objective, holder, 0);
|
||||
} else {
|
||||
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
|
||||
Constructor<?> newResetScore = SetScorePacket.getConstructor(String.class);
|
||||
Field SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
|
||||
Constructor<?> newScoreboardScore = ScoreboardScore.getConstructor(PacketScoreboard.Scoreboard, PacketScoreboard.ScoreboardObjective, String.class);
|
||||
Constructor<?> newSetScore;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
newSetScore = SetScorePacket.getConstructor(ScoreboardScore);
|
||||
} else {
|
||||
newSetScore = SetScorePacket.getConstructor(ScoreboardScore, int.class);
|
||||
}
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(
|
||||
PacketScoreboard.emptyScoreboard,
|
||||
PacketScoreboard.newScoreboardObjective.newInstance(PacketScoreboard.emptyScoreboard, objective, PacketScoreboard.IScoreboardCriteria_dummy),
|
||||
holder
|
||||
);
|
||||
Object packet;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
packet = newSetScore.newInstance(scoreboardScore);
|
||||
} else {
|
||||
packet = newSetScore.newInstance(scoreboardScore, Scoreboard.ScoreAction.CHANGE);
|
||||
}
|
||||
SetScorePacket_SCORE.set(packet, score);
|
||||
return packet;
|
||||
};
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates set score packet.
|
||||
*
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param scoreHolder
|
||||
* Score holder
|
||||
* @param score
|
||||
* Score value
|
||||
* @param displayName
|
||||
* Score holder display name
|
||||
* @param numberFormat
|
||||
* Score value number format
|
||||
* @return Set score packet with given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object setScore(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
return setScore.apply(objective, scoreHolder, score, displayName, numberFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates reset score packet.
|
||||
*
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param scoreHolder
|
||||
* Score holder
|
||||
* @return Reset score packet with given parameters.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object removeScore(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
return removeScore.apply(objective, scoreHolder);
|
||||
}
|
||||
}
|
||||
+312
@@ -0,0 +1,312 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class storing all team related fields and methods.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TeamPacketData {
|
||||
|
||||
/** First version with modern team data using components */
|
||||
private final int MODERN_TEAM_DATA_VERSION = 13;
|
||||
|
||||
/** First version with static constructor-like methods */
|
||||
private final int STATIC_CONSTRUCTOR_VERSION = 17;
|
||||
|
||||
private Class<?> Component;
|
||||
private final Object emptyScoreboard;
|
||||
@Getter private final Class<?> TeamPacketClass;
|
||||
private Constructor<?> newTeamPacket;
|
||||
private final Constructor<?> newScoreboardTeam;
|
||||
private Method TeamPacketConstructor_of;
|
||||
private Method TeamPacketConstructor_ofBoolean;
|
||||
private final Field TeamPacket_NAME;
|
||||
private final Field TeamPacket_ACTION;
|
||||
private final Field TeamPacket_PLAYERS;
|
||||
private final Method ScoreboardTeam_getPlayerNameSet;
|
||||
private final Method ScoreboardTeam_setPrefix;
|
||||
private final Method ScoreboardTeam_setSuffix;
|
||||
private Method ScoreboardTeam_setColor;
|
||||
private final Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
private final Enum<?>[] chatFormats;
|
||||
|
||||
private BiConsumerWithException<Object, Scoreboard.NameVisibility> setVisibility = (team, visibility) -> {};
|
||||
private BiConsumerWithException<Object, Scoreboard.CollisionRule> setCollision = (team, collision) -> {};
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS classes, fields and methods.
|
||||
* If anything fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If anything fails
|
||||
*/
|
||||
public TeamPacketData() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Class<?> Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
|
||||
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
|
||||
TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
|
||||
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
|
||||
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
|
||||
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
|
||||
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
|
||||
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
|
||||
boolean.class
|
||||
);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
|
||||
boolean.class
|
||||
);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
}
|
||||
if (minorVersion >= 8) loadVisibility(scoreboardTeam);
|
||||
if (minorVersion >= 9) loadCollision(scoreboardTeam);
|
||||
if (minorVersion >= MODERN_TEAM_DATA_VERSION) {
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
Component
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
Component
|
||||
);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
}
|
||||
if (minorVersion >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void loadVisibility(@NonNull Class<?> scoreboardTeam) {
|
||||
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
|
||||
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumNameTagVisibility" // Bukkit 1.8.0
|
||||
);
|
||||
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
Method setNameTagVisibility = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
|
||||
enumNameTagVisibility
|
||||
);
|
||||
setVisibility = (team, visibility) -> setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void loadCollision(@NonNull Class<?> scoreboardTeam) {
|
||||
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
|
||||
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
Method setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
|
||||
setCollision = (team, collision) -> setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team register packet with specified parameters.
|
||||
*
|
||||
* @param team
|
||||
* Team to register
|
||||
* @param prefix
|
||||
* Team prefix for 1.12-
|
||||
* @param prefixComponent
|
||||
* Team prefix component for 1.13+
|
||||
* @param suffix
|
||||
* Team suffix for 1.12-
|
||||
* @param suffixComponent
|
||||
* Team suffix component for 1.13+
|
||||
* @param visibility
|
||||
* Nametag visibility rule
|
||||
* @param collision
|
||||
* Player collision rule
|
||||
* @param players
|
||||
* Team members
|
||||
* @param options
|
||||
* Team flags
|
||||
* @param color
|
||||
* Team color for 1.13+
|
||||
* @return Register team packet with specified parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object registerTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
|
||||
@NonNull String suffix, @Nullable Object suffixComponent,
|
||||
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
|
||||
((Collection<String>) ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
|
||||
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates unregister team packet with given team.
|
||||
*
|
||||
* @param team
|
||||
* Team to unregister
|
||||
* @return Packet for unregistering team
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object unregisterTeam(@NonNull Object team) {
|
||||
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_of.invoke(null, team);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team update packet with specified parameters.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
* @param prefix
|
||||
* Team prefix for 1.12-
|
||||
* @param prefixComponent
|
||||
* Team prefix component for 1.13+
|
||||
* @param suffix
|
||||
* Team suffix for 1.12-
|
||||
* @param suffixComponent
|
||||
* Team suffix component for 1.13+
|
||||
* @param visibility
|
||||
* Nametag visibility rule
|
||||
* @param collision
|
||||
* Player collision rule
|
||||
* @param options
|
||||
* Team flags
|
||||
* @param color
|
||||
* Team color for 1.13+
|
||||
* @return Update team packet with specified parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object updateTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
|
||||
@NonNull String suffix, @Nullable Object suffixComponent,
|
||||
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
|
||||
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates team properties.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
* @param prefix
|
||||
* Team prefix for 1.12-
|
||||
* @param prefixComponent
|
||||
* Team prefix component for 1.13+
|
||||
* @param suffix
|
||||
* Team suffix for 1.12-
|
||||
* @param suffixComponent
|
||||
* Team suffix component for 1.13+
|
||||
* @param visibility
|
||||
* Nametag visibility rule
|
||||
* @param collision
|
||||
* Player collision rule
|
||||
* @param options
|
||||
* Team flags
|
||||
* @param color
|
||||
* Team color for 1.13+
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void updateTeamData(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
|
||||
@NonNull String suffix, @Nullable Object suffixComponent,
|
||||
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
|
||||
if (BukkitReflection.getMinorVersion() >= MODERN_TEAM_DATA_VERSION) {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
|
||||
ScoreboardTeam_setColor.invoke(team, chatFormats[color.ordinal()]);
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix.invoke(team, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(team, suffix);
|
||||
}
|
||||
setVisibility.accept(team, visibility);
|
||||
setCollision.accept(team, collision);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a team with given name.
|
||||
*
|
||||
* @param name
|
||||
* Team name
|
||||
* @return Team with specified name
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packet is team packet and removes all real players from team
|
||||
* if sent by other plugins and anti-override is fully active on a player.
|
||||
*
|
||||
* @param player
|
||||
* Player who received the packet
|
||||
* @param packet
|
||||
* Received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (!TeamPacketClass.isInstance(packet)) return;
|
||||
int action = TeamPacket_ACTION.getInt(packet);
|
||||
if (action == TeamAction.REMOVE || action == TeamAction.UPDATE) return;
|
||||
TeamPacket_PLAYERS.set(packet, player.getScoreboard().onTeamPacket(
|
||||
action, (String) TeamPacket_NAME.get(packet), (Collection<String>) TeamPacket_PLAYERS.get(packet)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using the almighty Bukkit API.
|
||||
*/
|
||||
public class BukkitTabList extends TabListBase<String> {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public BukkitTabList(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
|
||||
Player p = Bukkit.getPlayer(entry);
|
||||
if (p == null) return;
|
||||
p.setPlayerListName(displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable String displayName) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
return player.getPlatform().toBukkitFormat(component, true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.util.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Header/footer sender that uses NMS to send packets. Available
|
||||
* on all versions since 1.8 when the feature was added into the game.
|
||||
*/
|
||||
public class PacketHeaderFooter {
|
||||
|
||||
private final PacketSender packetSender = new PacketSender();
|
||||
private final BiFunctionWithException<Object, Object, Object> createPacket;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS content. Throws exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something went wrong
|
||||
*/
|
||||
public PacketHeaderFooter() throws ReflectiveOperationException {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
|
||||
ComponentConverter.ensureAvailable();
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor(Component, Component);
|
||||
createPacket = newHeaderFooter::newInstance;
|
||||
} else {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor();
|
||||
Field HEADER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(0);
|
||||
Field FOOTER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(1);
|
||||
createPacket = (header, footer) -> {
|
||||
Object packet = newHeaderFooter.newInstance();
|
||||
HEADER.set(packet, header);
|
||||
FOOTER.set(packet, footer);
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends header/footer to player.
|
||||
*
|
||||
* @param player
|
||||
* Player to send header/footer to.
|
||||
* @param header
|
||||
* Header to use.
|
||||
* @param footer
|
||||
* Footer to use.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull Object header, @NotNull Object footer) {
|
||||
packetSender.sendPacket(player.getPlayer(), createPacket.apply(header, footer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ servers using packets.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList1193 extends PacketTabList18 {
|
||||
|
||||
/** Map of actions to prevent creating new EnumSet on each packet send */
|
||||
private static final Map<Action, EnumSet<?>> actionToEnumSet = new EnumMap<>(Action.class);
|
||||
|
||||
private static Enum actionAddPlayer;
|
||||
private static Enum actionUpdateDisplayName;
|
||||
private static Enum actionUpdateLatency;
|
||||
|
||||
private static Constructor<?> newRemovePacket;
|
||||
|
||||
private static Field PlayerInfoData_UUID;
|
||||
private static Field PlayerInfoData_GameMode;
|
||||
private static Field PlayerInfoData_Listed;
|
||||
private static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList1193(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void loadNew() throws ReflectiveOperationException {
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType", "world.level.EnumGamemode");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a" // Bukkit
|
||||
);
|
||||
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket");
|
||||
Class<?> playerInfoDataClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b" // Bukkit
|
||||
);
|
||||
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
|
||||
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
|
||||
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
|
||||
newRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket").getConstructor(List.class);
|
||||
|
||||
actionAddPlayer = Enum.valueOf(ActionClass, Action.ADD_PLAYER.name());
|
||||
actionUpdateDisplayName = Enum.valueOf(ActionClass, Action.UPDATE_DISPLAY_NAME.name());
|
||||
actionUpdateLatency = Enum.valueOf(ActionClass, Action.UPDATE_LATENCY.name());
|
||||
Enum actionUpdateGameMode = Enum.valueOf(ActionClass, Action.UPDATE_GAME_MODE.name());
|
||||
|
||||
actionToEnumSet.put(Action.ADD_PLAYER, EnumSet.allOf(ActionClass));
|
||||
actionToEnumSet.put(Action.UPDATE_GAME_MODE, EnumSet.of(actionUpdateGameMode));
|
||||
actionToEnumSet.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(actionUpdateDisplayName));
|
||||
actionToEnumSet.put(Action.UPDATE_LATENCY, EnumSet.of(actionUpdateLatency));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
packetSender.sendPacket(player.getPlayer(), newRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NonNull
|
||||
@Override
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
int latency, int gameMode, @Nullable Object displayName) {
|
||||
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(
|
||||
id,
|
||||
action == Action.ADD_PLAYER ? createProfile(id, name, skin) : null,
|
||||
true,
|
||||
latency,
|
||||
gameModes[gameMode],
|
||||
displayName,
|
||||
null
|
||||
)));
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(PlayerInfoClass.isInstance(packet))) return;
|
||||
EnumSet<?> actions = (EnumSet<?>) ACTION.get(packet);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
|
||||
boolean rewriteEntry = false;
|
||||
UUID id = (UUID) PlayerInfoData_UUID.get(nmsData);
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
if (actions.contains(actionUpdateDisplayName)) {
|
||||
Object expectedName = getExpectedDisplayName(id);
|
||||
if (expectedName != null && expectedName != displayName) {
|
||||
displayName = expectedName;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(actionUpdateLatency)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(actionAddPlayer)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData.newInstance(
|
||||
id,
|
||||
profile,
|
||||
PlayerInfoData_Listed.getBoolean(nmsData),
|
||||
latency,
|
||||
PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
|
||||
}
|
||||
if (rewritePacket) PLAYERS.set(packet, updatedList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.TriFunctionWithException;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.7- servers using packets.
|
||||
*/
|
||||
public class PacketTabList17 extends TabListBase<String> {
|
||||
|
||||
private static TriFunctionWithException<String, Boolean, Integer, Object> newPacket;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7- */
|
||||
@NonNull
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
|
||||
@NonNull
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList17(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<?> PlayerInfoClass = BukkitReflection.getClass("PacketPlayOutPlayerInfo", "Packet201PlayerInfo");
|
||||
try {
|
||||
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor(String.class, boolean.class, int.class);
|
||||
newPacket = newPlayerInfo::newInstance;
|
||||
} catch (NoSuchMethodException e) {
|
||||
// 1.7.10 spigot with protocol hack
|
||||
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor();
|
||||
Field USERNAME = ReflectionUtils.getField(PlayerInfoClass, "username");
|
||||
Field ACTION = ReflectionUtils.getField(PlayerInfoClass, "action");
|
||||
Field PING = ReflectionUtils.getField(PlayerInfoClass, "ping");
|
||||
newPacket = (name, addOrUpdate, latency) -> {
|
||||
Object packet = newPlayerInfo.newInstance();
|
||||
USERNAME.set(packet, name);
|
||||
ACTION.set(packet, addOrUpdate ? 3 : 4);
|
||||
PING.set(packet, latency);
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
packetSender = new PacketSender();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), false, 0));
|
||||
userNames.remove(entry);
|
||||
displayNames.remove(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), false, 0));
|
||||
addEntry0(entry, userNames.get(entry), null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), true, latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
// Added in 1.8
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable String displayName) {
|
||||
String display = displayName == null ? name : displayName;
|
||||
packetSender.sendPacket(player.getPlayer(), newPacket.apply(display, true, latency));
|
||||
userNames.put(id, name);
|
||||
displayNames.put(id, display);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
String name = component.toLegacyText();
|
||||
if (name.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7) name = name.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.8 - 1.19.2 servers using packets.
|
||||
*/
|
||||
@Setter
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList18 extends TabListBase<Object> {
|
||||
|
||||
protected static Class<?> PlayerInfoClass;
|
||||
protected static Constructor<?> newPlayerInfo;
|
||||
protected static Field ACTION;
|
||||
protected static Field PLAYERS;
|
||||
protected static Class<Enum> ActionClass;
|
||||
|
||||
protected static Constructor<?> newPlayerInfoData;
|
||||
protected static Field PlayerInfoData_Profile;
|
||||
protected static Field PlayerInfoData_Latency;
|
||||
protected static Field PlayerInfoData_DisplayName;
|
||||
|
||||
protected static Object[] gameModes;
|
||||
|
||||
protected static PacketSender packetSender;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList18(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
|
||||
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumPlayerInfoAction" // Bukkit 1.8.0
|
||||
);
|
||||
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
|
||||
Class<?> playerInfoDataClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
|
||||
"PlayerInfoData" // Bukkit 1.8.0
|
||||
);
|
||||
|
||||
Class<?> classType = BukkitReflection.getMinorVersion() >= 17 ? Collection.class : Iterable.class;
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, classType);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
}
|
||||
|
||||
protected static void loadSharedContent(Class<?> infoData, Class<Enum> gameMode) throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
newPlayerInfoData = infoData.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(infoData, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(infoData, int.class);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(infoData, IChatBaseComponent);
|
||||
gameModes = new Object[] {
|
||||
Enum.valueOf(gameMode, "SURVIVAL"),
|
||||
Enum.valueOf(gameMode, "CREATIVE"),
|
||||
Enum.valueOf(gameMode, "ADVENTURE"),
|
||||
Enum.valueOf(gameMode, "SPECTATOR")
|
||||
};
|
||||
packetSender = new PacketSender();
|
||||
ComponentConverter.ensureAvailable();
|
||||
try {
|
||||
skinData = new SkinData();
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "getting player's game profile", null,
|
||||
"Player skins not working in layout feature");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.REMOVE_PLAYER, entry, "", null, 0, 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable Object displayName) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.UPDATE_LATENCY, entry, "", null, latency, 0, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable Object displayName) {
|
||||
packetSender.sendPacket(player.getPlayer(),
|
||||
createPacket(Action.ADD_PLAYER, id, name, skin, latency, gameMode, displayName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates packet from given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param id
|
||||
* Entry UUID
|
||||
* @param name
|
||||
* Entry name
|
||||
* @param skin
|
||||
* Entry skin
|
||||
* @param latency
|
||||
* Entry latency
|
||||
* @param gameMode
|
||||
* Entry game mode
|
||||
* @param displayName
|
||||
* Entry display name
|
||||
* @return Packet from given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NonNull
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
int latency, int gameMode, @Nullable Object displayName) {
|
||||
Object packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Collections.emptyList());
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(createProfile(id, name, skin));
|
||||
parameters.add(latency);
|
||||
parameters.add(gameModes[gameMode]);
|
||||
parameters.add(displayName);
|
||||
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(parameters.toArray())));
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NonNull
|
||||
public GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(PlayerInfoClass.isInstance(packet))) return;
|
||||
String action = ACTION.get(packet).toString();
|
||||
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
Object expectedName = getExpectedDisplayName(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName);
|
||||
}
|
||||
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, PlayerInfoData_Latency.getInt(nmsData));
|
||||
PlayerInfoData_Latency.set(nmsData, latency);
|
||||
}
|
||||
if (action.equals(Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class for getting skin of players.
|
||||
*/
|
||||
public class SkinData {
|
||||
|
||||
private final Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
|
||||
|
||||
// There is only supposed to be one, however there are exceptions:
|
||||
// #1 - CatServer adds another method
|
||||
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
|
||||
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
|
||||
private final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
|
||||
"world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads NMS fields. If it fails, throws an Exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public SkinData() throws ReflectiveOperationException {}
|
||||
|
||||
/**
|
||||
* Returns player's skin. If server is in offline mode, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get skin of
|
||||
* @return Player's skin or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
public TabList.Skin getSkin(@NotNull Player player) {
|
||||
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player))).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return new TabList.Skin(
|
||||
(String) property.getClass().getMethod("value").invoke(property),
|
||||
(String) property.getClass().getMethod("signature").invoke(property)
|
||||
);
|
||||
} else {
|
||||
return new TabList.Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Base TabList class for all implementations.
|
||||
*
|
||||
* @param <C>
|
||||
* Component class
|
||||
*/
|
||||
public abstract class TabListBase<C> extends TabList<BukkitTabPlayer, C> {
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
private static Function<BukkitTabPlayer, TabListBase<?>> instance;
|
||||
|
||||
@Nullable
|
||||
protected static SkinData skinData;
|
||||
|
||||
@Nullable
|
||||
private static PacketHeaderFooter headerFooter;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
protected TabListBase(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
try {
|
||||
if (BukkitReflection.is1_19_3Plus()) {
|
||||
PacketTabList1193.loadNew();
|
||||
instance = PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
PacketTabList18.load();
|
||||
instance = PacketTabList18::new;
|
||||
} else {
|
||||
PacketTabList17.load();
|
||||
instance = PacketTabList17::new;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
instance = BukkitTabList::new;
|
||||
}
|
||||
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
headerFooter = new PacketHeaderFooter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "sending header/footer", null,
|
||||
"Header/footer will not work");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull Object header, @NonNull Object footer) {
|
||||
if (headerFooter != null) headerFooter.set(player, header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's skin. If NMS fields did not load or server is in
|
||||
* offline mode, returns {@code null}.
|
||||
*
|
||||
* @return Player's skin or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
if (skinData == null) return null;
|
||||
return skinData.getSkin(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
@@ -35,7 +35,7 @@ public class BungeeBossBar implements BossBar {
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
|
||||
packet.setHealth(progress);
|
||||
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
|
||||
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
|
||||
packet.setColor(color.ordinal());
|
||||
packet.setDivision(style.ordinal());
|
||||
player.sendPacket(packet);
|
||||
@@ -49,7 +49,7 @@ public class BungeeBossBar implements BossBar {
|
||||
if (player.getVersion().getMinorVersion() < 9) return;
|
||||
|
||||
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
|
||||
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
|
||||
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
|
||||
+27
-2
@@ -17,13 +17,26 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* The core for BungeeCord forwarding events into all enabled features
|
||||
*/
|
||||
public class BungeeEventListener extends EventListener<ProxiedPlayer> implements Listener {
|
||||
public class BungeeEventListener implements EventListener<ProxiedPlayer>, Listener {
|
||||
|
||||
/**
|
||||
* Listens to player disconnecting from the server.
|
||||
*
|
||||
* @param e
|
||||
* Disconnect event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onQuit(PlayerDisconnectEvent e) {
|
||||
quit(e.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to player connecting to a backend server. This handles
|
||||
* both initial connections and server switch.
|
||||
*
|
||||
* @param e
|
||||
* Server switch event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onSwitch(ServerSwitchEvent e) {
|
||||
TAB tab = TAB.getInstance();
|
||||
@@ -58,6 +71,12 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to command execute event to potentially cancel it.
|
||||
*
|
||||
* @param e
|
||||
* Command execute event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onCommand(ChatEvent e) {
|
||||
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
|
||||
@@ -65,8 +84,14 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to plugin messages.
|
||||
*
|
||||
* @param e
|
||||
* Plugin message event
|
||||
*/
|
||||
@EventHandler
|
||||
public void on(PluginMessageEvent e) {
|
||||
public void onPluginMessage(PluginMessageEvent e) {
|
||||
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
|
||||
if (e.getReceiver() instanceof ProxiedPlayer) {
|
||||
e.setCancelled(true);
|
||||
|
||||
+26
-129
@@ -1,36 +1,29 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisPlayer;
|
||||
import me.neznamy.tab.shared.features.sorting.Sorting;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.Protocol;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import net.md_5.bungee.protocol.packet.Login;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
|
||||
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
|
||||
import net.md_5.bungee.protocol.packet.Team;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -43,9 +36,6 @@ import java.util.function.Supplier;
|
||||
public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** Inaccessible bungee internals */
|
||||
@Nullable
|
||||
private static Field wrapperField;
|
||||
|
||||
@Nullable
|
||||
private static Object directionData;
|
||||
|
||||
@@ -54,7 +44,6 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
static {
|
||||
try {
|
||||
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
|
||||
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
|
||||
getId = ReflectionUtils.setAccessible(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
|
||||
} catch (ReflectiveOperationException exception) {
|
||||
@@ -69,6 +58,21 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
@NotNull
|
||||
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
|
||||
|
||||
/** Whether ByteBuf deserialization should be enabled or not */
|
||||
protected boolean byteBufDeserialization;
|
||||
|
||||
/**
|
||||
* Constructs new instance of the feature
|
||||
*/
|
||||
public BungeePipelineInjector() {
|
||||
super("inbound-boss");
|
||||
boolean antiOverrideTeams = config().getBoolean("scoreboard-teams.enabled", true) &&
|
||||
config().getBoolean("scoreboard-teams.anti-override", true);
|
||||
boolean respectOtherScoreboardPlugins = config().getBoolean("scoreboard.enabled", false) &&
|
||||
config().getBoolean("scoreboard.respect-other-plugins", true);
|
||||
byteBufDeserialization = antiOverrideTeams || respectOtherScoreboardPlugins;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
|
||||
@@ -76,108 +80,9 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
if (wrapperField == null) return null;
|
||||
return ((ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection())).getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
|
||||
((ScoreboardDisplay)packet).getPosition(), ((ScoreboardDisplay) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisplayObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardDisplay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isObjective(@NotNull Object packet) {
|
||||
return packet instanceof ScoreboardObjective;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeam(@NotNull Object packet) {
|
||||
return packet instanceof Team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Object packet) {
|
||||
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyPlayers(@NotNull Object team) {
|
||||
if (TAB.getInstance().getNameTagManager() == null) return;
|
||||
Team packet = (Team) team;
|
||||
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
|
||||
Collection<String> col = Lists.newArrayList(packet.getPlayers());
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
|
||||
String expectedTeam = sorting.getShortTeamName(p);
|
||||
if (expectedTeam != null && (col.contains(p.getNickname()) || col.contains(p.getName())) &&
|
||||
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
|
||||
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
|
||||
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
|
||||
col.remove(p.getNickname());
|
||||
col.remove(p.getName());
|
||||
}
|
||||
}
|
||||
RedisSupport redis = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
|
||||
if (redis != null) {
|
||||
RedisTeams teams = redis.getRedisTeams();
|
||||
if (teams != null) {
|
||||
for (RedisPlayer p : redis.getRedisPlayers().values()) {
|
||||
if (col.contains(p.getNickname()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
|
||||
logTeamOverride(packet.getName(), p.getNickname(), teams.getTeamNames().get(p));
|
||||
col.remove(p.getNickname());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
packet.setPlayers(col.toArray(new String[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (packet instanceof PlayerListItem) {
|
||||
PlayerListItem listItem = (PlayerListItem) packet;
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
|
||||
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
return ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh().getHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -185,19 +90,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
return packet instanceof Login;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance of the feature
|
||||
*/
|
||||
public BungeePipelineInjector() {
|
||||
super("inbound-boss");
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel duplex handler override if features using packets that must be
|
||||
* deserialized manually are used. If they are disabled, deserialization is
|
||||
* disabled for better performance.
|
||||
*/
|
||||
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
|
||||
private class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player
|
||||
@@ -205,7 +103,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
* @param player
|
||||
* player to inject
|
||||
*/
|
||||
public DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
|
||||
private DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@@ -227,12 +125,11 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
*/
|
||||
@NotNull
|
||||
private Object deserialize(@NotNull ByteBuf buf) {
|
||||
if (wrapperField == null) return buf;
|
||||
int marker = buf.readerIndex();
|
||||
try {
|
||||
int packetId = buf.readByte();
|
||||
for (int i=0; i<extraPacketClasses.length; i++) {
|
||||
ChannelWrapper ch = (ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection());
|
||||
ChannelWrapper ch = ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh();
|
||||
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,21 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.hook.BungeePremiumVanishHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.*;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ComponentCache;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyConfig;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.ClickEvent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
@@ -30,27 +24,43 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BungeeCord implementation of Platform
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
/** Flag tracking plugin presence */
|
||||
@Getter
|
||||
private final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
|
||||
|
||||
/** Component cache for better performance */
|
||||
private final ComponentCache<IChatBaseComponent, BaseComponent> cache = new ComponentCache<>(1000, this::toComponent0);
|
||||
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin instance.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance
|
||||
*/
|
||||
public BungeePlatform(@NotNull BungeeTAB plugin) {
|
||||
this.plugin = plugin;
|
||||
if (ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null) {
|
||||
PremiumVanishHook.setInstance(new BungeePremiumVanishHook(this));
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
ViaVersionHook.getInstance().printProxyWarn();
|
||||
try {
|
||||
ProxyConfig config = ProxyServer.getInstance().getConfig();
|
||||
if ((boolean) config.getClass().getMethod("isDisableTabListRewrite").invoke(config)) {
|
||||
logWarn(new SimpleComponent("Waterfall's \"disable_tab_list_rewrite: true\" option may cause " +
|
||||
"the plugin to not work correctly. Disable it to avoid issues."));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Not waterfall
|
||||
}
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
|
||||
}
|
||||
@@ -67,13 +77,13 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull IChatBaseComponent message) {
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
plugin.getLogger().info(message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull IChatBaseComponent message) {
|
||||
plugin.getLogger().warning(EnumChatFormat.RED.getFormat() + message.toLegacyText());
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
plugin.getLogger().warning(EnumChatFormat.RED + message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,7 +104,7 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
new Metrics(plugin, 10535).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
|
||||
new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUNGEE).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
|
||||
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
|
||||
}
|
||||
|
||||
@@ -116,42 +126,18 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
//noinspection ConstantConditions
|
||||
if (premiumVanish && BungeeVanishAPI.canSee(
|
||||
((BungeeTabPlayer)viewer).getPlayer(),
|
||||
((BungeeTabPlayer)target).getPlayer())) return true;
|
||||
return super.canSee(viewer, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal component class to platform's component class. If the component is
|
||||
* present in the cache, it is taken from it.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param version
|
||||
* Game version to convert component for
|
||||
* @return Converted component
|
||||
*/
|
||||
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
return cache.get(component, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal component class to platform's component class
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param version
|
||||
* Game version to convert component for
|
||||
* @return Converted component
|
||||
*/
|
||||
private BaseComponent toComponent0(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
TextComponent textComponent = new TextComponent(component.getText());
|
||||
ChatModifier modifier = component.getModifier();
|
||||
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
|
||||
modifier.getColor().toString(version.getMinorVersion() >= 16)));
|
||||
public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return new TextComponent(component.toLegacyText());
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
TextComponent textComponent = new TextComponent(iComponent.getText());
|
||||
ChatModifier modifier = iComponent.getModifier();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
|
||||
} else {
|
||||
textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
}
|
||||
|
||||
if (modifier.isBold()) textComponent.setBold(true);
|
||||
if (modifier.isItalic()) textComponent.setItalic(true);
|
||||
@@ -161,15 +147,14 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
textComponent.setFont(modifier.getFont());
|
||||
|
||||
if (modifier.getClickEvent() != null) {
|
||||
textComponent.setClickEvent(new ClickEvent(
|
||||
ClickEvent.Action.valueOf(modifier.getClickEvent().getAction().name()),
|
||||
modifier.getClickEvent().getValue()
|
||||
));
|
||||
if (!iComponent.getExtra().isEmpty()) {
|
||||
List<BaseComponent> list = new ArrayList<>();
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
list.add(convertComponent(extra, modern));
|
||||
}
|
||||
textComponent.setExtra(list);
|
||||
}
|
||||
|
||||
if (!component.getExtra().isEmpty()) textComponent.setExtra(
|
||||
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
|
||||
return textComponent;
|
||||
}
|
||||
}
|
||||
+57
-39
@@ -1,14 +1,16 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.Either;
|
||||
import net.md_5.bungee.protocol.NumberFormat;
|
||||
import net.md_5.bungee.protocol.packet.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -18,32 +20,40 @@ import java.util.Collection;
|
||||
* any Scoreboard API and the scoreboard class it has is just a
|
||||
* downstream tracker, we need to use packets.
|
||||
*/
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer, BaseComponent> {
|
||||
|
||||
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
|
||||
/** Version with a minor team recode */
|
||||
private final int TEAM_REWORK_VERSION = 13;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public BungeeScoreboard(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot0(int slot, @NotNull String objective) {
|
||||
public void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
player.sendPacket(new ScoreboardDisplay(slot, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable BaseComponent numberFormat) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
either(title),
|
||||
ScoreboardObjective.HealthDisplay.values()[display],
|
||||
(byte) ObjectiveAction.REGISTER,
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
|
||||
player.getPlatform().toComponent(numberFormat, player.getVersion()))
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
public void unregisterObjective0(@NonNull String objectiveName) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
either(""), // Empty value instead of null to prevent NPE kick on 1.7
|
||||
@@ -54,26 +64,21 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable BaseComponent numberFormat) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objectiveName,
|
||||
either(title),
|
||||
ScoreboardObjective.HealthDisplay.values()[display],
|
||||
(byte) ObjectiveAction.UPDATE,
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
|
||||
player.getPlatform().toComponent(numberFormat, player.getVersion()))
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
player.sendPacket(new Team(
|
||||
name,
|
||||
(byte) TeamAction.CREATE,
|
||||
@@ -82,24 +87,21 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
either(suffix),
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
color,
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
|
||||
(byte)options,
|
||||
players.toArray(new String[0])
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
public void unregisterTeam0(@NonNull String name) {
|
||||
player.sendPacket(new Team(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
int color = 0;
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
|
||||
}
|
||||
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
player.sendPacket(new Team(
|
||||
name,
|
||||
(byte) TeamAction.UPDATE,
|
||||
@@ -108,28 +110,27 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
either(suffix),
|
||||
visibility.toString(),
|
||||
collision.toString(),
|
||||
color,
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
|
||||
(byte)options,
|
||||
null
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
|
||||
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
|
||||
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable BaseComponent displayName, @Nullable BaseComponent numberFormat) {
|
||||
player.sendPacket(new ScoreboardScore(
|
||||
scoreHolder,
|
||||
(byte) ScoreAction.CHANGE,
|
||||
objective,
|
||||
score,
|
||||
displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()),
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
|
||||
player.getPlatform().toComponent(numberFormat, player.getVersion()))
|
||||
displayName,
|
||||
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
|
||||
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
player.sendPacket(new ScoreboardScoreReset(scoreHolder, objective));
|
||||
} else {
|
||||
@@ -137,11 +138,28 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
|
||||
}
|
||||
}
|
||||
|
||||
private Either<String, BaseComponent> either(@NotNull String text) {
|
||||
if (player.getVersion().getMinorVersion() >= 13) {
|
||||
return Either.right(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion()));
|
||||
private Either<String, BaseComponent> either(@NonNull String text) {
|
||||
if (player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION) {
|
||||
return Either.right(TabComponent.optimized(text).convert(player.getVersion()));
|
||||
} else {
|
||||
return Either.left(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ScoreboardDisplay) {
|
||||
ScoreboardDisplay display = (ScoreboardDisplay) packet;
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getPosition(), display.getName());
|
||||
}
|
||||
if (packet instanceof ScoreboardObjective) {
|
||||
ScoreboardObjective objective = (ScoreboardObjective) packet;
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getAction(), objective.getName());
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof Team) {
|
||||
Team team = (Team) packet;
|
||||
if (team.getMode() == TeamAction.REMOVE || team.getMode() == TeamAction.UPDATE) return;
|
||||
team.setPlayers(onTeamPacket(team.getMode(), team.getName(), Lists.newArrayList(team.getPlayers())).toArray(new String[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,8 @@ public class BungeeTAB extends Plugin {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.ScoreboardScoreReset")) {
|
||||
getLogger().warning(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1774 " +
|
||||
"(released on November 25th, 2023) and up (or an equivalent fork) to work."));
|
||||
getLogger().warning(EnumChatFormat.RED + "The plugin requires BungeeCord build #1774 " +
|
||||
"(released on November 25th, 2023) and up (or an equivalent fork) to work.");
|
||||
return;
|
||||
}
|
||||
TAB.create(new BungeePlatform(this));
|
||||
|
||||
@@ -13,6 +13,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command on BungeeCord.
|
||||
*/
|
||||
public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
|
||||
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
@@ -30,17 +31,17 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
/** Player's scoreboard */
|
||||
@NotNull
|
||||
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
|
||||
private final BungeeScoreboard scoreboard = new BungeeScoreboard(this);
|
||||
|
||||
/** Player's tab list based on version */
|
||||
@NotNull
|
||||
private final TabList tabList1_7 = new BungeeTabList17(this);
|
||||
private final BungeeTabList tabList1_7 = new BungeeTabList17(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList1_8 = new BungeeTabList18(this);
|
||||
private final BungeeTabList tabList1_8 = new BungeeTabList18(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList1_19_3 = new BungeeTabList1193(this);
|
||||
private final BungeeTabList tabList1_19_3 = new BungeeTabList1193(this);
|
||||
|
||||
/** Player's boss bar view */
|
||||
@NotNull
|
||||
@@ -69,8 +70,8 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage((BaseComponent) message.convert(getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,11 +90,6 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
return (ProxiedPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return getPlayer().isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BungeePlatform getPlatform() {
|
||||
return (BungeePlatform) platform;
|
||||
@@ -131,18 +127,13 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
if (getPlatform().isPremiumVanish() && BungeeVanishAPI.isInvisible(getPlayer())) return true;
|
||||
} catch (IllegalStateException ignored) {
|
||||
// PV Bug: PremiumVanish must be enabled to use its API
|
||||
}
|
||||
if (PremiumVanishHook.getInstance() != null && PremiumVanishHook.getInstance().isVanished(this)) return true;
|
||||
return super.isVanished();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList getTabList() {
|
||||
public BungeeTabList getTabList() {
|
||||
int version = getPlayer().getPendingConnection().getVersion();
|
||||
if (version >= ProtocolVersion.V1_19_3.getNetworkId()) return tabList1_19_3;
|
||||
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
|
||||
|
||||
+6
@@ -21,6 +21,12 @@ public class BungeeRedisSupport extends RedisSupport implements Listener {
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
/**
|
||||
* Listens to messages coming from other proxies.
|
||||
*
|
||||
* @param e
|
||||
* Message event
|
||||
*/
|
||||
@EventHandler
|
||||
public void onMessage(@NotNull PubSubMessageEvent e) {
|
||||
if (!e.getChannel().equals(TabConstants.REDIS_CHANNEL_NAME)) return;
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.Platform;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* PremiumVanish hook for BungeeCord.
|
||||
*/
|
||||
public class BungeePremiumVanishHook extends PremiumVanishHook {
|
||||
|
||||
private final boolean canSeeEnabled;
|
||||
|
||||
/**
|
||||
* Constructs new instance. If using an old version of PV, warn
|
||||
* is printed into the platform's logger.
|
||||
*
|
||||
* @param platform
|
||||
* Platform to print potential warn to
|
||||
*/
|
||||
public BungeePremiumVanishHook(@NotNull Platform platform) {
|
||||
if (ReflectionUtils.methodExists(BungeeVanishAPI.class, "canSee", ProxiedPlayer.class, ProxiedPlayer.class)) {
|
||||
canSeeEnabled = true;
|
||||
} else {
|
||||
canSeeEnabled = false;
|
||||
platform.logWarn(new SimpleComponent("Detected an outdated version of " +
|
||||
"PremiumVanish with limited API. Vanish compatibility " +
|
||||
"may not work as expected. Update PremiumVanish to version 2.7.11+ for optimal experience."));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
//noinspection ConstantValue
|
||||
return canSeeEnabled && BungeeVanishAPI.canSee(((BungeeTabPlayer)viewer).getPlayer(), ((BungeeTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished(@NotNull TabPlayer player) {
|
||||
try {
|
||||
return BungeeVanishAPI.isInvisible(((BungeeTabPlayer)player).getPlayer());
|
||||
} catch (IllegalStateException ignored) {
|
||||
// PV Bug: PremiumVanish must be enabled to use its API
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+108
-29
@@ -1,74 +1,153 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
|
||||
import net.md_5.bungee.tab.ServerUnique;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class BungeeTabList implements TabList {
|
||||
|
||||
/** Player this TabList belongs to */
|
||||
@NotNull
|
||||
protected final BungeeTabPlayer player;
|
||||
/**
|
||||
* Abstract TabList class for BungeeCord containing
|
||||
* common code for all implementations.
|
||||
*/
|
||||
public abstract class BungeeTabList extends TabList<BungeeTabPlayer, BaseComponent> {
|
||||
|
||||
/** Pointer to UUIDs in player's TabList */
|
||||
private final Collection<UUID> uuids;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public BungeeTabList(@NotNull BungeeTabPlayer player) {
|
||||
this.player = player;
|
||||
protected BungeeTabList(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
uuids = (Collection<UUID>) ReflectionUtils.getField(ServerUnique.class, "uuids").get(((UserConnection)player.getPlayer()).getTabListHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.sendPacket(new PlayerListHeaderFooter(
|
||||
player.getPlatform().toComponent(header, player.getVersion()),
|
||||
player.getPlatform().toComponent(footer, player.getVersion())
|
||||
));
|
||||
public void setPlayerListHeaderFooter0(@NonNull BaseComponent header, @NonNull BaseComponent footer) {
|
||||
player.sendPacket(new PlayerListHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Item item(@NotNull UUID id) {
|
||||
/**
|
||||
* Creates new {@link Item} with given UUID.
|
||||
*
|
||||
* @param id
|
||||
* UUID to use
|
||||
* @return New {@link Item} with given UUID.
|
||||
*/
|
||||
@NonNull
|
||||
public Item item(@NonNull UUID id) {
|
||||
Item item = new Item();
|
||||
item.setUuid(id);
|
||||
return item;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Item entryToItem(Entry entry) {
|
||||
Item item = item(entry.getUniqueId());
|
||||
if (entry.getDisplayName() != null) {
|
||||
item.setDisplayName(player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()));
|
||||
}
|
||||
item.setGamemode(entry.getGameMode());
|
||||
/**
|
||||
* Converts entry data to item.
|
||||
*
|
||||
* @param id
|
||||
* Entry UUID
|
||||
* @param name
|
||||
* Entry name
|
||||
* @param skin
|
||||
* Entry skin
|
||||
* @param latency
|
||||
* Entry latency
|
||||
* @param gameMode
|
||||
* Entry game mode
|
||||
* @param displayName
|
||||
* Entry display name
|
||||
* @return Converted item from parameters
|
||||
*/
|
||||
@NonNull
|
||||
public Item entryToItem(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
Item item = item(id);
|
||||
item.setUsername(name);
|
||||
item.setDisplayName(displayName);
|
||||
item.setGamemode(gameMode);
|
||||
item.setListed(true);
|
||||
item.setPing(entry.getLatency());
|
||||
if (entry.getSkin() != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
|
||||
item.setPing(latency);
|
||||
if (skin != null) {
|
||||
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature())});
|
||||
} else {
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setUsername(entry.getName());
|
||||
return item;
|
||||
}
|
||||
|
||||
public void addUuid(UUID id) {
|
||||
/**
|
||||
* Adds given UUID to BungeeCord's tablist uuid tracker.
|
||||
*
|
||||
* @param id
|
||||
* UUID to add
|
||||
*/
|
||||
public void addUuid(@NonNull UUID id) {
|
||||
uuids.add(id);
|
||||
}
|
||||
|
||||
public void removeUuid(UUID id) {
|
||||
/**
|
||||
* Removes given UUID from BungeeCord's tablist uuid tracker.
|
||||
*
|
||||
* @param id
|
||||
* UUID to remove
|
||||
*/
|
||||
public void removeUuid(@NonNull UUID id) {
|
||||
uuids.remove(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PlayerListItem) {
|
||||
PlayerListItem listItem = (PlayerListItem) packet;
|
||||
for (PlayerListItem.Item item : listItem.getItems()) {
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
BaseComponent expectedDisplayName = getExpectedDisplayName(item.getUuid());
|
||||
if (expectedDisplayName != null) item.setDisplayName(expectedDisplayName);
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(player, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
} else if (packet instanceof PlayerListItemUpdate) {
|
||||
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
|
||||
for (PlayerListItem.Item item : update.getItems()) {
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
|
||||
BaseComponent expectedDisplayName = getExpectedDisplayName(item.getUuid());
|
||||
if (expectedDisplayName != null) item.setDisplayName(expectedDisplayName);
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
|
||||
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(player, item.getUuid(), item.getPing()));
|
||||
}
|
||||
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, item.getUuid(), item.getUsername());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return uuids.contains(entry);
|
||||
}
|
||||
}
|
||||
+33
-18
@@ -1,15 +1,14 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ players using the new tab list packets.
|
||||
@@ -17,12 +16,28 @@ import java.util.UUID;
|
||||
*/
|
||||
public class BungeeTabList1193 extends BungeeTabList {
|
||||
|
||||
public BungeeTabList1193(@NotNull BungeeTabPlayer player) {
|
||||
/** Map of actions to prevent creating new EnumSet on each packet send */
|
||||
private static final Map<Action, EnumSet<PlayerListItemUpdate.Action>> actions = new EnumMap<>(Action.class);
|
||||
|
||||
static {
|
||||
actions.put(Action.ADD_PLAYER, EnumSet.allOf(PlayerListItemUpdate.Action.class));
|
||||
actions.put(Action.UPDATE_GAME_MODE, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE));
|
||||
actions.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME));
|
||||
actions.put(Action.UPDATE_LATENCY, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public BungeeTabList1193(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
removeUuid(entry);
|
||||
PlayerListItemRemove remove = new PlayerListItemRemove();
|
||||
remove.setUuids(new UUID[]{entry});
|
||||
@@ -30,35 +45,35 @@ public class BungeeTabList1193 extends BungeeTabList {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
|
||||
item.setDisplayName(displayName);
|
||||
sendPacket(Action.UPDATE_DISPLAY_NAME, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY), item);
|
||||
sendPacket(Action.UPDATE_LATENCY, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE), item);
|
||||
sendPacket(Action.UPDATE_GAME_MODE, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
addUuid(entry.getUniqueId());
|
||||
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), entryToItem(entry));
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
addUuid(id);
|
||||
sendPacket(Action.ADD_PLAYER, entryToItem(id, name, skin, latency, gameMode, displayName));
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
|
||||
private void sendPacket(@NonNull Action action, @NonNull Item item) {
|
||||
PlayerListItemUpdate packet = new PlayerListItemUpdate();
|
||||
packet.setActions(actions);
|
||||
packet.setActions(actions.get(action));
|
||||
packet.setItems(new Item[]{item});
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
+36
-23
@@ -1,10 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -23,18 +25,24 @@ import java.util.UUID;
|
||||
public class BungeeTabList17 extends BungeeTabList {
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7 */
|
||||
@NotNull
|
||||
@NonNull
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
@NonNull
|
||||
private final Map<UUID, BaseComponent> displayNames = new HashMap<>();
|
||||
|
||||
public BungeeTabList17(@NotNull BungeeTabPlayer player) {
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public BungeeTabList17(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
removeUuid(entry);
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
@@ -45,51 +53,56 @@ public class BungeeTabList17 extends BungeeTabList {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
addEntry(new Entry.Builder(entry).displayName(displayName).name(userNames.get(entry)).build());
|
||||
addEntry0(entry, userNames.get(entry), null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.UPDATE_LATENCY, createItem(null, displayNames.get(entry), latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {/*Added in 1.8*/}
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {/*Added in 1.8*/}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
addUuid(entry.getUniqueId());
|
||||
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
|
||||
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
|
||||
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
addUuid(id);
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(name, displayName == null ? new TextComponent(name) : displayName, latency));
|
||||
|
||||
// Add to map
|
||||
userNames.put(entry.getUniqueId(), entry.getName());
|
||||
displayNames.put(entry.getUniqueId(), displayNameString);
|
||||
userNames.put(id, name);
|
||||
displayNames.put(id, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
public void setPlayerListHeaderFooter0(@NonNull BaseComponent header, @NonNull BaseComponent footer) {
|
||||
// Not available on 1.7
|
||||
}
|
||||
|
||||
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
|
||||
@Override
|
||||
public BaseComponent toComponent(@NonNull TabComponent component) {
|
||||
String displayNameString = component.toLegacyText();
|
||||
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
|
||||
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
return TabComponent.optimized(displayNameString).convert(player.getVersion());
|
||||
}
|
||||
|
||||
private void update(@NonNull PlayerListItem.Action action, @NonNull PlayerListItem.Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new PlayerListItem.Item[]{item});
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
|
||||
private PlayerListItem.Item createItem(@Nullable String username, @NonNull BaseComponent displayName, int latency) {
|
||||
PlayerListItem.Item item = new PlayerListItem.Item();
|
||||
item.setUsername(username);
|
||||
item.setPing(latency);
|
||||
item.setDisplayName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), player.getVersion()));
|
||||
item.setDisplayName(displayName);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
+18
-12
@@ -1,10 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
@@ -15,44 +15,50 @@ import java.util.UUID;
|
||||
*/
|
||||
public class BungeeTabList18 extends BungeeTabList {
|
||||
|
||||
public BungeeTabList18(@NotNull BungeeTabPlayer player) {
|
||||
/**
|
||||
* Constructs new instance with given parameter.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public BungeeTabList18(@NonNull BungeeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
removeUuid(entry);
|
||||
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
|
||||
item.setDisplayName(displayName);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_LATENCY, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(PlayerListItem.Action.UPDATE_GAMEMODE, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
addUuid(entry.getUniqueId());
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(entry));
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable BaseComponent displayName) {
|
||||
addUuid(id);
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(id, name, skin, latency, gameMode, displayName));
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
|
||||
private void sendPacket(@NonNull PlayerListItem.Action action, @NonNull Item item) {
|
||||
PlayerListItem packet = new PlayerListItem();
|
||||
packet.setAction(action);
|
||||
packet.setItems(new Item[]{item});
|
||||
|
||||
+19
-4
@@ -12,13 +12,28 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(projects.shared)
|
||||
minecraft("com.mojang:minecraft:1.20.4")
|
||||
minecraft("com.mojang:minecraft:1.20.5")
|
||||
mappings(loom.officialMojangMappings())
|
||||
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
|
||||
modImplementation("net.fabricmc:fabric-loader:0.14.17")
|
||||
val version = "0.91.1+1.20.4"
|
||||
modImplementation("net.fabricmc:fabric-loader:0.15.9")
|
||||
val version = "0.96.14+1.20.5"
|
||||
modImplementation(fabricApi.module("fabric-api-base", version))
|
||||
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
|
||||
modImplementation(fabricApi.module("fabric-networking-api-v1", version))
|
||||
modImplementation(fabricApi.module("fabric-entity-events-v1", version))
|
||||
}
|
||||
modImplementation(fabricApi.module("fabric-command-api-v1", "0.77.0+1.18.2"))
|
||||
modImplementation(fabricApi.module("fabric-command-api-v2", version))
|
||||
}
|
||||
|
||||
loom {
|
||||
accessWidenerPath.set(file("src/main/resources/resources/tab.accesswidener"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(17)
|
||||
}
|
||||
validateAccessWidener {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.fabric;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent.BossBarColor;
|
||||
@@ -14,19 +14,24 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar implementation for Fabric using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class FabricBossBar implements BossBar {
|
||||
|
||||
/** Player this BossBar belongs to */
|
||||
@NotNull
|
||||
private final FabricTabPlayer player;
|
||||
|
||||
/** Map of BossBars visible to the player */
|
||||
@NotNull
|
||||
private final Map<UUID, ServerBossEvent> bars = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
|
||||
TabComponent.optimized(title).convert(player.getVersion()),
|
||||
BossBarColor.valueOf(color.name()),
|
||||
BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
@@ -37,7 +42,7 @@ public class FabricBossBar implements BossBar {
|
||||
|
||||
@Override
|
||||
public void update(@NotNull UUID id, @NotNull String title) {
|
||||
bars.get(id).setName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
|
||||
bars.get(id).setName(TabComponent.optimized(title).convert(player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,37 +1,55 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
/**
|
||||
* EntityView implementation for Fabric using packets.
|
||||
*/
|
||||
public class FabricEntityView implements EntityView {
|
||||
|
||||
/** Player this view belongs to */
|
||||
@NotNull
|
||||
private final FabricTabPlayer player;
|
||||
|
||||
@NotNull
|
||||
private static final ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
|
||||
private final ArmorStand dummyEntity;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this view will belong to
|
||||
*/
|
||||
public FabricEntityView(@NotNull FabricTabPlayer player) {
|
||||
this.player = player;
|
||||
|
||||
// Make level not null, because some mods hacking deep into the server code cause NPE
|
||||
dummyEntity = new ArmorStand(FabricMultiVersion.getLevel(player.getPlayer()), 0, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
|
||||
@NotNull EntityData data) {
|
||||
player.sendPacket(FabricTAB.getVersion().spawnEntity(entityId, id, entityType, location));
|
||||
updateEntityMetadata(entityId, data);
|
||||
FabricMultiVersion.sendPackets(player.getPlayer(), Arrays.asList(
|
||||
FabricMultiVersion.spawnEntity(FabricMultiVersion.getLevel(player.getPlayer()), entityId, id, entityType, location),
|
||||
FabricMultiVersion.newEntityMetadata(entityId, data)
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
player.sendPacket(FabricTAB.getVersion().newEntityMetadata(entityId, data));
|
||||
player.sendPacket(FabricMultiVersion.newEntityMetadata(entityId, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +61,7 @@ public class FabricEntityView implements EntityView {
|
||||
|
||||
@Override
|
||||
public void destroyEntities(int... entities) {
|
||||
FabricTAB.getVersion().destroyEntities(player, entities);
|
||||
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +76,7 @@ public class FabricEntityView implements EntityView {
|
||||
|
||||
@Override
|
||||
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
|
||||
return FabricTAB.getVersion().isSpawnPlayerPacket(packet);
|
||||
return FabricMultiVersion.isSpawnPlayerPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,35 +90,50 @@ public class FabricEntityView implements EntityView {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getTeleportEntityId(@NotNull Object teleportPacket) {
|
||||
// Reflection because on 1.16.5 there is no getter and per-version code would only add unnecessary code
|
||||
return ReflectionUtils.getFields(ClientboundTeleportEntityPacket.class, int.class).get(0).getInt(teleportPacket);
|
||||
return ((ClientboundTeleportEntityPacket)teleportPacket).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getMoveEntityId(@NotNull Object movePacket) {
|
||||
return (int) ReflectionUtils.getFields(ClientboundMoveEntityPacket.class, int.class).get(0).get(movePacket);
|
||||
return ((ClientboundMoveEntityPacket)movePacket).entityId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
|
||||
return FabricTAB.getVersion().getSpawnedPlayer(playerSpawnPacket);
|
||||
return FabricMultiVersion.getSpawnedPlayerId((Packet<?>) playerSpawnPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
|
||||
return FabricTAB.getVersion().getDestroyedEntities(destroyPacket);
|
||||
return FabricMultiVersion.getDestroyedEntities((Packet<?>) destroyPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Object packet) {
|
||||
return FabricTAB.getVersion().isBundlePacket(packet);
|
||||
return FabricMultiVersion.isBundlePacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
|
||||
return FabricTAB.getVersion().getPackets(bundlePacket);
|
||||
return FabricMultiVersion.getBundledPackets((Packet<?>) bundlePacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Location getMoveDiff(@NotNull Object movePacket) {
|
||||
ClientboundMoveEntityPacket packet = (ClientboundMoveEntityPacket) movePacket;
|
||||
return new Location(packet.xa, packet.ya, packet.za);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveEntity(int entityId, @NotNull Location moveDiff) {
|
||||
player.sendPacket(new ClientboundMoveEntityPacket.Pos(
|
||||
entityId,
|
||||
(short) moveDiff.getX(),
|
||||
(short) moveDiff.getY(),
|
||||
(short) moveDiff.getZ(),
|
||||
false
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -10,22 +9,28 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class FabricEventListener extends EventListener<ServerPlayer> {
|
||||
/**
|
||||
* Event listener for Fabric.
|
||||
*/
|
||||
public class FabricEventListener implements EventListener<ServerPlayer> {
|
||||
|
||||
/**
|
||||
* Registers all event listeners.
|
||||
*/
|
||||
public void register() {
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
|
||||
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
|
||||
//TODO command preprocess
|
||||
if (ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion()).getMinorVersion() >= 16) {
|
||||
if (FabricTAB.supportsEntityEvents()) {
|
||||
// Added in 1.16
|
||||
ServerPlayerEvents.AFTER_RESPAWN.register(
|
||||
(oldPlayer, newPlayer, alive) -> {
|
||||
replacePlayer(newPlayer.getUUID(), newPlayer);
|
||||
// respawning from death & taking end portal in the end do not call world change event
|
||||
worldChange(newPlayer.getUUID(), FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(newPlayer)));
|
||||
worldChange(newPlayer.getUUID(), FabricMultiVersion.getLevelName(FabricMultiVersion.getLevel(newPlayer)));
|
||||
});
|
||||
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
|
||||
(player, origin, destination) -> worldChange(player.getUUID(), FabricTAB.getVersion().getName(destination)));
|
||||
(player, origin, destination) -> worldChange(player.getUUID(), FabricMultiVersion.getLevelName(destination)));
|
||||
} // TODO else
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.fabric.loader.Loader;
|
||||
import me.neznamy.tab.platforms.fabric.loader.Loader_1_20_5;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Class managing cross-version code in shared module.
|
||||
*/
|
||||
public class FabricMultiVersion {
|
||||
|
||||
public static final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(FabricTAB.minecraftVersion);
|
||||
public static final Loader loaderNew = new Loader_1_20_5();
|
||||
public static final Loader loaderOld = createOldLoader();
|
||||
|
||||
@SneakyThrows
|
||||
private static Loader createOldLoader() {
|
||||
return (Loader) Class.forName("me.neznamy.tab.platforms.fabric.loader.Loader_1_14_4")
|
||||
.getConstructor(ProtocolVersion.class).newInstance(serverVersion);
|
||||
}
|
||||
|
||||
public static boolean isSneaking(@NotNull ServerPlayer player) {
|
||||
if (serverVersion.getMinorVersion() >= 15) return loaderNew.isSneaking(player);
|
||||
return loaderOld.isSneaking(player);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getLevelName(@NotNull Level level) {
|
||||
if (serverVersion.getMinorVersion() >= 16) return loaderNew.getLevelName(level);
|
||||
return loaderOld.getLevelName(level);
|
||||
}
|
||||
|
||||
public static void addSibling(@NotNull Component parent, @NotNull Component child) {
|
||||
if (serverVersion.getMinorVersion() >= 16) loaderNew.addSibling(parent, child);
|
||||
else loaderOld.addSibling(parent, child);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
|
||||
if (serverVersion.getMinorVersion() >= 16) return loaderNew.convertModifier(modifier, modern);
|
||||
return loaderOld.convertModifier(modifier, modern);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> newHeaderFooter(@NotNull Component header, @NotNull Component footer) {
|
||||
if (serverVersion.getMinorVersion() >= 17) return loaderNew.newHeaderFooter(header, footer);
|
||||
return loaderOld.newHeaderFooter(header, footer);
|
||||
}
|
||||
|
||||
public static boolean isTeamPacket(@NotNull Packet<?> packet) {
|
||||
if (serverVersion.getMinorVersion() >= 17) return loaderNew.isTeamPacket(packet);
|
||||
return loaderOld.isTeamPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> registerTeam(@NotNull PlayerTeam team) {
|
||||
if (serverVersion.getMinorVersion() >= 17) return loaderNew.registerTeam(team);
|
||||
return loaderOld.registerTeam(team);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> unregisterTeam(@NotNull PlayerTeam team) {
|
||||
if (serverVersion.getMinorVersion() >= 17) return loaderNew.unregisterTeam(team);
|
||||
return loaderOld.unregisterTeam(team);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> updateTeam(@NotNull PlayerTeam team) {
|
||||
if (serverVersion.getMinorVersion() >= 17) return loaderNew.updateTeam(team);
|
||||
return loaderOld.updateTeam(team);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static int[] getDestroyedEntities(Packet<?> destroyPacket) {
|
||||
if (serverVersion.getMinorVersion() >= 17) return loaderNew.getDestroyedEntities(destroyPacket);
|
||||
return loaderOld.getDestroyedEntities(destroyPacket);
|
||||
}
|
||||
|
||||
public static void logInfo(@NotNull TabComponent message) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) loaderNew.logInfo(message);
|
||||
else loaderOld.logInfo(message);
|
||||
}
|
||||
|
||||
public static void logWarn(@NotNull TabComponent message) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) loaderNew.logWarn(message);
|
||||
else loaderOld.logWarn(message);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Component newTextComponent(@NotNull String text) {
|
||||
if (serverVersion.getMinorVersion() >= 19) return loaderNew.newTextComponent(text);
|
||||
return loaderOld.newTextComponent(text);
|
||||
}
|
||||
|
||||
public static void sendMessage(@NotNull CommandSourceStack source, @NotNull Component message) {
|
||||
if (serverVersion.getMinorVersion() >= 19) loaderNew.sendMessage(source, message);
|
||||
else loaderOld.sendMessage(source, message);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<ClientGamePacketListener> spawnEntity(Level level, int id, UUID uuid, Object type, Location location) {
|
||||
if (serverVersion.getMinorVersion() >= 19) return loaderNew.spawnEntity(level, id, uuid, type, location);
|
||||
return loaderOld.spawnEntity(level, id, uuid, type, location);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void setStyle(@NotNull Component component, @NotNull Style style) {
|
||||
if (serverVersion.getMinorVersion() >= 19) loaderNew.setStyle(component, style);
|
||||
else if (serverVersion.getMinorVersion() >= 16) component.getClass().getMethod("method_10862", Style.class).invoke(component, style);
|
||||
else loaderOld.setStyle(component, style);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void sendMessage(@NotNull ServerPlayer player, @NotNull Component message) {
|
||||
if (serverVersion.getMinorVersion() >= 19) loaderNew.sendMessage(player, message);
|
||||
else if (serverVersion.getMinorVersion() >= 16)
|
||||
player.getClass().getMethod("method_9203", Component.class, UUID.class).invoke(player, message, new UUID(0, 0));
|
||||
else loaderOld.sendMessage(player, message);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<ClientGamePacketListener> newEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.newEntityMetadata(entityId, data);
|
||||
return loaderOld.newEntityMetadata(entityId, data);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
|
||||
int position = EntityData.getArmorStandFlagsPosition(serverVersion.getMinorVersion());
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.createDataWatcher(viewer, flags, displayName, nameVisible, position);
|
||||
return loaderOld.createDataWatcher(viewer, flags, displayName, nameVisible, position);
|
||||
}
|
||||
|
||||
public static boolean isPlayerInfo(@NotNull Packet<?> packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.isPlayerInfo(packet);
|
||||
return loaderOld.isPlayerInfo(packet);
|
||||
}
|
||||
|
||||
public static void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) loaderNew.onPlayerInfo(receiver, packet);
|
||||
else loaderOld.onPlayerInfo(receiver, packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> buildTabListPacket(@NotNull TabList.Action action, @NotNull FabricTabList.Builder builder) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.buildTabListPacket(action, builder);
|
||||
return loaderOld.buildTabListPacket(action, builder);
|
||||
}
|
||||
|
||||
public static boolean isBundlePacket(@NotNull Packet<?> packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_4.getNetworkId()) return loaderNew.isBundlePacket(packet);
|
||||
return loaderOld.isBundlePacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Iterable<Object> getBundledPackets(@NotNull Packet<?> bundlePacket) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_4.getNetworkId()) return loaderNew.getBundledPackets(bundlePacket);
|
||||
return loaderOld.getBundledPackets(bundlePacket);
|
||||
}
|
||||
|
||||
public static void sendPackets(@NotNull ServerPlayer player, @NotNull Iterable<Packet<ClientGamePacketListener>> packets) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_4.getNetworkId()) loaderNew.sendPackets(player, packets);
|
||||
else loaderOld.sendPackets(player, packets);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Level getLevel(@NotNull ServerPlayer player) {
|
||||
if (serverVersion.getMinorVersion() >= 20) return loaderNew.getLevel(player);
|
||||
return loaderOld.getLevel(player);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static TabList.Skin propertyToSkin(@NotNull Property property) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.propertyToSkin(property);
|
||||
return loaderOld.propertyToSkin(property);
|
||||
}
|
||||
|
||||
public static boolean isSpawnPlayerPacket(@NotNull Packet<?> packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.isSpawnPlayerPacket(packet);
|
||||
return loaderOld.isSpawnPlayerPacket(packet);
|
||||
}
|
||||
|
||||
public static int getSpawnedPlayerId(@NotNull Packet<?> packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getSpawnedPlayerId(packet);
|
||||
return loaderOld.getSpawnedPlayerId(packet);
|
||||
}
|
||||
|
||||
public static int getPing(@NotNull ServerPlayer player) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getPing(player);
|
||||
return loaderOld.getPing(player);
|
||||
}
|
||||
|
||||
public static int getDisplaySlot(@NotNull ClientboundSetDisplayObjectivePacket packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getDisplaySlot(packet);
|
||||
return loaderOld.getDisplaySlot(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> setDisplaySlot(int slot, @NotNull Objective objective) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.setDisplaySlot(slot, objective);
|
||||
return loaderOld.setDisplaySlot(slot, objective);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Channel getChannel(@NotNull ServerPlayer player) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getChannel(player);
|
||||
return loaderOld.getChannel(player);
|
||||
}
|
||||
|
||||
public static float getMSPT(@NotNull MinecraftServer server) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.getMSPT(server);
|
||||
return loaderOld.getMSPT(server);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> removeScore(@NotNull String objective, @NotNull String holder) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.removeScore(objective, holder);
|
||||
return loaderOld.removeScore(objective, holder);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Objective newObjective(@NotNull String name, @NotNull Component displayName,
|
||||
@NotNull RenderType renderType, @Nullable Component numberFormat) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.newObjective(name, displayName, renderType, numberFormat);
|
||||
return loaderOld.newObjective(name, displayName, renderType, numberFormat);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Packet<?> setScore(@NotNull String objective, @NotNull String holder, int score,
|
||||
@Nullable Component displayName, @Nullable Component numberFormat) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.setScore(objective, holder, score, displayName, numberFormat);
|
||||
return loaderOld.setScore(objective, holder, score, displayName, numberFormat);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Pipeline injector for Fabric.
|
||||
*/
|
||||
public class FabricPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*/
|
||||
public FabricPipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return FabricMultiVersion.getChannel(((FabricTabPlayer)player).getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,20 @@ package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
||||
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
@@ -34,10 +34,15 @@ import java.io.File;
|
||||
@Getter
|
||||
public class FabricPlatform implements BackendPlatform {
|
||||
|
||||
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
|
||||
|
||||
/** Minecraft server reference */
|
||||
private final MinecraftServer server;
|
||||
|
||||
/** Flag tracking presence of permission API */
|
||||
private final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
|
||||
|
||||
/** Server version */
|
||||
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(FabricTAB.minecraftVersion);
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
@@ -53,7 +58,7 @@ public class FabricPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return FabricTAB.getVersion().createPipelineInjector();
|
||||
return new FabricPipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,34 +80,19 @@ public class FabricPlatform implements BackendPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void logInfo(@NotNull IChatBaseComponent message) {
|
||||
Object logger = getLogger();
|
||||
logger.getClass().getMethod("info", String.class).invoke(logger, message.toRawText());
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
FabricMultiVersion.logInfo(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void logWarn(@NotNull IChatBaseComponent message) {
|
||||
Object logger = getLogger();
|
||||
logger.getClass().getMethod("warn", String.class).invoke(logger, message.toRawText());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object getLogger() {
|
||||
Class<?> loggerClass;
|
||||
if (getServerVersion().getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) {
|
||||
loggerClass = Class.forName("org.slf4j.Logger");
|
||||
} else {
|
||||
loggerClass = Class.forName("org.apache.logging.log4j.Logger");
|
||||
}
|
||||
return ReflectionUtils.getFields(MinecraftServer.class, loggerClass).get(0).get(null);
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
FabricMultiVersion.logWarn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Fabric] " + FabricTAB.getVersion().getServerVersion();
|
||||
return "[Fabric] " + FabricTAB.minecraftVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,29 +110,24 @@ public class FabricPlatform implements BackendPlatform {
|
||||
// Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProtocolVersion getServerVersion() {
|
||||
return ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts internal component class to platform's component class
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param version
|
||||
* Game version to convert component for
|
||||
* @return Converted component
|
||||
*/
|
||||
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
|
||||
return FabricTAB.getVersion().deserialize(component.toString(version));
|
||||
@Override
|
||||
public Component convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return FabricMultiVersion.newTextComponent(((SimpleComponent) component).getText());
|
||||
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Component nmsComponent = FabricMultiVersion.newTextComponent(component1.getText());
|
||||
|
||||
FabricMultiVersion.setStyle(nmsComponent, FabricMultiVersion.convertModifier(component1.getModifier(), modern));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
FabricMultiVersion.addSibling(nmsComponent, convertComponent(extra, modern));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,9 +137,18 @@ public class FabricPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return FabricTAB.getVersion().getMSPT();
|
||||
return FabricMultiVersion.getMSPT(server);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for permission and returns the result.
|
||||
*
|
||||
* @param source
|
||||
* Source to check permission of
|
||||
* @param permission
|
||||
* Permission node to check
|
||||
* @return {@code true} if has permission, {@code false} if not
|
||||
*/
|
||||
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
|
||||
if (source.hasPermission(4)) return true;
|
||||
return fabricPermissionsApi && Permissions.check(source, permission);
|
||||
|
||||
@@ -1,59 +1,73 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Team;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
|
||||
/**
|
||||
* Scoreboard implementation for Fabric using packets.
|
||||
*/
|
||||
public class FabricScoreboard extends Scoreboard<FabricTabPlayer, Component> {
|
||||
|
||||
@NotNull
|
||||
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
|
||||
|
||||
private final Map<String, Objective> objectives = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public FabricScoreboard(FabricTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot0(int slot, @NotNull String objective) {
|
||||
player.sendPacket(FabricTAB.getVersion().setDisplaySlot(slot, objectives.get(objective)));
|
||||
public void setDisplaySlot0(int slot, @NonNull String objective) {
|
||||
player.sendPacket(FabricMultiVersion.setDisplaySlot(slot, objectives.get(objective)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
Objective obj = FabricTAB.getVersion().newObjective(
|
||||
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Component numberFormat) {
|
||||
Objective obj = FabricMultiVersion.newObjective(
|
||||
objectiveName,
|
||||
toComponent(title),
|
||||
RenderType.values()[display],
|
||||
numberFormat == null ? null : toComponent(numberFormat)
|
||||
numberFormat
|
||||
);
|
||||
objectives.put(objectiveName, obj);
|
||||
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective0(@NotNull String objectiveName) {
|
||||
public void unregisterObjective0(@NonNull String objectiveName) {
|
||||
player.sendPacket(new ClientboundSetObjectivePacket(objectives.remove(objectiveName), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
|
||||
@Nullable IChatBaseComponent numberFormat) {
|
||||
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
|
||||
@Nullable Component numberFormat) {
|
||||
Objective obj = objectives.get(objectiveName);
|
||||
obj.setDisplayName(toComponent(title));
|
||||
obj.setRenderType(RenderType.values()[display]);
|
||||
@@ -61,64 +75,74 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
|
||||
@NotNull Collection<String> players, int options) {
|
||||
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
|
||||
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
|
||||
team.setAllowFriendlyFire((options & 0x01) > 0);
|
||||
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
|
||||
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
team.setColor(ChatFormatting.valueOf(color.name()));
|
||||
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
|
||||
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
|
||||
team.setPlayerPrefix(toComponent(prefix));
|
||||
team.setPlayerSuffix(toComponent(suffix));
|
||||
team.getPlayers().addAll(players);
|
||||
player.sendPacket(FabricTAB.getVersion().registerTeam(team));
|
||||
player.sendPacket(FabricMultiVersion.registerTeam(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam0(@NotNull String name) {
|
||||
player.sendPacket(FabricTAB.getVersion().unregisterTeam(new PlayerTeam(dummyScoreboard, name)));
|
||||
public void unregisterTeam0(@NonNull String name) {
|
||||
player.sendPacket(FabricMultiVersion.unregisterTeam(new PlayerTeam(dummyScoreboard, name)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
|
||||
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
|
||||
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
|
||||
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
|
||||
int options, @NonNull EnumChatFormat color) {
|
||||
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
|
||||
team.setAllowFriendlyFire((options & 0x01) != 0);
|
||||
team.setSeeFriendlyInvisibles((options & 0x02) != 0);
|
||||
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
|
||||
team.setColor(ChatFormatting.valueOf(color.name()));
|
||||
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
|
||||
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
|
||||
team.setPlayerPrefix(toComponent(prefix));
|
||||
team.setPlayerSuffix(toComponent(suffix));
|
||||
player.sendPacket(FabricTAB.getVersion().updateTeam(team));
|
||||
player.sendPacket(FabricMultiVersion.updateTeam(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
|
||||
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
|
||||
player.sendPacket(FabricTAB.getVersion().setScore(
|
||||
objective,
|
||||
scoreHolder,
|
||||
score,
|
||||
displayName == null ? null : toComponent(displayName),
|
||||
numberFormat == null ? null : toComponent(numberFormat)
|
||||
));
|
||||
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Component displayName, @Nullable Component numberFormat) {
|
||||
player.sendPacket(FabricMultiVersion.setScore(objective, scoreHolder, score, displayName, numberFormat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
|
||||
player.sendPacket(FabricTAB.getVersion().removeScore(objective, scoreHolder));
|
||||
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
player.sendPacket(FabricMultiVersion.removeScore(objective, scoreHolder));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Component toComponent(@NotNull String string) {
|
||||
return toComponent(IChatBaseComponent.optimizedComponent(string));
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, FabricMultiVersion.getDisplaySlot(display), display.objectiveName);
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.method, objective.objectiveName);
|
||||
}
|
||||
if (isAntiOverrideTeams() && FabricMultiVersion.isTeamPacket((Packet<?>) packet)) {
|
||||
int action = ReflectionUtils.getInstanceFields(packet.getClass(), int.class).get(0).getInt(packet);
|
||||
if (action == TeamAction.REMOVE || action == TeamAction.UPDATE) return;
|
||||
Field playersField = ReflectionUtils.getFields(packet.getClass(), Collection.class).get(0);
|
||||
Collection<String> players = (Collection<String>) playersField.get(packet);
|
||||
String teamName = String.valueOf(ReflectionUtils.getFields(packet.getClass(), String.class).get(0).get(packet));
|
||||
playersField.set(packet, onTeamPacket(action, teamName, players));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Component toComponent(@NotNull IChatBaseComponent component) {
|
||||
return player.getPlatform().toComponent(component, player.getVersion());
|
||||
@NonNull
|
||||
private Component toComponent(@NonNull String string) {
|
||||
return TabComponent.optimized(string).convert(player.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,50 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.minecraft.SharedConstants;
|
||||
|
||||
/**
|
||||
* Main class for Fabric.
|
||||
*/
|
||||
public class FabricTAB implements DedicatedServerModInitializer {
|
||||
|
||||
@Getter
|
||||
private static VersionLoader version;
|
||||
/** Minecraft version string */
|
||||
public static final String minecraftVersion = getServerVersion();
|
||||
|
||||
@Override
|
||||
public void onInitializeServer() {
|
||||
String[] modules = {
|
||||
"v1_15_2", // 1.14.4, 1.15.2
|
||||
"v1_16_5",
|
||||
"v1_17_1", // 1.17, 1.17.1
|
||||
"v1_18_2", // 1.18, 1.18.1, 1.18.2
|
||||
"v1_19_2", // 1.19, 1.19.1, 1.19.2
|
||||
"v1_19_3",
|
||||
"v1_20_1", // 1.19.4, 1.20, 1.20.1
|
||||
"v1_20_2",
|
||||
"v1_20_4" // 1.20.3, 1.20.4
|
||||
};
|
||||
for (String module : modules) {
|
||||
try {
|
||||
version = (VersionLoader) Class.forName("me.neznamy.tab.platforms.fabric." + module + ".FabricTAB").getConstructor().newInstance();
|
||||
if (!version.getSupportedVersions().contains(version.getServerVersion())) continue;
|
||||
version.registerCommandCallback();
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
|
||||
return;
|
||||
} catch (Throwable ignored) {}
|
||||
if (ProtocolVersion.fromFriendlyName(minecraftVersion).getMinorVersion() >= 19) {
|
||||
net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
} else {
|
||||
net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
}
|
||||
throw new IllegalStateException("Your server version is marked as compatible, but a compatibility issue was found.");
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static String getServerVersion() {
|
||||
try {
|
||||
// 1.19.4+
|
||||
return SharedConstants.getCurrentVersion().getName();
|
||||
} catch (Throwable e) {
|
||||
// 1.19.3-
|
||||
@SuppressWarnings("JavaReflectionMemberAccess") // Fabric-mapped method name
|
||||
Object gameVersion = SharedConstants.class.getMethod("method_16673").invoke(null);
|
||||
return (String) gameVersion.getClass().getMethod("getName").invoke(gameVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if fabric api contains entity events, {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if supports entity events, {@code false} if not
|
||||
*/
|
||||
public static boolean supportsEntityEvents() {
|
||||
return ProtocolVersion.fromFriendlyName(minecraftVersion).getMinorVersion() >= 16;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
@@ -18,8 +18,17 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command for Fabric.
|
||||
*/
|
||||
public class FabricTabCommand {
|
||||
|
||||
/**
|
||||
* Handles command register request from server and registers own command.
|
||||
*
|
||||
* @param dispatcher
|
||||
* Dispatcher to register command to
|
||||
*/
|
||||
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> command = Commands.literal(TabConstants.COMMAND_BACKEND)
|
||||
.executes(context -> executeCommand(context.getSource(), new String[0]))
|
||||
@@ -46,13 +55,14 @@ public class FabricTabCommand {
|
||||
return args;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameReturnValue") // Unused by plugin
|
||||
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
|
||||
FabricPlatform platform = (FabricPlatform) TAB.getInstance().getPlatform();
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
boolean hasReloadPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
boolean hasReloadPermission = platform.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = platform.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
|
||||
FabricTAB.getVersion().sendMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
|
||||
IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
|
||||
FabricMultiVersion.sendMessage(source, TabComponent.optimized(message).convert(platform.getServerVersion()));
|
||||
}
|
||||
} else {
|
||||
if (source.getEntity() == null) {
|
||||
|
||||
@@ -2,110 +2,97 @@ package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import lombok.*;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FabricTabList implements TabList {
|
||||
/**
|
||||
* TabList implementation for Fabric using packets.
|
||||
*/
|
||||
public class FabricTabList extends TabList<FabricTabPlayer, Component> {
|
||||
|
||||
@NotNull
|
||||
private final FabricTabPlayer player;
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NotNull UUID entry) {
|
||||
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME, new Builder(entry)));
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public FabricTabList(@NotNull FabricTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
|
||||
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME,
|
||||
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))
|
||||
));
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.REMOVE_PLAYER,
|
||||
new Builder(entry, "", null, 0, 0, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NotNull UUID entry, int latency) {
|
||||
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_LATENCY,
|
||||
new Builder(entry).setLatency(latency)));
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable Component displayName) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_DISPLAY_NAME,
|
||||
new Builder(entry, "", null, 0, 0, displayName)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NotNull UUID entry, int gameMode) {
|
||||
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_GAME_MODE,
|
||||
new Builder(entry).setGameMode(gameMode)));
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_LATENCY,
|
||||
new Builder(entry, "", null, latency, 0, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NotNull Entry entry) {
|
||||
player.sendPacket(FabricTAB.getVersion().build(Action.ADD_PLAYER,
|
||||
new Builder(entry.getUniqueId())
|
||||
.setName(entry.getName())
|
||||
.setSkin(entry.getSkin())
|
||||
.setGameMode(entry.getGameMode())
|
||||
.setLatency(entry.getLatency())
|
||||
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))
|
||||
));
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_GAME_MODE,
|
||||
new Builder(entry, "", null, 0, gameMode, null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
|
||||
player.sendPacket(FabricTAB.getVersion().newHeaderFooter(
|
||||
player.getPlatform().toComponent(header, player.getVersion()),
|
||||
player.getPlatform().toComponent(footer, player.getVersion())
|
||||
));
|
||||
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable Component displayName) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.ADD_PLAYER,
|
||||
new Builder(id, name, skin, latency, gameMode, displayName)));
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull Component header, @NonNull Component footer) {
|
||||
player.sendPacket(FabricMultiVersion.newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (FabricMultiVersion.isPlayerInfo((Packet<?>) packet)) {
|
||||
FabricMultiVersion.onPlayerInfo(player, packet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TabList entry builder.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public static class Builder {
|
||||
|
||||
@NotNull private final UUID id;
|
||||
@NotNull private String name = ""; // Avoid nullability issues as things are changing over versions
|
||||
@NonNull private final UUID id;
|
||||
@NonNull private String name;
|
||||
@Nullable private Skin skin;
|
||||
private int latency;
|
||||
private int gameMode;
|
||||
@Nullable private Component displayName;
|
||||
|
||||
@NotNull
|
||||
public Builder setName(@NotNull String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setSkin(@Nullable Skin skin) {
|
||||
this.skin = skin;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setLatency(int latency) {
|
||||
this.latency = latency;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setGameMode(int gameMode) {
|
||||
this.gameMode = gameMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Builder setDisplayName(@Nullable Component displayName) {
|
||||
this.displayName = displayName;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
/**
|
||||
* Creates profile of this entry.
|
||||
*
|
||||
* @return Profile of this entry
|
||||
*/
|
||||
@NonNull
|
||||
public GameProfile createProfile() {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
|
||||
@@ -4,8 +4,7 @@ import com.mojang.authlib.properties.Property;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.backend.entityview.EntityView;
|
||||
import me.neznamy.tab.shared.chat.IChatBaseComponent;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
@@ -15,14 +14,17 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Fabric.
|
||||
*/
|
||||
@Getter
|
||||
public class FabricTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@NotNull
|
||||
private final Scoreboard<FabricTabPlayer> scoreboard = new FabricScoreboard(this);
|
||||
private final FabricScoreboard scoreboard = new FabricScoreboard(this);
|
||||
|
||||
@NotNull
|
||||
private final TabList tabList = new FabricTabList(this);
|
||||
private final FabricTabList tabList = new FabricTabList(this);
|
||||
|
||||
@NotNull
|
||||
private final BossBar bossBar = new FabricBossBar(this);
|
||||
@@ -30,9 +32,17 @@ public class FabricTabPlayer extends BackendTabPlayer {
|
||||
@NotNull
|
||||
private final EntityView entityView = new FabricEntityView(this);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param player
|
||||
* Platform's player object
|
||||
*/
|
||||
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
|
||||
FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(player)));
|
||||
FabricMultiVersion.getLevelName(FabricMultiVersion.getLevel(player)), platform.getServerVersion().getNetworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,12 +52,12 @@ public class FabricTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return FabricTAB.getVersion().getPing(getPlayer());
|
||||
return FabricMultiVersion.getPing(getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull IChatBaseComponent message) {
|
||||
FabricTAB.getVersion().sendMessage(getPlayer(), getPlatform().toComponent(message, getVersion()));
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
FabricMultiVersion.sendMessage(getPlayer(), message.convert(getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,7 +75,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
|
||||
public TabList.Skin getSkin() {
|
||||
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
return FabricTAB.getVersion().propertyToSkin(properties.iterator().next());
|
||||
return FabricMultiVersion.propertyToSkin(properties.iterator().next());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,11 +84,6 @@ public class FabricTabPlayer extends BackendTabPlayer {
|
||||
return (ServerPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FabricPlatform getPlatform() {
|
||||
return (FabricPlatform) platform;
|
||||
@@ -102,7 +107,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getDisplayName().getString(); // Will make it work properly if someone asks
|
||||
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface VersionLoader {
|
||||
|
||||
Scoreboard dummyScoreboard = new Scoreboard();
|
||||
ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
|
||||
|
||||
String getName(Level level);
|
||||
|
||||
int getPing(ServerPlayer player);
|
||||
|
||||
void sendMessage(ServerPlayer player, Component message);
|
||||
|
||||
TabList.Skin propertyToSkin(Property property);
|
||||
|
||||
void sendMessage(CommandSourceStack sender, Component message);
|
||||
|
||||
String getServerVersion();
|
||||
|
||||
double getMSPT();
|
||||
|
||||
Component deserialize(String string);
|
||||
|
||||
Packet<?> build(TabList.Action action, FabricTabList.Builder entry);
|
||||
|
||||
Level getLevel(ServerPlayer player);
|
||||
|
||||
boolean isSneaking(ServerPlayer player);
|
||||
|
||||
EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible);
|
||||
|
||||
Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location);
|
||||
|
||||
Packet<?> newEntityMetadata(int entityId, EntityData data);
|
||||
|
||||
boolean isSpawnPlayerPacket(Object packet);
|
||||
|
||||
int getSpawnedPlayer(Object spawnPacket);
|
||||
|
||||
int[] getDestroyedEntities(Object entityDestroyPacket);
|
||||
|
||||
boolean isBundlePacket(Object packet);
|
||||
|
||||
Iterable<Object> getPackets(Object bundlePacket);
|
||||
|
||||
PipelineInjector createPipelineInjector();
|
||||
|
||||
void destroyEntities(FabricTabPlayer player, int... entities);
|
||||
|
||||
void registerCommandCallback();
|
||||
|
||||
List<String> getSupportedVersions();
|
||||
|
||||
Packet<?> newHeaderFooter(Component header, Component footer);
|
||||
|
||||
Packet<?> setDisplaySlot(int slot, Objective objective);
|
||||
|
||||
Objective newObjective(String name, Component displayName, RenderType renderType, @Nullable Component numberFormat);
|
||||
|
||||
Packet<?> registerTeam(PlayerTeam team);
|
||||
|
||||
Packet<?> unregisterTeam(PlayerTeam team);
|
||||
|
||||
Packet<?> updateTeam(PlayerTeam team);
|
||||
|
||||
Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat);
|
||||
|
||||
Packet<?> removeScore(String objective, String scoreHolder);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.neznamy.tab.platforms.fabric.features;
|
||||
|
||||
import me.neznamy.tab.platforms.fabric.FabricMultiVersion;
|
||||
import me.neznamy.tab.platforms.fabric.FabricTAB;
|
||||
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -16,12 +16,19 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Unlimited nametag mode implementation for Fabric.
|
||||
*/
|
||||
public class FabricNameTagX extends BackendNameTagX {
|
||||
|
||||
public boolean enabled = true;
|
||||
/** Flag tracking whether this instance is still running or not */
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers event listener.
|
||||
*/
|
||||
public FabricNameTagX() {
|
||||
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 16) {
|
||||
if (FabricTAB.supportsEntityEvents()) {
|
||||
// Added in 1.16
|
||||
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
|
||||
if (enabled) respawn(oldPlayer.getUUID());
|
||||
@@ -39,8 +46,8 @@ public class FabricNameTagX extends BackendNameTagX {
|
||||
|
||||
@Override
|
||||
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
|
||||
return FabricTAB.getVersion().getLevel(((FabricTabPlayer)player1).getPlayer()) ==
|
||||
FabricTAB.getVersion().getLevel(((FabricTabPlayer)player2).getPlayer());
|
||||
return FabricMultiVersion.getLevel(((FabricTabPlayer)player1).getPlayer()) ==
|
||||
FabricMultiVersion.getLevel(((FabricTabPlayer)player2).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,7 +85,7 @@ public class FabricNameTagX extends BackendNameTagX {
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(@NotNull TabPlayer player) {
|
||||
return FabricTAB.getVersion().isSneaking(((FabricTabPlayer)player).getPlayer());
|
||||
return FabricMultiVersion.isSneaking(((FabricTabPlayer)player).getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -120,7 +127,7 @@ public class FabricNameTagX extends BackendNameTagX {
|
||||
@Override
|
||||
@NotNull
|
||||
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
|
||||
return FabricTAB.getVersion().createDataWatcher(viewer, flags, displayName, nameVisible);
|
||||
return FabricMultiVersion.createDataWatcher(viewer, flags, displayName, nameVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,4 +139,9 @@ public class FabricNameTagX extends BackendNameTagX {
|
||||
public boolean isDead(@NotNull TabPlayer player) {
|
||||
return !((FabricTabPlayer)player).getPlayer().isAlive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFlying(@NotNull TabPlayer player) {
|
||||
return ((FabricTabPlayer)player).getPlayer().getAbilities().flying;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package me.neznamy.tab.platforms.fabric.loader;
|
||||
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.fabric.FabricTabList;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Loader {
|
||||
|
||||
Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
@NotNull
|
||||
String getLevelName(@NotNull Level level);
|
||||
|
||||
@NotNull
|
||||
TabList.Skin propertyToSkin(@NotNull Property property);
|
||||
|
||||
@NotNull
|
||||
Component newTextComponent(@NotNull String text);
|
||||
|
||||
@NotNull
|
||||
Style convertModifier(@NotNull ChatModifier modifier, boolean modern);
|
||||
|
||||
void addSibling(@NotNull Component parent, @NotNull Component child);
|
||||
|
||||
@NotNull
|
||||
Packet<?> registerTeam(@NotNull PlayerTeam team);
|
||||
|
||||
@NotNull
|
||||
Packet<?> unregisterTeam(@NotNull PlayerTeam team);
|
||||
|
||||
@NotNull
|
||||
Packet<?> updateTeam(@NotNull PlayerTeam team);
|
||||
|
||||
boolean isSneaking(@NotNull ServerPlayer player);
|
||||
|
||||
void sendMessage(@NotNull ServerPlayer player, @NotNull Component message);
|
||||
|
||||
void sendMessage(@NotNull CommandSourceStack source, @NotNull Component message);
|
||||
|
||||
@NotNull
|
||||
Packet<?> newHeaderFooter(@NotNull Component header, @NotNull Component footer);
|
||||
|
||||
boolean isTeamPacket(@NotNull Packet<?> packet);
|
||||
|
||||
@NotNull
|
||||
Packet<ClientGamePacketListener> spawnEntity(@NotNull Level level, int id, @NotNull UUID uuid, @NotNull Object type, @NotNull Location location);
|
||||
|
||||
@NotNull
|
||||
Packet<ClientGamePacketListener> newEntityMetadata(int entityId, @NotNull EntityData data);
|
||||
|
||||
@NotNull
|
||||
EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible, int markerPosition);
|
||||
|
||||
boolean isPlayerInfo(@NotNull Packet<?> packet);
|
||||
|
||||
void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet);
|
||||
|
||||
@NotNull
|
||||
Packet<?> buildTabListPacket(@NotNull TabList.Action action, @NotNull FabricTabList.Builder builder);
|
||||
|
||||
boolean isBundlePacket(@NotNull Packet<?> packet);
|
||||
|
||||
@NotNull
|
||||
Iterable<Object> getBundledPackets(@NotNull Packet<?> bundlePacket);
|
||||
|
||||
void sendPackets(@NotNull ServerPlayer player, @NotNull Iterable<Packet<ClientGamePacketListener>> packets);
|
||||
|
||||
@NotNull
|
||||
Level getLevel(@NotNull ServerPlayer player);
|
||||
|
||||
boolean isSpawnPlayerPacket(@NotNull Packet<?> packet);
|
||||
|
||||
int getSpawnedPlayerId(@NotNull Packet<?> packet);
|
||||
|
||||
int getPing(@NotNull ServerPlayer player);
|
||||
|
||||
int getDisplaySlot(@NotNull ClientboundSetDisplayObjectivePacket packet);
|
||||
|
||||
@NotNull
|
||||
Packet<?> setDisplaySlot(int slot, @NotNull Objective objective);
|
||||
|
||||
@NotNull
|
||||
Channel getChannel(@NotNull ServerPlayer player);
|
||||
|
||||
float getMSPT(@NotNull MinecraftServer server);
|
||||
|
||||
@NotNull
|
||||
Packet<?> removeScore(@NotNull String objective, @NotNull String holder);
|
||||
|
||||
int[] getDestroyedEntities(Packet<?> destroyPacket);
|
||||
|
||||
@NotNull
|
||||
Objective newObjective(@NotNull String name, @NotNull Component displayName, @NotNull RenderType renderType, @Nullable Component numberFormat);
|
||||
|
||||
@NotNull
|
||||
Packet<?> setScore(@NotNull String objective, @NotNull String holder, int score, @Nullable Component displayName, @Nullable Component numberFormat);
|
||||
|
||||
void setStyle(@NotNull Component component, @NotNull Style style);
|
||||
|
||||
void logInfo(@NotNull TabComponent message);
|
||||
|
||||
void logWarn(@NotNull TabComponent message);
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
package me.neznamy.tab.platforms.fabric.loader;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.fabric.FabricTabList;
|
||||
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.EntityData;
|
||||
import me.neznamy.tab.shared.backend.Location;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.chat.numbers.NumberFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.network.syncher.EntityDataSerializers;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.scores.DisplaySlot;
|
||||
import net.minecraft.world.scores.Objective;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Method loader compiled using Minecraft 1.20.5.
|
||||
*/
|
||||
@SuppressWarnings({
|
||||
"unchecked", // Java generic types
|
||||
"DataFlowIssue", // Profile is not null on add action
|
||||
"unused" // Actually used, just via reflection
|
||||
})
|
||||
public class Loader_1_20_5 implements Loader {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLevelName(@NotNull Level level) {
|
||||
String path = level.dimension().location().getPath();
|
||||
return ((ServerLevelData)level.getLevelData()).getLevelName() + switch (path) {
|
||||
case "overworld" -> ""; // No suffix for overworld
|
||||
case "the_nether" -> "_nether";
|
||||
default -> "_" + path; // End + default behavior for other dimensions created by mods
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList.Skin propertyToSkin(@NotNull Property property) {
|
||||
return new TabList.Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component newTextComponent(@NotNull String text) {
|
||||
return Component.literal(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
|
||||
TextColor color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
color = TextColor.fromRgb(modifier.getColor().getRgb());
|
||||
} else {
|
||||
color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
|
||||
return new Style(
|
||||
color,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : new ResourceLocation(modifier.getFont())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Component parent, @NotNull Component child) {
|
||||
parent.getSiblings().add(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> registerTeam(@NotNull PlayerTeam team) {
|
||||
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> unregisterTeam(@NotNull PlayerTeam team) {
|
||||
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> updateTeam(@NotNull PlayerTeam team) {
|
||||
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking(@NotNull ServerPlayer player) {
|
||||
return player.isCrouching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull ServerPlayer player, @NotNull Component message) {
|
||||
player.sendSystemMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull CommandSourceStack source, @NotNull Component message) {
|
||||
source.sendSystemMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> newHeaderFooter(@NotNull Component header, @NotNull Component footer) {
|
||||
return new ClientboundTabListPacket(header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTeamPacket(@NotNull Packet<?> packet) {
|
||||
return packet instanceof ClientboundSetPlayerTeamPacket; // Fabric-mapped name changed
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<ClientGamePacketListener> spawnEntity(@NotNull Level level, int id, @NotNull UUID uuid, @NotNull Object type, @NotNull Location location) {
|
||||
return new ClientboundAddEntityPacket(id, uuid, location.getX(), location.getY(), location.getZ(),
|
||||
0, 0, (EntityType<?>) type, 0, Vec3.ZERO, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<ClientGamePacketListener> newEntityMetadata(int entityId, @NotNull EntityData data) {
|
||||
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible, int markerPosition) {
|
||||
Optional<Component> name = Optional.of(TabComponent.optimized(displayName).convert(viewer.getVersion()));
|
||||
return () -> Arrays.asList(
|
||||
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
|
||||
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT, name),
|
||||
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
|
||||
new SynchedEntityData.DataValue<>(markerPosition, EntityDataSerializers.BYTE, EntityData.MARKER_FLAG)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlayerInfo(@NotNull Packet<?> packet) {
|
||||
return packet instanceof ClientboundPlayerInfoUpdatePacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
|
||||
GameProfile profile = nmsData.profile();
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
Component expectedDisplayName = ((FabricTabPlayer)receiver).getTabList().getExpectedDisplayName(nmsData.profileId());
|
||||
if (expectedDisplayName != null) displayName = expectedDisplayName;
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
|
||||
}
|
||||
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
|
||||
}
|
||||
packet.entries = updatedList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> buildTabListPacket(@NotNull TabList.Action action, @NotNull FabricTabList.Builder entry) {
|
||||
if (action == TabList.Action.REMOVE_PLAYER) {
|
||||
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
|
||||
}
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(Register1_19_3.actionMap.get(action), Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry.getId(),
|
||||
action == TabList.Action.ADD_PLAYER ? entry.createProfile() : null,
|
||||
true,
|
||||
entry.getLatency(),
|
||||
GameType.byId(entry.getGameMode()),
|
||||
entry.getDisplayName(),
|
||||
null
|
||||
));
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBundlePacket(@NotNull Packet<?> packet) {
|
||||
return packet instanceof ClientboundBundlePacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Iterable<Object> getBundledPackets(@NotNull Packet<?> bundlePacket) {
|
||||
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void sendPackets(@NotNull ServerPlayer player, @NotNull Iterable<Packet<ClientGamePacketListener>> packets) {
|
||||
// Reflection because
|
||||
// 1.20.4- uses Iterable<Packet<ClientGamePacketListener>
|
||||
// 1.20.5+ uses Iterable<Packet<? super ClientGamePacketListener>
|
||||
// this is the only way to merge them
|
||||
player.connection.send(ClientboundBundlePacket.class.getConstructor(Iterable.class).newInstance(packets));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Level getLevel(@NotNull ServerPlayer player) {
|
||||
return player.level();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpawnPlayerPacket(@NotNull Packet<?> packet) {
|
||||
return packet instanceof ClientboundAddEntityPacket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnedPlayerId(@NotNull Packet<?> packet) {
|
||||
return ((ClientboundAddEntityPacket)packet).getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull ServerPlayer player) {
|
||||
return player.connection.latency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDisplaySlot(@NotNull ClientboundSetDisplayObjectivePacket packet) {
|
||||
return packet.getSlot().ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> setDisplaySlot(int slot, @NotNull Objective objective) {
|
||||
return new ClientboundSetDisplayObjectivePacket(DisplaySlot.values()[slot], objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull ServerPlayer player) {
|
||||
return player.connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMSPT(@NotNull MinecraftServer server) {
|
||||
return (float) server.getAverageTickTimeNanos() / 1000000;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> removeScore(@NotNull String objective, @NotNull String holder) {
|
||||
return new ClientboundResetScorePacket(holder, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getDestroyedEntities(Packet<?> destroyPacket) {
|
||||
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Objective newObjective(@NotNull String name, @NotNull Component displayName,
|
||||
@NotNull RenderType renderType, @Nullable Component numberFormat) {
|
||||
return Register1_20_3.newObjective(name, displayName, renderType, numberFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> setScore(@NotNull String objective, @NotNull String holder, int score, @Nullable Component displayName, @Nullable Component numberFormat) {
|
||||
return Register1_20_3.setScore(objective, holder, score, displayName, numberFormat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStyle(@NotNull Component component, @NotNull Style style) {
|
||||
((MutableComponent)component).setStyle(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
MinecraftServer.LOGGER.info("[TAB] " + message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
MinecraftServer.LOGGER.warn("[TAB] " + message.toLegacyText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Why is this needed? Because otherwise it throws error about a class
|
||||
* not existing despite the code never running.
|
||||
* Why? Nobody knows.
|
||||
*/
|
||||
private static class Register1_20_3 {
|
||||
|
||||
@NotNull
|
||||
public static Objective newObjective(@NotNull String name, @NotNull Component displayName,
|
||||
@NotNull RenderType renderType, @Nullable Component numberFormat) {
|
||||
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType, false,
|
||||
numberFormat == null ? null : new FixedFormat(numberFormat));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public static Packet<?> setScore(@NotNull String objective, @NotNull String holder, int score, @Nullable Component displayName, @Nullable Component numberFormat) {
|
||||
try {
|
||||
// 1.20.5+
|
||||
return new ClientboundSetScorePacket(holder, objective, score, Optional.ofNullable(displayName),
|
||||
Optional.ofNullable(numberFormat == null ? null : new FixedFormat(numberFormat)));
|
||||
} catch (Throwable t) {
|
||||
// 1.20.3 / 1.20.4
|
||||
return ClientboundSetScorePacket.class.getConstructor(String.class, String.class, int.class, Component.class, NumberFormat.class)
|
||||
.newInstance(holder, objective, score, displayName, numberFormat == null ? null : new FixedFormat(numberFormat));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Register1_19_3 {
|
||||
|
||||
public static final Map<TabList.Action, EnumSet<ClientboundPlayerInfoUpdatePacket.Action>> actionMap = createActionMap();
|
||||
|
||||
public static EnumSet<ClientboundPlayerInfoUpdatePacket.Action> convertAction(TabList.Action action) {
|
||||
return EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
|
||||
}
|
||||
|
||||
private static Map<TabList.Action, EnumSet<ClientboundPlayerInfoUpdatePacket.Action>> createActionMap() {
|
||||
Map<TabList.Action, EnumSet<ClientboundPlayerInfoUpdatePacket.Action>> actions = new EnumMap<>(TabList.Action.class);
|
||||
actions.put(TabList.Action.ADD_PLAYER, EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class));
|
||||
actions.put(TabList.Action.UPDATE_GAME_MODE, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE));
|
||||
actions.put(TabList.Action.UPDATE_DISPLAY_NAME, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME));
|
||||
actions.put(TabList.Action.UPDATE_LATENCY, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 351 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user