Compare commits

..
150 Commits
Author SHA1 Message Date
NEZNAMY 3c0b44b365 4.1.0 2023-12-05 20:45:58 +01:00
NEZNAMY ec4aec50ef Mark 1.20.3 as latest known version 2023-12-05 18:55:42 +01:00
NEZNAMY 9c86cfe056 Add Vault to softdepend to avoid console warn 2023-12-05 18:53:49 +01:00
NEZNAMY 6bb2337276 Print warn if RedisSupport received unknown message instead of throwing a NPE 2023-12-05 18:49:53 +01:00
NEZNAMY 4702ca255b Update Fabric to 1.20.3 2023-12-05 18:48:54 +01:00
NEZNAMY ab47aa36cc Confirm network id of 1.20.3 2023-12-05 18:40:55 +01:00
NEZNAMY 4bb14c4a4e Rename new config options 2023-12-05 17:27:24 +01:00
NEZNAMY 1dd75162f9 Add a version to list 2023-12-04 21:09:37 +01:00
NEZNAMY a2242e3c27 Move server version check 2023-12-04 20:52:29 +01:00
NEZNAMY 26ecf494a9 Little fix 2023-12-04 20:45:51 +01:00
NEZNAMY 659af5d0b3 Completely get rid of NMSStorage class 2023-12-04 20:33:23 +01:00
NEZNAMY 382a5ee7fc Move packet sending method to a separate class and make loading optional 2023-12-04 19:17:51 +01:00
NEZNAMY 7364496844 Move ping retrieving method to a separate class 2023-12-04 17:52:32 +01:00
NEZNAMY d342316b87 Reduce dependence on the main NMS class on Bukkit 2023-12-04 17:29:25 +01:00
NEZNAMY b38141da88 Add &f at the beginning of scoreboard lines for 1.13+ players to prevent color field from coloring text if no color is used 2023-12-04 16:40:06 +01:00
NEZNAMY 98f80f7dae Remove unused methods 2023-12-03 21:36:22 +01:00
NEZNAMY c134ec6bfe Simplify class loading 2023-12-03 21:32:00 +01:00
NEZNAMY 4e3f759243 Use NMS prefix only once instead of every usage 2023-12-03 21:03:08 +01:00
NEZNAMY a5f2e0157b Make player game profile getting optional instead of throwing a compatibility error 2023-12-03 20:52:29 +01:00
NEZNAMY fd21032e51 Make pipeline injection compatibility optional and print a warning if a compatibility error is found 2023-12-03 19:22:33 +01:00
NEZNAMY c4f5f9b7d3 Update comment 2023-12-03 18:11:40 +01:00
NEZNAMY 6baf5a7068 Fix NPE 2023-12-03 17:28:41 +01:00
NEZNAMY d3460174de Enable fallback solutions for Scoreboard implementation on Bukkit 2023-12-03 16:07:05 +01:00
NEZNAMY f9f19628ca Fix BukkitScoreboard class issues 2023-12-03 15:38:22 +01:00
NEZNAMY 04e48fea5d Clean up method 2023-12-01 20:22:41 +01:00
NEZNAMY 3e99e22fce Simplify 2023-12-01 19:18:58 +01:00
NEZNAMY 0b244d5ff7 Refactor PacketScoreboard class and draft 1.20.3 support 2023-12-01 19:15:56 +01:00
NEZNAMY 703f2ff316 Fix potentially wrong uuid write 2023-12-01 09:35:47 +01:00
NEZNAMY 34a2bbdbae Simplify 2023-12-01 09:34:14 +01:00
NEZNAMY cd8697c1a4 Add exclusions for some dependencies 2023-11-30 19:15:20 +01:00
NEZNAMY ed2dc2dd51 Avoid rare error on Velocity 2023-11-30 18:50:53 +01:00
NEZNAMY 2dfdc1b993 Update Fabric to 1.20.3-rc1 2023-11-30 18:27:49 +01:00
NEZNAMY 6f2d7f1194 Fix 1.20.3 client crash on BungeeCord 2023-11-30 17:52:01 +01:00
NEZNAMY 30009ef127 Attempt to fix error (#1146) 2023-11-29 17:19:11 +01:00
NEZNAMY 051a422331 Update Fabric to 1.20.3-pre4 2023-11-29 16:56:35 +01:00
NEZNAMY fab6489e9c Fix compilation 2023-11-28 16:12:27 +01:00
NEZNAMY a448614e4a Slightly better organize method 2023-11-27 18:54:28 +01:00
NEZNAMY 84ad874b2a Split complex method into 2 2023-11-27 18:43:43 +01:00
NEZNAMY 19ba978291 Simplify 2023-11-27 18:16:48 +01:00
NEZNAMY e304469e55 Remove star import 2023-11-27 18:10:28 +01:00
NEZNAMY 7ce9e2592a Update README.md 2023-11-27 18:07:35 +01:00
NEZNAMY 681b812c7b Rename feature in /tab cpu output 2023-11-26 13:59:29 +01:00
NEZNAMY ee1ab6785d Rename objective 2023-11-26 12:36:12 +01:00
NEZNAMY 57704b6622 Move a few classes around 2023-11-26 11:15:23 +01:00
NEZNAMY 7fe804e0fc Fix disconnect on bungeecord on 1.20.3 due to null text in component 2023-11-25 22:24:02 +01:00
NEZNAMY a71529b641 Simplify 2023-11-25 18:07:47 +01:00
NEZNAMY 4055173bdb Replace .length() == 0 with .isEmpty() 2023-11-25 17:09:20 +01:00
NEZNAMY 3f8841a276 Update to latest BungeeCord (for the 3rd time in a row) 2023-11-25 17:04:34 +01:00
NEZNAMY 15c38851bc Add misconfiguration check and prevent startup error 2023-11-25 16:56:11 +01:00
NEZNAMY 05030ae817 Update outdated message 2023-11-25 13:05:57 +01:00
NEZNAMY 255fa11371 Fix NPE 2023-11-24 18:41:08 +01:00
NEZNAMY a074c0bf74 Simplify 2023-11-24 10:49:12 +01:00
NEZNAMY 6ccd649fc4 Remove unused imports 2023-11-24 10:46:59 +01:00
NEZNAMY 21dbea07dd Add support for nested relational placeholders (#1153) 2023-11-23 20:28:21 +01:00
NEZNAMY 4c21d9d127 Remove unused import 2023-11-23 20:27:16 +01:00
NEZNAMY 766002b079 Delete aligned suffix feature, since 1.20.3 now has it naturally using playerlist objective (rip 1.20.2-) 2023-11-23 19:39:47 +01:00
NEZNAMY 2ce430ec41 Add support for all Fabric versions in a single jar 2023-11-23 19:18:31 +01:00
NEZNAMY cf2b151045 Bump Velocity version and use new method 2023-11-19 16:57:22 +01:00
NEZNAMY 92216179df Replace try/catch with SneakyThrows annotation 2023-11-19 16:27:06 +01:00
NEZNAMY a2a95b8591 Replace scoreboard enums with fields 2023-11-19 10:50:54 +01:00
NEZNAMY 4a22dc111a Fix attempt to send header/footer to 1.7 players on bungeecord, causing disconnect 2023-11-19 10:16:27 +01:00
NEZNAMY 5df7ecc245 Fix compatibility error message 2023-11-18 20:49:24 +01:00
NEZNAMY d4ef520f26 Print required bridge version in debug command if not connected 2023-11-18 20:39:09 +01:00
NEZNAMY e0f8737865 Use direct code for bungeecord instead of reflection and drop support for old versions 2023-11-18 20:37:37 +01:00
NEZNAMY 37b13a54a7 Create classes for plugin message types instead of writing all content on each usage + bump bridge version to 5 due to braking changes 2023-11-18 14:44:22 +01:00
NEZNAMY d9d28d31d0 Prepare for new 1.20.3 scoreboard features 2023-11-17 20:21:17 +01:00
NEZNAMY 66bf5da3f5 Delete CustomLine class due to not being viable 2023-11-17 11:41:11 +01:00
NEZNAMY 840c721afc Simplify, the performance gain was pretty much zero 2023-11-17 10:59:28 +01:00
NEZNAMY 859e705a48 Drop multi-version code for fabric (hard to maintain + a lot of it didn't actually work) 2023-11-16 22:10:45 +01:00
NEZNAMY 7375070b21 oops (#1144) 2023-11-15 16:53:42 +01:00
NEZNAMY 3e2eda6a71 Add ClickEvent to components 2023-11-11 20:17:22 +01:00
NEZNAMY 798d327dcb Fix plugin not removing redundant character width overrides from config 2023-11-11 18:11:37 +01:00
NEZNAMY f5ca9139e6 Fix double bossbar on Velocity on server switch (#1139) 2023-11-11 14:02:07 +01:00
NEZNAMY f35e8cfa51 Improve vanish tracking for nametag feature 2023-11-10 21:31:21 +01:00
NEZNAMY 0f6953711b Update build.yml 2023-11-10 15:57:34 +01:00
NEZNAMY e2c5e6f9ac Allow empty text for fixed slots in layout 2023-11-10 15:46:26 +01:00
NEZNAMY 5ef5d6ddba Update README.md 2023-11-10 12:51:41 +01:00
NEZNAMY 5b19a5d798 Only unregister team for other players when player is vanishing 2023-11-10 11:02:35 +01:00
NEZNAMY bf804e8923 Merge pull request #1137 from NEZNAMY/vanish-patch
Alternate implementation of #1136
2023-11-09 18:53:45 +01:00
NEZNAMY f47b8b5528 Alternate implementation of #1136 2023-11-09 18:30:56 +01:00
NEZNAMY c353f6787a Fix endsWith condition (#1134) 2023-11-07 17:37:52 +01:00
NEZNAMY 1b7d5c1a5c Add support for latest BungeeCord (yes, again), as well as some previous versions and remove compatibility check. 2023-11-06 17:12:26 +01:00
NEZNAMY 956456f157 4.1.0-SNAPSHOT 2023-11-06 13:41:51 +01:00
NEZNAMY 49077bd798 Create enum constants for packet actions for better readability and understanding of code 2023-11-05 21:16:07 +01:00
NEZNAMY 019a0287b2 4.0.9 2023-11-05 14:27:55 +01:00
NEZNAMY a71dac28c0 Hotfix after adding support for latest bungeecord (#1132) 2023-11-05 14:21:25 +01:00
NEZNAMY 155886e000 Fix typo 2023-11-04 15:39:45 +01:00
NEZNAMY ed4c303d2e 4.0.8 2023-11-04 14:55:32 +01:00
NEZNAMY 9da0db5f74 Disable creation of blank issues 2023-11-04 11:55:58 +01:00
NEZNAMY 562a973c84 Create new issue template 2023-11-04 11:53:10 +01:00
NEZNAMY 967980ddf7 Update bug_report.yml 2023-11-03 21:27:24 +01:00
NEZNAMY 81d3675a87 Check for bundle packet on 1.19.4+ when reading entity packets for unlimited nametags 2023-11-03 21:25:21 +01:00
NEZNAMY 7be786bbaf Fix vanish change being called on join even if vanish status did not change 2023-11-02 17:24:02 +01:00
NEZNAMY f41c9be1f7 Fix race condition on Velocity causing errors on shutdown 2023-11-02 17:21:48 +01:00
NEZNAMY e88062d028 Fix unvanish method not being called on server switch when clearing vanish status 2023-11-02 16:08:15 +01:00
NEZNAMY edb2828643 Fix wrong java instruction 2023-11-02 15:58:34 +01:00
NEZNAMY 58c189cd0e Catch all exceptions/errors on load/unload to prevent players from getting kicked on bungeecord if something changes again in the future 2023-10-31 16:41:29 +01:00
NEZNAMY 012994847e Unregister teams of players who vanished for those who cannot see vanished players to prevent 3rd party clients from revealing vanished players by finding out who is vanished and who actually left. 2023-10-30 20:39:21 +01:00
NEZNAMY d87801be5f Add support for latest BungeeCord (#1123) 2023-10-30 16:02:31 +01:00
NEZNAMY 6d78c0c104 Create a class storing values of minecraft limitations 2023-10-29 15:25:00 +01:00
NEZNAMY 3a83519122 Fix tablist entry related issues on both BungeeCord and Velocity (each one was affected by a different issue) 2023-10-27 22:27:42 +02:00
NEZNAMY a9f940ebed Avoid attempts to send packets to offline players on bungeecord 2023-10-27 19:17:56 +02:00
NEZNAMY db73d9ef81 Simplify 2023-10-24 17:46:25 +02:00
NEZNAMY e2bd21c717 Cache adventure components & use Paper API for header/footer by default. This has reduced CPU usage of the feature by 50% in the test I have done. 2023-10-24 17:40:22 +02:00
NEZNAMY 871215b7b5 Do not attempt to send scoreboard-related packets between login packet send and process 2023-10-24 15:18:17 +02:00
NEZNAMY a6153ef46f Force-fix NPE that doesn't make sense 2023-10-24 13:42:55 +02:00
NEZNAMY 35475a6103 Fix the previous error on Fabric & Sponge as well 2023-10-24 13:42:35 +02:00
NEZNAMY bf77ffc1a5 Fix error (#1117) 2023-10-22 23:01:53 +02:00
NEZNAMY 7a64a9a534 Code style 2023-10-22 20:08:27 +02:00
NEZNAMY 68e057b99c Fabric - Implement world name recognition and everything related to it (#1115) 2023-10-22 17:44:34 +02:00
NEZNAMY 7338afff96 Fix error when using 3 years old version of PlaceholderAPI 2023-10-21 11:27:38 +02:00
NEZNAMY 97ae7813aa Add vanish status updating to RedisBungee (#1109) 2023-10-18 16:48:01 +02:00
NEZNAMY c0f3667ac1 4.0.8-SNAPSHOT 2023-10-18 16:47:37 +02:00
NEZNAMY d5c5502abe 4.0.7 2023-10-16 20:45:35 +02:00
NEZNAMY 194e8ffbd9 Code style 2023-10-15 20:39:06 +02:00
NEZNAMY ef39458abd With all the problems on 1.20.2 BungeeCord it turns out that Velocity isn't that bad. 2023-10-15 17:39:21 +02:00
NEZNAMY 6a6907fb0a Process login packet with a delay to avoid problems 2023-10-15 17:33:06 +02:00
NEZNAMY 8bb7c4a4a6 Adapt redis support subfeatures to recent changes 2023-10-15 17:00:55 +02:00
NEZNAMY 91700e7ed0 Fix global playerlist not readding players for 1.20.2 clients on server switch 2023-10-15 17:00:16 +02:00
NEZNAMY 661c2e6fc3 Simplify 2023-10-15 15:37:38 +02:00
NEZNAMY 3d053a6149 Implement backup solutions if header/footer fields fail to initialize 2023-10-15 15:35:25 +02:00
NEZNAMY 34179e4f62 Fix startup on old versions due to wrong enum names 2023-10-15 14:56:40 +02:00
NEZNAMY a23e5d9f50 Remove toComponent method from Platform interface and only keep it in platforms that actually use it 2023-10-15 14:26:09 +02:00
NEZNAMY 1cbcbf70d9 Fix aligned playerlist attempting to send packets to players who just left 2023-10-15 14:08:35 +02:00
NEZNAMY 0a7371aaec Micro optimizations with enum conversions 2023-10-14 13:55:52 +02:00
NEZNAMY da13e2d861 Fix Bridge still injecting placeholders that are not used on proxy anymore 2023-10-13 12:27:12 +02:00
NEZNAMY 5b18fa8c25 Fix PingSpoof spoofing everything instead of just online players (or their respective layout entries) 2023-10-13 11:34:38 +02:00
NEZNAMY 347231850a Fix error 2023-10-13 09:51:40 +02:00
NEZNAMY b1f9bdc26c Resend BossBar on server switch for 1.20.2 players as Login packet now clears it 2023-10-12 20:51:50 +02:00
NEZNAMY 03c9b95041 Listen to login packet instead of server switch delay for 1.20.2 players for better consistency (also fix layout not being resent on server switch on 1.20.2) 2023-10-12 19:20:22 +02:00
NEZNAMY 6e7e1d783b Fix rare placeholder formatting error 2023-10-12 16:33:58 +02:00
NEZNAMY cfb9b0a029 Fix compatibility with some random 1.8.8 overpriced fork (#1105) 2023-10-12 14:32:21 +02:00
NEZNAMY b7ea6e5af6 Fix wrong exception being caught causing the try/catch to not work (#1092) 2023-10-12 09:33:50 +02:00
NEZNAMY 44104df712 Change error message about invalid team/objective actions to a debug message. They don't cause any issues as they are properly caught and debugging is hard, especially if reporter has no interest in cooperating (#1083) 2023-10-11 19:35:57 +02:00
NEZNAMY 5de20d1c58 Add try/catch for BungeeCord sendPacket method to avoid a NPE in BungeeCord code (#1090, #1092) 2023-10-11 16:24:43 +02:00
NEZNAMY d156303740 Remove warn when placeholders use too much CPU. Since they have been moved to another thread, they will no longer prevent the plugin from working. While the warning can be useful for more advanced users, this is a public plugin used by many and most of them don't care about performance at all. This has resulted in having to answer people who would have never contacted me otherwise. 2023-10-10 15:04:00 +02:00
NEZNAMY 6e36eb3554 Add back fabric class for multi-version code for easier backport 2023-10-09 21:49:21 +02:00
NEZNAMY f7f3891817 Avoid attempts to send plugin message to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-08 12:41:12 +02:00
NEZNAMY 1ec28856ff Avoid attempts to send packets to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-06 11:41:32 +02:00
NEZNAMY 9658e0b74d Add debug message showing how long bridge took to respond to player join plugin message 2023-10-06 11:31:09 +02:00
NEZNAMY 86cd8d4151 Make feature loading more readable 2023-10-05 17:39:27 +02:00
NEZNAMY 2418eb206e Improve unknown version handling code & error messages 2023-10-05 13:57:56 +02:00
NEZNAMY 08fa715d26 Fix runtime warns false triggering on proxy when placeholders are not loaded due to bridge not having a chance to respond yet (#1088) 2023-10-04 14:16:08 +02:00
NEZNAMY 52426de283 Remove double method call 2023-10-04 14:04:57 +02:00
NEZNAMY 64b3da14c4 It's called disable condition, not display condition 2023-10-02 20:38:21 +02:00
NEZNAMY bf7341c52c Change way getProfile method is retrieved (#1089) 2023-10-02 19:09:53 +02:00
NEZNAMY e3490564a2 4.0.7-SNAPSHOT 2023-10-02 19:07:05 +02:00
NEZNAMY 6218f0ed16 4.0.6 2023-10-01 16:35:39 +02:00
NEZNAMY 54e8e1ea98 Add a temporary workaround for https://github.com/SpigotMC/BungeeCord/issues/3542 2023-10-01 16:35:33 +02:00
NEZNAMY 4cd50fb3d5 Use sendPacketQueued method on BungeeCord to avoid error on 1.20.2 clients 2023-10-01 16:31:25 +02:00
228 changed files with 8501 additions and 68594 deletions

No files matched your search

+2
View File
@@ -60,4 +60,6 @@ body:
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
required: true
@@ -0,0 +1,38 @@
name: Compatibility issue
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
labels: ["Type: Compatibility issue"]
body:
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 4.0.0
validations:
required: true
- type: textarea
attributes:
label: Software causing compatibility issue
description: plugin/mod/platform version causing the issue
validations:
required: true
- type: textarea
attributes:
label: Additional info
description: Anything else you want to provide that will help in resolving the issue faster.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: The compatibility wiki page does not mention this incompatibility
required: true
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
required: true
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+1 -4
View File
@@ -3,9 +3,6 @@ on:
push:
branches:
- master
- v4
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
@@ -41,5 +38,5 @@ jobs:
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Jar
name: Click here to download
path: jar/build/libs/TAB-**.jar
+34
View File
@@ -3,6 +3,17 @@ TAB aims to be a superior all-in-one minecraft plugin for displaying information
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# 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).
### 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).
# Compiling
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
@@ -21,3 +32,26 @@ is in quality of the report. Bug reports where steps to reproduce are "idk" have
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
# Provided services
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
Here is the full list of potential services and their status:
| Service type | Availability |
|-------------------------------|--------------|
| Updates for new MC versions | ✔ |
| Bug fixes | ✔ |
| Wiki updates<sup>1</sup> | ✔ |
| Feature requests | ❌ |
| Customer support<sup>2</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>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.
@@ -5,5 +5,12 @@ package me.neznamy.tab.api.bossbar;
* Calling ordinal() will return color's network ID.
*/
public enum BarColor {
PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE
PINK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
WHITE
}
@@ -1,22 +1,14 @@
package me.neznamy.tab.api.bossbar;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
@AllArgsConstructor
public enum BarStyle {
PROGRESS("SOLID"),
NOTCHED_6("SEGMENTED_6"),
NOTCHED_10("SEGMENTED_10"),
NOTCHED_12("SEGMENTED_12"),
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
@Getter private final String bukkitName;
PROGRESS,
NOTCHED_6,
NOTCHED_10,
NOTCHED_12,
NOTCHED_20
}
+13 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.5"
version = "4.1.0"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -18,7 +18,18 @@ val platforms = setOf(
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.fabric
projects.fabric,
projects.fabric.v1144,
projects.fabric.v1152,
projects.fabric.v1165,
projects.fabric.v117,
projects.fabric.v1171,
projects.fabric.v1182,
projects.fabric.v1192,
projects.fabric.v1193,
projects.fabric.v1201,
projects.fabric.v1202,
projects.fabric.v1203
).map { it.dependencyProject }
val special = setOf(
+5
View File
@@ -9,5 +9,10 @@ dependencies {
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
exclude("org.spigotmc", "spigot-api")
exclude("com.github.dmulloy2", "ProtocolLib")
exclude("org.ow2.asm", "asm")
exclude("net.md-5", "bungeecord-chat")
}
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -2,21 +2,26 @@ 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.platforms.bukkit.nms.NMSStorage;
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.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;
@@ -25,6 +30,14 @@ import java.util.stream.Collectors;
*/
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;
/**
* Constructs new instance
*/
@@ -32,35 +45,55 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
super("packet_handler");
}
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);
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;
} 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.");
}
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
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.DisplayObjective_POSITION.get(packet)).ordinal();
position = ((Enum<?>)PacketScoreboard.displayPacketData.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
position = PacketScoreboard.displayPacketData.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
(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));
@@ -68,17 +101,20 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(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) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.teamPacketData.TeamPacketClass.isInstance(packet);
}
@Override
@@ -87,67 +123,85 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
if (BukkitReflection.is1_19_3Plus()) {
onPlayerInfo1_19_3(receiver, packet);
} else {
//1.19.2-
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
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;
if (BukkitReflection.is1_19_3Plus()) {
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
} else {
id = profile.getId();
}
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
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 = TAB.getInstance().getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
}
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
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());
}
if (BukkitReflection.is1_19_3Plus()) {
// 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)));
// 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());
}
}
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@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.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
int action = PacketScoreboard.teamPacketData.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
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<>();
@@ -170,6 +224,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
newList.add(entry);
}
}
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -1,6 +1,6 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
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;
@@ -40,16 +40,19 @@ public class BukkitTAB extends JavaPlugin {
*/
private boolean isVersionSupported() {
try {
NMSStorage.setInstance(new NMSStorage());
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(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
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.color("&c[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug."));
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;
}
@@ -2,20 +2,24 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
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.*;
/**
@@ -32,19 +36,12 @@ import java.util.*;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
private static Class<?> HeaderFooterClass;
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
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 Class<?> RemoteChatSession$Data;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
@@ -59,78 +56,55 @@ public class BukkitTabList implements TabList {
private static Object[] gameModes;
@Getter
private static boolean available;
@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 (yet?)
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported
// Classes
Class<?> playerInfoDataClass;
Class<?> IChatBaseComponent;
Class<Enum> EnumGamemodeClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
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
);
// Header & Footer
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);
}
// Info packet
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(playerInfoDataClass);
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);
@@ -147,13 +121,21 @@ public class BukkitTabList implements TabList {
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
available = true;
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 (!available) return;
if (PlayerInfoClass == null) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
@@ -165,40 +147,32 @@ public class BukkitTabList implements TabList {
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
if (!available) return;
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 (!available) return;
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 (!available) return;
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 (!available) return;
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (HeaderFooterClass == null) return;
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(toComponent(header), toComponent(footer)));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, toComponent(header));
FOOTER.set(packet, toComponent(footer));
player.sendPacket(packet);
}
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
}
@SneakyThrows
@@ -240,8 +214,8 @@ public class BukkitTabList implements TabList {
return packet;
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
public Object toComponent(IChatBaseComponent component) {
return componentCache.get(component, player.getVersion());
}
@NotNull
@@ -253,4 +227,43 @@ public class BukkitTabList implements TabList {
}
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,33 +1,31 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
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.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* TabPlayer implementation for Bukkit platform
*/
@@ -35,23 +33,17 @@ import java.util.Collection;
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
@NotNull
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
@NotNull
private final Object playerConnection;
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
private final Scoreboard<BukkitTabPlayer> scoreboard = ScoreboardLoader.getInstance().apply(this);
@NotNull
private final TabList tabList = new BukkitTabList(this);
private final BukkitTabList tabList = new BukkitTabList(this);
@NotNull
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
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);
@@ -64,11 +56,8 @@ public class BukkitTabPlayer extends BackendTabPlayer {
* @param p
* bukkit player
*/
@SneakyThrows
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@Override
@@ -79,21 +68,21 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
@SneakyThrows
public int getPing() {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
return NMSStorage.getInstance().PING.getInt(handle);
return PingRetriever.getPing(getPlayer());
}
@SneakyThrows
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
PacketSender.sendPacket(getPlayer(), nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
if (spigot) {
getPlayer().spigot().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
} else {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
}
@Override
@@ -107,20 +96,9 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
@Nullable
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
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());
}
return tabList.getSkin();
}
@Override
@@ -62,11 +62,7 @@ public class BukkitUtils {
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
}
}
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
sb.append(component.getModifier().getMagicCodes());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
@@ -5,7 +5,7 @@ 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.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -21,6 +21,9 @@ import java.util.UUID;
@RequiredArgsConstructor
public class BukkitBossBar implements BossBar {
/** Style array for fast access */
private static final org.bukkit.boss.BarStyle[] styles = org.bukkit.boss.BarStyle.values();
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@@ -35,7 +38,8 @@ public class BukkitBossBar implements BossBar {
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
styles[style.ordinal()]
);
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
@@ -53,7 +57,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bossBars.get(id).setStyle(styles[style.ordinal()]);
}
@Override
@@ -1,7 +1,7 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
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;
@@ -5,7 +5,7 @@ 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.platform.bossbar.BossBar;
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;
@@ -21,6 +21,9 @@ import java.util.UUID;
@RequiredArgsConstructor
public class ViaBossBar implements BossBar {
/** Style array for fast access */
private static final BossStyle[] styles = BossStyle.values();
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@@ -36,7 +39,8 @@ public class ViaBossBar implements BossBar {
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
styles[style.ordinal()]
);
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@@ -53,7 +57,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
viaBossBars.get(id).setStyle(styles[style.ordinal()]);
}
@Override
@@ -1,6 +1,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.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
@@ -73,7 +74,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
if (BukkitReflection.getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
@@ -109,13 +110,13 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
@@ -7,6 +7,7 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -112,4 +113,15 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
}
@Override
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
public void unregisterExpansion() {
if (ReflectionUtils.methodExists(PlaceholderExpansion.class, "unregister")) {
// Added in 2.10.7 (Jul 28, 2020)
unregister();
} else {
PlaceholderAPI.unregisterExpansion(this);
}
}
}
@@ -5,6 +5,7 @@ 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;
@@ -28,6 +29,8 @@ 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"));
@@ -113,10 +116,4 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
}
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
public String getFeatureName() {
return "Per world PlayerList";
}
}
@@ -0,0 +1,33 @@
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)
);
}
}
@@ -0,0 +1,55 @@
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);
}
@@ -0,0 +1,73 @@
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());
}
}
@@ -0,0 +1,32 @@
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())
);
}
}
@@ -17,10 +17,6 @@ public class BukkitReflection {
@Getter
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether this server jar is mojang mapped or not */
@Getter
private static final boolean mojangMapped = checkMojangMapped();
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@@ -31,20 +27,15 @@ public class BukkitReflection {
/** Flag determining whether the server version is at least 1.20.2 or not */
@Getter
private static final boolean is1_20_2Plus = minorVersion >= 20 && !serverPackage.equals("v1_20_R1");
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
private static boolean checkMojangMapped() {
try {
Class.forName("net.minecraft.network.syncher.SynchedEntityData").getMethod("define",
Class.forName("net.minecraft.network.syncher.EntityDataAccessor"), Object.class);
return true;
} catch (ReflectiveOperationException | NullPointerException ex) {
return false;
}
}
/** Flag determining whether the server version is at least 1.20.3 or not */
@Getter
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
/**
* Returns class with given potential names in same order
* 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.
*
* @param names
* possible class names
@@ -52,32 +43,19 @@ public class BukkitReflection {
* @throws ClassNotFoundException
* if class does not exist
*/
public static Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
ClassLoader loader = BukkitReflection.class.getClassLoader();
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
if (minorVersion >= 17) {
return Class.forName("net.minecraft." + name);
} else {
return loader.loadClass("net.minecraft.server." + serverPackage + "." + 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 class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return BukkitReflection.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
}
@@ -2,9 +2,9 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
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;
@@ -31,8 +31,6 @@ public class DataWatcher implements EntityData {
private static Class<?> DataWatcherObject;
private static Constructor<?> newDataWatcherObject;
private static Class<?> DataWatcherRegistry;
private static Class<?> DataWatcherSerializer;
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
@@ -40,7 +38,11 @@ public class DataWatcher implements EntityData {
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<>();
@@ -56,7 +58,7 @@ public class DataWatcher implements EntityData {
} else if (BukkitReflection.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (BukkitReflection.getMinorVersion() >= 14) {
} else if (BukkitReflection.getMinorVersion() == 14) {
//1.14.x
return 13;
} else if (BukkitReflection.getMinorVersion() >= 10) {
@@ -73,62 +75,60 @@ public class DataWatcher implements EntityData {
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
if (BukkitReflection.isMojangMapped()) {
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
} else if (minorVersion >= 17) {
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
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 {
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
}
newDataWatcher = DataWatcher.getConstructor();
}
newDataWatcher = ReflectionUtils.getOnlyConstructor(DataWatcher);
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_"}, // {Bukkit 1.20.2+, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
} else {
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang | 1.20.2+, 1.20.2-}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
if (minorVersion >= 9) {
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
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);
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);
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "j").get(null);
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (minorVersion >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry,
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry,
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
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);
}
}
@@ -167,7 +167,7 @@ public class DataWatcher implements EntityData {
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(TAB.getInstance().getPlatform().toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
Optional.of(componentCache.get(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
@@ -1,120 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
public class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = ReflectionUtils.getMethod(EntityHuman, new String[] {"getProfile"});
}
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
BukkitTabList.load();
DataWatcher.load();
PacketEntityView.load();
PacketScoreboard.load();
if (minorVersion < 17) {
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
}
private void loadClasses() throws ClassNotFoundException {
if (minorVersion >= 17 && ReflectionUtils.classExists("net.minecraft.ChatFormatting")) {
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
} else if (minorVersion >= 17) {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
} else {
EntityHuman = BukkitReflection.getLegacyClass("EntityHuman");
Packet = BukkitReflection.getLegacyClass("Packet");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerConnection = BukkitReflection.getLegacyClass("PlayerConnection");
NetworkManager = BukkitReflection.getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
ChatSerializer = BukkitReflection.getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
}
}
}
@@ -4,7 +4,6 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
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.backend.entityview.EntityView;
@@ -66,6 +65,9 @@ public class PacketEntityView implements EntityView {
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Class<?> ClientboundBundlePacket;
private static Field ClientboundBundlePacket_packets;
private static Object dummyEntity;
@Getter
@@ -86,6 +88,10 @@ public class PacketEntityView implements EntityView {
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;
}
@@ -248,7 +254,7 @@ public class PacketEntityView implements EntityView {
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
player.sendPacket(nmsPacket);
}
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
if (BukkitReflection.getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@@ -366,6 +372,17 @@ public class PacketEntityView implements EntityView {
}
}
@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.
*
@@ -0,0 +1,47 @@
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.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;
public class PacketSender {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Method sendPacket;
@Getter private static boolean available;
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.");
}
}
@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);
}
}
@@ -0,0 +1,41 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
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;
public class PingRetriever {
private static Method getHandle;
private static Field PING;
public static void tryLoad() {
try {
if (BukkitReflection.getMinorVersion() < 17) {
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".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
}
} 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.");
}
}
@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));
}
}
@@ -1,9 +1,15 @@
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.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
@@ -15,7 +21,6 @@ import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
@@ -47,25 +52,42 @@ import java.lang.reflect.Field;
* Implementation of Platform interface for Bukkit platform
*/
@Getter
public class BukkitPlatform implements BackendPlatform<Object> {
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
/** Flag tracking plugin presence */
private final boolean premiumVanish = Bukkit.getPluginManager().isPluginEnabled("PremiumVanish");
/**
* Plugin instance for registering tasks and events
*/
@NotNull
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
/**
* 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 */
@Nullable private Object server;
/**
* NMS server to get TPS from on spigot
*/
@Nullable
private Object server;
/** TPS field */
@Nullable private Field spigotTps;
/**
* 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");
public BukkitPlatform(@NotNull JavaPlugin plugin) {
@@ -76,6 +98,13 @@ public class BukkitPlatform implements BackendPlatform<Object> {
} catch (ReflectiveOperationException e) {
//not spigot
}
PingRetriever.tryLoad();
PacketSender.tryLoad();
ScoreboardLoader.findInstance();
if (BukkitReflection.getMinorVersion() >= 8) {
BukkitPipelineInjector.tryLoad();
HeaderFooter.findInstance();
}
}
@Override
@@ -106,20 +135,20 @@ public class BukkitPlatform implements BackendPlatform<Object> {
}
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
p -> ((TabPlayer)p).getPing());
p -> ((TabPlayer) p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
return BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
}
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
return BukkitReflection.getMinorVersion() >= 8 ? new BukkitNameTagX(plugin) : new NameTag();
}
@Override
@@ -142,7 +171,7 @@ public class BukkitPlatform implements BackendPlatform<Object> {
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
if (!placeholderAPI) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
registerDummyPlaceholder(identifier);
return;
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
@@ -169,7 +198,7 @@ public class BukkitPlatform implements BackendPlatform<Object> {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
});
return null;
});
@@ -218,7 +247,7 @@ public class BukkitPlatform implements BackendPlatform<Object> {
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
() -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
() -> "1." + BukkitReflection.getMinorVersion() + ".x"));
}
@Override
@@ -233,11 +262,6 @@ public class BukkitPlatform implements BackendPlatform<Object> {
return plugin.getDataFolder();
}
@Override
public Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return NMSStorage.getInstance().componentCache.get(component, version);
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
@@ -267,11 +291,26 @@ public class BukkitPlatform implements BackendPlatform<Object> {
@Override
public double getMSPT() {
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
}
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Bukkit.getScheduler().runTask(plugin, task);
}
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();
//noinspection ConstantConditions
if (premiumVanish && VanishAPI.canSee(bViewer, bTarget)) return true;
if (bViewer.canSee(bTarget)) return true;
return BackendPlatform.super.canSee(viewer, target);
}
}
@@ -74,9 +74,10 @@ public class FoliaPlatform extends BukkitPlatform {
* @param task
* Task to run
*/
@Override
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(@NotNull Entity entity, @NotNull Runnable task) {
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
@@ -1,9 +1,11 @@
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;
@@ -12,13 +14,12 @@ 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.NameTagVisibility;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
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:
@@ -46,49 +47,68 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
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();
@NotNull
protected final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
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
player.getPlayer().setScoreboard(scoreboard);
runSync(() -> {
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
player.getPlayer().setScoreboard(scoreboard);
});
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
public void setDisplaySlot0(int slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
scoreboard.getObjective(objective).setDisplaySlot(slots[slot]);
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
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(playerName).setScore(score);
scoreboard.getObjective(objective).getScore(scoreHolder).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(playerName)).setScore(score);
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(scoreHolder)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(playerName);
scoreboard.resetScores(scoreHolder);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
scoreboard.resetScores(Bukkit.getOfflinePlayer(scoreHolder));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
newObjective(objectiveName, "dummy", title, display);
}
@@ -100,11 +120,12 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
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.valueOf(display.name()));
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.values()[display]);
}
@Override
@@ -153,24 +174,29 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
public void newObjective(String objectiveName, String criteria, String title, int display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, 32), RenderType.valueOf(display.name()));
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, 32));
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, 16));
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, 16));
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
/**
@@ -209,11 +235,34 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
* 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) {
player.getPlayer().setScoreboard(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();
}
}
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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> {
public NullScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
protected void setDisplaySlot0(int slot, @NotNull String objective) {
// Do nothing
}
@Override
protected void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
// Do nothing
}
@Override
protected void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void unregisterObjective0(@NotNull String objectiveName) {
// Do nothing
}
@Override
protected void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent 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) {
// Do nothing
}
@Override
protected void unregisterTeam0(@NotNull 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) {
// Do nothing
}
}
@@ -7,8 +7,10 @@ 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;
@@ -16,225 +18,98 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import static me.neznamy.tab.platforms.bukkit.nms.BukkitReflection.getLegacyClass;
/**
* 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", "rawtypes"})
@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 Field IScoreboardCriteria_self;
private static Class<?> NumberFormat;
private static Constructor<?> newFixedFormat;
// PacketPlayOutScoreboardDisplayObjective
public static Class<?> DisplayObjectiveClass;
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
public static Enum[] DisplaySlot_values;
// PacketPlayOutScoreboardScore
private static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
private static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
// 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 Field Objective_DISPLAY_NAME;
private static Class<Enum> EnumScoreboardHealthDisplay;
private static Constructor<?> newScoreboardObjective;
private static Method ScoreboardObjective_setDisplayName;
private static Enum<?>[] healthDisplays;
private static Object IScoreboardCriteria_dummy;
// PacketPlayOutScoreboardTeamStorage
public static Class<?> TeamPacketClass;
private static Constructor<?> newTeamPacket;
private static Method TeamPacketConstructor_of;
private static Method TeamPacketConstructor_ofBoolean;
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
private static Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Class<Enum> EnumChatFormatClass;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
private static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
private static Method ScoreboardTeam_setPrefix;
private static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static ScorePacketData scorePacketData;
public static TeamPacketData teamPacketData;
public static DisplayPacketData displayPacketData;
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam;
Class<?> scoreboardObjective;
Class<?> scoreboardScoreClass;
Class<?> IScoreboardCriteria;
Class<?> scoreboard;
Class<?> scorePacketClass;
Class<?> IChatBaseComponent = null;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
scoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
scoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
} else if (minorVersion >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
scoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
scoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
} else {
EnumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
scorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
scoreboard = getLegacyClass("Scoreboard");
scoreboardObjective = getLegacyClass("ScoreboardObjective");
scoreboardScoreClass = getLegacyClass("ScoreboardScore");
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
scoreboardTeam = getLegacyClass("ScoreboardTeam");
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) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
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) {
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
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 >= 9) {
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
if (minorVersion < 13) {
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
}
}
emptyScoreboard = scoreboard.getConstructor().newInstance();
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
DisplaySlot_values = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
} else {
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
}
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(scoreboardObjective);
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);
newScoreboardScore = scoreboardScoreClass.getConstructor(scoreboard, scoreboardObjective, String.class);
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);
if (minorVersion >= 13) {
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumChatFormatClass);
} else {
newScorePacket_String = scorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
if (minorVersion >= 8) {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.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
}
if (minorVersion >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
}
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);
}
ScoreboardScore_setScore = ReflectionUtils.getMethod(
scoreboardScoreClass,
new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, // {Thermos, 1.5.1 - 1.17.1 & 1.20.2+, 1.18 - 1.20.1, 1.5, Mohist 1.18.2}
int.class
);
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 >= 13) {
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+}
IChatBaseComponent
);
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+}
IChatBaseComponent
);
} 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 >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
EnumNameTagVisibility
);
}
available = true;
}
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
@@ -243,50 +118,36 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
Object displaySlot;
if (BukkitReflection.is1_20_2Plus()) {
displaySlot = DisplaySlot_values[slot.ordinal()];
} else {
displaySlot = slot.ordinal();
}
player.sendPacket(newDisplayObjective.newInstance(displaySlot, newScoreboardObjective(objective)));
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, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(0, objectiveName, title, display));
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(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
player.sendPacket(newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(2, objectiveName, title, display));
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 buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
toComponent(IChatBaseComponent.optimizedComponent(title)),
Enum.valueOf(EnumScoreboardHealthDisplay, display.name())),
action
);
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]);
}
Object nmsPacket = newObjectivePacket.newInstance();
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, display.name()));
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
return packet;
}
@Override
@@ -294,109 +155,363 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
}
player.sendPacket(teamPacketData.registerTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, players, options));
}
@Override
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
}
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) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
}
}
@SneakyThrows
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @NotNull String suffix,
@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, toComponent(IChatBaseComponent.optimizedComponent(prefix)));
ScoreboardTeam_setSuffix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(suffix)));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(EnumChatFormatClass, EnumChatFormat.lastColorsOf(prefix).toString()));
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (BukkitReflection.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (BukkitReflection.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
return team;
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 void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
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) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score));
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (BukkitReflection.getMinorVersion() >= 8) {
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
// 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 {
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
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);
}
}
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
}
public static class TeamPacketData {
/**
* Creates a new Scoreboard Objective with given name.
*
* @param objectiveName
* Objective name
* @return NMS Objective
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
toComponent(new IChatBaseComponent("")),
null
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;
}
return newScoreboardObjective.newInstance(
null,
objectiveName,
IScoreboardCriteria_self.get(null)
);
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
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);
}
}
}
@@ -32,8 +32,8 @@ public class PaperScoreboard extends BukkitScoreboard {
}
@Override
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.valueOf(display.name()));
public void newObjective(String objectiveName, String criteria, String title, int display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.values()[display]);
}
@Override
@@ -0,0 +1,45 @@
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.shared.platform.Scoreboard;
import org.bukkit.Bukkit;
import java.util.function.Function;
public class ScoreboardLoader {
/** Instance function */
@Getter
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer>> instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
if (PacketScoreboard.isAvailable() && PacketSender.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.");
}
}
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.");
}
}
+1 -4
View File
@@ -7,7 +7,7 @@ load: POSTWORLD
author: @author@
website: "@website@"
depend: []
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate, Vault]
loadbefore: []
prefix: TAB
libraries: []
@@ -49,9 +49,6 @@ permissions:
tab.change.*:
description: Allows player to change property using command
default: op
tab.width:
description: Allows player to run /tab width
default: op
tab.announce.bar:
description: Allows player to run /tab announce bar
default: op
+1 -1
View File
@@ -1,7 +1,7 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bungeecord:3.0.1")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.2-SNAPSHOT")
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -1,10 +1,10 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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.shared.chat.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -35,10 +35,10 @@ 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(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
packet.setColor(color.ordinal());
packet.setDivision(style.ordinal());
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
colors.put(id, color.ordinal());
styles.put(id, style.ordinal());
@@ -49,8 +49,8 @@ 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(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
player.getPlayer().unsafe().sendPacket(packet);
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
player.sendPacket(packet);
}
@Override
@@ -59,7 +59,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
packet.setHealth(progress);
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
}
@Override
@@ -69,7 +69,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
packet.setDivision(style.ordinal());
packet.setColor(colors.get(id));
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
styles.put(id, style.ordinal());
}
@@ -81,7 +81,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
packet.setDivision(styles.get(id));
packet.setColor(color.ordinal());
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
colors.put(id, color.ordinal());
}
@@ -90,7 +90,7 @@ public class BungeeBossBar implements BossBar {
public void remove(@NotNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
player.getPlayer().unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
colors.remove(id);
styles.remove(id);
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.EventListener;
@@ -25,7 +26,36 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
@EventHandler
public void onSwitch(ServerSwitchEvent e) {
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
TAB tab = TAB.getInstance();
if (tab.isPluginDisabled()) return;
// Avoid 1.20.3 client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
TabPlayer p = tab.getPlayer(e.getPlayer().getUniqueId());
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
p.getScoreboard().freeze();
}
tab.getCPUManager().runTask(() -> {
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
if (player == null) {
player = createPlayer(e.getPlayer());
// Things will get cleared immediately after, so no point in sending it, also someone said it fixed some warn from Geyser
// Sending these packets before login packet will also crash the client on 1.20.3
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getScoreboard().freeze();
}
tab.getFeatureManager().onJoin(player);
} else {
tab.getFeatureManager().onServerChange(player.getUniqueId(), e.getPlayer().getServer().getInfo().getName());
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_20_2.getNetworkId()) {
// For versions below 1.20.2 the tablist is already clean when this event is called
// For 1.20.2+ this event is called before, so we listen to Login packet instead
tab.getFeatureManager().onTabListClear(player);
}
}
});
}
@EventHandler
@@ -56,7 +56,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
getId = ReflectionUtils.setAccessible(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
@@ -77,25 +77,16 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
if (wrapperField == null) return null;
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
return ((ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection())).getHandle();
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
((ScoreboardDisplay) packet).getName()
);
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay)packet).getPosition(), ((ScoreboardDisplay) packet).getName());
}
@Override
@@ -158,26 +149,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (packet instanceof PlayerListItemUpdate) {
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(newDisplayName.toString(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());
}
}
} else {
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(newDisplayName.toString(receiver.getVersion()));
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()));
@@ -186,9 +163,28 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
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());
}
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return packet instanceof Login;
}
/**
* Constructs new instance of the feature
*/
@@ -231,10 +227,15 @@ 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());
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
continue;
}
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
DefinedPacket packet = extraPacketSuppliers[i].get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
@@ -258,15 +259,10 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* packet class
* @return packet ID
*/
@SneakyThrows
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, protocolVersion);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + protocolVersion, e);
return -1;
}
return (int) getId.invoke(directionData, clazz, protocolVersion);
}
}
}
@@ -1,7 +1,9 @@
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.shared.TAB;
@@ -12,12 +14,14 @@ import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
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.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;
@@ -32,16 +36,18 @@ import java.util.stream.Collectors;
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform<BaseComponent> {
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;
/** Component cache for BungeeCord components */
@NotNull
private final ComponentCache<IChatBaseComponent, BaseComponent> bungeeCache =
new ComponentCache<>(1000, this::toComponent0);
@Override
public void loadPlayers() {
ViaVersionHook.getInstance().printProxyWarn();
@@ -98,27 +104,6 @@ public class BungeePlatform extends ProxyPlatform<BaseComponent> {
return plugin.getDataFolder();
}
@Override
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return bungeeCache.get(component, version);
}
private BaseComponent toComponent0(IChatBaseComponent component, ProtocolVersion version) {
ChatModifier modifier = component.getModifier();
TextComponent textComponent = new TextComponent(component.getText());
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (modifier.getFont() != null) textComponent.setFont(modifier.getFont());
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent0(c, version)).collect(Collectors.toList()));
return textComponent;
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
@@ -129,4 +114,62 @@ public class BungeePlatform extends ProxyPlatform<BaseComponent> {
public void registerChannel() {
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
}
@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)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
textComponent.setFont(modifier.getFont());
if (modifier.getClickEvent() != null) {
textComponent.setClickEvent(new ClickEvent(
ClickEvent.Action.valueOf(modifier.getClickEvent().getAction().name()),
modifier.getClickEvent().getValue()
));
}
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -1,17 +1,16 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
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.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
/**
@@ -21,48 +20,49 @@ import java.util.Collection;
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
/** Constructor to support both <1.20.2 and 1.20.2+ builds */
@SuppressWarnings("unchecked")
private static final Constructor<ScoreboardDisplay> newScoreboardDisplay = (Constructor<ScoreboardDisplay>)
Arrays.stream(ScoreboardDisplay.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findAny().orElse(null);
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.getPlayer().unsafe().sendPacket(newScoreboardDisplay.newInstance((byte)slot.ordinal(), objective));
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay(slot, objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
jsonOrRaw(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.REGISTER,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
player.sendPacket(new ScoreboardObjective(
objectiveName,
"", // Empty string to prevent kick on 1.7
either(""), // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) 1
(byte) ObjectiveAction.UNREGISTER,
null
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
jsonOrRaw(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.UPDATE,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@@ -74,13 +74,23 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 0, jsonOrRaw(name), jsonOrRaw(prefix), jsonOrRaw(suffix),
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
player.sendPacket(new Team(
name,
(byte) TeamAction.CREATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
(byte)options,
players.toArray(new String[0])
));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.getPlayer().unsafe().sendPacket(new Team(name));
player.sendPacket(new Team(name));
}
@Override
@@ -90,36 +100,48 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 2, jsonOrRaw(name), jsonOrRaw(prefix),
jsonOrRaw(suffix), visibility.toString(), collision.toString(), color, (byte)options, null));
player.sendPacket(new Team(
name,
(byte) TeamAction.UPDATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
(byte)options,
null
));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent 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()))
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private String jsonOrRaw(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(player.getVersion());
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
player.sendPacket(new ScoreboardScoreReset(scoreHolder, objective));
} else {
return text;
player.sendPacket(new ScoreboardScore(scoreHolder, (byte) ScoreAction.REMOVE, objective, 0, null, null));
}
}
private Either<String, BaseComponent> either(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return Either.right(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion()));
} else {
return Either.left(text);
}
}
}
@@ -12,9 +12,9 @@ public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
"(with 1.19.3 support) and up (or an equivalent fork) to work."));
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."));
return;
}
TAB.create(new BungeePlatform(this));
@@ -6,16 +6,18 @@ 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.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.api.ProxyServer;
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.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -26,9 +28,6 @@ import org.jetbrains.annotations.Nullable;
@Getter
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Flag tracking plugin presence */
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
/** Player's scoreboard */
@NotNull
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
@@ -102,11 +101,19 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
errorNoServer(message);
return;
if (!getPlayer().isConnected()) return;
try {
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.ServerConnection.sendPacketQueued(ServerConnection.java:48)
// at net.md_5.bungee.ServerConnection.sendData(ServerConnection.java:70)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver plugin message to player " + getName() +
" (online = " + getPlayer().isConnected() + ")", BungeeCordBug);
}
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
}
/**
@@ -126,7 +133,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
public boolean isVanished() {
try {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
if (getPlatform().isPremiumVanish() && BungeeVanishAPI.isInvisible(getPlayer())) return true;
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
@@ -141,4 +148,26 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
return tabList1_7;
}
/**
* Sends packet to the player. If BungeeCord supports 1.20.2+, new packet queue method is used
* to avoid error when sending packet in configuration phase.
*
* @param packet
* Packet to send
*/
public void sendPacket(@NotNull DefinedPacket packet) {
if (!getPlayer().isConnected()) return;
try {
((UserConnection)getPlayer()).sendPacketQueued(packet);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + getName() +
" (online = " + getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
}
}
@@ -0,0 +1,74 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.tab.ServerUnique;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.UUID;
public abstract class BungeeTabList implements TabList {
/** Player this TabList belongs to */
@NotNull
protected final BungeeTabPlayer player;
/** Pointer to UUIDs in player's TabList */
private final Collection<UUID> uuids;
@SneakyThrows
@SuppressWarnings("unchecked")
public BungeeTabList(@NotNull BungeeTabPlayer player) {
this.player = 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())
));
}
@NotNull
public Item item(@NotNull 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());
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())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
return item;
}
public void addUuid(UUID id) {
uuids.add(id);
}
public void removeUuid(UUID id) {
uuids.remove(id);
}
}
@@ -1,11 +1,7 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
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;
@@ -18,27 +14,25 @@ import java.util.UUID;
/**
* TabList handler for 1.19.3+ players using the new tab list packets.
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList1193 implements TabList {
public class BungeeTabList1193 extends BungeeTabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
public BungeeTabList1193(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
removeUuid(entry);
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(new UUID[]{entry});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
player.sendPacket(remove);
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
}
@@ -58,39 +52,14 @@ public class BungeeTabList1193 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
item.setGamemode(entry.getGameMode());
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())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
}
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
addUuid(entry.getUniqueId());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), entryToItem(entry));
}
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(actions);
packet.setItems(new Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
player.sendPacket(packet);
}
}
@@ -1,10 +1,8 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
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.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -19,17 +17,10 @@ import java.util.UUID;
* names are changed by removing old entry and adding a new one, which
* requires further tracking than just the UUID.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
* While BungeeCord itself does not support 1.7, some of its forks do.
* This was tested on FlameCord fork.
*/
@RequiredArgsConstructor
public class BungeeTabList17 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
public class BungeeTabList17 extends BungeeTabList {
/** Because entries are identified by names and not uuids on 1.7 */
@NotNull
@@ -38,9 +29,14 @@ public class BungeeTabList17 implements TabList {
@NotNull
private final Map<UUID, String> displayNames = new HashMap<>();
public BungeeTabList17(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull 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));
// Remove from map
@@ -66,8 +62,10 @@ public class BungeeTabList17 implements TabList {
@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() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
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()));
// Add to map
@@ -75,23 +73,23 @@ public class BungeeTabList17 implements TabList {
displayNames.put(entry.getUniqueId(), displayNameString);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Not available on 1.7
}
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new PlayerListItem.Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
player.sendPacket(packet);
}
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUsername(username);
item.setDisplayName(displayName);
item.setPing(latency);
item.setDisplayName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), player.getVersion()));
return item;
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Added in 1.8
}
}
@@ -1,11 +1,7 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
@@ -16,25 +12,23 @@ import java.util.UUID;
/**
* TabList handler for players between 1.8 and 1.19.2.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList18 implements TabList {
public class BungeeTabList18 extends BungeeTabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
public BungeeTabList18(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
removeUuid(entry);
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
}
@@ -54,38 +48,14 @@ public class BungeeTabList18 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
item.setGamemode(entry.getGameMode());
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
}
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
addUuid(entry.getUniqueId());
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(entry));
}
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
player.sendPacket(packet);
}
}
+10 -61
View File
@@ -1,38 +1,4 @@
val minecraftVersion = "1.20.2"
val v114 = "0.28.5+1.14"
val v115 = "0.28.5+1.15"
val v116 = "0.42.0+1.16"
val fabricApiVersions = mapOf(
"1.20.2" to "0.88.5+1.20.2",
"1.20.1" to "0.83.0+1.20.1",
"1.20" to "0.83.0+1.20",
"1.19.4" to "0.77.0+1.19.4",
"1.19.3" to "0.76.0+1.19.3",
"1.19.2" to "0.76.0+1.19.2",
"1.19.1" to "0.58.5+1.19.1",
"1.19" to "0.58.0+1.19",
"1.18.2" to "0.76.0+1.18.2",
"1.18.1" to "0.46.6+1.18",
"1.18" to "0.46.6+1.18",
"1.17.1" to "0.46.1+1.17",
"1.17" to "0.46.1+1.17",
"1.16.5" to v116,
"1.16.4" to v116,
"1.16.3" to v116,
"1.16.2" to v116,
"1.16.1" to v116,
"1.16" to v116,
"1.15.2" to v115,
"1.15.1" to v115,
"1.15" to v115,
"1.14.4" to v114,
"1.14.3" to v114,
"1.14.2" to v114,
"1.14.1" to v114,
"1.14" to v114,
)
val minecraftVersion = "1.20.3"
plugins {
id("fabric-loom")
@@ -47,31 +13,14 @@ repositories {
}
dependencies {
implementation(projects.shared)
api(projects.shared)
minecraft("com.mojang:minecraft:$minecraftVersion")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc", "fabric-loader", "0.14.17")
modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
}
loom {
accessWidenerPath.set(file("src/main/resources/tab.accesswidener"))
}
tasks {
compileJava {
options.release.set(17)
}
validateAccessWidener {
// We don't want to validate the access wideners, as we have wideners for multiple versions, and the validation task will
// fail, as the classes for the old wideners don't exist in the new versions and vice versa.
enabled = false
}
}
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val version = "0.90.4+1.20.3"
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))
}
@@ -1,17 +1,19 @@
package me.neznamy.tab.platforms.fabric;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
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.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
import net.minecraft.world.BossEvent.BossBarOverlay;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
@@ -25,10 +27,10 @@ public class FabricBossBar implements BossBar {
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()),
BossEvent.BossBarColor.valueOf(color.name()),
BossEvent.BossBarOverlay.valueOf(style.name())
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
bar.setProgress(progress);
bar.setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
bars.put(id, bar);
bar.addPlayer(player.getPlayer());
}
@@ -40,17 +42,17 @@ public class FabricBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, float progress) {
bars.get(id).setProgress(progress);
bars.get(id).setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bars.get(id).setOverlay(BossEvent.BossBarOverlay.valueOf(style.name()));
bars.get(id).setOverlay(BossBarOverlay.valueOf(style.name()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bars.get(id).setColor(BossEvent.BossBarColor.valueOf(color.name()));
bars.get(id).setColor(BossBarColor.valueOf(color.name()));
}
@Override
@@ -1,38 +1,37 @@
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 net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import me.neznamy.tab.shared.util.ReflectionUtils;
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 net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@RequiredArgsConstructor
public class FabricEntityView implements EntityView {
private final FabricTabPlayer player;
@NotNull
private static final Entity dummyEntity = new ArmorStand(null, 0, 0, 0);
private static final ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
@NotNull EntityData data) {
player.sendPacket(new ClientboundAddEntityPacket(entityId, id,
location.getX(), location.getY(), location.getZ(), 0, 0,
(EntityType<?>) entityType, 0, Vec3.ZERO, 0));
player.sendPacket(FabricTAB.getVersion().spawnEntity(entityId, id, entityType, location));
updateEntityMetadata(entityId, data);
}
@Override
@SuppressWarnings("unchecked")
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build()));
player.sendPacket(FabricTAB.getVersion().newEntityMetadata(entityId, data));
}
@Override
@@ -44,7 +43,7 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public void destroyEntities(int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
FabricTAB.getVersion().destroyEntities(player, entities);
}
@Override
@@ -59,7 +58,7 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return packet instanceof ClientboundAddEntityPacket;
return FabricTAB.getVersion().isSpawnPlayerPacket(packet);
}
@Override
@@ -73,22 +72,35 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
// 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);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return ((ClientboundMoveEntityPacket) movePacket).entityId;
return (int) ReflectionUtils.getFields(ClientboundMoveEntityPacket.class, int.class).get(0).get(movePacket);
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
return FabricTAB.getVersion().getSpawnedPlayer(playerSpawnPacket);
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
return FabricTAB.getVersion().getDestroyedEntities(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricTAB.getVersion().isBundlePacket(packet);
}
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return FabricTAB.getVersion().getPackets(bundlePacket);
}
}
@@ -1,5 +1,6 @@
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;
@@ -14,10 +15,18 @@ public class FabricEventListener extends EventListener<ServerPlayer> {
public void register() {
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) ->
worldChange(player.getUUID(), destination.dimension().location().toString()));
//TODO command
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
//TODO command preprocess
if (ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion()).getMinorVersion() >= 16) {
// 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)));
});
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChange(player.getUUID(), FabricTAB.getVersion().getName(destination)));
} // TODO else
}
@Override
@@ -1,5 +1,9 @@
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;
@@ -11,32 +15,32 @@ 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.ChatFormatting;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.contents.LiteralContents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Platform implementation for Fabric
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform<Component> {
@RequiredArgsConstructor
@Getter
public class FabricPlatform implements BackendPlatform {
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
private final MinecraftServer server;
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
registerDummyPlaceholder(identifier);
}
@Override
@@ -49,7 +53,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform<
@Override
@NotNull
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
return FabricTAB.getVersion().createPipelineInjector();
}
@Override
@@ -71,19 +75,34 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform<
}
@Override
@SneakyThrows
public void logInfo(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
Object logger = getLogger();
logger.getClass().getMethod("info", String.class).invoke(logger, message.toRawText());
}
@Override
@SneakyThrows
public void logWarn(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
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);
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
return "[Fabric] " + FabricTAB.getVersion().getServerVersion();
}
@Override
@@ -104,7 +123,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform<
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName());
return ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion());
}
@Override
@@ -113,34 +132,17 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform<
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
}
@Override
/**
* 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) {
// Text
MutableComponent comp = MutableComponent.create(new LiteralContents(component.getText()));
// Color and style
List<ChatFormatting> formats = new ArrayList<>();
if (component.getModifier().isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
if (component.getModifier().isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
if (component.getModifier().isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
if (component.getModifier().isItalic()) formats.add(ChatFormatting.ITALIC);
if (component.getModifier().isBold()) formats.add(ChatFormatting.BOLD);
Style style = comp.getStyle().applyFormats(formats.toArray(new ChatFormatting[0]));
if (component.getModifier().getColor() != null) {
if (version.getMinorVersion() >= 16) {
style = style.withColor(component.getModifier().getColor().getRgb());
} else {
style = style.withColor(ChatFormatting.valueOf(component.getModifier().getColor().getLegacyColor().name()));
}
}
if (component.getModifier().getFont() != null)
style = style.withFont(new ResourceLocation(component.getModifier().getFont()));
comp.withStyle(style);
// Extra
comp.getSiblings().addAll(component.getExtra().stream().map(c -> toComponent(c, version)).toList());
return comp;
return FabricTAB.getVersion().deserialize(component.toString(version));
}
@Override
@@ -150,6 +152,11 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform<
@Override
public double getMSPT() {
return server.getAverageTickTime();
return FabricTAB.getVersion().getMSPT();
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
}
@@ -1,94 +1,63 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.server.ServerScoreboard;
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;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
@NotNull
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
private final Map<String, Objective> objectives = new HashMap<>();
public FabricScoreboard(FabricTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[slot.ordinal()],
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
Component.empty(),
ObjectiveCriteria.RenderType.INTEGER
)
)
);
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(FabricTAB.getVersion().setDisplaySlot(slot, objectives.get(objective)));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
0
)
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = FabricTAB.getVersion().newObjective(
objectiveName,
toComponent(title),
RenderType.values()[display],
numberFormat == null ? null : toComponent(numberFormat)
);
objectives.put(objectiveName, obj);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
Component.empty(),
ObjectiveCriteria.RenderType.INTEGER
),
1
)
);
player.sendPacket(new ClientboundSetObjectivePacket(objectives.remove(objectiveName), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
2
)
);
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = objectives.get(objectiveName);
obj.setDisplayName(toComponent(title));
obj.setRenderType(RenderType.values()[display]);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
@@ -101,15 +70,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
team.getPlayers().addAll(players);
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
player.sendPacket(FabricTAB.getVersion().registerTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket(new PlayerTeam(dummyScoreboard, name)));
player.sendPacket(FabricTAB.getVersion().unregisterTeam(new PlayerTeam(dummyScoreboard, name)));
}
@Override
@@ -121,18 +90,35 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
player.sendPacket(FabricTAB.getVersion().updateTeam(team));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, playerName, score));
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)
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, playerName, 0));
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
player.sendPacket(FabricTAB.getVersion().removeScore(objective, scoreHolder));
}
@NotNull
private Component toComponent(@NotNull String string) {
return toComponent(IChatBaseComponent.optimizedComponent(string));
}
@NotNull
private Component toComponent(@NotNull IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
}
@@ -1,27 +1,40 @@
package me.neznamy.tab.platforms.fabric;
import me.lucko.fabric.api.permissions.v0.Permissions;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.NotNull;
public class FabricTAB implements DedicatedServerModInitializer {
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Getter
private static VersionLoader version;
@Override
public void onInitializeServer() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
String[] modules = {
"v1_14_4",
"v1_15_2",
"v1_16_5",
"v1_17",
"v1_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_3"
};
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) {}
}
throw new IllegalStateException("Your server version is marked as compatible, but a compatibility issue was found.");
}
}
@@ -48,10 +48,11 @@ public class FabricTabCommand {
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
boolean hasReloadPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
source.sendSystemMessage(((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
FabricTAB.getVersion().sendMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
}
} else {
if (source.getEntity() == null) {
@@ -4,73 +4,64 @@ 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 me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.UUID;
public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList {
@RequiredArgsConstructor
public class FabricTabList implements TabList {
@NotNull
private final FabricTabPlayer player;
@Override
public void removeEntry(@NotNull UUID entry) {
player.sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME, new Builder(entry)));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME),
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME,
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))
));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), new Builder(entry).setLatency(latency)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_LATENCY,
new Builder(entry).setLatency(latency)));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE), new Builder(entry).setGameMode(gameMode)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_GAME_MODE,
new Builder(entry).setGameMode(gameMode)));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(build(EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class), new Builder(entry.getUniqueId())
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()))));
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))
));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().connection.send(new ClientboundTabListPacket(
player.sendPacket(FabricTAB.getVersion().newHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion()))
);
}
@NotNull
private Packet<?> build(@NotNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, @NotNull FabricTabList.Builder entry) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
player.getPlatform().toComponent(footer, player.getVersion())
));
return packet;
}
@RequiredArgsConstructor
@@ -78,14 +69,14 @@ public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList
public static class Builder {
@NotNull private final UUID id;
@Nullable private String name;
@NotNull private String name = ""; // Avoid nullability issues as things are changing over versions
@Nullable private Skin skin;
private int latency;
private int gameMode;
@Nullable private Component displayName;
@NotNull
public Builder setName(@Nullable String name) {
public Builder setName(@NotNull String name) {
this.name = name;
return this;
}
@@ -114,9 +105,8 @@ public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList
return this;
}
@Nullable
@NotNull
public GameProfile createProfile() {
if (name == null) return null;
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
@@ -7,7 +7,7 @@ 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.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
@@ -31,22 +31,23 @@ public class FabricTabPlayer extends BackendTabPlayer {
private final EntityView entityView = new FabricEntityView(this);
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(), player.level().dimension().location().toString());
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(player)));
}
@Override
public boolean hasPermission(@NotNull String permission) {
return FabricTAB.hasPermission(getPlayer().createCommandSourceStack(), permission);
return getPlatform().hasPermission(getPlayer().createCommandSourceStack(), permission);
}
@Override
public int getPing() {
return getPlayer().connection.latency();
return FabricTAB.getVersion().getPing(getPlayer());
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendSystemMessage(getPlatform().toComponent(message, getVersion()));
FabricTAB.getVersion().sendMessage(getPlayer(), getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -64,8 +65,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
Property skinProperty = properties.iterator().next();
return new TabList.Skin(skinProperty.value(), skinProperty.signature());
return FabricTAB.getVersion().propertyToSkin(properties.iterator().next());
}
@Override
@@ -0,0 +1,90 @@
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,33 +1,33 @@
package me.neznamy.tab.platforms.fabric.features;
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.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class FabricNameTagX extends BackendNameTagX {
private boolean enabled = true;
public boolean enabled = true;
public FabricNameTagX() {
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
// no sneaking for fabric //TODO
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 16) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
} // TODO else
// TODO sneaking
}
@Override
@@ -39,7 +39,8 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((FabricTabPlayer)player1).getPlayer().level() == ((FabricTabPlayer)player2).getPlayer().level();
return FabricTAB.getVersion().getLevel(((FabricTabPlayer)player1).getPlayer()) ==
FabricTAB.getVersion().getLevel(((FabricTabPlayer)player2).getPlayer());
}
@Override
@@ -77,7 +78,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isCrouching();
return FabricTAB.getVersion().isSneaking(((FabricTabPlayer)player).getPlayer());
}
@Override
@@ -119,13 +120,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return () -> Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
return FabricTAB.getVersion().createDataWatcher(viewer, flags, displayName, nameVisible);
}
@Override
+10 -4
View File
@@ -10,12 +10,18 @@
"server": ["me.neznamy.tab.platforms.fabric.FabricTAB"]
},
"depends": {
"fabricloader": ">=0.9.0",
"fabric-api-base": "*",
"minecraft": "1.20.2"
"minecraft": [
"1.14.4",
"1.15.2",
"1.16.5",
"1.17", "1.17.1",
"1.18", "1.18.1", "1.18.2",
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
"1.20", "1.20.1", "1.20.2", "1.20.3"
]
},
"recommends": {
"fabric-permissions-api-v0": "*"
},
"accessWidener": "tab.accesswidener"
}
}
@@ -1,29 +0,0 @@
accessWidener v1 named
accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I
accessible field net/minecraft/server/network/ServerGamePacketListenerImpl connection Lnet/minecraft/network/Connection;
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
# old (~1.16.5-?)
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/apache/logging/log4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/apache/logging/log4j/Logger;
# 1.19.3+
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
# 1.19.2-
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoPacket entries Ljava/util/List;
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoPacket entries Ljava/util/List;
# 1.16.5-
accessible field net/minecraft/network/protocol/game/ClientboundTabListPacket header Lnet/minecraft/network/chat/Component;
accessible field net/minecraft/network/protocol/game/ClientboundTabListPacket footer Lnet/minecraft/network/chat/Component;
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.14.4")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.28.5+1.14"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_14_4;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
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);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,223 @@
package me.neznamy.tab.platforms.fabric.v1_14_4;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
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;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.14.4");
}
@Override
public String getName(Level level) {
return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isSneaking();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(13, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.15.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.28.5+1.15"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_15_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
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);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,220 @@
package me.neznamy.tab.platforms.fabric.v1_15_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
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;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.15.2");
}
@Override
public String getName(Level level) {
return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(14, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.16.5")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.42.0+1.16"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_16_5;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
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);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,234 @@
package me.neznamy.tab.platforms.fabric.v1_16_5;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.16.5");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix;
switch (path) {
case "overworld":
dimensionSuffix = ""; // No suffix for overworld
break;
case "the_nether":
dimensionSuffix = "_nether";
break;
default:
dimensionSuffix = "_" + path; // End + default behavior for other dimensions created by mods
break;
}
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(14, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.17")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.46.1+1.17"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_17;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,221 @@
package me.neznamy.tab.platforms.fabric.v1_17;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
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.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.17");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return new int[]{((ClientboundRemoveEntityPacket) entityDestroyPacket).getEntityId()};
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
for (int entity : entities) {
player.sendPacket(new ClientboundRemoveEntityPacket(entity));
}
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.17.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.46.1+1.17"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_17_1;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.fabric.v1_17_1;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
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.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.17.1");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.18.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.18.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_18_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.fabric.v1_18_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
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.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.18", "1.18.1", "1.18.2");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.19.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.19.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_19_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,220 @@
package me.neznamy.tab.platforms.fabric.v1_19_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
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.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.19", "1.19.1", "1.19.2");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.19.3")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.19.3"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -1,7 +1,9 @@
package me.neznamy.tab.platforms.fabric;
package me.neznamy.tab.platforms.fabric.v1_19_3;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
@@ -9,13 +11,14 @@ import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,14 +35,17 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().connection.connection.channel;
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket)packet).getSlot().ordinal(),
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@@ -71,34 +77,41 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<Action> actions = packet.actions();
List<Entry> updatedList = new ArrayList<>();
for (Entry nmsData : packet.entries()) {
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(Action.UPDATE_DISPLAY_NAME)) {
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(Action.UPDATE_LATENCY)) {
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(Action.ADD_PLAYER)) {
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
packet.entries = updatedList;
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = packet.method;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
@@ -124,6 +137,6 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
newList.add(entry);
}
}
packet.players = newList;
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,225 @@
package me.neznamy.tab.platforms.fabric.v1_19_3;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
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.server.ServerScoreboard;
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.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.19.3");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.20.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.83.0+1.20.1"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,142 @@
package me.neznamy.tab.platforms.fabric.v1_20_1;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
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)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
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()));
}
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,226 @@
package me.neznamy.tab.platforms.fabric.v1_20_1;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
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.server.ServerScoreboard;
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.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.19.4", "1.20", "1.20.1");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level();
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return packet instanceof ClientboundBundlePacket;
}
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> getPackets(Object bundlePacket) {
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.20.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.88.5+1.20.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,142 @@
package me.neznamy.tab.platforms.fabric.v1_20_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerCommonPacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot().ordinal(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
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)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
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()));
}
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,227 @@
package me.neznamy.tab.platforms.fabric.v1_20_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
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.server.ServerScoreboard;
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 org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.20.2");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.connection.latency();
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.value(), property.signature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level();
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddEntityPacket) spawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return packet instanceof ClientboundBundlePacket;
}
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> getPackets(Object bundlePacket) {
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(DisplaySlot.values()[slot], objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+27
View File
@@ -0,0 +1,27 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.20.3")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.90.4+1.20.3"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,142 @@
package me.neznamy.tab.platforms.fabric.v1_20_3;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerCommonPacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot().ordinal(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
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)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
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()));
}
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
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);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,233 @@
package me.neznamy.tab.platforms.fabric.v1_20_3;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
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.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.numbers.FixedFormat;
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.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.*;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.20.3");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.connection.latency();
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.value(), property.signature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return (float) ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTimeNanos() / 1000000;
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level();
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddEntityPacket) spawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return packet instanceof ClientboundBundlePacket;
}
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> getPackets(Object bundlePacket) {
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(DisplaySlot.values()[slot], objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType, false,
numberFormat == null ? null : new FixedFormat(numberFormat));
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName,
@Nullable Component numberFormat) {
return new ClientboundSetScorePacket(
scoreHolder,
objective,
score,
displayName,
numberFormat == null ? null : new FixedFormat(numberFormat)
);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundResetScorePacket(scoreHolder, objective);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xms512M -Xmx4G
org.gradle.jvmargs=-Xms512M -Xmx8G
# Kotlin properties
kotlin.code.style=official
+19 -4
View File
@@ -9,7 +9,22 @@ val platforms = setOf(
rootProject.projects.bungeecord,
rootProject.projects.velocity,
rootProject.projects.sponge7,
rootProject.projects.sponge8,
rootProject.projects.sponge8
).map { it.dependencyProject }
val fabrics = setOf(
rootProject.projects.fabric,
rootProject.projects.fabric.v1144,
rootProject.projects.fabric.v1152,
rootProject.projects.fabric.v1165,
rootProject.projects.fabric.v117,
rootProject.projects.fabric.v1171,
rootProject.projects.fabric.v1182,
rootProject.projects.fabric.v1192,
rootProject.projects.fabric.v1193,
rootProject.projects.fabric.v1201,
rootProject.projects.fabric.v1202,
rootProject.projects.fabric.v1203
).map { it.dependencyProject }
tasks {
@@ -27,9 +42,9 @@ tasks {
registerPlatform(it, it.tasks.named<ShadowJar>("shadowJar").get())
}
// Fabric is different
val fabricProject = rootProject.projects.fabric.dependencyProject
registerPlatform(fabricProject, fabricProject.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
fabrics.forEach {
registerPlatform(it, it.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
}
}
build {
dependsOn(shadowJar)
+13 -2
View File
@@ -5,12 +5,12 @@ dependencyResolutionManagement {
mavenCentral() // Netty, SnakeYaml, json-simple, Guava, Kyori event, bStats, AuthLib, LuckPerms
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord-proxy
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.purpurmc.org/snapshots") // Purpur
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist, RedisBungee
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
maven("https://nexus.codecrafter47.dyndns.eu/content/repositories/public/") // BungeeCord-proxy // I feel bad for doing this
}
}
@@ -33,4 +33,15 @@ include(":bungeecord")
include(":sponge7")
include(":sponge8")
include(":fabric")
include(":fabric:v1_14_4")
include(":fabric:v1_15_2")
include(":fabric:v1_16_5")
include(":fabric:v1_17")
include(":fabric:v1_17_1")
include(":fabric:v1_18_2")
include(":fabric:v1_19_2")
include(":fabric:v1_19_3")
include(":fabric:v1_20_1")
include(":fabric:v1_20_2")
include(":fabric:v1_20_3")
include(":jar")
Loaded 100 of 228 files, more files were not shown because too many files have changed in this diff. Show more