Compare commits

..
1184 Commits
Author SHA1 Message Date
NotAlexNoyle 0f5f7e48dd Update canonical Gitea links 2026-10-30 14:19:35 -04:00
NotAlexNoyle 35e5791926 Update canonical Gitea links 2026-10-30 14:19:35 -04:00
NotAlexNoyle 6ca711435f Exclude CODEOWNERS from language statistics 2026-10-29 16:19:04 -04:00
NotAlexNoyle e8be71a34a Add CODEOWNERS (one staff approval) 2026-10-29 16:19:04 -04:00
NotAlexNoyle 7d0beb6ff2 Add Unions-OG tag 2026-08-09 12:02:07 -04:00
NotAlexNoyleandClaude Fable 5 68d0cdcbe7 TrueOG Network fork of TAB
Soft fork of upstream TAB at 93cfd74, versioned 5.9.9 for Purpur 1.19.4
serving 1.8-1.21.11 clients through ViaVersion/ViaBackwards/ViaRewind.

- Build only Bukkit v1_19_R3, BungeeCord, and Velocity; drop Fabric,
  Forge, NeoForge, and unused NMS modules
- LuckPerms 5.5 (Vault removed), GraalVM JDK 17, one shaded JAR
- Vanish-OG and AFK-OG compatibility, legacy scoreboard team colors,
  disabled scoreboard number formats
- Legacy (1.8-1.12.2) floating nametags: rank brackets dropped, AFK
  badge bolded everywhere, bold rank kept only when the whole tag can
  render bold (legacy width calc counts post-paragraph-l glyphs as bold
  and never receives paragraph-r, else phantom pixels pad the tag end);
  needs ViaBackwards add-teamcolor-to-prefix: false
- Ship live TrueOG Network config values

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N82WDRsXtPi7iRxx1wypXW
2026-07-12 17:58:53 -04:00
NEZNAMY 93cfd74fc0 [Bukkit] Refactor NMS implementation finding 2026-04-03 16:17:09 +02:00
NEZNAMY 79eb6a9078 [Bukkit] Rename classes and add console print 2026-04-03 12:54:07 +02:00
NEZNAMY 2e9e5042e1 Improve handling of IO errors on startup 2026-04-03 12:34:59 +02:00
NEZNAMY bb520edd53 [Bukkit] Catch correct exception and improve error message 2026-03-31 19:13:57 +02:00
NEZNAMY 2beb9563bb [BossBar] Forward UUID to platforms to prepare for 26.1 changes 2026-03-31 17:02:28 +02:00
NEZNAMY 87c15f7611 Bump dependencies 2026-03-31 16:35:52 +02:00
NEZNAMY 655bc176fa [Scoreboard] Fix line score recalculate issue (#1632) 2026-03-30 19:43:59 +02:00
NEZNAMY 66cb30d235 [Commands] Register debug command back to inform users about dump command 2026-03-28 19:32:43 +01:00
NEZNAMY cbe0b25b5f Java 25 and Spigot 26.1 support 2026-03-28 18:22:08 +01:00
NEZNAMY a8ccf4e1c9 Properly initialize minor version for 26.1 2026-03-28 18:17:04 +01:00
NEZNAMY cce29c51e3 Add 26.1 protocol constant 2026-03-25 17:24:49 +01:00
NEZNAMY 242015b0cc [Per world playerlist] Further improve compatibility with vanish plugins (#1621) 2026-03-24 17:41:56 +01:00
NEZNAMY f209e1b0f9 [Per world playerlist] Add vanish check for better compatibility with other plugins (#1621) 2026-03-21 16:16:06 +01:00
NEZNAMY 8a1ab06585 [Bukkit] Update bStats chart 2026-03-21 15:33:21 +01:00
NEZNAMY 50e817a86d [Per world playerlist] Use modern showPlayer/hidePlayer methods with plugin argument for better compatibility with other plugins (#1621) 2026-03-21 10:45:14 +01:00
NEZNAMY dbd596f079 [Belowname / Playerlist objective] Improve parity between the features 2026-03-09 18:43:34 +01:00
NEZNAMY f8e3a07e6f [Belowname / Playerlist objective] Finish vanish support checks after #1629 2026-03-09 17:49:47 +01:00
NEZNAMY ccd4ac3271 Merge pull request #1629 from c0bl/fix/vanished_score_packets
Consider canSee in YellowNumber.java setScore
2026-03-09 17:46:54 +01:00
c m fce1925448 consider canSee when sending scoreboard score packets in yellownumber feature 2026-03-08 21:14:14 -04:00
NEZNAMY 335eb5ef4d [Wiki] Monthly wiki backup for changes made in February 2026 2026-03-08 08:37:45 +01:00
NEZNAMY 654c40b892 [MySQL] Include MySQL driver in the jar for it to work on Velocity and modded platforms (#1620) 2026-03-07 11:46:09 +01:00
NEZNAMY a937243cdf [Dump command] Add proxy players to nametag player table 2026-03-03 17:17:13 +01:00
NEZNAMY 665d6baf1c [Configuration] Add config conversion for the new option (#1626) 2026-03-02 18:41:53 +01:00
NEZNAMY b4d326b52c Merge pull request #1626 from BlvckBytes/placeholders_locale
[Placeholders] Added locale of date-/time-format to config.yml
2026-03-02 18:26:36 +01:00
BlvckBytes 3aacec826b [Placeholders] Added locale of date-/time-format to config.yml 2026-03-01 22:42:27 +01:00
NEZNAMY 3261856385 [Forge/NeoForge] Remove redundant content for easier backporting 2026-02-28 18:05:51 +01:00
NEZNAMY 8cf479bf3b [Forge/NeoForge] Workaround to fix tablist tracking not working properly caused by events being fired too late (#1623) 2026-02-28 11:21:34 +01:00
NEZNAMY 3c3ef636eb [Code style] Remove redundant calls 2026-02-21 21:49:06 +01:00
NEZNAMY 3bd0b13740 [Bukkit] Fix error on Folia 2026-02-15 13:48:34 +01:00
NEZNAMY 92349227a2 Merge pull request #1617 from Arava-0/feat/scoreboard-api-setLines
feat(scoreboard): add setLines method for dynamic line updates
2026-02-13 16:47:01 +01:00
Arava-0 4dfc2a638a feat(scoreboard): add setLines method for dynamic line updates
- Introduces a new method to replace all current scoreboard lines with a provided list.
- Prevents unnecessary updates if the new list is identical to the current one.
2026-02-13 14:58:36 +01:00
NEZNAMY d7c73a8072 [Tablist Formatting] Add information about proxy players to dump output 2026-02-10 17:44:55 +01:00
NEZNAMY 297acc0322 [Belowname & Playerlist objectives] Add information about proxy players to dump output 2026-02-10 17:14:44 +01:00
NEZNAMY 1126b3b471 [Global playerlist] Do not remove players from the same server on quit, let backend handle it (#1611) 2026-02-08 18:07:59 +01:00
NEZNAMY ac59973283 [Tablist] Use ConcurrentHashMap as WeakHashMap was not used the intended way anyway (also may have been causing an issue) 2026-02-08 16:00:15 +01:00
NEZNAMY 4b130ed71d Update Gradle 9.1.0 -> 9.3.1 2026-02-07 18:13:00 +01:00
NEZNAMY ce90a33ce9 [BossBar] Fix unfreezing issue on Velocity and rename some methods (#1610) 2026-02-07 10:09:42 +01:00
NEZNAMY 50fee32218 [Velocity] Bump minimum required Velocity build to 546 (after the bossbar bug was partially fixed) to avoid my head exploding when trying to fix the issue in TAB (and having to make it support the older versions as well) (#1610) 2026-02-06 20:02:51 +01:00
NEZNAMY 39334a4cd9 [Dump command] Add placeholders sent by bridge on proxy installation 2026-02-04 18:26:12 +01:00
NEZNAMY fec2131ba8 [Wiki] Monthly wiki backup for changes made in January 2026 2026-02-02 17:57:02 +01:00
NEZNAMY 7c63436da4 Fix error 2026-02-01 15:05:41 +01:00
NEZNAMY de8fe63bd0 [Components] Partially implement click event and use it in dump command to make the link clickable 2026-01-31 14:40:07 +01:00
NEZNAMY 1c22f2387b [Sorting] Remove redundant variable 2026-01-31 11:16:49 +01:00
NEZNAMY e1b277582a [Sorting] Remove team name note tracking as debug command was deleted and dump already contains this information in a better way 2026-01-31 10:44:11 +01:00
NEZNAMY 94acc4f356 [Placeholders] Create class for holding placeholders to fix issues with re-registering placeholders 2026-01-31 10:39:58 +01:00
NEZNAMY 9afb62a6a4 [Placeholders] Simplify internal placeholder refresh interval handling and make it technically closer to simply pretending they are in the config 2026-01-30 18:58:15 +01:00
NEZNAMY 95ed957d54 [Configuration] Remove comment from default config that is no longer true 2026-01-30 17:10:24 +01:00
NEZNAMY 58814bc2da [API] [Layout] Add method for fixed slot ping as string 2026-01-29 16:07:13 +01:00
NEZNAMY 9e7556c3c1 [Dump Command] Do not show anonymous conditions as they are ugly in the output 2026-01-27 17:42:43 +01:00
NEZNAMY abaf723fb7 [Tablist Formatting] Fix tracked name not being assigned if target player cannot be seen, breaking the feature on server switch 2026-01-26 20:18:51 +01:00
NEZNAMY f52e99c86e [Conditions] Fix conditions not refreshing 2026-01-26 19:05:06 +01:00
NEZNAMY d6e70c35d8 [Dump Command] [Nametags] Dump all display values for all players in a table 2026-01-26 17:55:11 +01:00
NEZNAMY 4c91e73f0e [API] [Layout] Add method for specifying slot count when creating a new layout 2026-01-26 17:16:38 +01:00
NEZNAMY d083fc6e43 [Dump Command] Add table of tablist formatting for all players into dump 2026-01-25 19:47:35 +01:00
NEZNAMY ff2df25e3b [Commands] Delete debug command and update references to dump command 2026-01-25 17:10:40 +01:00
NEZNAMY 86c2342097 Replace most minor version checks with protocol version to prepare for new versioning system. 2026-01-25 16:49:28 +01:00
NEZNAMY 6dbd176683 [Dump Command] Include bridge info about player on proxy installation 2026-01-25 16:06:36 +01:00
NEZNAMY 141aab1c22 [Dump Command] Include list of downloaded PlaceholderAPI expansions 2026-01-25 15:29:10 +01:00
NEZNAMY 126ea4f456 [Dump Command] Print values for all players in belowname and playerlist objectives instead of just the selected player 2026-01-25 15:14:43 +01:00
NEZNAMY a0e5a97594 [Dump Command] Add general config settings to output 2026-01-25 14:54:19 +01:00
NEZNAMY 0420c11937 [Dump Command] Add placeholder-related config sections 2026-01-25 13:46:57 +01:00
NEZNAMY 27472fd387 [Dump Command] Format placeholder values in a table 2026-01-25 12:31:19 +01:00
NEZNAMY 104bdc646f [Dump Command] Improve sorting feature output 2026-01-25 11:44:34 +01:00
NEZNAMY cbc4c275d1 [Description] Update badges 2026-01-24 14:18:17 +01:00
NEZNAMY 372d51f873 Merge branch 'master' of https://github.com/NEZNAMY/TAB
# Conflicts:
#	build.gradle.kts
2026-01-24 14:16:40 +01:00
NEZNAMY 0dfd404215 5.5.0 2026-01-24 11:31:27 +01:00
NEZNAMY 21b7393274 [Dump command] Add more features to output 2026-01-23 20:03:14 +01:00
NEZNAMY dcd5f5a3e6 [Dump command] Improve output and create a class for features with display condition to avoid code duplication 2026-01-23 19:42:29 +01:00
NEZNAMY bf313b3ba1 [Commands] Use better error logging than e.printStackTrace() 2026-01-21 19:50:57 +01:00
NEZNAMY 5b4e3c479c [Commands] Add player info to dump command 2026-01-21 19:40:56 +01:00
NEZNAMY 6dedb05c29 [Commands] Drastically improve the new dump command 2026-01-21 18:18:31 +01:00
NEZNAMY 5f78e01342 [Commands] Initial implementation of /tab dump command 2026-01-18 17:38:07 +01:00
NEZNAMY 881506a3d5 [Tab expansion] Refactor the way data is stored. 2026-01-18 13:59:27 +01:00
NEZNAMY e26fc661b2 [Scoreboard] Execute scoreboard commands in scoreboard thread to avoid error 2026-01-17 20:50:19 +01:00
NEZNAMY f921758ec0 [Placeholders] Small optimization to replacing 2026-01-17 13:21:42 +01:00
NEZNAMY 01d65ba780 [Placeholders] Start rework of placeholder retrieving for better performance and dynamic placeholders 2026-01-17 12:29:55 +01:00
NEZNAMY d77e2d6f45 [Belowname] Clean up and use a different check for vanished players 2026-01-11 18:49:18 +01:00
NEZNAMY 7c30f55f31 [Bukkit] Remove support for several old MC versions 2026-01-11 16:48:47 +01:00
NEZNAMY c2d63865d5 [Bukkit] Repackage paper classes 2026-01-11 15:09:17 +01:00
NEZNAMY f406b518c0 [Bukkit] Add bStats chart 2026-01-11 15:01:42 +01:00
NEZNAMY 78f8c99f11 6.0.0-SNAPSHOT 2026-01-11 14:03:24 +01:00
NEZNAMY f97b9a419f [Performance] Clean up after PR #1600 2026-01-11 12:56:18 +01:00
NEZNAMY f2b88b8cd6 Merge pull request #1600 from groundbreakingmc/master
perf: replace String.format with StringBuilder for faster placeholder…
2026-01-11 12:20:22 +01:00
GroundbreakingMC cd06700c82 perf: implement element-based text parsing for cleaner placeholder replacement 2026-01-11 12:03:50 +01:00
NEZNAMY fa69040e00 Increase thread shutdown timeout to 5000 2026-01-11 11:19:48 +01:00
GroundbreakingMC fd88c1c0bb perf: skip StringBuilder allocation when no placeholders present 2026-01-11 07:22:23 +01:00
GroundbreakingMC cae7d8d061 perf: replace String.format with StringBuilder for faster placeholder replacement 2026-01-11 06:56:11 +01:00
NEZNAMY 2e1e24c225 Merge pull request #1599 from byPixelTV/master
feat: better wildcard support and enhanced pettern matching w/ regex for global player list
2026-01-10 18:47:56 +01:00
Lukas da0307b14f docs(global player list): describe new regex support in the wiki +
old wildcard support
2026-01-10 18:13:26 +01:00
Lukas 082c94ac07 fix(dependencies): change libs disguises repo because the old one is returning a 404 when trying to access it 2026-01-10 18:08:25 +01:00
Lukas 2ed4b1b6fc feat(global player list): enhance wildcard support and add advanced pattern matching w/ regex 2026-01-10 18:06:40 +01:00
NEZNAMY 8c7c8042e5 Merge pull request #1598 from Mattrolol/master
Layout: allow placeholder-based ping values for fixed slots
2026-01-07 18:47:06 +01:00
Mattrolol cca15a2b04 Fix fixed-slot ping validation and warnings 2026-01-07 11:43:05 -06:00
NEZNAMY 5f40411be0 [Debug] Add more messages unload 2026-01-07 18:00:30 +01:00
NEZNAMY 191273a728 [Debug] Add more messages on load/unload 2026-01-07 17:09:05 +01:00
Mattrolol 9eb9e9589f Allow placeholder-based ping values for fixed slots 2026-01-07 02:05:59 -06:00
NEZNAMY fdd45952c7 [Placeholders] Make %online_<server>% work on backend as well 2026-01-06 13:16:52 +01:00
NEZNAMY 98378b222d [Bukkit] Add relational placeholders into tab expansion and parse nested placeholders in normal placeholders 2026-01-03 09:45:52 +01:00
NEZNAMY 15a0446d70 [Conditions] Add warning text if condition is used in the wrong rel / non-rel way 2026-01-02 17:55:12 +01:00
NEZNAMY 7bd1b7c670 [Wiki] Monthly wiki backup for changes made in December 2025 2026-01-01 11:46:28 +01:00
NEZNAMY 46aaa47fdd [Forge] Add hook into a random Vanish mod for checking vanish status 2025-12-31 20:29:04 +01:00
NEZNAMY 2027e9f58e [Forge] Add support for ForgePlaceholderAPI 2025-12-31 17:51:32 +01:00
NEZNAMY 162e4690e9 [Scoreboard] Remove old options from valid key list 2025-12-30 21:06:22 +01:00
NEZNAMY c360575dec [Commands] Support args in dynamic commands 2025-12-28 17:45:56 +01:00
NEZNAMY 5b0354c2c0 [Layout] Rename "condition" to "display-condition" for better consistency with other features 2025-12-27 18:41:13 +01:00
NEZNAMY 52b0f21bd9 [Layout] Small refactor to layout code 2025-12-27 13:39:46 +01:00
NEZNAMY 1e3cbde3b3 [Layout] Merge both fake player layout types due to how similar they are 2025-12-27 12:33:02 +01:00
NEZNAMY 267fb727c7 [Layout] Only mark relational condition as used if actually used as relational 2025-12-26 21:02:50 +01:00
NEZNAMY 68904f58ec [Configuration] Reorder config options for more consistency 2025-12-24 11:54:12 +01:00
NEZNAMY 5cbb1bb5dd [Commands] Return list of messages to send when plugin is disabled as components instead of strings, also fix error when printing them on BungeeCord 2025-12-23 15:21:26 +01:00
NEZNAMY 038d221206 [Performance] Fix conditions being parsed as relational placeholders when not needed 2025-12-23 10:13:55 +01:00
NEZNAMY 22d6ebd0d4 [Performance] Small optimization to condition parsing 2025-12-23 09:21:51 +01:00
NEZNAMY e8dd46c701 [Commands] Modify console vs player check to account for other types of command sender (#1594) 2025-12-22 17:49:54 +01:00
NEZNAMY 1150d07dbe [Configuration] Update config version in default config 2025-12-22 15:52:34 +01:00
NEZNAMY 5c294f4ad3 [Header/Footer] Fix condition update not being processed in feature's thread 2025-12-22 15:51:34 +01:00
NEZNAMY 1b5bf0aab7 [Proxy support] Make channel name partially configurable 2025-12-21 14:13:39 +01:00
NEZNAMY 8d8a03602e [Scoreboard] Make red numbers fully configurable and remove config options related to it as they are redundant now (#1592) 2025-12-21 09:59:23 +01:00
NEZNAMY df7bdb7a0c [Velocity] Change bossbar bug compensation code as it wasn't actually fully fixed yet (#1593) 2025-12-20 10:03:58 +01:00
NEZNAMY 00a5eb2588 [Velocity] Remove header/footer packet modifying since it doesn't work anyway (they are never decoded when coming from backend) 2025-12-19 16:08:39 +01:00
NEZNAMY 90631c66e0 [Velocity] Use internals to inject into pipeline and listen to and modify packets. 2025-12-19 12:52:53 +01:00
NEZNAMY c1ba692a79 Bump version to 5.5.0-SNAPSHOT considering the massive upcoming changes and additions 2025-12-19 12:50:06 +01:00
NEZNAMY 8f3324e0d8 [Layout] Add new slot-count option to default config 2025-12-18 16:01:37 +01:00
NEZNAMY 5a09a9ce78 [Layout] Add support for layouts with less than 4 columns for 1.19.3+ (does not work on Velocity (yet?)) 2025-12-18 15:36:45 +01:00
NEZNAMY 33fcee7641 [Layout] Add support for new relational conditions and relational placeholders 2025-12-17 12:22:45 +01:00
NEZNAMY 12b91940c7 [Conditions] Add support for relational conditions and specifying who to parse placeholders for using %viewer:placeholder% and %target:placeholder% (optional) 2025-12-17 11:57:20 +01:00
NEZNAMY 0e6246f044 [Forge] Update to 1.21.11 2025-12-12 19:01:27 +01:00
NEZNAMY 7f7cf97408 [Tablist] Avoid sending tablist packets of entries which are not in the tablist (yet, on join), which caused a warning message in the client. 2025-12-12 15:46:39 +01:00
NEZNAMY 090fe9ccb6 [Tablist] Start improved tracking of tablist entries 2025-12-10 20:40:54 +01:00
NEZNAMY ffae161282 5.4.1-SNAPSHOT 2025-12-10 18:00:47 +01:00
NEZNAMY 1443850344 [Description] Update badges 2025-12-10 16:58:58 +01:00
NEZNAMY 63f9daf334 5.4.0 2025-12-10 16:45:20 +01:00
NEZNAMY b277d0973a Update to 1.21.11 (except Forge, which is broken) 2025-12-10 16:44:56 +01:00
NEZNAMY 33c0f1672a [Fabric] Update to 1.21.11-rc2 2025-12-05 20:15:39 +01:00
NEZNAMY 4224922ebb [Bukkit] Fix HEARTS render type not working on 1.8 - 1.12 2025-12-04 19:10:40 +01:00
NEZNAMY 6eb30d84b4 [Fabric] Update to 1.21.11-pre4 2025-12-01 18:27:07 +01:00
NEZNAMY 592a75b53f [Wiki] Monthly wiki backup for December 2025 2025-12-01 18:12:35 +01:00
NEZNAMY 8e08ea4e44 [Tablist] Fix gamemode being changed in packets when it shouldn't be (#1568) 2025-11-29 16:19:34 +01:00
NEZNAMY cff1e831d0 [Components] Fix packet decoding error due to too many nested levels caused by disabling MM compacting + resetting magic codes before every color 2025-11-29 09:34:17 +01:00
NEZNAMY 83b5a0f88a [NameTags] Fix nametag being forcibly hidden when player is invisible, making them invisible even in spectator gamemode 2025-11-26 18:25:09 +01:00
NEZNAMY 2ba535111e [Fabric] Update to 1.21.11-pre3 2025-11-26 18:10:48 +01:00
NEZNAMY 4bbf0bb0f1 [Configuration] Change default config to use true/false instead of yes/no to avoid confusion (#1581) 2025-11-24 16:33:51 +01:00
NEZNAMY 1e80070d2d [Fabric] Update to 1.21.11-pre2 2025-11-22 10:59:53 +01:00
NEZNAMY 0afe0f28e1 [Fabric] Update to 1.21.11-pre1 (atlas sprites broke, probably a client bug that will be fixed) 2025-11-20 18:28:50 +01:00
NEZNAMY fbc04b1183 [Commands] Finish custom toggle command registration on Bukkit and remove old command preprocess code (#1578) 2025-11-16 10:58:23 +01:00
NEZNAMY bdd2a139e1 Remove sponge support and bump version to 5.4.0-SNAPSHOT 2025-11-15 18:14:29 +01:00
NEZNAMY e956e59888 [Commands] Turn custom toggle commands into real registered commands for most platforms (bukkit and sponge are left) (#1578) 2025-11-15 17:35:21 +01:00
NEZNAMY 1debc54db6 Update loom to 1.13-SNAPSHOT 2025-11-15 12:36:34 +01:00
NEZNAMY 37786935c3 [NameTags] Fix random error 2025-11-15 10:38:52 +01:00
NEZNAMY 425748af12 [Placeholders] Change int to long to fix overflow (#1576) 2025-11-11 20:19:55 +01:00
NEZNAMY c0deaa40ed [Components] Fix last component style detection, causing team color to not be decided correctly sometimes (again) (#1575) 2025-11-10 17:35:10 +01:00
NEZNAMY 70be098f8a [Components] Fix last component style detection, causing team color to not be decided correctly sometimes 2025-11-06 19:23:01 +01:00
NEZNAMY 838ef437d8 [Wiki] Monthly wiki backup for November 2025 2025-11-02 09:05:52 +01:00
NEZNAMY 44d7473c71 [Placeholders] Change player count related placeholders to check for proxy players and remove overriding attempt (which did not even work with %online_<server>% placeholders 2025-10-31 18:57:56 +01:00
NEZNAMY b8a7dfe543 [Placeholders] Change internal error value to avoid issues when a placeholder actually returned that value 2025-10-31 18:55:21 +01:00
NEZNAMY 4c4bf79371 Merge pull request #1563 from JT122406/master
Gradle 9
2025-10-19 13:54:37 +02:00
NEZNAMY a4b4bd0146 [Velocity] Make bossbar bug compensation dynamic, as it was fixed in build 546 2025-10-19 12:56:51 +02:00
Joseph T. McQuigg 9553e310e4 Disable ShadowJar tasks on Modded platforms 2025-10-18 16:40:44 -04:00
Joseph T. McQuigg e00caddb1e Gradle 9 2025-10-18 11:08:41 -04:00
NEZNAMY 898887225d [Commands] Add offline UUID of player to debug command 2025-10-18 12:28:02 +02:00
NEZNAMY 8eef805418 [Configuration] Avoid console warning during header/footer config conversion 2025-10-13 19:16:39 +02:00
NEZNAMY 8509718d32 [Header/Footer] Remove anti-override logging due to false positives when pipeline lags and fast animations are used 2025-10-11 20:46:40 +02:00
NEZNAMY f2cb7c564f [Adventure hook] Fix object component conversion missing "return" statement 2025-10-10 17:10:29 +02:00
NEZNAMY 9335e5af65 [Velocity] Disable header/footer anti-override, because it does not work properly due to velocity internal logic 2025-10-10 17:01:15 +02:00
NEZNAMY 5a5eadac1f [Bukkit] Use correct method to get gamemode by id to fix error <1.17 and generally avoid risks on all versions 2025-10-09 20:16:37 +02:00
NEZNAMY bcd015b6eb [Header/Footer] Add anti-override to the feature 2025-10-09 20:02:45 +02:00
NEZNAMY 91c6b8f08e 5.3.3-SNAPSHOT 2025-10-09 19:29:26 +02:00
NEZNAMY c7251295a1 [Description] Update badges 2025-10-08 18:47:28 +02:00
NEZNAMY db2110b11e 5.3.2 2025-10-08 18:33:02 +02:00
NEZNAMY be1122ce50 Update to 1.21.10 2025-10-08 17:08:59 +02:00
NEZNAMY cf265987db [Bukkit] Remove override for %player_ping%, since people have probably updated their expansion by now. 2025-10-07 17:54:05 +02:00
NEZNAMY cd82e75514 [Teams] Improve anti-override message to contain source of blocked team and how to fix it. 2025-10-07 17:13:38 +02:00
NEZNAMY 74d36f1ab5 [MySQL] Fix error on upload command and message saying success despite operation not being successful 2025-10-06 20:38:05 +02:00
NEZNAMY 67da0ff32b [GitHub] Add an item to CONTRIBUTING.md 2025-10-06 18:42:03 +02:00
NEZNAMY aabad04e28 [MySQL] Delete data in database on upload first, instead of just overwriting / adding new content 2025-10-05 15:05:20 +02:00
NEZNAMY 9236da40dd [Spectator fix] Rewrite packets to avoid the same packet identity from being sent to all players, causing wrong result from modifying it for another player 2025-10-05 14:51:29 +02:00
NEZNAMY 88ff78b7e3 [Commands] Fix debug command not displaying server name correctly 2025-10-05 11:44:25 +02:00
NEZNAMY 21ebd68607 [API] Restore TabPlayer#getWorld and TabPlayer#getServer 2025-10-05 11:39:31 +02:00
NEZNAMY 9adad40aa9 [Pipeline injection] Change return type of onPacketSend to Object to allow changing the packet 2025-10-05 11:17:46 +02:00
NEZNAMY f63b3de63a [Bukkit] Add Header/Footer sending on 1.7.10 if the method is available (modded server) (#1557) 2025-10-04 19:03:21 +02:00
NEZNAMY 767933e522 [Bukkit] Add craftbukkit package version into debug command output 2025-10-04 18:49:20 +02:00
NEZNAMY 0fb86ddf6c 5.3.2-SNAPSHOT 2025-10-04 17:36:02 +02:00
NEZNAMY d52969597f [Description] Update badges 2025-10-04 17:31:03 +02:00
NEZNAMY 70e1537bc0 5.3.1 2025-10-04 16:49:23 +02:00
NEZNAMY 605a09b290 [Bukkit] Fix error on 1.8 - 1.19.2 2025-10-04 16:11:51 +02:00
NEZNAMY 8e2f8d67f3 Use a separate field for player's version network id for better results on unknown versions 2025-10-04 13:31:58 +02:00
NEZNAMY d00764e548 [Commands] Add more info to debug command 2025-10-04 13:21:46 +02:00
NEZNAMY 953472a41a [Velocity] Add flag for compensating for a bossbar bug that might be fixed soon (and was fixed in a fork) 2025-10-04 11:43:26 +02:00
NEZNAMY 6be5bb4757 [Conditions] Fix anonymous conditions not refreshing 2025-10-03 20:17:50 +02:00
NEZNAMY b48a9e4f22 [Layout] Add support for per-layout default-skin (#1555) 2025-10-03 20:06:26 +02:00
NEZNAMY 6d42e252a1 [Teams] Remove old forgotten code causing nametag visibility to not refresh on proxies (also refresh it properly on join) 2025-10-03 16:22:51 +02:00
NEZNAMY ef98b15c86 5.3.1-SNAPSHOT 2025-10-02 19:59:52 +02:00
NEZNAMY 6945e8ee81 [Description] Update badges 2025-10-02 18:57:13 +02:00
NEZNAMY 9c9edbff75 5.3.0 2025-10-02 18:41:25 +02:00
NEZNAMY b8e3a07980 [Configuration] Change warning message in config conversion 2025-10-02 18:40:05 +02:00
NEZNAMY 06279e3708 [Forge] Update to 1.21.9 2025-10-02 18:39:00 +02:00
NEZNAMY 9812345f71 [MiniMessage support] Add component conversion for object components 2025-10-01 12:45:10 +02:00
NEZNAMY e1f667079a [MiniMessage support] Fix error when object components were used 2025-10-01 11:40:53 +02:00
NEZNAMY 6edc3babc8 Update Bukkit, Paper, BungeeCord, Fabric and NeoForge to 1.21.9 2025-10-01 11:03:15 +02:00
NEZNAMY 309c67969a [Wiki] Monthly wiki backup for October 2025 2025-10-01 09:05:32 +02:00
NEZNAMY bd681229db [Conditions] Recode conditions, add new types and add disable-condition conversion for header/footer 2025-10-01 08:54:41 +02:00
NEZNAMY 4f0a4feb03 [Components] Micro optimization 2025-09-28 19:31:08 +02:00
NEZNAMY 2d84a60c84 [NameTags] Rename caches 2025-09-28 19:29:22 +02:00
NEZNAMY 0f66bc7264 [NameTags] Split cache into prefix and suffix caches 2025-09-28 16:17:57 +02:00
NEZNAMY 9283f969c4 [Header/Footer] Fix display-condition updating and change way the feature is resent on server switch (to also prepare for a future improvement) 2025-09-28 11:05:17 +02:00
NEZNAMY dbcad7b11d [Multi-server support] Fix error on reload 2025-09-27 14:07:24 +02:00
NEZNAMY 8e96fc1595 [Multi-server support] Add debug message about current channel name 2025-09-27 11:37:15 +02:00
NEZNAMY fa46427b1f [Multi-server support] Change channel name to avoid errors when using mixed tab versions 2025-09-27 11:28:29 +02:00
NEZNAMY 5a112bff22 [Header/Footer] Correctly resend the feature on server switch 2025-09-26 20:14:42 +02:00
NEZNAMY 1a8bc5ee34 [Configuration] Change config conversion warning message 2025-09-25 19:05:04 +02:00
NEZNAMY 1001bcd507 [Error Logging] Fix warnings no longer having color 2025-09-25 18:48:47 +02:00
NEZNAMY 2f6f5f4958 [ProxySupport] Add compensation for packets being received in the wrong order 2025-09-25 18:42:37 +02:00
NEZNAMY 863cceb175 [Fabric] Update to 1.21.9-rc1 2025-09-25 18:24:25 +02:00
NEZNAMY d07d604f7c [Configuration] Fix per-server settings using * symbol not working anymore 2025-09-24 18:52:31 +02:00
NEZNAMY 9035f8b57f [Backend] Add %deaths% placeholder (default refresh 1000) 2025-09-23 16:33:57 +02:00
NEZNAMY 43bc22b8be [Fabric] Update to 1.21.9-pre3 2025-09-22 18:02:29 +02:00
NEZNAMY 1d59eb5787 Merge pull request #1550 from GG-MD/master
Packet Handling Safety Enhancement - Null Packet Protection
2025-09-21 18:28:44 +02:00
MGG 5383b87c3f Packet Handling Safety Enhancement - Null Packet Protection 2025-09-21 19:00:47 +03:00
NEZNAMY 5c3815aee0 [BungeeCord / Velocity] Add try/catch to RedisBungee hook 2025-09-21 09:09:27 +02:00
NEZNAMY 69b9acdc83 [Scoreboard] Do not actually unregister scoreboard if another plugin sends its own, just stop slotting it 2025-09-21 09:04:57 +02:00
NEZNAMY 3b68fe9bdd [Layout] Adapt to mineskin no longer supporting skins by id 2025-09-20 18:46:02 +02:00
NEZNAMY 4d9dd1cf83 [GitHub] Add new issue template 2025-09-20 13:48:22 +02:00
NEZNAMY 0854fd674f [Fabric] Update to 1.21.9-pre2 2025-09-20 13:26:36 +02:00
NEZNAMY 75478b59ae [Bukkit] Take advantage of new Java features and remove unnecessary reflection 2025-09-20 13:11:58 +02:00
NEZNAMY d39f6fd061 [Bukkit] [Code style] Use TYPE instead of Object for components 2025-09-17 20:30:34 +02:00
NEZNAMY a532cd4fc1 [Code style] Rename some component classes to avoid conflict with platform classes 2025-09-17 18:39:28 +02:00
NEZNAMY a6afd9de0a [Fabric] Update to 1.21.9-pre1 (thanks Mojang) 2025-09-16 19:43:59 +02:00
NEZNAMY 9a8df8c890 [Configuration] Add missing config conversion of "*" symbol in header/footer 2025-09-15 19:24:32 +02:00
NEZNAMY cd435cc716 [Code style] Remove pointless world/server instance creations 2025-09-15 19:10:56 +02:00
NEZNAMY b0bdc36b59 [Tablist formatting] [Performance] Only try to format players on the same server (or global playerlist group) 2025-09-15 18:58:53 +02:00
NEZNAMY 1971513acc Fix issue from previous commit 2025-09-15 18:56:09 +02:00
NEZNAMY 99646f2113 [Global playerlist] Refactor server groups for future use in other features 2025-09-15 18:07:14 +02:00
NEZNAMY 78349176d6 [Global playerlist] Optimize shouldSee method to reduce canSee calls to other plugins 2025-09-15 13:28:40 +02:00
NEZNAMY 92115ed493 [Global playerlist] Store "spy-server" attribute in Server class instead of player data 2025-09-15 13:04:11 +02:00
NEZNAMY 4ef8508c65 [Code style] Extract project variables from TabConstants class and move it back to source folder 2025-09-15 12:37:25 +02:00
NEZNAMY 1feafa884e [Code style] Create a class for holding servers and worlds and clear it on reload 2025-09-15 12:22:44 +02:00
NEZNAMY 02bff23b1f [Code style] Separate nested class into a regular class 2025-09-15 11:37:16 +02:00
NEZNAMY d80742bf20 [Code style] Separate nested class into a regular class 2025-09-15 11:28:57 +02:00
NEZNAMY 7bde3d9996 [Code style] Separate nested class into a regular class 2025-09-15 11:19:55 +02:00
NEZNAMY 4d86beb268 [Code style] Separate nested class into a regular class 2025-09-15 11:10:16 +02:00
NEZNAMY b039cbb7b6 [Code style] Separate nested class into a regular class 2025-09-15 11:00:29 +02:00
NEZNAMY 7d8af7efc9 [Code style] Separate nested class into a regular class 2025-09-15 10:51:48 +02:00
NEZNAMY 7019044837 [Nametags] Upgrade invisible-nametags option to support conditions 2025-09-14 18:09:17 +02:00
NEZNAMY 5916ec5d07 [Javadoc] Fix typo 2025-09-14 11:50:51 +02:00
NEZNAMY 4508fcf4b3 [Javadoc] Add some missing docs 2025-09-14 11:01:44 +02:00
NEZNAMY 2de69557d2 [Code style] Remove whitespace 2025-09-14 10:51:37 +02:00
NEZNAMY b01e793a26 [Javadoc] Add some missing docs 2025-09-14 10:49:40 +02:00
NEZNAMY 1f67ab7bdb [Code style] Rename variable to avoid shadowing outer scope variable 2025-09-14 10:45:39 +02:00
NEZNAMY 43ae5add60 [Code style] Remove redundant suppression 2025-09-14 10:43:14 +02:00
NEZNAMY 971620818b [Placeholders] No longer show last value of a placeholder when identifier was returned, it caused more harm than good (most notably on proxy installation when not all PAPI expansions are downloaded and therefore placeholders "freezing") 2025-09-13 18:53:55 +02:00
NEZNAMY c997b99935 [Bukkit] Add missing shadowColor implementation on spigot 2025-09-13 18:27:41 +02:00
NEZNAMY ae83ce1f42 [Code style] Remove unnecessary check 2025-09-13 17:38:28 +02:00
NEZNAMY 2410717510 [Code style] Remove no longer needed variable 2025-09-13 17:06:59 +02:00
NEZNAMY 5270a156b3 [Code style] Remove unused classes 2025-09-13 17:03:00 +02:00
NEZNAMY 07d807a7bd [Header/Footer] Rework the feature to allow conditional designs 2025-09-13 15:56:14 +02:00
NEZNAMY 5fb80f5661 5.3.0-SNAPSHOT 2025-09-13 15:47:23 +02:00
NEZNAMY ce6bef7f19 [Fabric] Update to 25w37a 2025-09-10 19:04:56 +02:00
NEZNAMY facedfdea7 [Components] Add config option to disable MiniMessage support (also introduce new config converter) 2025-09-07 12:44:14 +02:00
NEZNAMY e8fa66dac0 [Configuration] Removed forced lowercasing of server names 2025-09-06 19:08:54 +02:00
NEZNAMY 997d824d12 [Code style] Remove redundant call 2025-09-06 14:25:28 +02:00
NEZNAMY 86baf27ca0 [Nametags] Fix empty line being sent to player when toggle message is empty 2025-09-06 14:21:54 +02:00
NEZNAMY 2b8b5303c8 [Components] Properly set team color to used magic codes if both color and magic codes are used 2025-09-06 11:10:09 +02:00
NEZNAMY 24bd58130b [Components] Fix team color affecting prefix/suffix 2025-09-06 09:53:51 +02:00
NEZNAMY 907bbdb52b [Fabric] Update to 25w36b 2025-09-03 15:58:20 +02:00
NEZNAMY 26c491ce22 [Nick compatibility] Add debug message when an API request is made to change expected profile name 2025-09-01 17:26:43 +02:00
NEZNAMY 73def64743 [Wiki] Monthly wiki backup for September 2025 2025-09-01 17:14:40 +02:00
NEZNAMY d021d213f6 [Components] Extend player sprite object component to support player names the intended way and add UUID support 2025-08-30 19:29:44 +02:00
NEZNAMY dfd5ccf525 [Placeholders] Add %uuid% placeholder showing player's UUID 2025-08-30 18:48:07 +02:00
NEZNAMY a4cfe9f81b [Configuration] Replace \n with newline sooner, resulting in them being replaced even inside placeholders like animations 2025-08-30 15:52:56 +02:00
NEZNAMY 8402088cba [MiniMessage hook] Properly handle uppercased color codes and do not throw an error if text contains the § symbol 2025-08-27 20:50:25 +02:00
NEZNAMY 2adcc0d45b [Components] Allow player heads using <head:DEFINITION> using same definition syntax as layout 2025-08-27 19:52:58 +02:00
NEZNAMY da25752b73 [Configuration] Always load skin cache 2025-08-27 19:36:30 +02:00
NEZNAMY ac348cf207 [Components] Add a comment 2025-08-27 18:44:41 +02:00
NEZNAMY 6afe8f0081 [Components] Add PlayerSprite support 2025-08-27 18:24:41 +02:00
NEZNAMY 86c294592f Delete component module and move content to shared 2025-08-27 17:38:45 +02:00
NEZNAMY 3deba4a0f3 [Fabric] Fix error on 25w35a 2025-08-27 16:55:09 +02:00
NEZNAMY cfb3d10a7d [Bukkit] Remove unused dependency 2025-08-26 20:16:29 +02:00
NEZNAMY 458fa4e416 Update Fabric to 25w35a and prepare for new object components 2025-08-26 20:08:24 +02:00
NEZNAMY 6e7d2338a4 [Tablist] Remove unused class 2025-08-24 16:23:17 +02:00
NEZNAMY d0bd1b4b0b [Tablist] Fix possible issue from previous commit not applying forced display name 2025-08-24 12:35:28 +02:00
NEZNAMY 2076b589b4 [Tablist] Avoid sending tablist update packets of entries which are not present (currently the new check only works on BungeeCord, Velocity already worked) 2025-08-24 11:47:59 +02:00
NEZNAMY a4b8c2b730 [Components] Change sprite syntax to <sprite:ATLAS:SPRITE> to match MiniMessage 2025-08-24 09:43:29 +02:00
NEZNAMY 0cc83325bc [Components] Replace spaces with underscores in atlas and sprite 2025-08-23 19:10:31 +02:00
NEZNAMY f2fa63315e [Components] Remove toRawText method and replace its usage with the intended method 2025-08-23 18:58:43 +02:00
NEZNAMY 9631abf430 [Components] Force lowercase atlas and sprite 2025-08-23 18:35:51 +02:00
NEZNAMY 084123132a [Bukkit] Remove fallback using Bukkit API on unsupported versions (this also drops 1.5 - 1.6 support) 2025-08-23 18:18:55 +02:00
NEZNAMY 4e9d2df0b2 [Bukkit] Fix 1.9.4 and 1.10.x 2025-08-23 17:45:35 +02:00
NEZNAMY e3a3a65987 Revert unintended change 2025-08-23 16:39:03 +02:00
NEZNAMY 17feac0501 [Ping Spoof] Rework the feature for a faster check 2025-08-23 16:37:58 +02:00
NEZNAMY 84e280a562 [Bukkit] Include 1.7 in supported versions list 2025-08-23 10:40:02 +02:00
NEZNAMY 906e304018 [Bukkit] Add NMS Implementations for 1.7.x 2025-08-23 10:39:35 +02:00
NEZNAMY 48cdeb7283 [Bukkit] Add NMS Implementations for 1.8.x and 1.9.x 2025-08-23 09:43:24 +02:00
NEZNAMY 4efb55f685 [Code style] Add NMS Implementations for 1.10.x and 1.11.x 2025-08-21 18:08:32 +02:00
NEZNAMY 3ed6f8158d [Code style] Remove unused methods 2025-08-21 17:39:11 +02:00
NEZNAMY c89a38b0da [Bukkit] Add NMS Implementations for 1.13.x and 1.14.x 2025-08-20 18:55:14 +02:00
NEZNAMY eb40cf873f [Bukkit] Check for ObjectComponent in another method 2025-08-20 16:53:37 +02:00
NEZNAMY 892c626312 [Components] Add atlas-sprite support to text parsing 2025-08-20 16:28:47 +02:00
NEZNAMY 7e0d447e75 [Fabric] Update to 25w34b 2025-08-20 15:56:21 +02:00
NEZNAMY 49b2723c19 [Components] Introducing ObjectComponent for future 1.21.9 content 2025-08-20 14:54:55 +02:00
NEZNAMY 5140d912e1 [Bukkit] Add NMS Implementation for 1.15.x 2025-08-20 13:51:03 +02:00
NEZNAMY 0fa1165652 [Bukkit] Add NMS Implementation for 1.16 - 1.16.3 2025-08-20 13:42:57 +02:00
NEZNAMY 375d2297e7 [Bukkit] Remove unused code 2025-08-20 13:23:50 +02:00
NEZNAMY fbd089d87a [Bukkit] Add NMS Implementation for 1.18 - 1.18.1 2025-08-20 13:16:23 +02:00
NEZNAMY 5076c80e2c [Bukkit] Remove reflection entirely now that adapters cover everything reflection did 2025-08-20 13:13:50 +02:00
NEZNAMY 4fcbec7a4a [Spectator fix] Fix memory leak (introduced in fix for #1537) 2025-08-20 12:32:36 +02:00
NEZNAMY cb82d3ad13 [Bukkit] Add NMS Implementation for 1.21.6 - 1.21.8 2025-08-20 12:14:24 +02:00
NEZNAMY a6172c705d [Bukkit] Add NMS Implementation for 1.21.5 2025-08-20 11:44:54 +02:00
NEZNAMY 3c2c480c11 [Bukkit] Add NMS Implementation for 1.21.4 2025-08-20 11:23:00 +02:00
NEZNAMY da1a839014 [Bukkit] Add NMS Implementation for 1.21.2 - 1.21.3 2025-08-20 11:04:24 +02:00
NEZNAMY bf6628c6a2 [Bukkit] Add NMS Implementation for 1.21 - 1.21.1 2025-08-20 10:43:59 +02:00
NEZNAMY 42674bbfdd [Bukkit] Clean up implementation detection 2025-08-20 10:20:48 +02:00
NEZNAMY 225164b7b6 [Bukkit] Add NMS Implementation for 1.20.5 - 1.20.6 2025-08-20 10:16:32 +02:00
NEZNAMY 35ce37acf8 [Bukkit] Add NMS Implementation for 1.20.3 - 1.20.4 2025-08-20 09:48:42 +02:00
NEZNAMY 2df5b00e07 [Bukkit] Add NMS Implementation for 1.20.2 2025-08-20 09:35:40 +02:00
NEZNAMY 3fa25c0662 [Bukkit] Add NMS Implementation for 1.20 - 1.20.1 2025-08-20 09:00:06 +02:00
NEZNAMY b7e233834e [Bukkit] Add NMS Implementation for 1.19.4 2025-08-19 20:35:52 +02:00
NEZNAMY ff80a4807f [Bukkit] Add NMS Implementation for 1.19.3 2025-08-19 20:16:28 +02:00
NEZNAMY c56592c19a [Bukkit] Add constants for enums for better readability 2025-08-19 19:42:55 +02:00
NEZNAMY e93df8e38d [Fabric] Add Fabric API version for 25w33a 2025-08-19 18:57:50 +02:00
NEZNAMY 15664bc9fb Update loom to 1.11 2025-08-19 18:57:24 +02:00
NEZNAMY 9cedbabd75 [Nametags] Fix memory leak 2025-08-19 17:58:33 +02:00
NEZNAMY a98ecee8f3 [Spectator Fix] Force gamemode always, even if not currently in spectator (#1537) 2025-08-19 17:19:52 +02:00
NEZNAMY f64d0d7b41 Merge pull request #1539 from tooobiiii/master
Fix: Compilation errors showing generated src instead of actual src -> introducing blossom 2.1.0
2025-08-19 16:50:30 +02:00
TobisuchtetXD 7a1f1ed484 introducing new blossom version 2.1.0 fixes weird compilation behaviour 2025-08-19 12:32:43 +02:00
NEZNAMY 85ade0dd15 [Spectator Fix] Use packet / periodic detection to make the feature more reliable instead of delays (#1537) 2025-08-18 18:06:05 +02:00
NEZNAMY 9c70f5a187 [Code style] Rename "expectedDisplayName" to "forcedDisplayName" 2025-08-17 18:23:11 +02:00
NEZNAMY f622d0c8b3 [Scoreboard / Bossbar] Fix announce command syntax in docs 2025-08-07 17:09:29 +02:00
NEZNAMY a3c4362a90 [MiniMessage Support] Reset magic codes before every color code to match vanilla behavior 2025-08-03 19:20:54 +02:00
NEZNAMY 05609ca436 [Code style] Remove case insensitivity from spy-servers option for safety due to rewrite 2025-08-03 17:10:05 +02:00
NEZNAMY fde7fefe9f [Performance] Use Server object for storing player servers instead of Strings 2025-08-03 15:23:37 +02:00
NEZNAMY 19f45aeb4d [Code style] Use World object for storing player worlds instead of Strings 2025-08-03 14:43:34 +02:00
NEZNAMY 540eafc41b [Wiki] Monthly wiki backup for August 2025 2025-08-02 13:35:43 +02:00
NEZNAMY 1ba62e0ae9 [Descriptions] Update badges 2025-07-24 17:20:37 +02:00
NEZNAMY 07b213d0af [Playerlist Objective] [Configuration] Fix warning when setting value to 0 (without quotes, resulting in it being an Integer) 2025-07-24 17:18:34 +02:00
NEZNAMY 0528baaa64 [Belowname] [Configuration] Fix warning when setting value to 0 (without quotes, resulting in it being an Integer) 2025-07-22 18:42:29 +02:00
NEZNAMY 264b8e69d8 [Configuration] Add missing config conversion to add register-tab-expansion option (#1524) 2025-07-22 17:31:23 +02:00
NEZNAMY b4c6da418d [Configuration] Fix misinformation and missing link in default config.yml 2025-07-20 15:09:00 +02:00
NEZNAMY 3811f942a1 5.2.6-SNAPSHOT 2025-07-20 15:05:17 +02:00
NEZNAMY 6d295960d5 5.2.5 2025-07-19 17:53:04 +02:00
NEZNAMY 983d6a055b [Sponge] Fix 1.21.5+ error caused by a Sponge bug 2025-07-19 11:48:43 +02:00
NEZNAMY 9de3fc4bbb Update to 1.21.8 2025-07-19 11:31:29 +02:00
NEZNAMY f4c986f0de [Sponge] Set listed tablist entry value 2025-07-19 11:21:27 +02:00
NEZNAMY 434f629c0b [BungeeCord] Update to build 1990 and up 2025-07-15 19:10:35 +02:00
NEZNAMY dac32b6daf [bStats] Fix "1.99.x" version being sent for unknown server versions 2025-07-06 19:44:59 +02:00
NEZNAMY dd845013ce [Layout] [Performance] replace removeAll with removeIf for massively improved performance 2025-07-03 18:02:18 +02:00
NEZNAMY b9bad50dbd 5.2.5-SNAPSHOT 2025-07-03 18:00:59 +02:00
NEZNAMY 56540df419 [Wiki] Monthly wiki backup for July 2025 2025-07-02 17:44:18 +02:00
NEZNAMY d0b0c55ab8 [Description] Update badges 2025-07-01 20:49:11 +02:00
NEZNAMY 6f2b92e044 5.2.4 2025-07-01 20:38:47 +02:00
NEZNAMY 5da16e8055 Update to 1.21.7 2025-07-01 20:33:36 +02:00
NEZNAMY a522ea9902 [Proxy Support] Add try/catch for message decoding 2025-07-01 20:32:55 +02:00
NEZNAMY 36801184a5 5.2.4-SNAPSHOT 2025-06-30 16:39:37 +02:00
NEZNAMY d212eb108d [Bukkit] Remove unused empty lists from plugin.yml to avoid Polymart bug breaking them 2025-06-26 18:32:51 +02:00
NEZNAMY cbc1de0f33 [Description] Update badges 2025-06-23 18:49:25 +02:00
NEZNAMY fcdc04e228 5.2.3 2025-06-23 18:36:49 +02:00
NEZNAMY 890f1ee131 [Velocity] Improve message telling users to install VelocityScoreboardAPI 2025-06-23 18:36:37 +02:00
NEZNAMY f3aec47ac3 [BungeeCord] Send header/footer using API since it no longer throws an error 2025-06-22 10:48:31 +02:00
NEZNAMY bcf1c828b5 [BungeeCord] Update to build 1980 (older versions are no longer supported) 2025-06-22 10:40:34 +02:00
NEZNAMY 5fd319a152 [BossBar] Fix %tab_bossbar_visible% not loading on join when player has bossbar disabled and toggling is saved 2025-06-22 10:04:38 +02:00
NEZNAMY abecb58a8d [API] Add methods for removing bossbars and scoreboards (#1507) 2025-06-21 20:35:22 +02:00
NEZNAMY 135166b01e [Bukkit] Remove ViaVersion hook for sending packets due to many unfixable problems 2025-06-21 18:13:46 +02:00
NEZNAMY 0770293452 [Placeholders] Fix placeholder updating when placeholder returned its identifier not being skipped properly (#1502) 2025-06-21 13:57:16 +02:00
NEZNAMY f3c40ad582 [Bukkit] Disable ViaVersion hook for sending raw packets to newer players due to issues 2025-06-21 13:09:44 +02:00
NEZNAMY aacc115a36 [API] Add functions for getting original prefix/suffix values with placeholders parsed 2025-06-21 12:51:03 +02:00
NEZNAMY b7ba07b78d [Bukkit] Remove redundant SneakyThrows annotations 2025-06-21 10:45:27 +02:00
NEZNAMY e13445f55d [Bukkit] Fix skin writing in ViaVersion hook 2025-06-21 10:41:11 +02:00
NEZNAMY bd527d3868 [Configuration] Fix false positive in configuration checking (#1506) 2025-06-20 18:34:24 +02:00
NEZNAMY f288112eb2 [Bukkit] Fix error in ViaVersion hook 2025-06-20 16:18:03 +02:00
NEZNAMY 6b415e64a8 5.2.3-SNAPSHOT 2025-06-19 17:29:10 +02:00
NEZNAMY cfb2cb6b0a [Description] Update badges 2025-06-18 19:20:23 +02:00
NEZNAMY 622deb6fd7 5.2.2 2025-06-18 18:48:14 +02:00
NEZNAMY 7f41421d65 [Bukkit] Add 1.21.6 to supported versions 2025-06-18 17:34:51 +02:00
NEZNAMY 73e706465a Add 1.21.6 protocol constant 2025-06-18 17:31:54 +02:00
NEZNAMY a8c543ca9e [Paper] Update paperweight to 2.0.0-beta.17 2025-06-18 17:23:38 +02:00
NEZNAMY 9594ff452b Update Gradle to 8.14.2 2025-06-18 17:18:09 +02:00
NEZNAMY d5c81298dd [Modded platforms] Update to 1.21.6 2025-06-18 16:50:36 +02:00
NEZNAMY 88faacb240 [Proxy support] Properly handle out of order redis messages (#1465) 2025-06-17 21:00:49 +02:00
NEZNAMY 0d2a04b3a5 [Proxy support] Make method synchronized to try to avoid concurrent access 2025-06-17 18:06:38 +02:00
NEZNAMY f8a90b28b5 [Code style] Create static methods for initializing components 2025-06-16 19:57:58 +02:00
NEZNAMY a948febb18 [Belowname & Playerlist objective] Suggest a PlaceholderAPI expansion might be missing when a placeholder did not parse 2025-06-16 18:44:58 +02:00
NEZNAMY 8e33481b97 [Proxy support] Massive fixes to handling players who switch servers and temporarily cause the player to be loaded twice (#1465) 2025-06-15 19:03:50 +02:00
NEZNAMY f54be214f9 [Vanish compatibility] Assume a player can always see themselves 2025-06-15 15:03:14 +02:00
NEZNAMY d50b47acb5 [Configuration] Remove case sensitivity when checking for valid properties / configuration option names 2025-06-14 19:35:55 +02:00
NEZNAMY 31011c243a [Fabric] Update to 1.21.6-rc1 2025-06-12 18:52:20 +02:00
NEZNAMY f4aa03f20d [Conditions] Fix issue from recent commits 2025-06-12 18:51:56 +02:00
NEZNAMY 28aacaea45 [Fabric] Update to 1.21.6-pre4 2025-06-10 19:52:30 +02:00
NEZNAMY d4fd9a2b78 [Sorting] Fix PLACEHOLDER type not loading properly when ":" symbol is used in values (#1504) 2025-06-09 18:41:06 +02:00
NEZNAMY 663e0be357 [Tablist] Do not send update packets of vanished players, exposing them to players 2025-06-07 18:43:17 +02:00
NEZNAMY 14d29577c5 [Conditions] Fix condition compiling when delimiter is a part of used placeholder (#1503) 2025-06-07 15:22:48 +02:00
NEZNAMY 951f8072c1 [Placeholders] Add %luckperms-prefixes% and %luckperms-suffixes% placeholders listing all values instead of just the ones with the highest weight 2025-06-07 14:54:13 +02:00
NEZNAMY 05f042eda0 [Conditions] Improve condition type detection (#1503) 2025-06-07 11:37:32 +02:00
NEZNAMY b00c2fbcb4 [Fabric] Update to 1.21.6-pre3 2025-06-07 10:29:37 +02:00
NEZNAMY dde4866e50 [Belowname] Improve warning message 2025-06-05 19:17:42 +02:00
NEZNAMY 9fa6b21c08 [Code style] Extract code into a method 2025-06-03 19:51:04 +02:00
NEZNAMY e95e6fc20c Remove anti-override from config and make it permanently enabled 2025-06-03 17:14:18 +02:00
NEZNAMY c7a3b00c3c Merge pull request #1499 from R00tB33rMan/master
Remove printline when testing TPS & MSPT getter
2025-06-03 07:11:07 +02:00
R00tB33rMan 74bc0e0cb7 Remove printline when testing TPS & MSPT getter 2025-06-02 23:30:02 -04:00
NEZNAMY 6248008b4d [Fabric] Update to 1.21.6-pre2 2025-06-02 18:32:42 +02:00
NEZNAMY 2d91e165ce [Wiki] Monthly wiki backup for June 2025 2025-06-01 18:00:37 +02:00
NEZNAMY b1220550eb [Multi-server support] Code improvements for future issue debugging 2025-06-01 16:52:12 +02:00
NEZNAMY 680ff3412f [Bukkit] Fix wrong supported version range of a module 2025-06-01 16:09:45 +02:00
NEZNAMY 799f6b8215 [Folia] Implement %tps% and %mspt% (#1498) 2025-05-31 16:52:18 +02:00
NEZNAMY 1be224b7de [Fabric] Update to 1.21.6-pre1 2025-05-29 17:56:03 +02:00
NEZNAMY a32adfbfc7 [Bukkit] Add implementations for all paper versions with mojang mappings 2025-05-27 19:48:01 +02:00
NEZNAMY dda415351a Revert "[Fabric] Downgrade to 1.21.5 for release"
This reverts commit 6873c59505.
2025-05-23 17:08:57 +02:00
NEZNAMY 685e4b9faa 5.2.2-SNAPSHOT 2025-05-23 17:08:48 +02:00
NEZNAMY 1f842563b3 [Bukkit] Hotfix error 2025-05-22 21:45:09 +02:00
NEZNAMY f237b61065 [Description] Update badge to 5.2.1 2025-05-22 17:56:44 +02:00
NEZNAMY 19d6d06791 5.2.1 2025-05-22 17:38:48 +02:00
NEZNAMY 6873c59505 [Fabric] Downgrade to 1.21.5 for release 2025-05-22 17:35:43 +02:00
NEZNAMY e7ae62db7b [Playerlist objective] Change default title 2025-05-21 15:51:52 +02:00
NEZNAMY d4362f2ed2 [Bukkit] Only get methods declared by the class, not superclasses (#1492) 2025-05-20 17:21:49 +02:00
NEZNAMY 9247f3d4fb [Fabric] Update to 25w21a 2025-05-20 16:37:09 +02:00
NEZNAMY b4993b657e [Paper] Downgrade to 1.21.4 to fix compilation (code is the same) 2025-05-20 16:32:40 +02:00
NEZNAMY b73aa93d01 [Commands] Ignore cancelled command events 2025-05-20 16:28:50 +02:00
NEZNAMY bce689e672 [Modded platforms] Use reflection instead of access transforming which may fail 2025-05-15 20:10:32 +02:00
NEZNAMY f4468261cf [Fabric] Update to 25w20a 2025-05-13 16:50:24 +02:00
NEZNAMY cc926ff974 [Modded platforms] Decrease required Java version to 17 (where possible) 2025-05-07 17:39:33 +02:00
NEZNAMY 6c3831ded4 [Fabric] Update to 25w19a 2025-05-07 17:30:29 +02:00
NEZNAMY 3c55866d46 [Code style] Move 1.8+ check to avoid duplicating it everywhere 2025-05-01 18:26:56 +02:00
NEZNAMY dfc9359f04 [Plugin messages] Do not write protocol version anymore, since it is not used by bridge anymore 2025-05-01 18:18:09 +02:00
NEZNAMY ea0897db31 [Bukkit] Remove redundant client version usage 2025-05-01 18:12:53 +02:00
NEZNAMY 25cf6c95e8 [Configuration] Print exception class when a file fails to save 2025-05-01 14:49:33 +02:00
NEZNAMY f5e2910e0b [Bukkit] Remove forgotten field from previous commit 2025-05-01 14:36:50 +02:00
NEZNAMY fda35986a4 [Bukkit] Use getters instead of reflection where possible and fix wrong field on 1.16.5 2025-05-01 14:35:59 +02:00
NEZNAMY b97e86c2ed [Wiki] Monthly wiki backup for May 2025 2025-05-01 13:43:03 +02:00
NEZNAMY ebb02717b5 [Scoreboard internals] Fix duplicated team / objective register actually overwriting existing value, causing NPE later (#1481) 2025-05-01 13:16:22 +02:00
NEZNAMY 1b82ebfd25 [Paper] Update version to fix compilation error 2025-05-01 13:10:59 +02:00
NEZNAMY 3fb788ef6d [Fabric] Update to 25w18a 2025-04-29 18:53:55 +02:00
NEZNAMY 11bc06182c [Scoreboard] Use available getter instead of reflection / access transforming (which may not work) 2025-04-27 13:57:22 +02:00
NEZNAMY 2fe5dc833d [Proxy support] Add more debug messages (#1465) 2025-04-27 10:02:56 +02:00
NEZNAMY 317cb975ce [Threading] Avoid throwing error 2025-04-27 09:31:21 +02:00
NEZNAMY 9bb2a97e50 [Threading] Increase thread shutdown timeout from 500ms to 2000ms 2025-04-27 09:28:36 +02:00
NEZNAMY 53dcc60587 [Code style] Use java 21 switches in modded platforms 2025-04-26 17:51:57 +02:00
NEZNAMY db347bc192 [Modded platforms] Create component style with setters to avoid using access transforming 2025-04-26 13:13:38 +02:00
NEZNAMY 608b8307dd [Velocity] Fix error in compatibility check 2025-04-26 12:56:58 +02:00
NEZNAMY 4c027da093 [Modded platforms] Use reflection to access tablist entries field instead of direct access (since access transforming sometimes doesn't work) 2025-04-26 12:52:10 +02:00
NEZNAMY bef60e688c [Bukkit] Fix prefix used in suffix field 2025-04-24 16:56:34 +02:00
NEZNAMY b987ad99f4 [Fabric] Update to 25w17a 2025-04-22 20:56:17 +02:00
NEZNAMY 3036e2d523 [Bukkit] Fix anti-override not working when using a ViaVersion implementation instead of server (#1478) 2025-04-19 11:33:34 +02:00
NEZNAMY 1bab2d0025 [Fabric] Update to 25w16a 2025-04-15 16:56:27 +02:00
NEZNAMY 4cac6c659e [Bukkit] Add v1_19_R1 submodule and remove reflection support for <1.19.3 2025-04-13 20:08:45 +02:00
NEZNAMY c7c59238e1 [Bukkit] Refactor feature implementation loading 2025-04-13 18:16:05 +02:00
NEZNAMY f51e74c066 [Bukkit] Update supported version list 2025-04-13 11:37:08 +02:00
NEZNAMY 52f8801638 [Bukkit] Add 1.17.1 and 1.18.2 submodules and delete the respective NMS reflection 2025-04-13 11:20:18 +02:00
NEZNAMY 4e5c5a7073 [Bukkit] [Code style] Change bossbar presence check 2025-04-13 09:03:49 +02:00
NEZNAMY 436f3879c4 [Bukkit] Remove redundant check 2025-04-13 08:36:56 +02:00
NEZNAMY c1395e26d4 [Fabric] Update to 25w15a 2025-04-09 17:32:03 +02:00
NEZNAMY 3750d6ddb1 [Commands] Fix error when running a property command 2025-04-07 19:02:02 +02:00
NEZNAMY 45c3601b49 [Conditions] Add "!permission:<value>" condition type 2025-04-07 18:29:24 +02:00
NEZNAMY 957da1d040 [Code style] Convert some NotNull annotations to NonNull to throw errors sooner 2025-04-07 18:25:46 +02:00
NEZNAMY 43932eb6b3 [Compilation] More changes to make backporting of mod platforms easier 2025-04-06 19:07:54 +02:00
NEZNAMY 024ddeefb4 [Compilation] Create maps for mod platform versions for easier backporting 2025-04-06 12:30:37 +02:00
NEZNAMY 94190b5cd7 [Velocity] Use new setShowHat tablist feature (and bump minimum required build to 485) 2025-04-05 10:15:55 +02:00
NEZNAMY 5d72f90a0f [Code style] Remove redundant code 2025-04-04 17:36:37 +02:00
NEZNAMY 90c30023c2 [Code style] Simplify 2025-04-02 20:27:59 +02:00
NEZNAMY d39a89659b [Bukkit] Remove reflection NMS support for 1.16.x and use direct 1.16.5 NMS hook 2025-04-02 20:23:36 +02:00
NEZNAMY 79f4386b62 [Bukkit] Remove reflection NMS support for versions below 1.16 (1.8.8 and 1.12.2 are supported directly) 2025-04-02 18:47:09 +02:00
NEZNAMY 39c4d8ae96 [Bukkit] Only send compatibility error messages relevant to server version 2025-04-02 18:12:33 +02:00
NEZNAMY c8b2abbb3e Merge pull request #1470 from JT122406/master
add curseforge link
2025-04-02 18:10:57 +02:00
NEZNAMY 06b7bbd711 [Bukkit] Fix startup error on 1.7.10 2025-04-02 18:05:20 +02:00
NEZNAMY 11afd15610 [Placeholder output replacements] Remove color replacing in output (may not be wanted if used in tab expansion) 2025-04-02 18:04:30 +02:00
Joseph T. McQuigg 4951025e61 add curseforge link 2025-04-02 09:54:48 -04:00
NEZNAMY 2337a122da [Wiki] Monthly wiki backup for April 2025 2025-04-01 17:23:23 +02:00
NEZNAMY 072333bf48 [Description] Use .svg instead of .png 2025-04-01 16:01:38 +02:00
NEZNAMY 2bd8924df7 [Description] Save badges for easier modification in only one place (will be linked to in all descriptions) 2025-04-01 15:58:28 +02:00
NEZNAMY 23cdc3721d [Global playerlist] Fix offline players being added into tablist when disconnecting immediately after switching server 2025-03-31 20:17:35 +02:00
NEZNAMY 957e4ba0c0 5.2.1-SNAPSHOT 2025-03-31 20:16:28 +02:00
NEZNAMY 71a19c63d8 5.2.0 2025-03-31 17:45:05 +02:00
NEZNAMY 657327e7ee [Bukkit] Move viaversion presence check to fix class loader error when it's not present 2025-03-31 17:44:50 +02:00
NEZNAMY efeea7cf0f Merge pull request #1460 from JT122406/master
Forge and NeoForge Support
2025-03-31 17:20:48 +02:00
Joseph T. McQuigg 7a4637368d Hard Versions 2025-03-31 11:18:36 -04:00
NEZNAMY 2e6ccd078b 5.2.0-SNAPSHOT 2025-03-31 17:09:19 +02:00
NEZNAMY 827197f916 5.1.1 2025-03-31 16:52:47 +02:00
Joseph T. McQuigg c8454ec0c1 Add MC versions to mods.toml 2025-03-31 10:52:34 -04:00
Joseph T. McQuigg 7127a77d92 Remove Versions from mods.toml, Fix register 2025-03-31 10:30:31 -04:00
Joseph T. McQuigg b23912b52f Forge/NeoForge Luckperms Hook 2025-03-30 18:57:32 -04:00
Joseph T. McQuigg 4b21148a03 Adjust Scoreboard classes for setDisplaySlot 2025-03-30 15:06:37 -04:00
Joseph T. McQuigg 2d5ef95e54 Update Forge to 1.21.5 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg b5e2d376dd Update NeoForge to 1.21.5 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg c40b337edc Fix Credits 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg f8583642a8 Correct License 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg 707f28df16 Permission check 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg dd78da089d Use Records for platform 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg c4ea65ed70 Forge Port 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg aabba806c4 Drop Parchment 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 88167fc42d Default to EmptyTabExpansion 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg e4d7005e46 moddedPlatforms List 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 5ee27f7ed7 Add Credits 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 9a4492c401 Add Parchment to Fabric, Use LogUtils instead of AW 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 39a2aee5a0 NeoForge Port 2025-03-30 14:51:58 -04:00
Joseph T. McQuigg 5184ef170f Update to new com.gradleup.shadow plugin
Gradle 8.13, Fabric Loom 1.10
2025-03-30 14:51:56 -04:00
NEZNAMY 29a35cf1cc [ViaVersion hook] Decrease check interval from 100 to 50 2025-03-30 18:51:39 +02:00
NEZNAMY f26516ecae [Code style] Separate objective register and set display slot functions 2025-03-30 18:47:39 +02:00
NEZNAMY f26a7ad2b1 [Bukkit] Add direct NMS hook for v1_8_R3 and v1_12_R1 2025-03-30 17:46:23 +02:00
NEZNAMY 96e0a29573 Merge pull request #1464 from MineLAT/fix/viaversion-delay
Fix ViaVersion packets delay
2025-03-29 17:12:29 +01:00
NEZNAMY f30d667ca8 [Code style] Add return value annotations and remove unused methods in ConfigurationFile 2025-03-29 17:05:53 +01:00
Rubén 8db7a1d6d9 Fix ViaVersion packets delay 2025-03-29 10:04:46 -06:00
NEZNAMY d285a0512e [Multi-server support] Provide some feedback about connection status (success/fail) (doesn't work for redis) 2025-03-29 16:52:13 +01:00
NEZNAMY 256d0d5c84 Revert "[Placeholders] Parse nested PAPI placeholders using {}"
This reverts commit 6501480980.
2025-03-29 09:57:02 +01:00
NEZNAMY b808e1f9d1 [Bukkit] Refactor scoreboard / tablist implementation finding for players 2025-03-29 09:55:52 +01:00
NEZNAMY 07cd781ec1 [Sponge] Drop Sponge 7 module 2025-03-28 15:43:37 +01:00
NEZNAMY 9115b687f4 [Sponge] Drop support for sponge versions 1.16.5 - 1.20.5 2025-03-27 18:51:28 +01:00
NEZNAMY 7c51377203 [Bukkit] Update paper submodule for 1.21.5 and allow it there 2025-03-27 18:24:02 +01:00
NEZNAMY 193cd02072 [Configuration] Fix startup error when some config options were missing 2025-03-27 16:49:01 +01:00
NEZNAMY dbb6d91f0c [Configuration] Add missing playerlist-objective.render-type option to default config 2025-03-26 19:48:20 +01:00
NEZNAMY a82714e811 [BungeeCord] Fix error (#1462) 2025-03-26 19:00:53 +01:00
NEZNAMY bcd1bbac62 5.1.1-SNAPSHOT 2025-03-26 18:53:58 +01:00
NEZNAMY 2fde0e68ca 5.1.0 2025-03-26 17:53:56 +01:00
NEZNAMY e6e9b1c5ae Add 1.21.5 protocol constant 2025-03-25 18:46:53 +01:00
NEZNAMY 48b789c51d [Fabric] Update to 1.21.5 2025-03-25 18:36:41 +01:00
NEZNAMY 433b39209d [Fabric] Remove server version detection as it is no longer used 2025-03-24 18:26:56 +01:00
NEZNAMY b6df7a9f33 [Code style] Improve parity between Fabric and Paper Scoreboard implementations 2025-03-24 17:16:54 +01:00
NEZNAMY ad1d55537f [Code style] Improve parity between Fabric and Paper TabList implementations 2025-03-24 17:02:35 +01:00
NEZNAMY 9982d2c6ef [Fabric] Update to 1.21.5-rc2 2025-03-24 16:32:17 +01:00
NEZNAMY 1e3f622a98 Remove server version checks for new content as everything is now supported thanks to new ViaVersion hook 2025-03-24 16:26:31 +01:00
NEZNAMY ce05d5f6a6 Merge pull request #1443 from MineLAT/feat/viaversion-packets
Add ViaVersion packets support
2025-03-24 16:15:50 +01:00
NEZNAMY 19378e2095 [RGB] Fix gradient pattern for <#RRGGBB></#RRGGBB> to not capture a newer color used inside of it (caused by using one of MiniMessage's RGB syntaxes) 2025-03-23 18:47:57 +01:00
Rubén 0d992f1e50 Execute delayed tasks at once 2025-03-23 10:39:10 -06:00
Rubén de7cabfd54 Add missing method call 2025-03-23 09:31:10 -06:00
Rubén cfe73a00ee Fix early packet send on viaversion decoder 2025-03-23 09:06:11 -06:00
Rubén 35a7acbbd9 Merge remote-tracking branch 'origin/master' into feat/viaversion-packets
# Conflicts:
#	bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/BukkitTabPlayer.java
#	bukkit/src/main/java/me/neznamy/tab/platforms/bukkit/platform/BukkitPlatform.java
2025-03-23 08:53:00 -06:00
NEZNAMY 20eebca395 [Belowname & Playerlist objective] Add startup warn if configured "value" will never evaluate to a number 2025-03-23 15:47:32 +01:00
NEZNAMY 7c2a4deb1a [Code style] Move getSkin() method to TabList 2025-03-23 13:40:27 +01:00
NEZNAMY e227e6a0f4 [Bukkit] Add back Paper submodule, but only for the latest MC version instead of an actual supported range 2025-03-23 13:09:30 +01:00
NEZNAMY e015c29a3d [Code style] Move legacy color rgb codes to TextColor class 2025-03-23 11:26:13 +01:00
NEZNAMY a2859937e2 [Bukkit] [Performance] Avoid using reflection for getting online players on 1.8+ 2025-03-22 19:53:59 +01:00
NEZNAMY cd26252444 [Scoreboard] Fix error when more than 21 lines are defined (will now error at 100) 2025-03-22 10:42:03 +01:00
NEZNAMY 47894a6e74 [Performance] Tiny performance boost of %luckperms-prefix% and suffix placeholders 2025-03-20 18:37:37 +01:00
NEZNAMY bcf94180d5 [Fabric] Update Fabric to 1.21.5-rc1 2025-03-20 18:33:00 +01:00
NEZNAMY 6d2eeda28c Merge pull request #1457 from Radu-Voinea/master2
Added %luckperms-weight% as a built-in placeholder
2025-03-20 12:09:57 +01:00
Radu Voinea 049c768013 Moved player lookup from LP to TabPlayer 2025-03-20 13:05:53 +02:00
Radu Voinea ffae488d1e Removed auto-formatted text 2025-03-20 12:54:36 +02:00
NEZNAMY 3ea0ae6e5b [Performance] Create a constant for empty component to avoid recreating it over and over 2025-03-19 17:54:56 +01:00
NEZNAMY 530c25655c [Error logging] Add missing " into error message 2025-03-19 17:44:03 +01:00
NEZNAMY ca830fd64c [Playerlist Objective] Make render type explicitly configurable 2025-03-19 17:42:16 +01:00
NEZNAMY 4272476230 [Playerlist Objective] Make title configurable (#1454) 2025-03-19 17:05:26 +01:00
NEZNAMY b7e44246ce [Fabric] Update to 1.21.5-pre3 2025-03-18 16:54:18 +01:00
NEZNAMY 33b9578fd1 [BossBar] Add %bossbar_announce_time_left_<bossbar>% and %bossbar_announce_time_total_<bossbar>% placeholders 2025-03-16 15:03:59 +01:00
NEZNAMY df0a96843d [Error Logging] Fix typo 2025-03-16 15:00:32 +01:00
NEZNAMY 5b5f7dfc12 [Code style] Remove unused fields 2025-03-15 16:49:21 +01:00
NEZNAMY 8b094adcca [Nametags] Add a lot more subcommands for manipulating nametag visibility 2025-03-15 16:48:34 +01:00
NEZNAMY de9d9ab844 [Nametags] Refactor classes and create enum for nametag invisibility reason 2025-03-13 18:08:17 +01:00
NEZNAMY 2fd6d2f50b [Fabric] Update to 1.21.5-pre2 2025-03-13 16:35:37 +01:00
Rubén 349c9eca4b Rename classes for more consistency 2025-03-12 18:18:09 -06:00
Rubén 1f119233d7 Move via module into bukkit module 2025-03-11 18:47:46 -06:00
Rubén 17c2d63892 Use instance method 2025-03-11 14:06:13 -06:00
NEZNAMY 79c76e913d [Fabric] Update to 1.21.5-pre1 2025-03-11 17:06:48 +01:00
NEZNAMY 6501480980 [Placeholders] Parse nested PAPI placeholders using {} 2025-03-08 13:40:58 +01:00
NEZNAMY 5ad9bbba37 [Global playerlist] Simplify expression (servers are never equal) 2025-03-08 09:44:31 +01:00
NEZNAMY f012448758 [Description] Improve image generator script 2025-03-07 20:25:11 +01:00
Rubén 99b6d635d7 Remove unchecked object handling 2025-03-07 08:37:14 -06:00
Rubén 5f742627e9 Avoid component italic format being reset 2025-03-07 07:58:19 -06:00
Rubén a9e725002a Use simple text component 2025-03-07 07:51:16 -06:00
Rubén 49150f9b1e Remove ViaVersion configuration support 2025-03-06 22:43:49 -06:00
Rubén 89aa1ba94c Fix class cast 2025-03-06 22:32:14 -06:00
Rubén 6aca313bd8 Use dummy object 2025-03-06 22:31:49 -06:00
Rubén f76d5a958f Fix component writing on newer versions 2025-03-06 22:15:51 -06:00
NEZNAMY b833a10de4 [Fabric] Update to 25w10a 2025-03-05 17:14:38 +01:00
Rubén f3ce5632fb Add safe check for ViaVersion internals usage 2025-03-05 07:54:09 -06:00
Rubén f5bf4c3831 Merge component conversion methods 2025-03-04 07:27:01 -06:00
Rubén 08a3acc5e0 Use direct return for viaversion providers 2025-03-04 07:26:13 -06:00
Rubén 5b63034607 Apply component style on compatible components 2025-03-03 07:36:33 -06:00
Rubén d4543032d9 Use the correct annotation 2025-03-02 09:25:52 -06:00
Rubén 254a91c3bd Fix legacy text check 2025-03-02 09:18:46 -06:00
NEZNAMY 4c01ddaaef [Description] Remove now no longer used images 2025-03-02 14:10:06 +01:00
NEZNAMY 2993d1450a [Description] Change text into code-generated images 2025-03-02 13:49:37 +01:00
Rubén 582c3a4c1a Initial ViaVersion packets support 2025-03-01 16:31:24 -06:00
NEZNAMY dd6b964405 [Fabric] Remove support for all versions below 1.21.4 2025-03-01 18:31:50 +01:00
NEZNAMY 16827bfbac Update references to version 5.1.0 2025-03-01 18:05:53 +01:00
NEZNAMY 3c4d72bb71 Bump version to 5.1.0-SNAPSHOT 2025-03-01 17:26:30 +01:00
NEZNAMY 004036e019 [BungeeCord / Velocity] Read server list from configuration for %online_<server>% placeholders instead of dynamically registering them when used 2025-03-01 16:15:03 +01:00
NEZNAMY ba06f80baa [BungeeCord / Velocity] Block bridge communication messages from player to avoid exploits 2025-03-01 14:07:50 +01:00
NEZNAMY ee47428a80 [Floodgate hook] Remove warning when floodgate is not configured correctly 2025-03-01 09:10:23 +01:00
NEZNAMY 1e40cae27b [Wiki] Monthly wiki backup for March 2025 2025-03-01 08:46:19 +01:00
NEZNAMY f5b7bb5549 [Belowname] Rename messages to use belowname "value" instead of "number" 2025-02-28 16:58:57 +01:00
NEZNAMY 115883f8d0 [Fabric] Update to 25w09b 2025-02-28 16:57:37 +01:00
NEZNAMY a73446a1c5 [Placeholders] Make LP placeholders also use configured value instead of permission refresh interval 2025-02-27 18:45:37 +01:00
NEZNAMY 7501c4f76b [Commands] Make placeholder refresh interval color in cpu output different if it was overridden in config 2025-02-27 18:40:46 +01:00
NEZNAMY 9f038fae63 [Placeholders] Allow placeholder refresh intervals to define overrides for internal placeholders 2025-02-27 18:21:49 +01:00
NEZNAMY a61a520570 [Fabric] Update to 25w09a 2025-02-26 18:27:51 +01:00
NEZNAMY 885783cd9a [Bukkit] Refactor feature provider loaders 2025-02-25 17:16:54 +01:00
NEZNAMY 181f850c58 Fix compilation 2025-02-23 17:39:25 +01:00
NEZNAMY fe8742b580 Merge pull request #1435 from MineLAT/feat/proxy-support
Expand multi-server support
2025-02-23 17:11:33 +01:00
Rubén daeba7caa9 Remove unused belowname code 2025-02-23 10:01:29 -06:00
NEZNAMY 1340e6dc1b [Code style] [Bukkit] Refactor team packet data 2025-02-23 09:24:27 +01:00
NEZNAMY cba7655f0d [Code style] Move method into a different class 2025-02-22 18:32:31 +01:00
NEZNAMY ac7e2a9aef [RGB] Fully support magic codes inside gradients, they no longer must all be at the beginning 2025-02-22 18:27:33 +01:00
Rubén fcc0bfbf32 Optimize dependencies 2025-02-22 10:40:58 -06:00
NEZNAMY a7e86bdb64 [Fabric] Update to 25w08a 2025-02-19 17:57:19 +01:00
Rubén c3eecd8d14 Remove duplicated check 2025-02-18 10:39:48 -06:00
Rubén 25e6da3ac5 Add safe check for already connected players 2025-02-18 10:37:49 -06:00
Rubén a7e5786fee Use global player list instead 2025-02-18 09:56:48 -06:00
Rubén a6bccc3dd3 Add missing config values 2025-02-17 18:12:19 -06:00
Rubén d8cd39dc3e Remove duplicated yellow number send 2025-02-17 14:13:14 -06:00
Rubén 6eacf833f5 Remove duplicated name tag send 2025-02-17 14:11:54 -06:00
Rubén dbdd417e0d Avoid bad configuration update 2025-02-17 14:11:30 -06:00
Rubén 4c8dd52f28 Ignore same player 2025-02-17 13:56:46 -06:00
Rubén 31a2d8a02b Add supported types 2025-02-17 12:49:38 -06:00
Rubén f246d57a7e Add backend messaging 2025-02-17 12:37:15 -06:00
Rubén 4ae856630f Add proxy support via messenger 2025-02-17 12:02:27 -06:00
Rubén 8652baf2ae Rename redis feature 2025-02-17 11:50:18 -06:00
NEZNAMY 4a18853e8c [BungeeCord] Remove warning about a waterfall option as it is now long abandoned 2025-02-16 19:58:03 +01:00
NEZNAMY a60be9d8c1 [Spectator fix] Fix not updating on proxies (#1434) 2025-02-16 19:21:07 +01:00
NEZNAMY e16b47409c [Code style] Keep expected tablist display names as TabComponent instead of platform components 2025-02-16 17:45:57 +01:00
NEZNAMY 6f14cbe746 5.0.8-SNAPSHOT 2025-02-16 16:32:10 +01:00
NEZNAMY ea79c9c5dd 5.0.7 2025-02-16 11:27:35 +01:00
NEZNAMY 650d13ed71 [Reloading] Fix plugin messages being ignored on Velocity on reload due to a check not working correctly 2025-02-15 09:11:50 +01:00
NEZNAMY 169b8b8e94 [Scoreboard] Fix scoreboard chain misconfiguration detection not working correctly 2025-02-14 16:57:15 +01:00
NEZNAMY 8cef4a5bcb [Fabric] Update to 25w07a 2025-02-13 19:16:32 +01:00
NEZNAMY 70b40802cc [Fabric] Remove redundant function split based on server version 2025-02-11 17:45:53 +01:00
NEZNAMY 416e707cda Revert "[Code style] Create a separate module for bossbar API"
This reverts commit 042ab6850b.
2025-02-11 16:32:05 +01:00
NEZNAMY 12578293bc [Bukkit] Delete paper submodule as it was not worth it 2025-02-10 17:14:46 +01:00
NEZNAMY 042ab6850b [Code style] Create a separate module for bossbar API 2025-02-09 20:03:17 +01:00
NEZNAMY 09fc042630 [Chat components] Fix magic codes not being assigned correctly when converting from an adventure component 2025-02-09 14:55:58 +01:00
NEZNAMY c73d351bf1 [Configuration] Update default config to avoid unwanted bold text caused by MiniMessage 2025-02-09 12:42:52 +01:00
NEZNAMY 4d4e0a9c2a [BungeeCord & Velocity] Make compatibility error message more visible 2025-02-08 13:03:25 +01:00
NEZNAMY 2fd29b2347 [Configuration] Disable SnakeYAML's max file size limit (layout skincache can exceed the default 3MB) 2025-02-08 12:45:19 +01:00
NEZNAMY f332068318 [Fabric] Fix 1.16.5 support 2025-02-08 12:28:52 +01:00
NEZNAMY 186c79f93c [Bukkit] Split component converter into multiple classes based on server version interval for better maintainability 2025-02-08 10:19:06 +01:00
NEZNAMY 3770066485 [Code style] Move component folder into a new separate module 2025-02-07 16:19:48 +01:00
NEZNAMY 17b0ffe52b [Bukkit] Fix component style magic codes conversion 2025-02-07 15:35:23 +01:00
NEZNAMY d259b5bb78 5.0.7-SNAPSHOT 2025-02-06 18:05:23 +01:00
NEZNAMY f6c8d24177 5.0.6 2025-02-06 17:54:47 +01:00
NEZNAMY ab3bd61d3c [Bukkit] Fix error 2025-02-06 17:27:46 +01:00
NEZNAMY 6a5cee8f0e [Fabric] Update to 25w06a 2025-02-05 20:15:43 +01:00
NEZNAMY 549732fc8b [Chat components] No longer track modern/legacy converted components, only handle them internally in bungeecord module 2025-02-05 19:13:56 +01:00
NEZNAMY 09d400c6fb [GitHub] Bump version to fix actions 2025-02-05 18:33:49 +01:00
NEZNAMY 7e1cdafa78 [Bukkit] Only enable packetevents bug compensation <1.13 where it's actually needed to avoid scoreboard gradient bug due to forced splitting when the option is enabled and doesn't need to be 2025-02-04 18:39:25 +01:00
NEZNAMY 434c0216d5 [Adventure hook] Fix startup error when an older version of the library is present 2025-02-04 18:28:34 +01:00
NEZNAMY d5a0ae8e71 [Scoreboard] Fix memory leak 2025-02-03 16:56:04 +01:00
NEZNAMY 79e56c6a30 [Chat components] Implement shadowColor field (currently only used for converting components created using MiniMessage) 2025-02-02 17:54:57 +01:00
NEZNAMY 11cc1c3f25 [Code style] Remove unused constructor 2025-02-02 16:12:53 +01:00
NEZNAMY 8b343bcfb6 [Chat components] Fix NPE 2025-02-02 15:36:25 +01:00
NEZNAMY ecd83beb7d [Performance] Replace stream with for loop in vanish check 2025-02-02 14:14:57 +01:00
NEZNAMY 438f21fb11 [BungeeCord] Update to build 1899 (and require it) 2025-02-01 20:43:05 +01:00
NEZNAMY dd1524cf31 [Dependencies] Bump and remove unnecessary 2025-02-01 18:40:02 +01:00
NEZNAMY 0f85aa5384 Fix broken messages from previous commit 2025-02-01 18:23:04 +01:00
NEZNAMY 4644a0ae96 [Code style] Further reduce references to EnumChatFormat class 2025-02-01 18:18:38 +01:00
NEZNAMY 84b3efe0fc [Code style] Simplify some component creations 2025-02-01 17:07:48 +01:00
NEZNAMY 511acbd5ff [Chat components] Rework component creation and converters 2025-02-01 16:45:20 +01:00
NEZNAMY 8749b3b354 [Wiki] Monthly wiki backup for February 2025 2025-02-01 13:09:27 +01:00
NEZNAMY b66e63d242 [Fabric] Implement adventure component conversion to MC components (#1425) 2025-02-01 09:43:03 +01:00
NEZNAMY ba951238c3 [Fabric] Update to 25w05a 2025-01-29 20:08:49 +01:00
NEZNAMY a4eb76f1cf 5.0.6-SNAPSHOT 2025-01-29 20:08:17 +01:00
NEZNAMY c486899dcf 5.0.5 2025-01-29 17:37:10 +01:00
NEZNAMY b212f0d5be [Velocity] Fix some console messages not being colored correctly 2025-01-27 18:44:55 +01:00
NEZNAMY caa534ff7f [MySQL] Fix upload command using wrong file path 2025-01-26 12:15:41 +01:00
NEZNAMY 9c2fac6749 [MiniMessage] Fix critical error when an ancient MM version is present 2025-01-25 19:12:47 +01:00
NEZNAMY 2a4f918a10 [RGB] Convert all RGB/Gradients for MiniMessage parser (#1408, #1414, #1421) 2025-01-25 08:55:22 +01:00
NEZNAMY b45e2afe4c [Code style] Move classes to a different package 2025-01-24 19:16:07 +01:00
NEZNAMY 2c7237e5d9 [RGB] Remove early gradient conversion (for future commits) 2025-01-24 19:10:59 +01:00
NEZNAMY 26477e3150 5.0.5-SNAPSHOT 2025-01-23 18:27:20 +01:00
NEZNAMY cba8cb57d5 5.0.4 2025-01-23 17:24:16 +01:00
NEZNAMY 599b4822e5 [Fabric] Update to 25w04a 2025-01-22 18:31:03 +01:00
NEZNAMY fbd150b353 [API] Expose TabPlayer#getServer, TabPlayer#getWorld and TabPlayer#isBedrockPlayer 2025-01-21 17:33:10 +01:00
NEZNAMY 10b252e934 [Placeholders] Fix placeholders staying incorrectly marked as bridge placeholders under very specific conditions (#1416) 2025-01-19 21:36:27 +01:00
NEZNAMY e9dfcdeb10 [Layout] If trying to use skin of an online player in fixed slot, try to get it from the player's profile if online 2025-01-19 18:16:04 +01:00
NEZNAMY 9cb2f27754 [Error management] Do not print full stack traces when a skin fails to download 2025-01-19 17:41:48 +01:00
NEZNAMY a090a83b7d [Debug] Replace debug messages about downloading skin with downloaded and include time it took 2025-01-19 17:04:44 +01:00
NEZNAMY 992e661e4c [Placeholders] Fix bridge overriding placeholders if they did not exist before a reload (such as conditions) (#1416) 2025-01-19 15:59:51 +01:00
NEZNAMY 3b993d114e [Velocity] Fix errors when plugin is disabled due to a broken configuration file 2025-01-19 15:21:34 +01:00
NEZNAMY d181d62466 [Code style] Call EnumChatFormat#getFormat instead of toString 2025-01-19 14:55:46 +01:00
NEZNAMY 968cef7a12 [Scoreboard & Nametags] Add a config option to enforce <1.13 limits even for 1.13+ players (#1412, #1413) 2025-01-19 13:48:39 +01:00
NEZNAMY 390c2395eb Fix compilation error from previous commits 2025-01-19 13:34:18 +01:00
NEZNAMY adc9444794 [Code style] Make code more readable 2025-01-19 13:25:20 +01:00
NEZNAMY f086318082 [Chat components] Remove redundant checks for player's version 2025-01-19 13:21:18 +01:00
NEZNAMY 1cef4400b9 [Scoreboard] Fix ConcurrentModificationException when using API (#1410) 2025-01-18 14:23:13 +01:00
NEZNAMY 1f879ac91e [Scoreboard] Fix setText API method breaking a line if setting it to empty value (#1410) 2025-01-18 14:12:00 +01:00
NEZNAMY bec129d98e [Scoreboard & Bossbar] Save toggled players by UUID instead of names 2025-01-18 10:44:22 +01:00
NEZNAMY 152c04e68f [Configuration] Remove file header restoration on file modification 2025-01-18 10:41:13 +01:00
NEZNAMY d6cdacef43 [Layout] Fix skins not being saved into memory when downloaded 2025-01-16 19:02:38 +01:00
NEZNAMY 09891a075d [Layout] Add debug messages when a skin is about to be downloaded 2025-01-16 19:02:00 +01:00
NEZNAMY 9500bab5b7 [Scoreboard] Fix resetScoreboard not applying scoreboard correctly (#1411) 2025-01-16 18:15:31 +01:00
NEZNAMY 231edd4bd4 [Chat components] Fix magic code null values being converted to false 2025-01-15 18:55:03 +01:00
NEZNAMY 433572e759 [Fabric] Update to 25w03a 2025-01-15 18:06:51 +01:00
NEZNAMY 3ec772e87f [Feature management] Load all features in plugin's main thread to avoid API calls from load event before a feature actually loads (#1409) 2025-01-10 21:55:55 +01:00
NEZNAMY ae8287157c [Scoreboard] Do not add API scoreboards to the chain of scoreboards to go through (#1409) 2025-01-10 21:32:42 +01:00
NEZNAMY f005907b41 [Fabric] Update to 25w02a 2025-01-09 18:44:55 +01:00
NEZNAMY 8ea16044f1 [Debug] Send a correct message if player does not have any group permission 2025-01-08 18:34:06 +01:00
NEZNAMY e787a28353 [Tablist formatting] Fix formatting applying even if disabled with condition sometimes 2025-01-06 18:59:32 +01:00
NEZNAMY 6925d9564c [Code style] Create Platform#getCommand method instead of storing it in TabConstants 2025-01-04 18:43:37 +01:00
NEZNAMY ccd4ea785a [Logging] Fix outdated value in a message 2025-01-04 18:27:29 +01:00
NEZNAMY 63e88c09d4 [MySQL] Add a startup warn message if database is empty 2025-01-04 18:24:51 +01:00
NEZNAMY 296191b502 [Code style] Replace uses of EnumChatFormat for color codes with the codes directly 2025-01-02 19:20:49 +01:00
NEZNAMY af0151f446 [RGB] Remove now redundant check for modern/legacy colors (partially) 2025-01-02 16:26:06 +01:00
NEZNAMY 2bdfaa167e [RGB] Remove support for |L for manually defining legacy colors 2025-01-02 15:56:49 +01:00
NEZNAMY cd96476f9e [Belowname] Improve a warning message 2025-01-01 19:16:12 +01:00
NEZNAMY 9b0545aa1a [Layout] Fix players on top of tablist if team implementation is not available 2025-01-01 18:57:49 +01:00
NEZNAMY 63823123f8 [BungeeCord] Simplify code 2025-01-01 14:27:43 +01:00
NEZNAMY e91728be98 [Sponge 8] Update sponge gradle plugin to fix warning 2025-01-01 14:02:00 +01:00
NEZNAMY e7ecbf1245 [Velocity] Remove usage of deprecated API 2025-01-01 13:54:17 +01:00
NEZNAMY 28c2363b67 [Wiki] Monthly wiki backup for January 2025 2025-01-01 13:12:21 +01:00
NEZNAMY 21a3d4fb55 [Paper] Fix mistake in magic codes (#1404) 2024-12-30 14:10:52 +01:00
NEZNAMY 4b459a3ed1 [Commands] Add a red message to debug command if assigning groups by permissions and the player has all permissions 2024-12-29 20:09:31 +01:00
NEZNAMY ebc6a1b842 Fix paper compilation 2024-12-29 20:08:10 +01:00
NEZNAMY b72b0f269a [Code style] Remove redundant warning suppression 2024-12-21 17:54:06 +01:00
NEZNAMY 4cbcd69ed2 [Code style] Remove redundant constructor 2024-12-21 17:45:13 +01:00
NEZNAMY 6ef8b7748c [Code style] Remove redundant double coloring 2024-12-21 17:05:35 +01:00
NEZNAMY 8b567cb976 [Code style] Optimize last color of component calculation 2024-12-21 16:15:04 +01:00
NEZNAMY d8c231b9ce [Code style] Further reduce usage of EnumChatFormat class 2024-12-21 15:49:33 +01:00
NEZNAMY cc4347c881 [Code style] Make TabComponent#fetchLastColor Nullable 2024-12-21 13:19:20 +01:00
NEZNAMY 7cc572feb5 [Code style] Replace some references to EnumChatFormat with the actual color codes 2024-12-21 11:58:37 +01:00
NEZNAMY 96d1d1feb4 [Code style] Use TextColor as team color internally to prepare for a potential future MC update 2024-12-21 11:39:36 +01:00
NEZNAMY a7d9bd9ab5 [Code style] Simplify TabComponent#toRawText implementation 2024-12-21 11:15:47 +01:00
NEZNAMY 1cdfdf1d43 [Code style] Simplify 2024-12-18 17:32:54 +01:00
NEZNAMY 0762a5e689 [Code style] Remove redundant method call 2024-12-18 17:22:55 +01:00
NEZNAMY 3e2906ccf8 [Dependencies] Version bump and remove unused 2024-12-18 17:18:09 +01:00
NEZNAMY 13761e032c [Bukkit] Set shadowColor to null instead of 0 (no shadow) (#1400) 2024-12-16 18:51:02 +01:00
NEZNAMY fb8fa3fa54 [MiniMessage] Implement Keybind component support as well (on 1.19+) 2024-12-14 11:34:02 +01:00
NEZNAMY b89bf5933b [MiniMessage] Add support for its translatable components (paper / velocity only) (#1399) 2024-12-13 22:40:20 +01:00
NEZNAMY 1c4f404fd2 [Code style] Remove unused method 2024-12-13 22:22:51 +01:00
NEZNAMY 9fe1560f1a [Sorting] Remove runtime warn spam when configured sorting placeholder is invalid (only keep startup warn) 2024-12-12 18:58:49 +01:00
NEZNAMY 6a9cca37c6 [Scoreboard] Fix toggle command being completely ignored when another plugin has sent scoreboard instead of processing it (to some degree) 2024-12-10 21:30:20 +01:00
NEZNAMY bfde75d4b1 [Configuration] Fix startup error when group name is a number (#1398) 2024-12-09 18:12:03 +01:00
NEZNAMY a83f98580d [Bukkit] Update Paper submodule to 1.21.4 2024-12-07 13:58:47 +01:00
NEZNAMY 673542d8e4 [Code style] Move canSee method into TabPlayer 2024-12-07 12:23:51 +01:00
NEZNAMY 81c5d89a25 Fix broken PR 2024-12-07 12:08:26 +01:00
NEZNAMY bf0bcb0b96 Merge pull request #1394 from WheelerShigley/master
Fabric now has a getTPS() approximation.
2024-12-07 09:57:25 +01:00
WheelerShigley ac7a664303 Fabric now has a getTPS() approximation.
Added Fabric TPS approximation, based on mspt.
2024-12-06 19:58:38 -06:00
NEZNAMY 182dc67a49 5.0.4-SNAPSHOT 2024-12-03 20:27:24 +01:00
NEZNAMY 27ee82d17c 5.0.3 2024-12-03 20:11:13 +01:00
NEZNAMY 9405e4e45d [Bukkit] Add 1.21.4 support 2024-12-03 20:06:48 +01:00
NEZNAMY 8616769e32 [Bukkit] Print component converter initialization error if print flag is enabled 2024-12-03 19:12:37 +01:00
NEZNAMY 5d85c4697b [Bukkit] Fix error when a plugin shades adventure library without relocating it 2024-12-03 19:09:55 +01:00
NEZNAMY c54a2b7353 Mark 1.21.4 as the latest supported version 2024-12-03 18:24:17 +01:00
NEZNAMY 35075e88e3 [Fabric] Update to 1.21.4 2024-12-03 18:18:09 +01:00
NEZNAMY 1266d34176 [Wiki] Monthly wiki backup for December 2024 2024-12-01 20:05:59 +01:00
NEZNAMY 66ddf0e3ca [Fabric] Update to 1.21.4-rc3 2024-11-30 14:37:59 +01:00
NEZNAMY 88ab129737 [Fabric] Update to 1.21.4-rc2 2024-11-29 17:18:43 +01:00
NEZNAMY bcd293a255 [Fabric] Update to 1.21.4-rc1 2024-11-28 17:50:24 +01:00
NEZNAMY 4a5ebe6dbd [Fabric] Mark 1.21.4-pre3 as compatible 2024-11-26 18:23:13 +01:00
NEZNAMY ef1c3b1b3b [BungeeCord] Use new features added in build 1885 (no longer supports older builds) 2024-11-26 18:16:04 +01:00
NEZNAMY f7e68ffb6b [Fabric] Update to 1.21.4-pre3 2024-11-26 18:06:10 +01:00
NEZNAMY 2cf2153c4d 5.0.3-SNAPSHOT 2024-11-26 17:43:19 +01:00
NEZNAMY 7e7d0025fa 5.0.2 2024-11-26 17:27:16 +01:00
NEZNAMY 8252b6486e [Fabric] Update to 1.21.4-pre2 2024-11-25 20:03:28 +01:00
NEZNAMY 378041cb16 Merge pull request #1388 from Syrent/master
fix: avoid using TAB's internal vanish checks when a registered vanish integration is available
2024-11-24 19:57:41 +01:00
syrent ee5ff07565 chore: move pv try/catch inside vanish integration if statement 2024-11-24 20:48:02 +03:30
syrent 80c63dd414 chore: don't check for vanished meta if there's a registered vanish integration 2024-11-24 19:55:57 +03:30
NEZNAMY e182366566 [Scoreboard] Fix detection of scoreboards from other plugins not resetting on server switch 2024-11-23 21:18:31 +01:00
NEZNAMY acee89cce3 [BungeeCord] Add TODO 2024-11-23 20:59:45 +01:00
NEZNAMY 3ef3d14c10 [Vanish detection] Properly hook into vanish plugins on Velocity just like on BungeeCord 2024-11-23 20:58:15 +01:00
NEZNAMY 74365780a7 [Code style] Minor fixes from PR 2024-11-23 14:27:26 +01:00
NEZNAMY f6ad80370f Merge pull request #1387 from Syrent/master
feat: add VanishIntegration api
2024-11-23 11:13:12 +01:00
syrent 87eb544936 feat: add VanishIntegration api 2024-11-23 09:20:39 +03:30
NEZNAMY cde0389380 [Fabric] Update to 1.21.4-pre1 2024-11-20 17:46:25 +01:00
NEZNAMY 9e46ffc4e1 [Nametags] Process API calls in feature's thread instead of caller thread 2024-11-17 10:53:42 +01:00
NEZNAMY f3c35de780 [Configuration] Fix startup error if an element in a list is not a String 2024-11-14 20:20:11 +01:00
NEZNAMY 75f5378c8c [Belowname] Do not send updates of vanished players to those who cannot see them 2024-11-14 18:07:07 +01:00
NEZNAMY 19d32127e8 [Fabric] Update to 24w46a 2024-11-13 16:49:59 +01:00
NEZNAMY 2f00aa7750 [Conditions] Fix mistake from past commits not loading true/false values if not wrapped in "" 2024-11-10 18:24:12 +01:00
NEZNAMY 07f5dc68b5 [Velocity] Use new listOrder API (and bump required velocity build number to 450) 2024-11-10 10:08:35 +01:00
NEZNAMY 96a39d7037 [Configuration] Fix unknown key detection not working 2024-11-09 17:40:54 +01:00
NEZNAMY 409f1c4230 [Fabric] Update to 24w45a 2024-11-06 19:19:51 +01:00
NEZNAMY c3d2f9aeea [Commands] Reuse the same list of valid properties 2024-11-05 17:35:17 +01:00
NEZNAMY 380b3dd1f6 [Scoreboard] Add startup warn if trying to use NumberFormat on <1.20.3 server and runtime warn when trying to send it to <1.20.3 player 2024-11-03 16:52:10 +01:00
NEZNAMY c15a6009a2 [MySQL] Execute MySQL queries in a separate thread 2024-11-03 10:04:45 +01:00
NEZNAMY e4e7c364fd [Code style] Make list mutable 2024-11-03 09:46:14 +01:00
NEZNAMY 438361eed2 [Code style] Make fields final 2024-11-03 09:31:50 +01:00
NEZNAMY 684b35f2df [Code style] Refactor config options loading and add a few more misconfiguration checks 2024-11-03 09:13:41 +01:00
NEZNAMY 1d603ae76f [Conditions] Fix warning from previous commit 2024-11-02 16:18:46 +01:00
NEZNAMY a342b23890 [Conditions] Fix conditions without true/false defined not refreshing correctly 2024-11-02 15:49:37 +01:00
NEZNAMY c0dde2eaa7 [Config] Change default default-skin value in layout as the old one contains extra layer (which will be visible on 1.21.4) 2024-11-01 11:25:35 +01:00
NEZNAMY 6245fe9553 [Wiki] Monthly wiki backup for November 2024 2024-11-01 10:19:39 +01:00
NEZNAMY fb5be9e2aa [Paper] Update direct NMS to 1.21.3 2024-10-31 21:11:40 +01:00
NEZNAMY 918620a7dc [Bukkit & Fabric] Update to 24w44a (predicted on Bukkit) 2024-10-31 20:46:51 +01:00
NEZNAMY d2855449f0 [Layout] Add misconfiguration checks if slot is out of range 2024-10-30 20:56:59 +01:00
NEZNAMY f59326c0ae [Configuration] Add console warning if using invalid property for groups / users 2024-10-30 19:58:12 +01:00
NEZNAMY 033b9ed035 [Configuration] Improve warning messages 2024-10-29 20:37:27 +01:00
NEZNAMY 5c299767ec [Vanish detection] Disable Bukkit canSee support due to issues with vanish plugins 2024-10-29 20:16:30 +01:00
NEZNAMY 818701cd07 [RedisSupport] Register messages for features in constructors instead of load to make sure they run immediately and sync in the main thread 2024-10-29 20:08:58 +01:00
NEZNAMY 9b5e63aa25 [RedisSupport] Slightly improve debug message 2024-10-29 20:04:56 +01:00
NEZNAMY 1ad285bd79 [RedisSupport] Queue incoming messages to avoid tasks running before plugin is fully enabled, causing errors 2024-10-28 18:06:57 +01:00
NEZNAMY 4507bb179a Merge pull request #1374 from Tanguygab/master
Added Text PlaceholderAPI support on Fabric
2024-10-27 18:00:39 +01:00
Tanguygab 2dd79295a9 Fixed spacing 2024-10-27 17:56:25 +01:00
Tanguygab b21d840e60 [Fabric] Added FabricTabExpansion 2024-10-27 17:44:24 +01:00
NEZNAMY f3fa40bb0a [GitHub] Store plugin description in source code as backup (phase 4) 2024-10-27 17:11:38 +01:00
Tanguygab d8e14de0cf Fixed error with Text PAPI placeholder 2024-10-26 21:34:25 +02:00
Tanguygab f7b96eceeb Added support for Text PlaceholderAPI on Fabric 2024-10-26 21:26:22 +02:00
NEZNAMY 6f760939f3 [Placeholders] Add validation check to PlaceholderManagerImpl#getPlaceholder 2024-10-26 21:23:17 +02:00
NEZNAMY 5273ad0c5f [Gradle] Update to 8.10.2 2024-10-26 20:22:51 +02:00
NEZNAMY ac12abfb8b [GitHub] Store plugin description in source code as backup (phase 3) 2024-10-26 19:19:04 +02:00
NEZNAMY e5146e7f53 [Misconfiguration] Fix color codes being parsed in warning messages when they should not be. 2024-10-26 19:09:53 +02:00
NEZNAMY 62d1210872 [RGB] Fix using "<" symbol inside gradient breaking it 2024-10-26 18:59:49 +02:00
NEZNAMY 0d0177cd40 Bump bStats to 3.1.0 (this also somehow magically fixes false positives of anti-viruses) 2024-10-26 14:07:38 +02:00
NEZNAMY 227ecf9c9d [Placeholders] Fix error when registering custom server placeholder on proxy 2024-10-26 11:03:09 +02:00
NEZNAMY 562cd50762 [Belowname] Rename config options to hopefully be more clear 2024-10-26 10:29:21 +02:00
NEZNAMY ec56d530a6 [Scoreboard] Fix startup error when a line is configured to be null 2024-10-25 20:57:39 +02:00
NEZNAMY 6350714508 [GitHub] Store plugin description in source code as backup (phase 2) 2024-10-25 20:42:13 +02:00
NEZNAMY 1b251dad6a [GitHub] Store plugin description in source code as backup (phase 1) 2024-10-24 17:12:04 +02:00
NEZNAMY 7765b51a55 5.0.2-SNAPSHOT 2024-10-23 21:16:18 +02:00
NEZNAMY ff6ba6a8e9 5.0.1 2024-10-23 21:04:39 +02:00
NEZNAMY ce3485c823 [Layout] Fix player group condition not getting refreshed properly 2024-10-23 21:04:09 +02:00
NEZNAMY 90a99d3a79 [Fabric] Update to 1.21.3 2024-10-23 20:08:52 +02:00
NEZNAMY 7248dc4fef [Layout] Make check for 1.21.2+ listOrder support better (and work correctly) 2024-10-23 18:36:15 +02:00
NEZNAMY d704db55fa [Code style] Simplify 2024-10-23 16:26:43 +02:00
NEZNAMY 0f581e47b1 5.0.1-SNAPSHOT 2024-10-23 16:25:29 +02:00
NEZNAMY 48013f9b3c 5.0.0 2024-10-22 19:29:31 +02:00
NEZNAMY 258c8ce989 [Velocity] Use proper API method for creating TabListEntry 2024-10-22 19:29:13 +02:00
NEZNAMY c56a703028 Confirm 1.21.2 protocol version 2024-10-22 18:51:17 +02:00
NEZNAMY 4b9ce1b067 [Fabric] Update to 1.21.2 2024-10-22 18:50:56 +02:00
NEZNAMY 39b56725dd [Fabric] Update to 1.21.2-rc1 2024-10-18 21:44:54 +02:00
NEZNAMY b281b18a4e [Code style] Properly implement Platform#getServerVersion on proxies to avoid incorrect results 2024-10-17 17:30:04 +02:00
NEZNAMY b85487375d [Configuration] Rename version in config conversion 2024-10-16 18:27:03 +02:00
NEZNAMY 9ccdc0cf7e [Fabric] Update to 1.20.2-pre5 2024-10-16 17:58:40 +02:00
NEZNAMY 51b1bcf507 [Fabric] Update to 1.20.2-pre4 2024-10-15 18:59:00 +02:00
NEZNAMY 0afe598f3c [Bukkit] Small NMS cleanup 2024-10-13 11:10:38 +02:00
NEZNAMY 4227b0c702 [Nametags] Fix double team register due to concurrency 2024-10-12 19:19:30 +02:00
NEZNAMY 2a4e4a4e2e [Nametags] Fix team register/unregister duplications in combination of (un)vanishing and disable condition change 2024-10-12 19:18:03 +02:00
NEZNAMY a9fa8291de [Velocity] Avoid new object instantiation 2024-10-12 12:39:58 +02:00
NEZNAMY 007c0abc95 [Plugin messages] Protocol break - Identify packets by their ID instead of name 2024-10-12 11:20:18 +02:00
NEZNAMY f9752b7090 [Performance] Disable CPU tracking in netty because getting system time on every packet send was causing high CPU usage by itself 2024-10-12 10:11:32 +02:00
NEZNAMY b44b91f8cc [GitHub] Update branch name in actions 2024-10-11 21:04:51 +02:00
NEZNAMY 352721540a [Fabric] Update to 1.21.2-pre3 2024-10-11 20:25:31 +02:00
NEZNAMY 1ab5f49c3b [Velocity] Remove TAB-Bridge support for scoreboard packet encoding 2024-10-11 19:27:35 +02:00
NEZNAMY 976e6e397a [Bedrock] Update warning message 2024-10-11 17:15:04 +02:00
NEZNAMY e95139b79e [Placeholders] Fix nested placeholders in relational placeholders updating not triggering the main placeholder refresh 2024-10-09 17:16:25 +02:00
NEZNAMY a75f50dcf6 [Bedrock] No longer disable a feature and implement sidebar workaround as new Geyser build resolves these issues on their side. 2024-10-09 14:13:42 +02:00
NEZNAMY 87334c586c I just love Velocity 2024-10-06 18:20:19 +02:00
NEZNAMY eddec70a99 [Nametags] Fix attempts to send team packets when disabled for player 2024-10-06 16:44:47 +02:00
NEZNAMY d1d3279c8e [Fabric] Update to 24w40a 2024-10-03 18:50:58 +02:00
NEZNAMY 13ff1dfb18 [Velocity] Fix exploit (not specifying more to keep users safe) 2024-10-02 20:36:38 +02:00
NEZNAMY e0d09132dd [Wiki] Monthly wiki backup for October 2024 2024-10-01 20:03:45 +02:00
NEZNAMY 99478e7f33 [Build] Remove publishing script 2024-09-28 13:35:01 +02:00
NEZNAMY 02087c735a [Conditions] Automatically trim all spaces 2024-09-27 17:50:12 +02:00
NEZNAMY cd13d588d6 [Layout] Fix ROWS direction not working on 1.21.2 from previous commit 2024-09-26 21:21:04 +02:00
NEZNAMY 6efc23cc15 [Fabric] ACTUALLY update to 24w39a (while keeping older version support) 2024-09-26 21:10:58 +02:00
NEZNAMY 5c28711b26 [Layout] Take advantage of the new 1.21.2 listOrder tablist sorting field to remove entries from chat complete 2024-09-26 20:05:58 +02:00
NEZNAMY e9ae57aff5 [Fabric] Update to 24w39a 2024-09-25 18:16:12 +02:00
NEZNAMY 92e4b1749e [Velocity] Improve error messages 2024-09-24 18:29:46 +02:00
NEZNAMY 854eed74d7 [Code style] Clean up method 2024-09-22 13:45:17 +02:00
NEZNAMY 7c5ef3aa8a [Bukkit] Bump dependency 2024-09-21 15:11:12 +02:00
NEZNAMY 78670b5d90 [Code style] Simplify code 2024-09-20 21:12:03 +02:00
NEZNAMY f43d54b6f6 [Bukkit] Fix team packet creation issue in a past commit 2024-09-20 20:51:24 +02:00
NEZNAMY db0dd75e27 [Fabric] Update to 24w38a 2024-09-19 18:49:06 +02:00
NEZNAMY d6d1649975 [Performance] Speed up TAB#getPlayer function 2024-09-15 14:04:07 +02:00
NEZNAMY f0f0e40897 [Velocity] Cache team display name objects 2024-09-14 18:11:53 +02:00
NEZNAMY 43c1116ac7 [Code style] Call shorter method 2024-09-14 12:02:08 +02:00
NEZNAMY 225f30a257 [Performance] Add check to speed up function 2024-09-13 22:38:43 +02:00
NEZNAMY ae03436592 [Performance] Fix Bridge response placeholder processing calling RefreshableFeature#refresh for every placeholder that changed instead of just once. 2024-09-13 13:09:00 +02:00
NEZNAMY 8a6014fd02 [Configuration] Add global-playerlist.update-latency adding to config converter 2024-09-13 12:33:09 +02:00
NEZNAMY dcc0fafe66 [Layout] Store properties separately and per layout and drop global properties map. This also fixes a theoretical memory leak if mass-switching to new layouts using the API. 2024-09-13 09:49:14 +02:00
NEZNAMY bc33679a1f [Velocity] Cache TextHolder instances for better performance 2024-09-11 19:22:07 +02:00
NEZNAMY d69efe76e5 [Fabric] Update to 24w37a 2024-09-11 18:26:37 +02:00
NEZNAMY 11babe4293 [BelowName] Fix text refresher not running in belowname thread 2024-09-10 20:55:34 +02:00
NEZNAMY 2ba7492f24 [Performance] Skip redundant check if team exists 2024-09-10 17:40:24 +02:00
NEZNAMY 6c65ffb701 [Error logging] Increase log size limit to 16MB from 1MB 2024-09-09 19:49:30 +02:00
NEZNAMY 864e30b3db [Bukkit] Fix direct NMS code check and split it per function (supported versions will start varying since 1.21.2) 2024-09-09 18:42:48 +02:00
NEZNAMY b9124ef873 [Performance] Micro optimization on Bukkit in Team packet 2024-09-09 18:03:33 +02:00
NEZNAMY 77165a5ffb [Performance] Micro optimization on Bukkit 2024-09-08 20:26:28 +02:00
NEZNAMY 54a519de0b [Layout] Use randomly generated property names for fixes slots instead of building them deterministically 2024-09-07 21:23:08 +02:00
NEZNAMY 462d38373f [Layout] Add API method for getting layout from config by name 2024-09-07 18:19:41 +02:00
NEZNAMY 44234422d0 [Performance] Use saved team/objective pointers instead of getting them from maps 2024-09-06 17:23:48 +02:00
NEZNAMY c71fcf51c1 [Velocity] Change checks for Scoreboard API access for better performance 2024-09-06 16:57:08 +02:00
NEZNAMY 33cc4b2aad [Fabric] Update to 24w36a 2024-09-04 17:59:30 +02:00
NEZNAMY 6485356c74 [Scoreboard] Use separate maps for scoreboard-related properties for faster access (also fix a theoretical memory leak if sending too many custom scoreboards with API) 2024-09-01 16:11:36 +02:00
NEZNAMY d0476a93ba [Wiki] Monthly wiki backup for September 2024 2024-09-01 10:07:12 +02:00
NEZNAMY 2eb3548008 [Performance] [BossBar] Store properties in a separate structure instead of using shared properties map 2024-08-31 15:51:13 +02:00
NEZNAMY 028a58c2ae [BungeeCord] Update to builds 1860+ (no longer works on older versions) 2024-08-29 19:10:25 +02:00
NEZNAMY 7675fc11b6 [Fabric] Fix wrong server version check 2024-08-29 19:08:03 +02:00
NEZNAMY 7a10a9e8f2 [Fabric] Remove redundant SneakyThrows annotations 2024-08-29 17:16:49 +02:00
NEZNAMY 8ee869830b [Fabric] Mark all recent supported snapshots as compatible 2024-08-29 16:53:58 +02:00
NEZNAMY 63ecd7b2a2 [Fabric] Move method to different module to reduce reflection usage 2024-08-29 16:44:24 +02:00
NEZNAMY a125076033 [Fabric] Update to 24w35a 2024-08-29 15:15:43 +02:00
NEZNAMY 12153f5783 [Wiki] Create wiki folder containing a backup of all wiki pages 2024-08-28 19:02:32 +02:00
NEZNAMY fdcec16181 [BungeeCord] Avoid sending UPDATE_LIST_ORDER tablist action to <1.21.2 players (#1359) 2024-08-25 20:09:25 +02:00
NEZNAMY 0c4d653921 [Fabric + Bukkit] Update to 24w34a (not tested) 2024-08-25 10:31:25 +02:00
NEZNAMY f488f18a55 Merge #1357 into v5 (Get skin from profile properties by property name) 2024-08-20 16:07:33 +02:00
NEZNAMY 6164dd8f5f [Performance] Change recursion to while loop 2024-08-18 15:14:33 +02:00
NEZNAMY 270062cfe4 [Code style] Remove redundant tablist tracking code 2024-08-16 16:34:33 +02:00
NEZNAMY 014e197a41 [Code style] Remove some redundancies 2024-08-14 19:25:03 +02:00
NEZNAMY bed587d6ba [Fabric] Split PermissionsAPI hook into a separate class 2024-08-14 19:10:02 +02:00
NEZNAMY c15934fdea [Configuration] Rework config loading (part 2) 2024-08-13 19:45:28 +02:00
NEZNAMY 385516f373 [Configuration] Fix bossbar order not being preserved 2024-08-12 18:29:29 +02:00
NEZNAMY c50f5e7728 [Configuration] Fix bossbar progress config loading 2024-08-12 18:25:50 +02:00
NEZNAMY 494bd7368d [Configuration] Fix config loading running before conversion 2024-08-11 08:38:36 +02:00
NEZNAMY b6e8f4c1a7 [Code style] Store TabList, BossBar and Scoreboard instances in TabPlayer object instead of each implementation 2024-08-10 21:15:06 +02:00
NEZNAMY 392e97df2c [Configuration] Fix global playerlist config loading 2024-08-10 21:07:13 +02:00
NEZNAMY 45dbd8c4b2 [Bukkit] Add 1.21.1 to list of versions supported by paper hook 2024-08-10 19:08:38 +02:00
NEZNAMY a0ac7d3324 [Code style] Change fields in TabFeature into getters and fix some annotations 2024-08-10 18:03:27 +02:00
NEZNAMY 4dd7ce5b98 [Configuration] Rework config loading and add a lot more checks (part 1) 2024-08-10 17:11:01 +02:00
NEZNAMY 1a96101672 Update to 1.21.1 2024-08-10 09:57:50 +02:00
NEZNAMY 88f40ab457 [BossBar] Fix ConcurrentModificationException (#1350) 2024-08-08 16:21:53 +02:00
NEZNAMY 70403042d2 [Layout] Fix API usage causing placeholders to not refresh 2024-08-06 17:56:58 +02:00
NEZNAMY ff5a9fa3a9 [Bukkit] Add null check for non-null field because other plugins can make it null 2024-07-28 11:14:36 +02:00
NEZNAMY 5ead664cf9 Update README.md 2024-07-27 23:02:01 +02:00
NEZNAMY bba2b6f2e8 [Code style] Remove redundant check that will never pass 2024-07-27 17:40:31 +02:00
NEZNAMY 4b25571e1a [Code style] Clean up property loading 2024-07-27 16:47:18 +02:00
NEZNAMY 407d0eaeac [Velocity] Ask people to install VelocityScoreboardAPI 2024-07-27 14:53:50 +02:00
NEZNAMY 05bb3a5fa9 [Code style] Make skin downloading return Skin instead of List<String> 2024-07-27 12:16:36 +02:00
NEZNAMY 714a72c849 Merge pull request #1335 from qdpy/v5
Add a signed_texture skin parameter to avoid an API call
2024-07-27 12:00:34 +02:00
NEZNAMY 9ba8f149f7 [Code style] Remove unused method 2024-07-27 10:23:29 +02:00
NEZNAMY c9bb38c036 [Scoreboard] Add misconfiguration check if scoreboard has more than 15 lines defined 2024-07-26 19:01:19 +02:00
NEZNAMY 3241f1cf41 [Sorting] Fix sorting placeholder using old instance when a placeholder is overridden 2024-07-25 21:01:47 +02:00
NEZNAMY 8a7099a6ab [BungeeCord] Add try/catch for yet another NPE in BungeeCord code 2024-07-25 15:57:25 +02:00
NEZNAMY 65c6adcdb2 [Bukkit] Fix compatibility with random modded server 2024-07-22 19:27:42 +02:00
evlad f3f2e69bcf prevent AIOOB exeception 2024-07-16 06:19:29 +03:00
qdpy 335f0a3d6f Add skin signed texture 2024-07-16 03:52:26 +02:00
NEZNAMY a7e711850c [Performance] Use Map#put return type to get rid of Map#get 2024-07-14 12:28:20 +02:00
NEZNAMY 6fb2a7ce76 [Performance] Switch to ConcurrentHashMap instead of synchronizing a map 2024-07-14 11:59:17 +02:00
NEZNAMY 56a6e34847 [Fix] Wrong boolean value from previous commit 2024-07-11 17:23:48 +02:00
NEZNAMY cfa4c869ca [Code style] Clean up task executing and fix mistakes from previous recodes 2024-07-10 21:38:51 +02:00
NEZNAMY d548fbcb74 [Technical] Remove a layer of task wrapping 2024-07-08 19:20:56 +02:00
NEZNAMY d40fbced1a [Performance] [NameTags] Skip containsTeam call 2024-07-08 19:05:39 +02:00
NEZNAMY 21c251b40b [Performance] Avoid redundant StructuredComponent creation when converting RGB colors to legacy in scoreboard 2024-07-07 21:30:51 +02:00
NEZNAMY ec3371085b [Performance] Avoid double map access on team/objective register 2024-07-07 15:56:49 +02:00
NEZNAMY 3779f38c5d [Playerlist Objective] [Performance] Avoid performing unnecessary operations on disable condition change 2024-07-07 15:46:58 +02:00
NEZNAMY 5b62fe2be7 [BelowName] [Performance] Avoid performing unnecessary operations on disable condition change 2024-07-07 15:40:42 +02:00
NEZNAMY ea223bbcb4 [Performance] Avoid double access to placeholder map 2024-07-07 11:17:09 +02:00
NEZNAMY 7f4a56149a [Placeholders] Make one more method synchronized to prevent incorrect re-register that breaks the placeholder on concurrent access 2024-07-06 21:51:02 +02:00
NEZNAMY 52a620c479 [Placeholders] Make methods synchronized to fix concurrent access potentially breaking some placeholders 2024-07-06 21:38:05 +02:00
NEZNAMY 593aaf8039 [Conditions] Avoid re-registering the same placeholder multiple times 2024-07-06 21:11:04 +02:00
NEZNAMY cebb6ea626 [NameTags] Register visibility refresher subfeature in constructor to prevent issues in placeholder updating when registering the placeholder 2024-07-06 21:02:30 +02:00
NEZNAMY 337dc1c4d5 [Technical] Decode plugin messages in a new separate thread 2024-07-06 19:30:20 +02:00
NEZNAMY e110f942e8 [Scoreboard] Add misconfiguration check for invalid scoreboard properties and improve code for existing features 2024-07-06 16:49:24 +02:00
NEZNAMY 6bd5f9dab1 [Error logging] Print warns when receiving redis message of unknown player and add some debug messages 2024-07-03 21:22:45 +02:00
NEZNAMY 09149c559d [Code style] Add missing NotNull annotation 2024-06-30 21:28:42 +02:00
NEZNAMY f9a1130e4b [Performance] Make some placeholders on proxy trigger-based instead of periodic refreshing 2024-06-29 15:43:44 +02:00
NEZNAMY 91304233aa [Code style] Finish getting rid of global component cache 2024-06-29 14:07:52 +02:00
NEZNAMY 99286578c1 [Code style] Create separate component cache for layout fixed slots 2024-06-29 13:43:07 +02:00
NEZNAMY 81888d9aa5 [Code style] Avoid creating simple components in some cases 2024-06-28 21:50:23 +02:00
NEZNAMY f2e4b1b94a [Debug] Add message showing how long it took to unregister teams, objectives and bossbars on unload 2024-06-28 11:26:36 +02:00
NEZNAMY ab269ad740 [Code style] Dynamically remove player boss bars on unload instead of calculating them inside the feature 2024-06-28 10:44:36 +02:00
NEZNAMY 5a1df75526 [Fix] Forcefully shutdown thread executor after it failed to shut down softly 2024-06-27 21:28:12 +02:00
NEZNAMY b74ed1d14e [Code style] Unregister all teams and objectives dynamically instead of in every feature separately and calculating all logic. Can't believe it took me years to come up with this. 2024-06-27 21:24:33 +02:00
NEZNAMY d087db38e3 [Bug fix] Update disable condition status in feature's thread 2024-06-27 21:08:21 +02:00
NEZNAMY ff8c406823 [Velocity] Avoid errors in VelocityScoreboardAPI hook by handling unexpected situations 2024-06-27 15:33:13 +02:00
NEZNAMY 7e12a75bea [Technical] Include thread name in error message if it failed to shut down 2024-06-27 15:27:40 +02:00
NEZNAMY aa7fb09399 [Teams] Fix vanish status change processing not respecting disable condition 2024-06-27 14:40:07 +02:00
NEZNAMY a805b3fe90 [Bukkit] Mark Paper 1.21 as compatible with direct NMS code 2024-06-27 11:06:13 +02:00
NEZNAMY 63d9122440 [Bossbar] Fix bars being resent on server switch even for <1.20.2 players 2024-06-27 10:54:38 +02:00
NEZNAMY f9293c9c92 [Bug fix] Unregister teams of redis players on unload 2024-06-26 16:11:44 +02:00
NEZNAMY 4892fd5817 [Error fix] Fix async features sometimes still using global onlinePlayers array 2024-06-26 14:37:25 +02:00
NEZNAMY cf4a8ae98d [Error fix] Add check for null team name for redis players (temporarily?) 2024-06-26 14:32:24 +02:00
NEZNAMY f0edfeeebf [Error fix] Add try/catch for RedisBungee message sending 2024-06-26 14:28:50 +02:00
NEZNAMY 4d54ad35ff [Bug fix] Colors not displaying correctly in Fabric and Velocity console 2024-06-26 14:13:46 +02:00
NEZNAMY b6b512f788 Merge pull request #1324 from Miyli/v5
[GitHub] Add build number to artifact
2024-06-25 18:02:00 +02:00
Miyli a6076eb6ac [GitHub] Add build number to artifact 2024-06-25 17:40:20 +02:00
NEZNAMY 01b74f0a6e [Bug fix] Unload features in main thread and wait for tasks to shut down to prevent unloading and loading at the same time on reloads 2024-06-25 15:37:40 +02:00
NEZNAMY 6c6752c348 [Velocity] Introducing: Velocity Scoreboard API 2024-06-25 15:10:40 +02:00
NEZNAMY 73873ac193 [Performance] Remove synchronized modifier from method 2024-06-25 14:20:23 +02:00
NEZNAMY 6542dad7bd [Bug fix] Fix synchronized deadlock in placeholder refreshing 2024-06-24 14:16:16 +02:00
NEZNAMY 9bf4a2b555 [Code style] Add and use Scoreboard#renameTeam method 2024-06-21 13:53:06 +02:00
NEZNAMY 66d18a4469 [Code style] Expose Scoreboard#containsTeam method in interface 2024-06-21 13:40:23 +02:00
NEZNAMY 7a489067e6 [NameTags] Fix unregistering and registering team on rename even for players who should not see the team because player is vanished 2024-06-20 22:27:37 +02:00
NEZNAMY 600d6de132 [Code style] Reduce access level of methods 2024-06-20 19:11:02 +02:00
NEZNAMY 48e35e7abd [Performance] Split Header and Footer caches as the values are practically never the same 2024-06-20 14:30:09 +02:00
NEZNAMY 8613e9d734 [Error fix] Add try/catch for PremiumVanish hook 2024-06-20 14:25:50 +02:00
NEZNAMY 9ed277d80d [Error fix] Make methods synchronized to avoid ConcurrentModificationException 2024-06-20 13:44:16 +02:00
NEZNAMY 26345d00a3 [Code style] Clean up method calls 2024-06-20 13:00:57 +02:00
NEZNAMY be70eea0f8 [Performance] [Global playerlist] Add option to disable latency refreshing and disable it by default 2024-06-20 12:14:06 +02:00
NEZNAMY 01357d9ce4 [Sorting] Fix NPE 2024-06-19 17:40:06 +02:00
NEZNAMY 05afafbc45 [Performance] [Global playerlist] Create object for server groups to compare identity instead of having to do heavy checks (also save spy-server status for fast access) 2024-06-18 19:25:55 +02:00
NEZNAMY 68f7272128 [Bug fix] Make plugin message encoding thread shared and don't shut it down, which caused scoreboard packets to not get sent fully on reload, resulting in "Network Protocol Error" on 1.20.5+ 2024-06-18 17:28:40 +02:00
NEZNAMY caa0ab723a [Code style] Reduce calls to "refresh" method in features 2024-06-18 16:40:47 +02:00
NEZNAMY ee0d6002c8 [Code style] Forward group change into features with a new method specifically for that purpose instead of refresh method 2024-06-18 16:30:03 +02:00
NEZNAMY 48f0d04305 [Performance] Only send tablist update packet once for connected player 2024-06-18 15:24:12 +02:00
NEZNAMY 6516b80bf0 Fix error from previous commit 2024-06-18 14:30:45 +02:00
NEZNAMY b0403ff94b [GitHub] Update build.yml dependencies 2024-06-18 14:23:07 +02:00
NEZNAMY 77f88edf08 [Performance] Submit feature refresh task only once instead of once for every player 2024-06-18 14:10:07 +02:00
NEZNAMY be70664c84 [Performance] Use method reference to avoid new lambda instantiation each time 2024-06-18 12:55:27 +02:00
NEZNAMY 37ca992233 [Performance] Store component flat text instead of calculating it each time 2024-06-18 12:42:57 +02:00
NEZNAMY 620904de1c [Performance] Store last placeholder values inside player with placeholder key instead of placeholder with player key 2024-06-18 12:19:33 +02:00
NEZNAMY 9c0fac5c44 [Performance] [BossBar] Store visible boss bars of a player for fast access 2024-06-18 11:48:36 +02:00
NEZNAMY 8846c5a9fe [Performance] [NameTags] Only initialize set if used 2024-06-18 11:14:18 +02:00
NEZNAMY 880d2a8570 [PremiumVanish hook] Make methods synchronized to fix ConcurrentModificationException 2024-06-18 10:18:02 +02:00
NEZNAMY 45b6591518 [API Break] Require placeholders to return String instead of Object 2024-06-17 20:48:09 +02:00
NEZNAMY c6ce99d527 [Code style] Remove redundant SneakyThrows 2024-06-17 20:45:13 +02:00
NEZNAMY 7a7861fd9c [Performance] [Bukkit] Only set field if value actually changed 2024-06-17 20:10:00 +02:00
NEZNAMY 3c11630a6c [Code style] Simplify 2024-06-17 15:50:51 +02:00
NEZNAMY 3b9f4c9884 [RedisBungee] Recode handling of sub-features and small fixes (#1317?) 2024-06-17 15:00:08 +02:00
NEZNAMY 0757fc4352 [RedisBungee] Fix error (#1317) 2024-06-17 10:54:27 +02:00
NEZNAMY c1af721ad2 [NameTags] Properly update online players 2024-06-16 21:17:29 +02:00
NEZNAMY 0ea04394f9 [RedisBungee] Override %serveronline% to count redis players as well (#1316) 2024-06-16 20:09:34 +02:00
NEZNAMY 576c178b4f [NameTags] Move the feature handling to a new custom thread 2024-06-16 19:05:58 +02:00
NEZNAMY 674708cea6 [Playerlist objective] Fix error 2024-06-16 16:54:45 +02:00
NEZNAMY 179b05f992 [Belowname] Fix error 2024-06-16 16:53:26 +02:00
NEZNAMY e2c768bb56 [Performance] Speed up all placeholders returning int by caching string values 2024-06-16 15:10:57 +02:00
NEZNAMY 79e492b230 [Performance] Speed up %gamemode% placeholder 2024-06-16 14:40:07 +02:00
NEZNAMY 708bc5c302 [Global playerlist] Fix error in placeholder before it's loaded 2024-06-16 13:44:19 +02:00
NEZNAMY 9543cd5b5f [Performance] Access player's world and server variables directly instead of via getter 2024-06-16 12:49:56 +02:00
NEZNAMY 0bc8313897 [Technical] Move global playerlist feature handling to a new custom thread 2024-06-16 12:22:30 +02:00
NEZNAMY 91986926cb [Performance] Avoid redundant text color attempt 2024-06-15 17:34:07 +02:00
NEZNAMY ac638add7a [Performance] [Paper] Only rewrite packet if something changed 2024-06-15 15:58:30 +02:00
NEZNAMY 9551783d49 [Performance] [NameTags] Avoid calculating other team properties when changing visibility rule 2024-06-15 15:00:22 +02:00
NEZNAMY 85cf2b3e91 [Performance] [NameTags] Avoid calculating other team properties when changing collision rule 2024-06-15 14:25:05 +02:00
NEZNAMY f98ccb9e44 [Performance] [NameTags] Avoid calculating nametag visibility when only updating prefix/suffix 2024-06-15 13:54:56 +02:00
NEZNAMY 066d50e492 [Performance] Save LuckPerms User object instead of getting it on every request 2024-06-15 12:58:21 +02:00
NEZNAMY 6c823e86fe [Performance] [Scoreboard] Check player's active scoreboard instead of set of players who can see the scoreboard 2024-06-15 12:16:13 +02:00
NEZNAMY 6d3ea577d5 [Error fix] Make method synchronized to avoid ConcurrentModificationException 2024-06-15 11:29:21 +02:00
NEZNAMY 2826e39ea2 [Code style] Make paper a submodule of bukkit 2024-06-15 09:58:48 +02:00
NEZNAMY d855f04612 [Performance] [Fabric] Save enum values in an array for fast access 2024-06-14 23:27:25 +02:00
NEZNAMY 99eb93e66f [Performance] [Bukkit] Add native NMS implementations on 1.20.5+ Paper (where mojang mappings are used) 2024-06-14 23:24:52 +02:00
NEZNAMY e91110d049 [Performance] Avoid double map access 2024-06-14 19:45:44 +02:00
NEZNAMY 8e1bb8519c [Performance] [Bukkit] Save PlayerConnection for sending packets instead of getting it using reflection each time 2024-06-14 19:12:09 +02:00
NEZNAMY 61df01ed1c [Performance] Massively boost last color of prefix getting, also remove cache and save it inside the component 2024-06-14 18:38:15 +02:00
NEZNAMY c219aa83b1 [Code style] Remove unused imports 2024-06-14 15:26:03 +02:00
NEZNAMY 71109b397d [Code style] Remove redundant initialization 2024-06-14 15:23:42 +02:00
NEZNAMY 4fe4c2981d [Code style] Add NotNull annotation 2024-06-14 15:15:16 +02:00
NEZNAMY 2f9f2f722c [Code style] Add NotNull annotation 2024-06-14 15:06:33 +02:00
NEZNAMY 40009d018c [Code style] Clean up code by passing Objective parameter instead of its values 2024-06-14 15:03:29 +02:00
NEZNAMY 251480fd4a [MiniMessage] Add dummy character to parsed text to disable color compacting (#1312) 2024-06-14 14:23:42 +02:00
NEZNAMY 6295145d5b [Scoreboard] Disable check to fix error on reload 2024-06-14 12:09:26 +02:00
NEZNAMY 17f2e67334 [Code style] Construct platform's team immediately to avoid nullability warn 2024-06-14 11:50:39 +02:00
NEZNAMY fe4130a846 [Bukkit] Fix text cutting not considering client version (#1313) 2024-06-14 10:52:57 +02:00
NEZNAMY 141201b7aa [Sponge 7] Add missing text cutting to 32 characters 2024-06-13 21:39:50 +02:00
NEZNAMY 3b3c511572 Change build.yml to use v5 branch 2024-06-13 21:15:10 +02:00
NEZNAMY 8c6cf8ee22 Add 1.21 support 2024-06-13 21:14:05 +02:00
NEZNAMY 231fd36ce9 [Performance] Save FixedFormat across all platforms and store it inside TabComponent class to reduce need for a cache 2024-06-12 20:50:13 +02:00
NEZNAMY fb9dd0a899 [Code style] Extend class to avoid code duplication 2024-06-12 20:02:50 +02:00
NEZNAMY d98bcdb4e8 [Fix] Concurrent access to online players causing features to try to work with players before loading them 2024-06-12 19:15:33 +02:00
NEZNAMY f94a87e6d5 [Performance] Use more efficient caching of last team prefix color 2024-06-12 17:39:30 +02:00
NEZNAMY 1501d5e71e [Performance] Store serialized json of a component inside of it instead of using a cache 2024-06-12 17:24:16 +02:00
NEZNAMY a7d955266d [Code style] Make cache efficiency calculation more accurate 2024-06-12 17:08:01 +02:00
NEZNAMY 9633a6c176 [Code style] Make cache efficiency calculation more accurate 2024-06-12 17:04:33 +02:00
NEZNAMY 9d50090dbd [Performance] Create separate component caches for scoreboard and nametags 2024-06-12 17:02:50 +02:00
NEZNAMY 8b96a8e654 [Performance] Add missing caching for Redis sub-features 2024-06-12 14:15:54 +02:00
NEZNAMY 453288bf77 [Performance] Convert text to TabComponent in BossBar immediately and cache it 2024-06-12 14:08:59 +02:00
NEZNAMY 26fb0ec2e9 [Performance] Split component caches for some features as they usually never share the same inputs across features, reducing efficiency 2024-06-12 13:53:03 +02:00
NEZNAMY 2671052cad [Code style] Remove redundant SneakyThrows 2024-06-11 22:08:20 +02:00
NEZNAMY 7f1bcd69c8 [Code style] Save platform's scoreboard teams and objectives inside TAB's classes for fast access 2024-06-11 21:23:18 +02:00
NEZNAMY 5fe83af514 [Fix] Make scoreboard methods synchronized to prevent values not getting saved when accessing from multiple threads (despite being concurrent) 2024-06-11 21:12:54 +02:00
NEZNAMY 89dc2f09fc [Fix] Make player properties ConcurrentHashMap to fix issues with parallel access 2024-06-11 20:53:15 +02:00
NEZNAMY 7ce564409a [Fix] Disable instance activity check as internals were accidentally triggering it, especially with parallel processing 2024-06-11 18:32:05 +02:00
NEZNAMY 2e34069d14 [Fix] Add "synchronized" to method to fix concurrent access error 2024-06-11 18:27:21 +02:00
NEZNAMY e435501501 [Sorting] Fix sorting by pre-defined values space trimming inconsistency 2024-06-11 15:38:38 +02:00
NEZNAMY 51402637c6 [Fabric] Update to 1.21-rc1 2024-06-10 16:29:25 +02:00
NEZNAMY 44593f7a75 [Code style] Create custom thread executor class for easier usage 2024-06-10 14:10:27 +02:00
NEZNAMY 48596efa77 [Technical] Process Belowname and Playerlist Objective feature in a separate custom thread 2024-06-10 13:10:11 +02:00
NEZNAMY 3e323df70d [Fix] Process Header/Footer disable condition change in its own thread 2024-06-10 11:01:50 +02:00
NEZNAMY 0d89cef519 [Technical] Process SpectatorFix feature in a separate custom thread 2024-06-09 13:00:03 +02:00
NEZNAMY f758fdf8f3 [Code style] Create runnables instead of using lambdas for cleaner stack traces 2024-06-09 12:43:15 +02:00
NEZNAMY 44b9f06087 [Technical] Process Scoreboard feature in a separate custom thread 2024-06-09 12:12:22 +02:00
NEZNAMY d1155d5e7a [Fabric] Update to 1.21-pre4 2024-06-07 18:39:15 +02:00
NEZNAMY 51ccd029ca [Layout] Fix feature attempting to format tablist names of players who have them disabled with condition 2024-06-07 15:26:48 +02:00
NEZNAMY 1df83dac91 [Fix] RejectedExecutionException in feature threads on reload 2024-06-07 14:49:21 +02:00
NEZNAMY a3c1d643fd [Fix] Refresh call not being called in feature's thread 2024-06-07 14:05:21 +02:00
NEZNAMY 4350e311e7 [Fix] Throwables not being logged when thrown thrown in new threads 2024-06-07 13:48:39 +02:00
NEZNAMY 89ec9355bd [Technical] Process PingSpoof feature in a separate custom thread 2024-06-07 12:15:58 +02:00
NEZNAMY f2be7220f3 [Layout] Send fake ping if ping spoof is enabled immediately instead of having to rewrite it 2024-06-07 12:05:08 +02:00
NEZNAMY 0cbac252a5 [Technical] Process BossBar feature in a separate custom thread 2024-06-07 11:43:16 +02:00
NEZNAMY 3ea60971be [Fix] ConcurrentModificationException on high player counts 2024-06-06 22:09:48 +02:00
NEZNAMY 8ea76e2016 [Technical] Create Runnables instead of using lambdas for cleaner stack traces 2024-06-06 22:01:02 +02:00
NEZNAMY 86d2d732eb [BungeeCord] Fix NPE in packet reader 2024-06-06 18:15:08 +02:00
NEZNAMY 55ce84a5ae [Technical] Process Header/Footer in a separate custom thread (and prepare other features) 2024-06-06 18:07:40 +02:00
NEZNAMY 0f0e4de682 [Code style] Replace forEach with for loop for shorter stack traces 2024-06-06 14:54:20 +02:00
NEZNAMY ad60372cf2 [Error handling] Properly print errors if thrown in recently added threads 2024-06-06 14:29:42 +02:00
NEZNAMY 3cf5fd7cbd [Technical] Run plugin message encode and send in a new custom thread 2024-06-06 13:48:18 +02:00
NEZNAMY ce090566cc [Technical] Run tablist entry anti-override task in a new custom thread 2024-06-05 22:19:58 +02:00
NEZNAMY 866f645eed [Code style] Replace forEach with for loop for shorter stack traces 2024-06-05 22:17:35 +02:00
NEZNAMY 64f3bebd3d [Fabric] Update to 1.21-pre3 2024-06-05 15:50:49 +02:00
NEZNAMY 239cc461d5 [Code style] Use shorter alternative 2024-06-05 11:28:57 +02:00
NEZNAMY b832784977 [Code style] Refactor pipeline injection on BungeeCord 2024-06-05 11:09:59 +02:00
NEZNAMY 0f37b0be54 [RedisBungee] Add config option for disabling RedisBungee hook 2024-06-03 17:10:11 +02:00
NEZNAMY bb428af3af [Configuration] Add another misconfiguration check 2024-06-03 11:37:03 +02:00
NEZNAMY 1c4d9704f2 [Commands] Fix error when setting property value to ' or " 2024-06-01 16:48:24 +02:00
NEZNAMY 53a5eae721 [Fabric] Update to 1.21-pre2 2024-05-31 20:45:42 +02:00
NEZNAMY 049ab257e2 [Placeholders] Fix server placeholders not returning proper value on request sometimes 2024-05-30 16:28:38 +02:00
NEZNAMY 0ece5319ea [Fabric] Update to 1.21-pre1 2024-05-29 20:32:10 +02:00
NEZNAMY b847cc1178 [Teams] [Bukkit] Fix team action not being taken correctly on 1.8 - 1.12, causing issues with previous commit 2024-05-29 13:36:22 +02:00
NEZNAMY 422da3e1ba [Commands] Fix error in debug command 2024-05-28 14:52:40 +02:00
NEZNAMY 868c6baebf [Teams] Fix another instance of "Network Protocol Error" disconnect on 1.20.5+ 2024-05-28 14:27:47 +02:00
NEZNAMY 9a3cf167ed [Performance] Change way placeholder values are stored in plugin's PlaceholderAPI expansion for better performance 2024-05-27 22:46:10 +02:00
NEZNAMY cdf47ebab0 [Visuals] Refresh permission groups in another thread (also split its CPU usage tracking to 2 parts) 2024-05-27 22:06:00 +02:00
NEZNAMY 929d1f945b [Code style] Replace try/catch with SneakyThrows 2024-05-27 15:46:01 +02:00
NEZNAMY 2671242d2c [Code style] Remove duplicate class mapping 2024-05-27 14:25:34 +02:00
NEZNAMY 78cff8dea8 [Bukkit] Add support for latest 1.21 snapshot 2024-05-27 13:43:32 +02:00
NEZNAMY 7ac3840831 [Javadoc] Fix typo 2024-05-27 13:42:51 +02:00
NEZNAMY 556ee6df01 [Sorting] Make number padding correct (may rarely only insert 1 character) 2024-05-26 12:48:28 +02:00
NEZNAMY 2eca28e497 [Sorting] Fix number compression bug (#1297) 2024-05-26 12:37:35 +02:00
NEZNAMY 76ff5d49e2 [Code style] Simplify array creation 2024-05-25 21:50:43 +02:00
NEZNAMY 2a919aa6b7 [Performance] Fix Header/Footer components not being cached on some platforms from recent commits 2024-05-25 14:21:12 +02:00
NEZNAMY c83f806e4c [Code style] Store expected TabList display names by UUID instead of TabPlayer 2024-05-25 14:07:50 +02:00
NEZNAMY afbfd38dfd [Bukkit] Add back fallback solutions for Scoreboards using Paper API and Bukkit API 2024-05-23 13:51:11 +02:00
NEZNAMY 6874916ce0 [Javadoc] Update class description 2024-05-23 13:10:39 +02:00
NEZNAMY 143c4fc7e4 [Code style] Remove expected header/footer tracking, since it's not used anyway (for now) 2024-05-23 13:04:49 +02:00
NEZNAMY f85e945a32 [Bukkit] Fix compatibility wih Mojang mapped servers jars 2024-05-22 21:21:24 +02:00
NEZNAMY d0110c9a4f [Fabric] Update to 24w21b 2024-05-22 20:54:15 +02:00
NEZNAMY 4307768bee [Code style] Split TabList class into an interface and abstract class 2024-05-22 20:24:59 +02:00
NEZNAMY 88773e3232 [BossBar] Fix error when trying to display bossbar that was not visible on join 2024-05-22 10:14:06 +02:00
NEZNAMY 8f32772f83 Add TODO 2024-05-21 18:09:02 +02:00
NEZNAMY 17b3c28d91 [Performance] Only track placeholder usage if cpu tracking is enabled 2024-05-21 17:58:34 +02:00
NEZNAMY 45945d9a13 [Sponge 8] Add 1.20.6 support 2024-05-21 16:57:45 +02:00
NEZNAMY ba9741a084 [Code style] Split Scoreboard class into and interface and abstract class with tracking, also resend content on server switch using tracked values instead of from each feature individually 2024-05-21 16:04:35 +02:00
NEZNAMY 3bcad371d2 [Code style] Create a class for BossBar managing all safety checks and extend it in all implementations and resend them on server switch internally instead of using bossbar feature itself 2024-05-20 22:06:49 +02:00
NEZNAMY 1e9bf68671 [BossBar] Fix Network Protocol Error disconnect on server switch 2024-05-20 12:37:31 +02:00
NEZNAMY f57df6a536 [BossBar] Make %countdown% return 0 after announcement is over instead of going to negative numbers 2024-05-20 11:18:44 +02:00
NEZNAMY 793536d2d3 [Fix] Fix typo 2024-05-18 18:18:00 +02:00
NEZNAMY 9a348da34f [Code style] Simplify 2024-05-18 17:45:03 +02:00
NEZNAMY 4819bf63b6 [Bukkit] Add back fallback solutions for Header/Footer using Paper API and Bukkit API if NMS fails to load 2024-05-18 17:32:43 +02:00
NEZNAMY 5777d49861 [Performance] [Bukkit] Use Adventure API for BossBars if available for massively improved performance 2024-05-18 12:28:46 +02:00
NEZNAMY 7540c61fde [Code style] Create fields for adventure components from TAB component to allow multiple options in the future 2024-05-18 11:57:59 +02:00
NEZNAMY 308da4cbac [Code style] Create field for feature name and define it in constructor instead of creating abstract functions 2024-05-17 22:17:11 +02:00
NEZNAMY 615af41de8 [Performance] Return placeholder values as strings to prevent boxing objects being created 2024-05-17 20:24:46 +02:00
NEZNAMY 7c473a4e3f [Performance] Change functional interface usage to avoid int -> Integer boxing 2024-05-17 18:58:03 +02:00
NEZNAMY 79b4f9821b [Performance] Add cooldown to player vanish check 2024-05-17 17:23:16 +02:00
NEZNAMY 80825ee701 [Code style] Mark fields of class as NonNull 2024-05-17 16:51:09 +02:00
NEZNAMY aa0e60b091 [Fix] Fix startup error caused by one of the previous commits 2024-05-17 16:50:49 +02:00
NEZNAMY 718cfb682d [Code style] Improve readability of some sorting functions (also probably fix sorting API not working in layout) 2024-05-17 12:49:02 +02:00
NEZNAMY 66f0426e6a [Code style] Remove unused method 2024-05-17 12:25:55 +02:00
NEZNAMY 145671ecdd [Code style] Remove unused methods 2024-05-17 12:22:08 +02:00
NEZNAMY 43d71f7e49 [Code style] Remove unused import 2024-05-17 12:20:26 +02:00
NEZNAMY 150de771d5 [Code style] Initialize map values in construction instead of creating anonymous class 2024-05-17 12:20:16 +02:00
NEZNAMY 50da728abf [Code style] Remove redundant checks 2024-05-17 11:05:28 +02:00
NEZNAMY 656ce6ecad [Performance] Create player data classes for Tablist formatting and teams to avoid Map lookup each time 2024-05-17 11:01:54 +02:00
NEZNAMY 8a758dffec [Performance] Create player data class for Belowname to avoid Map lookup each time 2024-05-16 21:14:07 +02:00
NEZNAMY 52bd538ed0 [Performance] Create player data class for Playerlist objective to avoid Map lookup each time 2024-05-16 20:58:49 +02:00
NEZNAMY 115c5ac890 [Bug fix] Fix disabled tablist formatting referencing wrong variable 2024-05-16 20:43:46 +02:00
NEZNAMY 0f7d15a4d0 [Performance] Create player data class for Header/Footer to avoid Map lookup each time 2024-05-16 20:39:04 +02:00
NEZNAMY 5b6e07d5d3 [Fabric] Update to 24w20a 2024-05-15 17:22:15 +02:00
NEZNAMY 8579ed322d [Code style] Remove redundant class wrapper for a Map 2024-05-15 15:19:53 +02:00
NEZNAMY 44464b605d [Fabric] Replace Minecraft version check with fabric networking api check (which is what is preventing the mod from working there) 2024-05-14 11:30:08 +02:00
NEZNAMY e8124dd280 [Fabric] Replace Fabric API call with direct NMS call 2024-05-13 22:25:19 +02:00
NEZNAMY 0105fc2087 [Fabric] Remove unused field 2024-05-12 14:10:28 +02:00
NEZNAMY 39e7b26128 [Fabric] Remove entity packets from access widener 2024-05-12 14:05:58 +02:00
NEZNAMY ddbc13fc27 [Scoreboard] Remove respect-other-plugins config option, it is now forced 2024-05-12 13:57:15 +02:00
NEZNAMY 80660fa6cf [Performance] Small optimization of scoreboard packet processing 2024-05-12 13:16:52 +02:00
NEZNAMY b4a94e8da5 [Code style] Remove unused method 2024-05-12 12:34:32 +02:00
NEZNAMY 051c9a8c85 [BossBar] Add console warn if used color or style is invalid 2024-05-12 12:27:28 +02:00
NEZNAMY 840b1ea4a2 [Code style] Remove unused field 2024-05-12 11:32:14 +02:00
NEZNAMY ea57695a31 [Fix] Remove forgotten references to deleted unlimited nametag feature 2024-05-12 11:28:49 +02:00
NEZNAMY 90778bc84c Merge branch 'master' into v5 2024-05-11 21:31:45 +02:00
NEZNAMY 949216325f [Nametags] Only make nametags of invisible players invisible for 1.8 players to compensate for the bug instead of everyone (#1287) 2024-05-11 21:29:55 +02:00
NEZNAMY cdb521de1f [BungeeCord] Initialize different BossBar instance for 1.8 players to avoid version check every time 2024-05-11 17:01:49 +02:00
NEZNAMY e50b39c559 [Javadoc] Document more methods 2024-05-11 14:36:01 +02:00
NEZNAMY 80afb497e0 [Code style] Remove unused field 2024-05-11 13:34:53 +02:00
NEZNAMY e83a12aaee [Fabric] Mark 1.17 as supported again 2024-05-11 13:24:12 +02:00
NEZNAMY 461860bb17 Delete unlimited nametag and wither bossbar features 2024-05-11 13:22:00 +02:00
NEZNAMY cff4e3e6b0 5.0.0-SNAPSHOT 2024-05-11 12:20:46 +02:00
NEZNAMY dddf0b35a2 [Code style] Add missing access level to field 2024-05-11 11:43:51 +02:00
NEZNAMY 35ec2bf215 [Code style] Make fields final 2024-05-11 11:43:05 +02:00
NEZNAMY 607b60b323 [Code style] Remove unused method 2024-05-11 11:41:35 +02:00
NEZNAMY bb6ad72962 [Code style] Improve presence check of Player#getPing method 2024-05-10 17:05:47 +02:00
NEZNAMY 4fb8420d69 [Code style] Remove unused method 2024-05-10 16:59:11 +02:00
NEZNAMY 74af19a9d9 [Code style] Move field to a local variable 2024-05-10 16:53:51 +02:00
NEZNAMY 737ac70e6e [Fabric] Update to 24w19a 2024-05-10 16:28:39 +02:00
NEZNAMY bfc72b735c [Fabric] Add another submodule for 1.18.2 to avoid hardcoding method and class names 2024-05-10 16:05:09 +02:00
NEZNAMY 323412f935 [Code style] Decrease field visibility 2024-05-10 14:56:00 +02:00
NEZNAMY db34ba4995 [Fabric] Improve check for Fabric API features availability 2024-05-10 14:54:01 +02:00
NEZNAMY 15d7eb6d91 [Javadoc] Document more methods 2024-05-10 14:50:02 +02:00
NEZNAMY e2b95df2e4 [Bukkit] Explicitly specify constructor to fix compatibility with forge mods that widen a constructor access, causing TAB to choose the wrong one on 1.19.3+ (#1276) 2024-05-10 13:42:50 +02:00
NEZNAMY 1117094412 [Bug fix] Update library to fix error 2024-05-09 14:54:46 +02:00
NEZNAMY 4bab076642 [Bug fix] Fix group change from Bridge not being processed correctly (#1281) 2024-05-09 13:38:36 +02:00
NEZNAMY d602818f67 [Bug fix] Disable tablist containsEntry usage due to a race condition that sometimes makes it not work properly until a better solution is found (#1284) 2024-05-08 09:37:35 +02:00
NEZNAMY 1a76321062 [Fabric] Update to 24w18a 2024-05-04 20:37:08 +02:00
NEZNAMY 4b056529dd [Fix] Add cpu usage of placeholders inside plugin's main thread instead of placeholder thread to avoid incorrect numbers when it's overloaded 2024-05-04 20:29:21 +02:00
NEZNAMY bdbf3ba5b1 [Velocity] Remove message printing if attempting to send plugin message to a player who is not connected to any server (#1248) 2024-05-03 22:35:29 +02:00
NEZNAMY aab9a650b0 Fix startup error when using a plugin that shades MiniMessage without shading component serializers (#1280) 2024-05-03 14:47:25 +02:00
NEZNAMY f8487f07a6 Create array of component decorations to take values from to avoid instantiation each time 2024-05-02 21:06:15 +02:00
NEZNAMY e38721fa69 Speed up function 2024-05-02 19:30:22 +02:00
NEZNAMY 18e52f2f10 Add 1.20.6 to known versions 2024-04-29 18:06:16 +02:00
NEZNAMY f1300410b8 Update Fabric to 1.20.6 2024-04-29 18:03:52 +02:00
NEZNAMY e17059f1f7 Update library to fix error (1276) 2024-04-29 12:26:29 +02:00
NEZNAMY f3e9f7cf06 [BungeeCord] Remove console warn regarding ViaVersion on BungeeCord as it no longer breaks anything 2024-04-28 18:08:50 +02:00
NEZNAMY 63ed24d136 [Bukkit] Use Spigot internals over Paper API as a primary TPS getter (uses 10x less CPU and 100x less memory allocations) 2024-04-28 14:01:33 +02:00
NEZNAMY 66008cd767 [Velocity] Fix "Received packet for unknown scoreboard objective: TAB-BelowName" on server switch 2024-04-27 19:03:46 +02:00
NEZNAMY 1852c1b5c4 Create array to iterate over to prevent creating new instance every time 2024-04-26 20:12:45 +02:00
NEZNAMY 9a4fc2fc73 Fix field type 2024-04-26 11:52:55 +02:00
NEZNAMY 6846a78833 Require plugin list in a bug report 2024-04-26 11:52:15 +02:00
NEZNAMY d3d9e85a7f Merge branch '4.1.5'
# Conflicts:
#	build.gradle.kts
2024-04-26 11:47:03 +02:00
NEZNAMY f867b6b2ea 4.1.5-SNAPSHOT 2024-04-23 21:09:31 +02:00
NEZNAMY 6b38954694 4.1.4 2024-04-23 20:57:31 +02:00
NEZNAMY 9eb350df30 Fix startup on 1.19.3 - 1.20.4 2024-04-23 20:57:12 +02:00
NEZNAMY 639aa5e251 Fix startup on 1.19.3 - 1.20.4 2024-04-23 20:56:06 +02:00
NEZNAMY f089e26998 Added 1.20.5 support to Bukkit 2024-04-23 19:30:14 +02:00
NEZNAMY 9390a402ff Update Fabric to 1.20.5 2024-04-23 17:35:38 +02:00
NEZNAMY 7fda2c5f88 Add 1.20.5 ProtocolVersion constant 2024-04-23 17:35:15 +02:00
NEZNAMY 7f0b2e2b13 Initialize feature data structured on declaration to make it cleaner & fix error 2024-04-23 16:16:51 +02:00
NEZNAMY bdc44d46c8 Add Exception throwing when attempting to use API class instances that got discarded on reload. 2024-04-23 16:10:24 +02:00
NEZNAMY 8d8480469a Update Fabric to 1.20.5-rc3 2024-04-23 15:28:30 +02:00
NEZNAMY a91fe6db36 Remove unused SneakyThrows 2024-04-23 12:51:41 +02:00
NEZNAMY 20d734aa80 Add missing action mapping 2024-04-23 12:43:20 +02:00
NEZNAMY f053bbd544 Access Team packet data directly on 1.17+ Fabric instead of using reflection 2024-04-23 12:40:14 +02:00
NEZNAMY 7eba26cab1 Rename class to match version number 2024-04-23 12:15:47 +02:00
NEZNAMY 36006045e2 Add "listed" TabList flag support into internal API 2024-04-22 16:54:39 +02:00
NEZNAMY d5d025c2c9 Store information about player meeting a feature's disable condition inside player class to improve performance by avoiding Map lookup each time 2024-04-22 14:41:34 +02:00
NEZNAMY 5b7053d500 Store feature data of players inside TabPlayer class to improve performance by avoiding Map lookup 2024-04-22 14:06:37 +02:00
NEZNAMY ce2e60c268 4.1.4-SNAPSHOT 2024-04-21 21:43:25 +02:00
711 changed files with 42967 additions and 21724 deletions

No files matched your search

+1
View File
@@ -5,3 +5,4 @@ gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
-65
View File
@@ -1,65 +0,0 @@
name: Bug Report
description: Plugin does not work as described.
labels: ["Type: Bug"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Describe the bug
description: How the plugin behaves
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: How the plugin is supposed to behave instead with your configuration.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: true
- type: textarea
attributes:
label: Additional info
description: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have read the wiki to make sure it's not an issue with configuration
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
- 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,38 +0,0 @@
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
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Support
url: https://discord.gg/YPqXt63YQj
about: Fresh new community discord providing support with all plugins, including TAB
-57
View File
@@ -1,57 +0,0 @@
name: Error
description: Error message with a stack trace in console or error file.
labels: ["Type: Error"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Stack trace
description: The error message and stack trace you received in console or errors.log file.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included a paste of the error
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
-56
View File
@@ -1,56 +0,0 @@
name: Memory leak
description: Memory leak caused by TAB.
labels: ["Type: Memory leak"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Heap dump
description: Link to uploaded memory dump which I can look into.
validations:
required: true
- type: textarea
attributes:
label: Uncleared object(s)
description: Objects you believe should have been removed from the memory but are not
placeholder: |
There is only 1 player online, but heap dump has 100 instances of TabPlayer
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included download link of my heap dump
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have explained what I believe is wrong with the heap dump
required: true
@@ -1,55 +0,0 @@
name: Performance issue
description: Performance issue in TAB or in server caused by TAB
labels: ["Type: Performance"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Evidence
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included sufficient evidence of the issue
required: true
- label: I ticked all of the boxes without actually reading them
required: false
-29
View File
@@ -1,29 +0,0 @@
name: Wiki change
description: Change to text on a wiki page or adding a new section.
labels: ["Type: Wiki change"]
body:
- type: input
attributes:
label: Page link
description: Link to the page that should receive the change.
placeholder: https://github.com/NEZNAMY/TAB/wiki/Installation
validations:
required: true
- type: textarea
attributes:
label: Current text (if applicable)
description: Current version of the text (if modifying), ignore in case of new section.
placeholder: |
Installation on Bukkit/Spigot/Paper is as simple as it can be. Just put the plugin into plugins folder and restart the server. You can also run `/plugman load tab` to load the plugin while server is running.
validations:
required: false
- type: textarea
attributes:
label: New text
description: What the text should be changed to / how should new section look
placeholder: |
Put the plugin into plugins folder and restart the server. In case you don't want to restart your server, loading with plugins like PlugMan is supported.
validations:
required: true
-42
View File
@@ -1,42 +0,0 @@
name: Build
on:
push:
branches:
- master
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 #https://github.com/actions/checkout/releases
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 21
uses: actions/setup-java@v4 #https://github.com/actions/setup-java/releases
with:
distribution: 'temurin'
java-version: 21
- name: Cache SonarCloud packages
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}"
restore-keys: |
${{ runner.os }}-gradle-
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
run: ./gradlew clean build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Click here to download
path: jar/build/libs/TAB-**.jar
+4 -1
View File
@@ -6,4 +6,7 @@ is in quality of the report (typically providing correct steps to reproduce that
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
* Create spark reports and then send them to me. I will analyze them and try to find optimizations to make the plugin run faster. Commands:
* `/spark profiler start --timeout 300 --thread * --interval 0.1`
* `/spark profiler start --timeout 300 --thread * --interval 0.1 --alloc`
+24 -55
View File
@@ -1,58 +1,27 @@
# About
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
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.
# TAB-OG
# Download
### Releases
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
TAB-OG is a soft fork of [TAB](https://github.com/NEZNAMY/TAB) for
[TrueOG Network](https://true-og.net/), based on upstream commit `93cfd74`.
### 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) (you must be logged into GitHub to be able to download it).
Differences from upstream TAB:
- Versioned `5.9.9` (upstream is on `6.x`), targeting Purpur 1.19.4 serving 1.8–1.21.11
clients through ViaVersion, ViaBackwards, and ViaRewind.
- Builds only Bukkit `v1_19_R3`, BungeeCord, and Velocity; drops Fabric, Forge, NeoForge,
and the unused Bukkit/Paper NMS modules.
- Uses LuckPerms `5.5` (Vault removed) with a GraalVM JDK 17 toolchain, producing one
universal shaded JAR.
- TrueOG compatibility fixes: [Vanish-OG](https://git.trueog.net/trueog/Vanish-OG), the
[AFK-OG](https://git.trueog.net/trueog/AFK-OG) indicator, legacy scoreboard team colors,
and disabled scoreboard number formats.
- Legacy (1.8–1.12.2) nametag rendering: over-head names drop the rank brackets (the
tablist keeps them), sit one space after the rank in its color, bold the AFK indicator
for every client, and fit within the 16-character team prefix/suffix limits. A bold
rank is kept only when the whole tag can render bold — legacy clients width-count
everything after `§l` as bold and never receive a `§r`, so a plain-rendered name after
a bold rank pads the nametag end with phantom pixels; when it cannot fit, bold is
stripped over-head. Keeping bold requires `add-teamcolor-to-prefix: false` in the
ViaBackwards config, otherwise the appended team color un-bolds the name again.
- Ships live TrueOG Network config values.
# Compiling
Compilation requires JDK 21 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Documentation
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
of all features, as well as information regarding compatibility or limitations of each feature.
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
# 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> - Unrestricted assistance with the plugin for those, who bought a copy of the plugin when it was purchasable.
<sup>3</sup> - Unlimited access to my free time for everyone in the world.
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
*Instead, you can use [this fresh new community discord](https://discord.gg/YPqXt63YQj) made by a nice person who will provide this service.
Build with `./gradlew build` (JAR in `jar/build/libs/`). Requires LuckPerms; bold legacy
nametags additionally require ViaBackwards with `add-teamcolor-to-prefix: false`.
+1 -1
View File
@@ -1,5 +1,5 @@
dependencies {
compileOnlyApi("org.jetbrains:annotations:24.1.0")
compileOnlyApi("org.jetbrains:annotations:26.0.2")
}
tasks.javadoc {
@@ -17,21 +17,24 @@ public interface TabPlayer {
*
* @return Player's name
*/
@NotNull String getName();
@NotNull
String getName();
/**
* Returns player's UUID
*
* @return Player's UUID
*/
@NotNull UUID getUniqueId();
@NotNull
UUID getUniqueId();
/**
* Returns platform-specific entity
*
* @return platform's player object
*/
@NotNull Object getPlayer();
@NotNull
Object getPlayer();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
@@ -47,7 +50,8 @@ public interface TabPlayer {
*
* @return player's primary permission group
*/
@NotNull String getGroup();
@NotNull
String getGroup();
/**
* Temporarily overrides player's group and applies all changes coming from new group.
@@ -73,7 +77,7 @@ public interface TabPlayer {
* Changes expected profile name of the player. This adapts all name-bound features
* to use this new profile name if another plugin changed profile name of the player. <p>
* Automatic profile name change detection is available on Bukkit, BungeeCord and Fabric,
* therefore using this is redundant there. This function is only needed on Sponge and Velocity,
* therefore using this is redundant there. This function is only needed on Velocity,
* where the detection is not available. <p>
* Warning: This function does NOT change player's profile name, it only updates the tracked
* name inside the plugin.
@@ -91,4 +95,31 @@ public interface TabPlayer {
*/
@NotNull
String getExpectedProfileName();
/**
* Returns name of the server the player is connected to.
* This requires TAB on proxy.
* On backend installation, this method returns {@code "N/A"}.
*
* @return Player's server on proxy or {@code "N/A"} on backend installation
*/
@NotNull
String getServer();
/**
* Returns name of the world the player is in.
* On proxy installation, TAB-Bridge must be installed to forward this info to the proxy.
* Otherwise, it will return {@code "N/A"}.
*
* @return Player's world or {@code "N/A"} on proxy installation without TAB-Bridge installed
*/
@NotNull
String getWorld();
/**
* Returns {@code true} if this player is a bedrock player, {@code false} if not.
*
* @return {@code true} if this player is a bedrock player, {@code false} if not
*/
boolean isBedrockPlayer();
}
@@ -10,11 +10,7 @@ import org.jetbrains.annotations.NotNull;
/**
* An interface representing a BossBar line.
* <p>
* For clients 1.8 and lower it uses wither, which only allows 1 bar
* to be displayed at a time. Entity packets are not available on BungeeCord,
* where nothing will be displayed for these players.
* <p>
* For 1.9+ it uses the new BossBar feature, allowing configurable styles
* It uses 1.9+ BossBar feature, allowing configurable styles
* and colors, as well as display several bars at once. Limit of maximum
* displayed BossBars depends on client resolution and GUI scale, up to
* 9 concurrent BossBars displayed at once.
@@ -205,7 +201,7 @@ public interface BossBar {
* Player to check
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
*/
boolean containsPlayer(TabPlayer player);
boolean containsPlayer(@NonNull TabPlayer player);
/**
* Returns {@code true} if this boss bar is configured as announcement-only bar,
@@ -1,14 +1,14 @@
package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* An interface allowing work with BossBars such as creating, sending
* and toggling.
@@ -78,6 +78,28 @@ public interface BossBarManager {
*/
@NotNull Map<String, BossBar> getRegisteredBossBars();
/**
* Removes BossBar with specified name from the manager, making it no longer available.
* If BossBar was displayed to any players, it is removed.
*
* @param name
* Name of registered BossBar to remove
* @throws IllegalArgumentException
* if no BossBar with specified name exists
*/
void removeBossBar(@NonNull String name);
/**
* Removes BossBar from the manager, making it no longer available.
* If BossBar was displayed to any players, it is removed.
*
* @param bossBar
* BossBar to remove
* @throws IllegalArgumentException
* if specified BossBar is not registered
*/
void removeBossBar(@NonNull BossBar bossBar);
/**
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
*
@@ -117,14 +139,14 @@ public interface BossBarManager {
void setBossBarVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
* Temporarily displays registered BossBar to player for specified amount of time in seconds.
*
* @param player
* Player to show BossBar to
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* In seconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #announceBossBar(String, int)
@@ -133,12 +155,12 @@ public interface BossBarManager {
void sendBossBarTemporarily(@NonNull TabPlayer player, @NonNull String bossBar, int duration);
/**
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
* Temporarily displays registered BossBar to all players for specified amount of time in seconds.
*
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* In seconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #sendBossBarTemporarily(TabPlayer, String, int)
@@ -1,48 +0,0 @@
package me.neznamy.tab.api.event.plugin;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.TabEvent;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Called when an unknown placeholder is about to be registered.
* It defaults to a PlaceholderAPI implementation, but can be overridden.
*/
public interface PlaceholderRegisterEvent extends TabEvent {
/**
* Returns placeholder identifier starting and ending with {@code %}
*
* @return placeholder identifier starting and ending with {@code %}
*/
@NotNull String getIdentifier();
/**
* Sets placeholder to specified server placeholder implementation.
*
* @param supplier
* Placeholder replacer
*/
void setServerPlaceholder(@NonNull Supplier<Object> supplier);
/**
* Sets placeholder to specified player placeholder implementation.
*
* @param function
* Placeholder replacer
*/
void setPlayerPlaceholder(@NonNull Function<TabPlayer, Object> function);
/**
* Sets placeholder to specified relational placeholder implementation.
*
* @param function
* Placeholder replacer
*/
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
}
@@ -0,0 +1,85 @@
package me.neznamy.tab.api.integration;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@Getter
@RequiredArgsConstructor
public abstract class VanishIntegration {
/** List of all registered VanishIntegration handlers */
private static final List<VanishIntegration> HANDLERS = new ArrayList<>();
/** Name of the plugin this integration is associated with */
@NotNull
private final String plugin;
/**
* Determines if a viewer can see a target player.
*
* @param viewer
* The player attempting to view another player
* @param target
* The player being viewed
* @return {@code true} if the viewer can see the target, {@code false} otherwise
*/
public abstract boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target);
/**
* Checks if the specified player is in a vanished state.
*
* @param player
* The player to check
* @return {@code true} if the player is vanished, {@code false} otherwise
*/
public abstract boolean isVanished(@NotNull TabPlayer player);
/**
* Registers this integration handler to the global list of handlers.
*/
public void register() {
registerHandler(this);
}
/**
* Unregisters this integration handler from the global list of handlers.
*/
public void unregister() {
unregisterHandler(this);
}
/**
* Adds a VanishIntegration handler to the global list of handlers.
*
* @param handler
* The handler to register
*/
public static void registerHandler(@NotNull VanishIntegration handler) {
HANDLERS.add(handler);
}
/**
* Removes a VanishIntegration handler from the global list of handlers.
*
* @param handler
* The handler to unregister
*/
public static void unregisterHandler(@NotNull VanishIntegration handler) {
HANDLERS.remove(handler);
}
/**
* Retrieves the list of all registered VanishIntegration handlers.
*
* @return A list of registered VanishIntegration handlers
*/
@NotNull
public static List<VanishIntegration> getHandlers() {
return HANDLERS;
}
}
@@ -3,6 +3,7 @@ package me.neznamy.tab.api.nametag;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -133,7 +134,7 @@ public interface NameTagManager {
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
@@ -147,7 +148,7 @@ public interface NameTagManager {
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
* @see #getOriginalRawSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
@@ -159,7 +160,7 @@ public interface NameTagManager {
* Player to get custom prefix of
* @return Custom prefix assigned using the API.
* @see #setPrefix(TabPlayer, String)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
@Nullable
String getCustomPrefix(@NonNull TabPlayer player);
@@ -172,7 +173,7 @@ public interface NameTagManager {
* Player to get custom suffix of
* @return Custom suffix assigned using the API.
* @see #setSuffix(TabPlayer, String)
* @see #getOriginalSuffix(TabPlayer)
* @see #getOriginalRawSuffix(TabPlayer)
*/
@Nullable
String getCustomSuffix(@NonNull TabPlayer player);
@@ -184,8 +185,8 @@ public interface NameTagManager {
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NonNull
String getOriginalPrefix(@NonNull TabPlayer player);
@NotNull
String getOriginalRawPrefix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
@@ -194,21 +195,87 @@ public interface NameTagManager {
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NonNull
@NotNull
String getOriginalRawSuffix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalReplacedPrefix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalReplacedSuffix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get replaced original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
* @deprecated Use {@link #getOriginalRawPrefix(TabPlayer)} instead.
*/
@Deprecated
@NotNull
String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get replaced original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
* @deprecated Use {@link #getOriginalRawSuffix(TabPlayer)} instead.
*/
@Deprecated
@NotNull
String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Toggles name tag visibility view on all players for specified player.
* On first call, name tags of all players will become invisible for specified
* player. On second call, they will become visible again.
* player. On the second call, they will become visible again.
*
* @param player
* player to toggle name tag visibility view for
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
* {@code true} if the configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Shows back name tag visibility view on all players for specified player.
* If the view is not hidden, nothing happens.
*
* @param player
* player to show name tag visibility view for
* @param sendToggleMessage
* {@code true} if the configured toggle message should be sent, {@code false} if not
*/
void showNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Hides name tag visibility view on all players for specified player.
* If the view is already hidden, nothing happens.
*
* @param player
* player to show name tag visibility view for
* @param sendToggleMessage
* {@code true} if the configured toggle message should be sent, {@code false} if not
*/
void hideNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Returns {@code true} if player has hidden name tags by either calling
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
@@ -1,129 +0,0 @@
package me.neznamy.tab.api.nametag;
import java.util.List;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface for manipulating player name tags.
* <p>
* Instance can be obtained using {@link TabAPI#getNameTagManager()} and then casting it.
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
* return {@code null} or not be an instance of this class.
*/
@SuppressWarnings("unused") // API class
public interface UnlimitedNameTagManager extends NameTagManager {
/**
* Disables armor stands of specified player and displays vanilla name tags instead.
*
* @param player
* Player to disable armor stands of
* @see #enableArmorStands(TabPlayer)
* @see #hasDisabledArmorStands(TabPlayer)
*/
void disableArmorStands(@NonNull TabPlayer player);
/**
* Enabled back armor stands of player, who was disabled previously using
* {@link #disableArmorStands(TabPlayer)}.
*
* @param player
* Player to enable back
* @see #disableArmorStands(TabPlayer)
* @see #hasDisabledArmorStands(TabPlayer)
*/
void enableArmorStands(@NonNull TabPlayer player);
/**
* Returns {@code true} if player has armor stands disabled using
* {@link #disableArmorStands(TabPlayer)}, {@code false} otherwise.
*
* @param player
* Player to check
* @return {@code true} if disabled, {@code false} if not.
* @see #disableArmorStands(TabPlayer)
* @see #enableArmorStands(TabPlayer)
*/
boolean hasDisabledArmorStands(@NonNull TabPlayer player);
/**
* Changes name to specified value. Set to {@code null} to
* reset back to original value.
*
* @param player
* Player to change name of
* @param customName
* New name, or {@code null} to reset to original value
*/
void setName(@NonNull TabPlayer player, @Nullable String customName);
/**
* Changes line to specified value. Set to {@code null} to reset back to original value.
*
* @param player
* Player to change line of
* @param line
* Name of defined line
* @param value
* Value to use, or {@code null} to reset back to original value
* @see #getDefinedLines()
*/
void setLine(@NonNull TabPlayer player, @NonNull String line, @Nullable String value);
/**
* Returns custom name value set using {@link #setName(TabPlayer, String)}.
*
* @param player
* Player to check
* @return Custom name set previously or {@code null} if not set
* @see #setName(TabPlayer, String)
*/
@Nullable String getCustomName(@NonNull TabPlayer player);
/**
* Returns custom line value set using {@link #setLine(TabPlayer, String, String)}.
*
* @param player
* Player to check
* @param line
* Defined line
* @return Custom name set previously or {@code null} if not set
* @see #setName(TabPlayer, String)
* @see #getDefinedLines()
*/
@Nullable String getCustomLineValue(@NonNull TabPlayer player, @NonNull String line);
/**
* Returns original name value configured in files.
*
* @param player
* Player to check
* @return Original configured name
* @see #setName(TabPlayer, String)
*/
@NotNull String getOriginalName(@NonNull TabPlayer player);
/**
* Returns original line value configured in files.
*
* @param player
* Player to check
* @param line
* Line name
* @return Original configured line
* @see #setLine(TabPlayer, String, String)
*/
@NotNull String getOriginalLineValue(@NonNull TabPlayer player, @NonNull String line);
/**
* Returns list of all defined lines in config, both static and dynamic lines.
*
* @return List of all defined lines in config
*/
@NotNull List<String> getDefinedLines();
}
@@ -8,6 +8,8 @@ import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* An interface allowing placeholder registration. Instance can be
@@ -30,7 +32,7 @@ public interface PlaceholderManager {
* If {@code identifier} does not start and end with {@code %} or
* {@code refresh} is not divisible by 50
*/
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<String> supplier);
/**
* Registers a player placeholder (placeholder different output per player)
@@ -46,7 +48,7 @@ public interface PlaceholderManager {
* If {@code identifier} does not start and end with {@code %} or
* {@code refresh} is not divisible by 50
*/
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, Object> function);
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, String> function);
/**
* Registers a relational placeholder (placeholder with output different for each player duo)
@@ -63,7 +65,15 @@ public interface PlaceholderManager {
* does not start with {@code %rel_} or
* {@code refresh} is not divisible by 50
*/
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, String> function);
void registerServerPlaceholder(@NonNull Pattern identifier, int refresh, @NonNull Function<Matcher, Supplier<String>> function);
void registerPlayerPlaceholder(@NonNull Pattern identifier, int refresh,
@NonNull Function<Matcher, Function<me.neznamy.tab.api.TabPlayer, String>> function);
void registerRelationalPlaceholder(@NonNull Pattern identifier, int refresh,
@NonNull Function<Matcher, BiFunction<me.neznamy.tab.api.TabPlayer, me.neznamy.tab.api.TabPlayer, String>> function);
/**
* Returns placeholder from specified identifier. If it does not exist, it is registered
@@ -19,7 +19,7 @@ public interface PlayerPlaceholder extends Placeholder {
* @param value
* New value
*/
void updateValue(@NonNull TabPlayer player, @Nullable Object value);
void updateValue(@NonNull TabPlayer player, @Nullable String value);
/**
* Force-updates placeholder value using previously provided update function.
@@ -22,7 +22,7 @@ public interface RelationalPlaceholder extends Placeholder {
* @param value
* New value
*/
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable Object value);
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable String value);
/**
* Force-updates placeholder value using previously provided update function.
@@ -15,7 +15,7 @@ public interface ServerPlaceholder extends Placeholder {
* @param value
* New value
*/
void updateValue(@Nullable Object value);
void updateValue(@Nullable String value);
/**
* Force-updates placeholder value using previously provided update function.
@@ -17,7 +17,7 @@ public interface Scoreboard {
/**
* Returns internal name of this scoreboard. Name is defined in registration,
* scoreboards from config use name they were defined with. This value is
* used internally and in /tab announce scoreboard command.
* used internally and in /tab scoreboard announce command.
*
* @return custom name of this scoreboard
*/
@@ -84,8 +84,24 @@ public interface Scoreboard {
*/
void removeLine(int index);
/**
* Replaces all current lines with the provided list.
* Supports placeholders, which will automatically be refreshed.
* Client can only see up to 15 lines at a time.
* If the new list is identical to the current one, nothing happens.
*
* @param lines
* New list of lines to display
* @throws NullPointerException
* if {@code lines} is {@code null}
* @see #getLines()
* @see #addLine(String)
* @see #removeLine(int)
*/
void setLines(@NonNull List<String> lines);
/**
* Unregisters this scoreboard from all players who can see it.
*/
void unregister();
}
}
@@ -1,13 +1,13 @@
package me.neznamy.tab.api.scoreboard;
import java.util.List;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* Interface allowing work with scoreboards, such as creating,
* sending, toggling and modifying.
@@ -54,6 +54,29 @@ public interface ScoreboardManager {
* @return map of registered scoreboards
*/
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
/**
* Removes scoreboard with specified name from list of scoreboards. If it was displayed to anyone,
* it will be removed from their display as well. Then, those players will receive a new scoreboard based
* on configuration.
*
* @param name Name of scoreboard to remove
* @throws IllegalArgumentException
* if scoreboard with such name does not exist
*/
void removeScoreboard(@NonNull String name);
/**
* Removes scoreboard from list of scoreboards. If it was displayed to anyone,
* it will be removed from their display as well. Then, those players will receive a new scoreboard based
* on configuration.
*
* @param scoreboard
* Scoreboard to remove
* @throws IllegalArgumentException
* if specified scoreboard is not registered
*/
void removeScoreboard(@NonNull Scoreboard scoreboard);
/**
* Displays scoreboard to defined player. This will disable all display
@@ -27,7 +27,7 @@ public interface TabListFormatManager {
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
@@ -41,7 +41,7 @@ public interface TabListFormatManager {
* @param customName
* new customName value
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
* @see #getOriginalRawName(TabPlayer)
*/
void setName(@NonNull TabPlayer player, @Nullable String customName);
@@ -55,7 +55,7 @@ public interface TabListFormatManager {
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
* @see #getOriginalRawSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
@@ -67,7 +67,7 @@ public interface TabListFormatManager {
* player to get custom prefix of
* @return player's custom prefix or {@code null} if not defined
* @see #setPrefix(TabPlayer, String)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
@@ -104,8 +104,11 @@ public interface TabListFormatManager {
* @return player's original prefix
* @see #setPrefix(TabPlayer, String)
* @see #getCustomPrefix(TabPlayer)
* @deprecated Use {@link #getOriginalRawPrefix(TabPlayer)} instead.
*/
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
@NotNull
@Deprecated
String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns player's original name applied using plugin's internal logic.
@@ -116,8 +119,11 @@ public interface TabListFormatManager {
* @return player's original name
* @see #setName(TabPlayer, String)
* @see #getCustomName(TabPlayer)
* @deprecated Use {@link #getOriginalRawName(TabPlayer)} instead.
*/
@NotNull String getOriginalName(@NonNull TabPlayer player);
@NotNull
@Deprecated
String getOriginalName(@NonNull TabPlayer player);
/**
* Returns player's original suffix applied using plugin's internal logic.
@@ -128,6 +134,69 @@ public interface TabListFormatManager {
* @return player's original suffix
* @see #setSuffix(TabPlayer, String)
* @see #getCustomSuffix(TabPlayer)
* @deprecated Use {@link #getOriginalRawSuffix(TabPlayer)} instead.
*/
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
@NotNull
@Deprecated
String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic.
* @param player
* Player to get original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalRawPrefix(@NonNull TabPlayer player);
/**
* Returns original name assigned by the plugin using internal logic.
* @param player
* Player to get original name of
* @return Original name assigned by the plugin's internal logic.
* @see #getCustomName(TabPlayer)
*/
@NotNull
String getOriginalRawName(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalRawSuffix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalReplacedPrefix(@NonNull TabPlayer player);
/**
* Returns original name assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original name of
* @return Original name assigned by the plugin's internal logic.
* @see #getCustomName(TabPlayer)
*/
@NotNull
String getOriginalReplacedName(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalReplacedSuffix(@NonNull TabPlayer player);
}
@@ -65,6 +65,20 @@ public interface Layout {
*/
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param skin
* Skin definition like in config
* @param ping
* Slot's ping, accepts placeholders that evaluate to a number
*/
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, @NonNull String ping);
/**
* Adds a player group.
*
@@ -3,6 +3,7 @@ package me.neznamy.tab.api.tablist.layout;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -20,7 +21,31 @@ public interface LayoutManager {
* Unique layout name
* @return Created layout
*/
Layout createNewLayout(String name);
@NotNull
Layout createNewLayout(@NonNull String name);
/**
* Creates new layout with given name and slot count (1.19.3+ only).
*
* @param name
* Unique layout name
* @param slotCount
* Amount of slots to use for 1.19.3+ players
* @return Created layout
*/
@NotNull
Layout createNewLayout(@NonNull String name, int slotCount);
/**
* Returns defined layout from config by name. If no such layout is defined in config,
* {@code null} is returned.
*
* @param name
* Name of layout defined in config
* @return Layout defined in config by name or {@code null} if no such layout is defined
*/
@Nullable
Layout getLayout(@NonNull String name);
/**
* Sends layout to player. Set to {@code null} to make player not see
+2 -3
View File
@@ -3,7 +3,6 @@ plugins {
}
dependencies {
implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("io.freefair.gradle:lombok-plugin:8.6")
implementation("fabric-loom:fabric-loom.gradle.plugin:1.6-SNAPSHOT")
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.0")
implementation("io.freefair.gradle:lombok-plugin:9.2.0")
}
+3
View File
@@ -3,6 +3,9 @@ rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
maven("https://maven.fabricmc.net/")
maven("https://maven.architectury.dev/")
maven("https://maven.minecraftforge.net/")
maven("https://maven.neoforged.net/releases/")
gradlePluginPortal()
mavenCentral()
}
@@ -24,13 +24,14 @@ tasks {
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(8)
options.release.set(17)
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
languageVersion.set(JavaLanguageVersion.of(17))
vendor.set(JvmVendorSpec.GRAAL_VM)
}
}
@@ -1,15 +1,26 @@
plugins {
id("tab.standard-conventions")
id("com.github.johnrengelman.shadow")
id("com.gradleup.shadow")
}
tasks.withType<Zip>().configureEach {
isZip64 = true
}
tasks {
shadowJar {
archiveFileName.set("TAB-${project.name}-${project.version}.jar")
archiveFileName.set("${rootProject.name}-${project.name}-${project.version}.jar")
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
relocate("redis.clients.jedis", "me.neznamy.tab.libs.redis.clients.jedis")
relocate("org.apache.commons.pool2", "me.neznamy.tab.libs.org.apache.commons.pool2")
relocate("org.json", "me.neznamy.tab.libs.org.json")
relocate("com.rabbitmq", "me.neznamy.tab.libs.com.rabbitmq")
relocate("com.saicone.delivery4j", "me.neznamy.tab.libs.com.saicone.delivery4j")
relocate("com.mysql", "me.neznamy.tab.libs.com.mysql")
relocate("com.google.protobuf", "me.neznamy.tab.libs.com.google.protobuf")
}
}
@@ -1,25 +1,8 @@
plugins {
id("tab.base-conventions")
`maven-publish`
}
java {
withSourcesJar()
withJavadocJar()
}
publishing {
repositories {
maven {
name = "krypton"
url = uri("https://repo.kryptonmc.org/releases")
credentials(PasswordCredentials::class)
}
}
publications.create<MavenPublication>("mavenJava") {
groupId = rootProject.group as String
artifactId = "tab-${project.name}"
version = rootProject.version as String
from(components["java"])
}
}
}
+27 -18
View File
@@ -1,36 +1,45 @@
plugins {
eclipse
id("tab.parent")
}
allprojects {
group = "me.neznamy"
version = "4.1.3"
version = "5.9.9"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
ext.set("website", "https://git.trueog.net/trueog/TAB-OG")
ext.set("author", "NEZNAMY")
ext.set("credits", "Joseph T. McQuigg (JT122406)")
}
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.fabric,
projects.fabric.v1144
).map { it.dependencyProject }
val platformPaths = setOf(
":bukkit",
":bukkit:v1_19_R3",
":bungeecord",
":velocity"
)
val special = setOf(
projects.api,
projects.shared
).map { it.dependencyProject }
val specialPaths = setOf(
":api",
":shared"
)
subprojects {
when (this) {
in platforms -> plugins.apply("tab.platform-conventions")
in special -> plugins.apply("tab.standard-conventions")
when (path) {
in platformPaths -> plugins.apply("tab.platform-conventions")
in specialPaths -> plugins.apply("tab.standard-conventions")
else -> plugins.apply("tab.base-conventions")
}
}
val copyShadowJar by tasks.registering(Copy::class) {
dependsOn(":jar:shadowJar")
from(project(":jar").tasks.named("shadowJar"))
into(layout.buildDirectory.dir("libs"))
}
tasks.register("build") {
dependsOn(copyShadowJar)
}
+4 -8
View File
@@ -1,12 +1,8 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bukkit:3.0.1")
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.2")
compileOnly("com.mojang:authlib:1.5.25")
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
exclude("org.bukkit", "bukkit")
}
api(projects.shared)
implementation("org.bstats:bstats-bukkit:3.1.0")
compileOnly("org.purpurmc.purpur:purpur-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.6")
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
exclude("org.spigotmc", "spigot-api")
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "1.20.6-R0.1-SNAPSHOT" // 1.20.5 has a broken dependency, but there were no changes, so it's ok
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,64 @@
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.literal(TabObjectComponent.ERROR_MESSAGE);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
return Component.literal(TabObjectComponent.ERROR_MESSAGE);
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,52 @@
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for paper 1.20.5 - 1.21.1.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().connection.connection.channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker() {
return new NMSTabListEntryTracker();
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,176 @@
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
return packet;
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
}
}
@@ -0,0 +1,200 @@
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
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.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
// Added in 1.21.2
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.21.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends TabListEntryTracker {
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "1.21.11-R0.1-SNAPSHOT"
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.*;
import net.minecraft.network.chat.contents.objects.AtlasSprite;
import net.minecraft.network.chat.contents.objects.PlayerSprite;
import net.minecraft.resources.Identifier;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.object(new AtlasSprite(Identifier.parse(sprite.getAtlas()), Identifier.parse(sprite.getSprite())));
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
ResolvableProfile profile;
if (sprite.getId() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getId());
} else if (sprite.getName() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getName());
} else if (sprite.getSkin() != null) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
builder.put(TabList.TEXTURES_PROPERTY, new Property(TabList.TEXTURES_PROPERTY, sprite.getSkin().getValue(), sprite.getSkin().getSignature()));
profile = ResolvableProfile.createResolved(new GameProfile(NIL_UUID, "", new PropertyMap(builder.build())));
} else {
throw new IllegalStateException("Player head component does not have id, name or skin set");
}
return Component.object(new PlayerSprite(profile, sprite.isShowHat()));
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : new FontDescription.Resource(Identifier.parse(modifier.getFont())));
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,52 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.11.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().connection.connection.channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker() {
return new NMSTabListEntryTracker();
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,176 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
return packet;
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,204 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
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.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().properties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().name());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
if (skin != null) {
builder.put(TEXTURES_PROPERTY, new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return new GameProfile(id, name, new PropertyMap(builder.build()));
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends TabListEntryTracker {
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "1.21.3-R0.1-SNAPSHOT" // 1.21.2 causes gradle error, but the versions are interchangeable
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.literal(TabObjectComponent.ERROR_MESSAGE);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
return Component.literal(TabObjectComponent.ERROR_MESSAGE);
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,52 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.2 - 1.21.3.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().connection.connection.channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker() {
return new NMSTabListEntryTracker();
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,176 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
return packet;
}
private static int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
}
}
@@ -0,0 +1,202 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
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.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.21.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends TabListEntryTracker {
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "1.21.4-R0.1-SNAPSHOT"
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,65 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.literal(TabObjectComponent.ERROR_MESSAGE);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
return Component.literal(TabObjectComponent.ERROR_MESSAGE);
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,52 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.4 - 1.21.8.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().connection.connection.channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker() {
return new NMSTabListEntryTracker();
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,176 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
return packet;
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,204 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
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.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends TabListEntryTracker {
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "1.21.9-R0.1-SNAPSHOT"
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.*;
import net.minecraft.network.chat.contents.objects.AtlasSprite;
import net.minecraft.network.chat.contents.objects.PlayerSprite;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.component.ResolvableProfile;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class NMSComponentConverter extends ComponentConverter<Component> {
@Override
@NotNull
public Component newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Component newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Component newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
return Component.object(new AtlasSprite(ResourceLocation.parse(sprite.getAtlas()), ResourceLocation.parse(sprite.getSprite())));
}
@Override
@NotNull
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
ResolvableProfile profile;
if (sprite.getId() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getId());
} else if (sprite.getName() != null) {
profile = ResolvableProfile.createUnresolved(sprite.getName());
} else if (sprite.getSkin() != null) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
builder.put(TabList.TEXTURES_PROPERTY, new Property(TabList.TEXTURES_PROPERTY, sprite.getSkin().getValue(), sprite.getSkin().getSignature()));
profile = ResolvableProfile.createResolved(new GameProfile(NIL_UUID, "", new PropertyMap(builder.build())));
} else {
throw new IllegalStateException("Player head component does not have id, name or skin set");
}
return Component.object(new PlayerSprite(profile, sprite.isShowHat()));
}
@Override
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : new FontDescription.Resource(ResourceLocation.parse(modifier.getFont())));
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Component parent, @NotNull Component child) {
((MutableComponent)parent).append(child);
}
}
@@ -0,0 +1,52 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
import io.netty.channel.Channel;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.9 - 1.21.10.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
@NotNull
public Channel getChannel(@NotNull Player player) {
return ((CraftPlayer)player).getHandle().connection.connection.channel;
}
@Override
@NotNull
public TabListEntryTracker newTabListEntryTracker() {
return new NMSTabListEntryTracker();
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,176 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
return packet;
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,206 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
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.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().properties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().name());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
if (skin != null) {
builder.put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return new GameProfile(id, name, new PropertyMap(builder.build()));
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Implementation of TabListEntryTracker.
*/
public class NMSTabListEntryTracker extends TabListEntryTracker {
@Override
public void onPacketSend(@NotNull Object packet) {
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
for (UUID id : remove.profileIds()) {
tablistEntries.remove(id);
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
tablistEntries.add(nmsData.profileId());
}
}
}
}
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
@@ -9,7 +10,6 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
@@ -17,8 +17,12 @@ import org.jetbrains.annotations.NotNull;
/**
* The core for bukkit forwarding events into all enabled features
*/
@RequiredArgsConstructor
public class BukkitEventListener implements EventListener<Player>, Listener {
/** Platform reference */
private final BukkitPlatform platform;
/**
* Listens to player quit event.
*
@@ -38,6 +42,11 @@ public class BukkitEventListener implements EventListener<Player>, Listener {
*/
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent e) {
TAB.getInstance().addTablistTracker(
e.getPlayer().getUniqueId(),
platform.getImplementationProvider().getChannel(e.getPlayer()),
platform.getImplementationProvider().newTabListEntryTracker()
);
join(e.getPlayer());
}
@@ -52,20 +61,9 @@ public class BukkitEventListener implements EventListener<Player>, Listener {
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
}
/**
* Listens to command event to potentially cancel it.
*
* @param e
* Command event
*/
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
}
@Override
@NotNull
public TabPlayer createPlayer(@NotNull Player player) {
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
return new BukkitTabPlayer(platform, player);
}
}
@@ -1,26 +1,16 @@
package me.neznamy.tab.platforms.bukkit;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Pipeline injection for Bukkit 1.8+.
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
/** Function for getting player's channel */
private static FunctionWithException<TabPlayer, Channel> getChannel;
/**
* Constructs new instance
*/
@@ -28,48 +18,9 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
super("packet_handler");
}
/**
* Attempts to load required classes, fields and methods and marks class as available.
* If something fails, error message is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Field NETWORK_MANAGER;
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "network channel injection", null,
"Unlimited nametag mode not working and being replaced with regular nametags",
"Anti-override for tablist & nametags not working",
"Compatibility with nickname plugins changing player names will not work",
"Scoreboard will not be checking for other plugins");
}
}
/**
* Returns {@code true} if pipeline injection is available, {@code false} if not.
*
* @return {@code true} if pipeline injection is available, {@code false} if not
*/
public static boolean isAvailable() {
return getChannel != null;
}
@Override
@Nullable
@SneakyThrows
@NotNull
protected Channel getChannel(@NotNull TabPlayer player) {
return getChannel.apply(player);
return ((BukkitPlatform)player.getPlatform()).getImplementationProvider().getChannel(((BukkitTabPlayer) player).getPlayer());
}
}
@@ -4,6 +4,7 @@ import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
/**
@@ -14,11 +15,22 @@ public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable() {
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
try {
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
} catch (IllegalStateException e) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion().split("-")[0] + ") is not supported.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.9.4 - 1.15.x, 1.16.5, 1.17.1, 1.18.2 - 1.21.11.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
}
}
@Override
public void onDisable() {
if (TAB.getInstance() == null) return;
TAB.getInstance().unload();
}
}
@@ -1,8 +1,9 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -22,8 +23,8 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
for (TabComponent message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(((BukkitPlatform)TAB.getInstance().getPlatform()).toBukkitFormat(message));
}
} else {
TabPlayer p = null;
@@ -1,45 +1,21 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.bossbar.BossBarLoader;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.entity.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
import me.neznamy.tab.shared.backend.entityview.DummyEntityView;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.chat.component.TabComponent;
import org.bukkit.Statistic;
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;
/**
* TabPlayer implementation for Bukkit platform
*/
@SuppressWarnings("deprecation")
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
@NotNull
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
@NotNull
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
@NotNull
private final BossBar bossBar = BossBarLoader.findInstance(this);
@NotNull
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
/**
* Constructs new instance with given bukkit player
*
@@ -59,12 +35,12 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public int getPing() {
return PingRetriever.getPing(getPlayer());
return getPlatform().getImplementationProvider().getPing(this);
}
@Override
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
getPlayer().sendMessage(getPlatform().toBukkitFormat(message));
}
@Override
@@ -77,12 +53,6 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return LibsDisguisesHook.isDisguised(this);
}
@Override
@Nullable
public TabList.Skin getSkin() {
return tabList.getSkin();
}
@Override
@NotNull
public Player getPlayer() {
@@ -95,13 +65,18 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
public boolean isVanished() {
public boolean isVanished0() {
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
if (v.asBoolean()) return true;
}
return false;
}
@Override
public int getDeaths() {
return getPlayer().getStatistic(Statistic.DEATHS);
}
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
@@ -1,88 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Class containing static util methods used on Bukkit.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BukkitUtils {
/** Whether compatibility exceptions should be printed or not, enabling when adding support for new versions */
public static final boolean PRINT_EXCEPTIONS = false;
private static boolean compatibilityIssue;
/**
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
* and we want to avoid errors.
*
* @return Online players from Bukkit API.
*/
@SuppressWarnings("unchecked")
@SneakyThrows
@NotNull
public static Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
/**
* Prints a console warn that some compatibility issue was found.
*
* @param exception
* Exception thrown when initializing fields, if applicable
* @param failedCheck
* Check that failed
* @param fallback
* Fallback implementation, if available
* @param missingFeatures
* Features that will be broken because of the incompatibility
*/
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
@Nullable String fallback, @NotNull String... missingFeatures) {
StringBuilder sb = new StringBuilder();
sb.append(EnumChatFormat.RED);
sb.append("[TAB] Failed to initialize minecraft fields for ");
sb.append(failedCheck);
sb.append(" due to a compatibility error. ");
if (fallback != null) {
sb.append("Using fallback solution using ").append(fallback).append(". ");
} else {
sb.append("No fallback solution was found. ");
}
if (missingFeatures.length > 0) sb.append("This will result in: ");
for (int i=0; i<missingFeatures.length; i++) {
sb.append("\n").append("#").append(i + 1).append(": ").append(missingFeatures[i]);
}
Bukkit.getConsoleSender().sendMessage(sb.toString());
if (PRINT_EXCEPTIONS) exception.printStackTrace();
compatibilityIssue = true;
}
/**
* Sends a message asking user to update the plugin if some compatibility issue was found.
*/
public static void sendCompatibilityMessage() {
if (!compatibilityIssue) return;
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
"a version with native support for your server version for optimal experience. This plugin version " +
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
}
}
@@ -1,31 +0,0 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.BossBar;
import org.jetbrains.annotations.NotNull;
/**
* Class for finding BossBar implementation based on server and client version.
*/
public class BossBarLoader {
/**
* Finds best available instance for given player.
*
* @param player
* Player to find instance for
* @return BossBar instance for player
*/
@NotNull
public static BossBar findInstance(@NotNull BukkitTabPlayer player) {
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
if (BukkitReflection.getMinorVersion() >= 9) return new BukkitBossBar(player);
// 1.9+ player on 1.8 server, handle using ViaVersion API
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar(player);
// 1.8- server and player, use wither
return new EntityBossBar(player);
}
}
@@ -1,16 +1,17 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.boss.BossBar;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
@@ -18,54 +19,58 @@ import java.util.UUID;
* to allow 1.8 players, it will handle the entity and teleporting by itself.
*/
@RequiredArgsConstructor
public class BukkitBossBar implements BossBar {
public class BukkitBossBar extends SafeBossBar<BossBar> {
/** Flag indicating whether this class is available or not */
@Getter
private static final boolean available = ReflectionUtils.classExists("org.bukkit.boss.BossBar");
/** Style array because names do not match */
private static final org.bukkit.boss.BarStyle[] styles = available ? org.bukkit.boss.BarStyle.values() : null;
/** 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;
/** Bukkit BossBars the player can currently see */
@NotNull
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()),
@NotNull
public BossBar constructBossBar(@NotNull UUID id, @NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
BossBar bar = Bukkit.createBossBar(
player.getPlatform().toBukkitFormat(title),
org.bukkit.boss.BarColor.valueOf(color.name()),
styles[style.ordinal()]
);
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
return bar;
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()));
public void show(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().addPlayer(player.getPlayer());
}
@Override
public void update(@NotNull UUID id, float progress) {
bossBars.get(id).setProgress(progress);
public void updateTitle(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().setTitle(player.getPlatform().toBukkitFormat(bar.getTitle()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).setStyle(styles[style.ordinal()]);
public void updateProgress(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().setProgress(bar.getProgress());
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
public void updateStyle(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().setStyle(styles[bar.getStyle().ordinal()]);
}
@Override
public void remove(@NotNull UUID id) {
bossBars.remove(id).removePlayer(player.getPlayer());
public void updateColor(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().setColor(org.bukkit.boss.BarColor.valueOf(bar.getColor().name()));
}
@Override
public void hide(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().removePlayer(player.getPlayer());
}
}
@@ -1,74 +0,0 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* BossBar using wither entity for <1.9 players on <1.9 servers.
* Additional logic, such as teleporting the entity must be done
* separately, as this class does not handle it.
*/
@RequiredArgsConstructor
public class EntityBossBar implements BossBar {
/** Max health of wither */
private static final int WITHER_MAX_HEALTH = 300;
/** Ideal value for invulnerable time for minimal wither size */
private static final int IDEAL_INVULNERABLE_TIME = 880;
/** Flag making entity invisible */
private static final byte INVISIBLE_FLAG = 1 << 5;
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
DataWatcher w = new DataWatcher();
float health = WITHER_MAX_HEALTH*progress;
if (health == 0) health = 1;
w.setHealth(health);
w.setCustomName(title, player.getVersion());
w.setEntityFlags(INVISIBLE_FLAG);
w.setWitherInvulnerableTime(IDEAL_INVULNERABLE_TIME);
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
DataWatcher w = new DataWatcher();
w.setCustomName(title, player.getVersion());
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NotNull UUID id, float progress) {
DataWatcher w = new DataWatcher();
float health = WITHER_MAX_HEALTH*progress;
if (health == 0) health = 1;
w.setHealth(health);
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {/*Added in 1.9*/}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {/*Added in 1.9*/}
@Override
public void remove(@NotNull UUID id) {
player.getEntityView().destroyEntities(id.hashCode());
}
}
@@ -1,25 +1,24 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossBar;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
*/
@RequiredArgsConstructor
public class ViaBossBar implements BossBar {
public class ViaBossBar extends SafeBossBar<BossBar> {
/** Style array for fast access */
private static final BossStyle[] styles = BossStyle.values();
@@ -28,45 +27,44 @@ public class ViaBossBar implements BossBar {
@NotNull
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
@NotNull
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
TabComponent.optimized(title).toLegacyText(),
@NotNull
public BossBar constructBossBar(@NotNull UUID id, @NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
return Via.getAPI().legacyAPI().createLegacyBossBar(
title.toLegacyText(),
progress,
BossColor.valueOf(color.name()),
styles[style.ordinal()]
);
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(TabComponent.optimized(title).toLegacyText());
public void show(@NotNull BossBarInfo bar) {
bar.getBossBar().addPlayer(player.getPlayer().getUniqueId());
}
@Override
public void update(@NotNull UUID id, float progress) {
viaBossBars.get(id).setHealth(progress);
public void updateTitle(@NotNull BossBarInfo bar) {
bar.getBossBar().setTitle(bar.getTitle().toLegacyText());
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
viaBossBars.get(id).setStyle(styles[style.ordinal()]);
public void updateProgress(@NotNull BossBarInfo bar) {
bar.getBossBar().setHealth(bar.getProgress());
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
public void updateStyle(@NotNull BossBarInfo bar) {
bar.getBossBar().setStyle(styles[bar.getStyle().ordinal()]);
}
@Override
public void remove(@NotNull UUID id) {
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
public void updateColor(@NotNull BossBarInfo bar) {
bar.getBossBar().setColor(BossColor.valueOf(bar.getColor().name()));
}
@Override
public void hide(@NotNull BossBarInfo bar) {
bar.getBossBar().removePlayer(player.getPlayer().getUniqueId());
}
}
@@ -1,273 +0,0 @@
package me.neznamy.tab.platforms.bukkit.entity;
import lombok.*;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.*;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
private static Object DataWatcherSerializer_BOOLEAN;
private static final int armorStandFlagsPosition = EntityData.getArmorStandFlagsPosition(BukkitReflection.getMinorVersion());
/** 1.19.3+ */
private static Constructor<?> newDataWatcher$Item;
/** 1.19.2- */
public static Class<?> DataWatcher;
private static Constructor<?> newDataWatcher;
private static Method DataWatcher_register;
private static Constructor<?> newDataWatcherObject;
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
/**
* Loads all required Fields and throws Exception if something went wrong.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
if (minorVersion >= 9) {
loadSerializers();
}
if (BukkitReflection.is1_19_3Plus()) {
Class<?> dataWatcher$Item = BukkitReflection.getClass("network.syncher.SynchedEntityData$DataValue", "network.syncher.DataWatcher$c");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
newDataWatcher$Item = dataWatcher$Item.getConstructor(int.class, dataWatcherSerializer, Object.class);
} else {
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
if (minorVersion >= 7) {
ComponentConverter.ensureAvailable();
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
Class<?> dataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
"network.syncher.DataWatcherObject", "DataWatcherObject");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
dataWatcherObject, Object.class
);
newDataWatcherObject = dataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
} else {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
Object.class
);
}
}
}
private static void loadSerializers() throws ReflectiveOperationException {
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
DataWatcherSerializer_BYTE = ReflectionUtils.getField(dataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(dataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(dataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
if (BukkitReflection.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
if (BukkitReflection.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (BukkitReflection.getMinorVersion() >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = dataWatcherRegistry.getDeclaredField("h").get(null);
}
}
}
/**
* Sets value into data values
*
* @param position
* data position
* @param serializer
* Object serializer (1.9+)
* @param value
* value
*/
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
dataValues.put(position, new Item(position, serializer, value));
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
setValue(0, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT, Optional.of(TabComponent.optimized(customName).convert(clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > Limitations.BOSSBAR_NAME_LENGTH_1_7 ?
customName.substring(0, Limitations.BOSSBAR_NAME_LENGTH_1_7) : customName);
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(10, null, cutName);
} else {
setValue(5, null, cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (BukkitReflection.getMinorVersion() >= 9) {
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
} else {
setValue(3, null, (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(6, DataWatcherSerializer_FLOAT, health);
} else {
setValue(16, null, (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (BukkitReflection.getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
setValue(20, null, time);
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
@NotNull
public Object build() {
if (BukkitReflection.is1_19_3Plus()) {
List<Object> items = new ArrayList<>();
for (Item item : dataValues.values()) {
items.add(newDataWatcher$Item.newInstance(item.position, item.serializer, item.value));
}
return items;
} else {
Object nmsWatcher;
if (newDataWatcher.getParameterCount() == 1) { //1.7+
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
} else {
nmsWatcher = newDataWatcher.newInstance();
}
for (Item item : dataValues.values()) {
Object nmsObject = item.createObject();
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
}
return nmsWatcher;
}
}
/**
* Class for specific data watcher item.
*/
@RequiredArgsConstructor
@Getter
private static class Item {
/** Item position */
private final int position;
/** Item serializer (1.9+) */
@Nullable
private final Object serializer;
/** Item value */
@NonNull
private final Object value;
/**
* Creates data watcher object with position and serializer
*
* @return NMS data watcher object
*/
@SneakyThrows
public Object createObject() {
if (BukkitReflection.getMinorVersion() >= 9) {
return newDataWatcherObject.newInstance(position, serializer);
} else {
return position;
}
}
}
}
@@ -1,432 +0,0 @@
package me.neznamy.tab.platforms.bukkit.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.*;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.Function;
/**
* EntityView implementation for Bukkit using packets.
*/
@RequiredArgsConstructor
@SuppressWarnings("unchecked")
public class PacketEntityView implements EntityView {
/** Version with entity metadata being split from spawn packet */
private static final int SPLIT_METADATA_VERSION = 15;
private static PacketSender packetSender;
/** PacketPlayOutEntityDestroy */
private static Class<?> EntityDestroyClass;
private static BiConsumerWithException<BukkitTabPlayer, int[]> destroyEntities;
private static FunctionWithException<Object, int[]> getDestroyedEntities;
/** PacketPlayOutEntityMetadata */
private static BiFunctionWithException<Integer, EntityData, Object> newEntityMetadata;
/** PacketPlayOutEntityTeleport */
private static BiFunctionWithException<Integer, Location, Object> newEntityTeleport;
private static Class<?> EntityTeleportClass;
private static Field EntityTeleport_EntityId;
/** PacketPlayOutSpawnEntityLiving */
private static QuintFunction<Integer, UUID, Object, Location, EntityData, Object> newSpawnEntity;
/** Other entity packets */
private static Class<?> PacketPlayOutEntity;
private static Field PacketPlayOutEntity_ENTITYID;
private static Field PacketPlayOutEntity_X;
private static Field PacketPlayOutEntity_Y;
private static Field PacketPlayOutEntity_Z;
private static Class<?> PacketPlayOutEntityLook;
private static QuadFunction<Integer, Long, Long, Long, Object> newMovePacket;
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Constructor<?> newClientboundBundlePacket;
private static Field ClientboundBundlePacket_packets;
private static Function<Object, Boolean> isBundlePacket = packet -> false;
private static BiConsumerWithException<BukkitTabPlayer, Iterable<?>> sendAsBundle = (player, packets) -> {
for (Object packet : packets) {
packetSender.sendPacket(player.getPlayer(), packet);
}
};
@Getter
private static boolean available;
/** Player this view belongs to */
private final BukkitTabPlayer player;
/**
* Loads all required classes and fields and marks class as available.
* If something goes wrong, a warning is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
DataWatcher.load();
loadEntityMetadata();
loadEntityDestroy();
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
if (BukkitReflection.is1_19_4Plus()) {
Class<?> ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
newClientboundBundlePacket = ClientboundBundlePacket.getConstructor(Iterable.class);
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
isBundlePacket = ClientboundBundlePacket::isInstance;
sendAsBundle = (player, packets) -> packetSender.sendPacket(player.getPlayer(), newClientboundBundlePacket.newInstance(packets));
}
packetSender = new PacketSender();
available = true;
} catch (Exception e) {
List<String> missingFeatures = new ArrayList<>();
if (BukkitReflection.getMinorVersion() >= 8) {
missingFeatures.add("Unlimited nametag mode not working and being replaced with regular nametags");
}
if (BukkitReflection.getMinorVersion() <= 8) {
missingFeatures.add("BossBar feature not working");
}
BukkitUtils.compatibilityError(e, "sending entity packets", null, missingFeatures.toArray(new String[0]));
}
}
@SuppressWarnings("JavaReflectionInvocation")
private static void loadEntityMetadata() throws ReflectiveOperationException {
Class<?> entityMetadataClass = BukkitReflection.getClass("network.protocol.game.ClientboundSetEntityDataPacket",
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
if (BukkitReflection.is1_19_3Plus()) {
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, List.class);
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build());
} else {
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build(), true);
}
}
private static void loadEntityDestroy() throws ReflectiveOperationException {
EntityDestroyClass = BukkitReflection.getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
Field entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
try {
Constructor<?> constructor = EntityDestroyClass.getConstructor(int[].class);
destroyEntities = (player, ids) -> packetSender.sendPacket(player.getPlayer(), constructor.newInstance(new Object[]{ids}));
if (BukkitReflection.getMinorVersion() >= 17) {
getDestroyedEntities = packet -> ((List<Integer>) entities.get(packet)).stream().mapToInt(i -> i).toArray();
} else {
getDestroyedEntities = packet -> (int[]) entities.get(packet);
}
} catch (NoSuchMethodException e) {
//1.17.0 Mojank
Constructor<?> constructor = EntityDestroyClass.getConstructor(int.class);
destroyEntities = (player, ids) -> { for (int entity : ids) packetSender.sendPacket(player.getPlayer(), constructor.newInstance(entity));};
getDestroyedEntities = packet -> new int[]{entities.getInt(packet)};
}
}
private static void loadEntityTeleport() throws ReflectiveOperationException {
EntityTeleportClass = BukkitReflection.getClass("network.protocol.game.ClientboundTeleportEntityPacket",
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
Callable<Object> newPacket;
if (BukkitReflection.getMinorVersion() >= 17) {
// Dummy armor stand for constructor
Class<?> world = BukkitReflection.getClass("world.level.Level", "world.level.World", "World");
Class<?> entityArmorStand = BukkitReflection.getClass("world.entity.decoration.ArmorStand",
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
Method World_getHandle = BukkitReflection.getBukkitClass("CraftWorld").getMethod("getHandle");
Object dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
Constructor<?> constructor = EntityTeleportClass.getConstructor(BukkitReflection.getClass("world.entity.Entity"));
newPacket = () -> constructor.newInstance(dummyEntity);
} else {
Constructor<?> constructor = EntityTeleportClass.getConstructor();
newPacket = constructor::newInstance;
}
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
Field EntityTeleport_X;
Field EntityTeleport_Y;
Field EntityTeleport_Z;
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
} else {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
}
newEntityTeleport = (entityId, location) -> {
Object packet = newPacket.call();
EntityTeleport_EntityId.set(packet, entityId);
EntityTeleport_X.set(packet, toPosition(location.getX()));
EntityTeleport_Y.set(packet, toPosition(location.getY()));
EntityTeleport_Z.set(packet, toPosition(location.getZ()));
return packet;
};
}
private static void loadEntityMove() throws ReflectiveOperationException {
// Classes
PacketPlayOutEntity = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket",
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityLook = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
"PacketPlayOutEntityLook", "Packet32EntityLook");
Class<?> packetPlayOutRelEntityMove = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Pos",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMove", "PacketPlayOutEntity$PacketPlayOutRelEntityMove",
"PacketPlayOutRelEntityMove", "Packet31RelEntityMove");
// Fields
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 14) {
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, short.class);
PacketPlayOutEntity_X = fields.get(0);
PacketPlayOutEntity_Y = fields.get(1);
PacketPlayOutEntity_Z = fields.get(2);
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, short.class, short.class, short.class, boolean.class);
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.shortValue(), y.shortValue(), z.shortValue(), false);
} else if (BukkitReflection.getMinorVersion() >= 9) {
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, int.class);
PacketPlayOutEntity_X = fields.get(1);
PacketPlayOutEntity_Y = fields.get(2);
PacketPlayOutEntity_Z = fields.get(3);
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, long.class, long.class, long.class, boolean.class);
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x, y, z, false);
} else if (BukkitReflection.getMinorVersion() == 8) {
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, byte.class);
PacketPlayOutEntity_X = fields.get(0);
PacketPlayOutEntity_Y = fields.get(1);
PacketPlayOutEntity_Z = fields.get(2);
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, byte.class, byte.class, byte.class, boolean.class);
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.byteValue(), y.byteValue(), z.byteValue(), false);
}
}
/**
* Loads required NMS classes, fields and methods. If it fails, exception is thrown.
*
* @throws ReflectiveOperationException
* If anything fails
*/
private static void loadEntitySpawn() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
// Entity ids
EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
if (minorVersion >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (minorVersion >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
Class<?> SpawnEntityClass = BukkitReflection.getClass("network.protocol.game.ClientboundAddEntityPacket",
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
Field SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
if (BukkitReflection.is1_20_2Plus()) {
PacketPlayOutNamedEntitySpawn = SpawnEntityClass;
PacketPlayOutNamedEntitySpawn_ENTITYID = SpawnEntity_EntityId;
} else {
PacketPlayOutNamedEntitySpawn = BukkitReflection.getClass("network.protocol.game.ClientboundAddPlayerPacket",
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
}
Constructor<?> constructor;
// 1.17+ constructor using Spawn entity packet
if (minorVersion >= 17) {
Class<?> Vec3D = BukkitReflection.getClass("world.phys.Vec3", "world.phys.Vec3D");
Object Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
Class<?> EntityTypes = BukkitReflection.getClass("world.entity.EntityType", "world.entity.EntityTypes");
if (minorVersion >= 19) {
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0.0d);
} else {
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
}
return;
}
// 1.16- constructor using spawn entity living packet
constructor = SpawnEntityClass.getConstructor();
@Nullable Field SpawnEntity_UUID;
Field SpawnEntity_X;
Field SpawnEntity_Y;
Field SpawnEntity_Z;
@Nullable Field SpawnEntity_DataWatcher;
Field SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
if (minorVersion >= 9) {
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
} else {
SpawnEntity_UUID = null;
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
}
if (minorVersion < SPLIT_METADATA_VERSION) {
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
} else {
SpawnEntity_DataWatcher = null;
}
newSpawnEntity = (id, uuid, type, l, data) -> {
Object nmsPacket = constructor.newInstance();
SpawnEntity_EntityId.set(nmsPacket, id);
if (minorVersion < SPLIT_METADATA_VERSION) SpawnEntity_DataWatcher.set(nmsPacket, data.build());
if (minorVersion >= 9) SpawnEntity_UUID.set(nmsPacket, uuid);
SpawnEntity_X.set(nmsPacket, toPosition(l.getX()));
SpawnEntity_Y.set(nmsPacket, toPosition(l.getY()));
SpawnEntity_Z.set(nmsPacket, toPosition(l.getZ()));
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) type));
return nmsPacket;
};
}
private static Object toPosition(double paramDouble) {
if (BukkitReflection.getMinorVersion() >= 9) {
return paramDouble;
} else {
int i = (int) (paramDouble*32);
return paramDouble < i ? i - 1 : i;
}
}
@SneakyThrows
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
if (BukkitReflection.getMinorVersion() >= SPLIT_METADATA_VERSION) {
sendAsBundle.accept(player, Arrays.asList(
newSpawnEntity.apply(entityId, id, entityType, l, null),
newEntityMetadata.apply(entityId, data)
));
} else {
packetSender.sendPacket(player.getPlayer(), newSpawnEntity.apply(entityId, id, entityType, l, data));
}
}
@Override
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
packetSender.sendPacket(player.getPlayer(), newEntityMetadata.apply(entityId, data));
}
@SneakyThrows
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
packetSender.sendPacket(player.getPlayer(), newEntityTeleport.apply(entityId, location));
}
@SneakyThrows
@Override
public void destroyEntities(int... entities) {
destroyEntities.accept(player, entities);
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return EntityDestroyClass.isInstance(packet);
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return EntityTeleportClass.isInstance(packet);
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return PacketPlayOutEntity.isInstance(packet);
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return PacketPlayOutEntityLook.isInstance(packet);
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return EntityTeleport_EntityId.getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
}
@Override
@SneakyThrows
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return getDestroyedEntities.apply(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return isBundlePacket.apply(packet);
}
@Override
@SneakyThrows
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
}
@Override
@SneakyThrows
@NotNull
public Location getMoveDiff(@NotNull Object movePacket) {
return new Location(
((Number)PacketPlayOutEntity_X.get(movePacket)).intValue(),
((Number)PacketPlayOutEntity_Y.get(movePacket)).intValue(),
((Number)PacketPlayOutEntity_Z.get(movePacket)).intValue()
);
}
@Override
@SneakyThrows
public void moveEntity(int entityId, @NotNull Location moveDiff) {
packetSender.sendPacket(player.getPlayer(), newMovePacket.apply(
entityId, (long) moveDiff.getX(), (long) moveDiff.getY(), (long) moveDiff.getZ()));
}
}
@@ -1,195 +0,0 @@
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.entity.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX implements Listener {
/** Version in which multiple passenger option was added */
private static final int MULTI_PASSENGER_VERSION = 11;
/**
* Constructs new instance and registers the class listener.
*
* @param plugin
* Plugin instance to use for registering listener
*/
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
/**
* Listener to player sneak event to sneak armor stands.
*
* @param e
* Sneak event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
}
/**
* Listener to respawn event to resend armor stands.
*
* @param e
* Respawn event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRespawn(PlayerRespawnEvent e) {
respawn(e.getPlayer().getUniqueId());
}
@Override
public double getDistance(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
Location loc1 = ((Player) player1.getPlayer()).getLocation();
Location loc2 = ((Player) player2.getPlayer()).getLocation();
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
}
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((Player) player1.getPlayer()).getWorld() == ((Player) player2.getPlayer()).getWorld();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player) target.getPlayer());
}
@Override
public void unregisterListener() {
HandlerList.unregisterAll(this);
}
@SuppressWarnings("deprecation")
@Override
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (BukkitReflection.getMinorVersion() >= MULTI_PASSENGER_VERSION) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger().getEntityId());
} else {
return Collections.emptyList();
}
}
}
@Override
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@Override
public int getEntityId(@NotNull Object entity) {
return ((Entity)entity).getEntityId();
}
@Override
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase(Locale.US);
}
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).isSneaking();
}
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
public boolean isSleeping(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).isSleeping();
}
@Override
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getX();
}
@Override
public double getY(@NotNull Object entity) {
return ((Entity)entity).getLocation().getY();
}
@Override
public double getZ(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getZ();
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.setEntityFlags(flags);
datawatcher.setCustomName(displayName, viewer.getVersion());
datawatcher.setCustomNameVisible(nameVisible);
datawatcher.setArmorStandFlags(datawatcher.MARKER_FLAG);
return datawatcher;
}
@Override
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isDead();
}
@Override
public boolean isFlying(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isFlying();
}
}
@@ -3,26 +3,28 @@ package me.neznamy.tab.platforms.bukkit.features;
import lombok.Getter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.shared.TabConstants;
import me.clip.placeholderapi.expansion.Relational;
import me.neznamy.tab.api.placeholder.Placeholder;
import me.neznamy.tab.shared.ProjectVariables;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.placeholders.types.RelationalPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* TAB's expansion for PlaceholderAPI
*/
@Getter
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
@NotNull
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion, Relational {
/** List of all placeholders offered by the plugin for command suggestions */
@NotNull
@@ -32,21 +34,14 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
"%tab_tagprefix%",
"%tab_tagsuffix%",
"%tab_customtabname%",
"%tab_customtagname%",
"%tab_belowname%",
"%tab_abovename%",
"%tab_tabprefix_raw%",
"%tab_tabsuffix_raw%",
"%tab_tagprefix_raw%",
"%tab_tagsuffix_raw%",
"%tab_customtabname_raw%",
"%tab_customtagname_raw%",
"%tab_belowname_raw%",
"%tab_abovename_raw%",
"%tab_scoreboard_name%",
"%tab_scoreboard_visible%",
"%tab_bossbar_visible%",
"%tab_nametag_preview%",
"%tab_nametag_visibility%",
"%tab_replace_<placeholder>%",
"%tab_placeholder_<placeholder>%"
@@ -60,19 +55,19 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
@Override
@NotNull
public String getAuthor() {
return TabConstants.PLUGIN_AUTHOR;
return ProjectVariables.PLUGIN_AUTHOR;
}
@Override
@NotNull
public String getIdentifier() {
return TabConstants.PLUGIN_ID;
return ProjectVariables.PLUGIN_ID;
}
@Override
@NotNull
public String getVersion() {
return TabConstants.PLUGIN_VERSION;
return ProjectVariables.PLUGIN_VERSION;
}
@Override
@@ -89,25 +84,55 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
String textBefore;
do {
textBefore = text;
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
for (String placeholder : PlaceholderManagerImpl.detectPlaceholders(text)) {
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
PlaceholderAPI.setPlaceholders(player, placeholder)));
}
} while (!textBefore.equals(text));
return text;
}
if (identifier.startsWith("placeholder_")) {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring("placeholder_".length()) + "%", TAB.getInstance().getPlaceholderManager());
}
if (player == null) return "<Player cannot be null>";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
if (p == null) return "<Player is not loaded>";
if (identifier.startsWith("placeholder_")) {
String requestedPlaceholder = "%" + identifier.substring("placeholder_".length()) + "%";
PlaceholderManagerImpl pm = TAB.getInstance().getPlaceholderManager();
pm.addUsedPlaceholder(requestedPlaceholder, pm);
return pm.getPlaceholder(requestedPlaceholder).parse(p);
}
return p.expansionData.getValue(identifier);
}
@Override
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
public String onPlaceholderRequest(@NotNull Player viewer, @NotNull Player target, @NotNull String identifier) {
if (identifier.startsWith("replace_")) {
String text = "%" + identifier.substring(8) + "%";
String textBefore;
do {
textBefore = text;
for (String placeholder : PlaceholderManagerImpl.detectPlaceholders(text)) {
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
PlaceholderAPI.setRelationalPlaceholders(viewer, target, placeholder)));
}
} while (!textBefore.equals(text));
return text;
}
if (identifier.startsWith("placeholder_")) {
String requestedPlaceholder = "%" + identifier.substring("placeholder_".length()) + "%";
PlaceholderManagerImpl pm = TAB.getInstance().getPlaceholderManager();
pm.addUsedPlaceholder(requestedPlaceholder, pm);
Placeholder placeholder = pm.getPlaceholder(requestedPlaceholder);
if (placeholder instanceof RelationalPlaceholderImpl) {
RelationalPlaceholderImpl rel = (RelationalPlaceholderImpl) placeholder;
TabPlayer v = TAB.getInstance().getPlayer(viewer.getUniqueId());
TabPlayer t = TAB.getInstance().getPlayer(target.getUniqueId());
if (v == null || t == null) return "<Player is not loaded>";
return rel.getLastValue(v, t);
} else {
return "<Not a relational placeholder: " + requestedPlaceholder + ">";
}
}
return "<Unknown identifier: \"" + identifier + "\">";
}
@Override
@@ -1,13 +1,15 @@
package me.neznamy.tab.platforms.bukkit.features;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.features.types.UnLoadable;
import me.neznamy.tab.shared.features.types.VanishListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -15,46 +17,59 @@ import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Map.Entry;
/**
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable, VanishListener {
/** Reference to platform */
@NotNull
private final BukkitPlatform platform;
/** Config options */
private final boolean allowBypass = config().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = config().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = config().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
@NotNull
private final PerWorldPlayerListConfiguration configuration;
/** Check for presence of modern (1.12.2+) methods that take plugin as argument to avoid conflict */
private final boolean modernMethodsAvailable = ReflectionUtils.methodExists(Player.class, "hidePlayer", Plugin.class, Player.class);
/**
* Constructs new instance and registers events
* Constructs new instance and registers events.
*
* @param plugin
* Plugin instance to register events
* @param platform
* Platform reference
* @param configuration
* Feature configuration
*/
public PerWorldPlayerList(JavaPlugin plugin) {
public PerWorldPlayerList(@NotNull JavaPlugin plugin, @NotNull BukkitPlatform platform, @NotNull PerWorldPlayerListConfiguration configuration) {
this.configuration = configuration;
this.platform = platform;
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
for (Player p : platform.getOnlinePlayers()) {
checkPlayer(p);
}
}
@Override
public void unload() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
for (Player pl : BukkitUtils.getOnlinePlayers()) {
p.showPlayer(pl);
for (Player p : platform.getOnlinePlayers()) {
for (Player pl : platform.getOnlinePlayers()) {
showPlayer(p, pl);
}
}
HandlerList.unregisterAll(this);
@@ -95,12 +110,34 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
* Player to update
*/
private void checkPlayer(@NotNull Player p) {
for (Player all : BukkitUtils.getOnlinePlayers()) {
for (Player all : platform.getOnlinePlayers()) {
if (all == p) continue;
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
if (shouldSee(p, all)) {
showPlayer(p, all);
} else {
hidePlayer(p, all);
}
if (shouldSee(all, p)) {
showPlayer(all, p);
} else {
hidePlayer(all, p);
}
}
}
private void hidePlayer(@NotNull Player viewer, @NotNull Player target) {
if (modernMethodsAvailable) {
viewer.hidePlayer(platform.getPlugin(), target);
} else {
viewer.hidePlayer(target);
}
}
private void showPlayer(@NotNull Player viewer, @NotNull Player target) {
if (modernMethodsAvailable) {
viewer.showPlayer(platform.getPlugin(), target);
} else {
viewer.showPlayer(target);
}
}
@@ -114,10 +151,13 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
*/
private boolean shouldSee(@NotNull Player viewer, @NotNull Player target) {
if (target == viewer) return true;
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
for (MetadataValue v : target.getMetadata("vanished")) {
if (v.asBoolean() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return false;
}
if ((configuration.isAllowBypassPermission() && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || configuration.getIgnoredWorlds().contains(viewer.getWorld().getName())) return true;
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
String targetWorldGroup = target.getWorld().getName() + "-default";
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
for (Entry<String, List<String>> group : configuration.getSharedWorlds().entrySet()) {
if (group.getValue() != null) {
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
@@ -126,9 +166,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
@Override
public String getFeatureName() {
return "Per world PlayerList";
}
@Override
public void onVanishStatusChange(@NotNull TabPlayer player) {
platform.runSync((Player) player.getPlayer(), () -> checkPlayer((Player) player.getPlayer()));
}
}
@@ -1,93 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.features.types.WorldSwitchListener;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
/**
* An additional class with additional code for &lt;1.9 servers due to an entity being required
*/
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
/** Distance of the wither in blocks */
private static final int WITHER_DISTANCE = 60;
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public WitherBossBar(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
getFeatureName(), TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
super.load();
teleport();
}
/**
* Updates Wither location for all online players
*/
private void teleport() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (BossBar line : getRegisteredBossBars().values()) {
if (!line.containsPlayer(p)) continue;
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
}
}
}
@Override
public void unload() {
super.unload();
HandlerList.unregisterAll(this);
}
/**
* Respawning wither as respawn screen destroys all entities in client.
*
* @param e
* Respawn event
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TabPlayer player = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (player == null) return;
TAB.getInstance().getCPUManager().runMeasuredTask(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> detectBossBarsAndSend(player));
}
@Override
public void onWorldChange(@NotNull TabPlayer p, @NotNull String from, @NotNull String to) {
for (BossBar line : lineValues) {
line.removePlayer(p);
}
detectBossBarsAndSend(p);
}
}
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.hook;
import de.myzelyam.api.vanish.VanishAPI;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
@@ -1,113 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* Class containing main NMS-related information and methods.
*/
public class BukkitReflection {
/** CraftBukkit package */
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
/** Server version data */
private static final ServerVersion serverVersion = detectServerVersion();
/** 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");
/** Flag determining whether the server version is at least 1.19.4 or not */
@Getter
private static final boolean is1_19_4Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundBundlePacket");
/** Flag determining whether the server version is at least 1.20.2 or not */
@Getter
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
/** 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");
private static ServerVersion detectServerVersion() {
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
String[] array = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
int minorVersion;
if (array.length > 3) {
// Normal packaging
String serverPackage = array[3];
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
if (minorVersion < 17) {
ClassLoader loader = BukkitReflection.class.getClassLoader();
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
}
} else {
// Paper without CB relocation
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
}
return new ServerVersion(classFunction, minorVersion);
}
/**
* Returns server's minor version, such as 20 for 1.20.4
*
* @return server's minor version
*/
public static int getMinorVersion() {
return serverVersion.getMinorVersion();
}
/**
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
* without "net.minecraft." prefix, for <1.17 it takes class name only.
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
@SneakyThrows
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return serverVersion.getClass.apply(name);
} catch (ClassNotFoundException | NullPointerException ignored) {
// not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns CraftBukkit class with given package and name.
*
* @param name
* Package and name of the class
* @return CraftBukkit class
* @throws ClassNotFoundException
* If class does not exist
*/
public static Class<?> getBukkitClass(@NotNull String name) throws ClassNotFoundException {
return Class.forName(CRAFTBUKKIT_PACKAGE + "." + name);
}
/**
* Class with server version information.
*/
@RequiredArgsConstructor
@Getter
private static class ServerVersion {
private final FunctionWithException<String, Class<?>> getClass;
private final int minorVersion;
}
}
@@ -1,161 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.BiFunction;
/**
* Class for converting TAB component into NMS components (1.7+).
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class ComponentConverter {
/** Instance of the class */
@Nullable
public static ComponentConverter INSTANCE;
private final FunctionWithException<String, Object> newTextComponent;
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.ChatModifier", "ChatModifier");
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("EnumChatFormat");
private final Constructor<?> newChatModifier;
private final Method ChatBaseComponent_addSibling;
private final Field Component_modifier;
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
// 1.15-
private Method ChatModifier_setColor;
// 1.16+
private Method ChatHexColor_fromRGB;
private Constructor<?> newMinecraftKey;
/**
* Constructs new instance and loads all NMS classes, constructors and methods.
*
* @throws ReflectiveOperationException
* If something failed
*/
private ComponentConverter() throws ReflectiveOperationException {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.IChatBaseComponent", "IChatBaseComponent");
if (BukkitReflection.getMinorVersion() >= 19) {
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.IChatMutableComponent", "IChatMutableComponent");
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
} else {
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.ChatComponentText", "ChatComponentText");
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
newTextComponent = newChatComponentText::newInstance;
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.ChatBaseComponent", "ChatBaseComponent");
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
}
if (BukkitReflection.getMinorVersion() >= 16) {
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.ChatHexColor", "ChatHexColor");
Class<?> MinecraftKey = BukkitReflection.getClass("resources.MinecraftKey", "MinecraftKey");
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ChatClickable", "ChatClickable");
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.ChatHoverable", "ChatHoverable");
newMinecraftKey = MinecraftKey.getConstructor(String.class);
ChatHexColor_fromRGB = ReflectionUtils.getOnlyMethod(chatHexColor, chatHexColor, int.class);
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, MinecraftKey));
convertModifier = this::createModifierModern;
} else {
newChatModifier = ChatModifier.getConstructor();
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
}
}
/**
* Converts TAB component to NMS component.
*
* @param component
* Component to convert
* @param modern
* Whether client supports RGB or not
* @return Converted component
*/
@SneakyThrows
public Object convert(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
StructuredComponent component1 = (StructuredComponent) component;
Object nmsComponent = newTextComponent.apply(component1.getText());
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
for (StructuredComponent extra : component1.getExtra()) {
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
}
return nmsComponent;
}
@SneakyThrows
private Object createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
Object color = null;
if (modifier.getColor() != null) {
if (modern) {
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
} else {
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
}
}
return newChatModifier.newInstance(
color,
modifier.isBold(),
modifier.isItalic(),
modifier.isUnderlined(),
modifier.isStrikethrough(),
modifier.isObfuscated(),
null,
null,
null,
modifier.getFont() == null ? null : newMinecraftKey.newInstance(modifier.getFont())
);
}
@SneakyThrows
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
Object nmsModifier = newChatModifier.newInstance();
if (modifier.getColor() != null) {
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
}
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
return nmsModifier;
}
/**
* Attempts to load component converter.
*/
public static void tryLoad() {
try {
INSTANCE = new ComponentConverter();
} catch (Exception ignored) {
}
}
/**
* Makes sure the component converter is available and throws an exception if not.
*/
public static void ensureAvailable() {
if (INSTANCE == null) throw new IllegalStateException("Component converter is not available");
}
}
@@ -1,56 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class for sending packets to players.
*/
public class PacketSender {
/** Function for sending packet */
@NotNull
private final BiConsumerWithException<Player, Object> send;
/**
* Constructs new instance and attempts to load required classes, fields and methods.
* If something fails, error is thrown.
*
* @throws ReflectiveOperationException
* If something fails
*/
public PacketSender() throws ReflectiveOperationException {
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection", "PlayerConnection");
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Method sendPacket;
if (BukkitReflection.getMinorVersion() >= 7) {
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
send = (player, packet) -> sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
}
/**
* Sends packet to specified player.
* If something goes wrong, throws an exception.
*
* @param player
* Player to send packet to
* @param packet
* Packet to send
*/
@SneakyThrows
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
send.accept(player, packet);
}
}
@@ -1,54 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class for retrieving ping of players.
*/
public class PingRetriever {
/** Ping getter function */
private static FunctionWithException<Player, Integer> getPing;
/**
* Attempts to load required classes, fields and methods and marks class as available.
* If something fails, error message is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
if (BukkitReflection.getMinorVersion() >= 17) {
getPing = Player::getPing;
} else {
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Field PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
getPing = player -> PING.getInt(getHandle.invoke(player));
}
} catch (Exception e) {
getPing = p -> -1;
BukkitUtils.compatibilityError(e, "getting player's ping", null, "%ping% returning -1");
}
}
/**
* Returns player's ping. If ping getter is not available and fields
* failed to load, returns {@code -1}. If an exception was throws by
* reflective operation, it is re-thrown.
*
* @param player
* Player to get ping of
* @return Player's ping or {@code -1} if it failed
*/
@SneakyThrows
public static int getPing(@NotNull Player player) {
return getPing.apply(player);
}
}
@@ -1,56 +1,61 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.*;
import me.neznamy.tab.platforms.bukkit.entity.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.platforms.bukkit.BukkitEventListener;
import me.neznamy.tab.platforms.bukkit.BukkitPipelineInjector;
import me.neznamy.tab.platforms.bukkit.BukkitTabCommand;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
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.shared.TAB;
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.*;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.chat.TabTextColor;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.chat.component.TabKeybindComponent;
import me.neznamy.tab.shared.chat.component.TabTextComponent;
import me.neznamy.tab.shared.chat.component.TabTranslatableComponent;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
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.placeholders.types.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.BossBar;
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.platform.impl.AdventureBossBar;
import me.neznamy.tab.shared.platform.impl.DummyBossBar;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
import net.kyori.adventure.audience.Audience;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.BiConsumer;
/**
* Implementation of Platform interface for Bukkit platform
@@ -63,19 +68,13 @@ public class BukkitPlatform implements BackendPlatform {
private final JavaPlugin plugin;
/** Server version */
@Getter
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
/** NMS server to get TPS from on spigot */
@Nullable
private Object server;
/** TPS field */
@Nullable
private Field spigotTps;
/** Spigot field for tracking TPS, the array is final and only being modified instead of re-instantiated */
private double[] recentTps;
/** Detection for presence of Paper's TPS getter */
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
@@ -83,83 +82,125 @@ public class BukkitPlatform implements BackendPlatform {
/** Detection for presence of Paper's MSPT getter */
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
/** Package name of the server implementation, null on Paper 1.20.5+ / Spigot 26+ */
@Nullable
private final String serverPackage;
/** Implementation for creating new instances using content available on the server */
@NotNull
@Setter
private ImplementationProvider implementationProvider;
private final boolean modernOnlinePlayers;
/** Command map for dynamic command registering */
private final SimpleCommandMap commandMap;
private final Map<String, Command> knownCommands;
/** List of custom commands registered to be able to unregister them on reload */
private final List<Command> customCommands = new ArrayList<>();
/**
* Constructs new instance with given plugin.
*
* @param plugin
* Plugin
*/
@SneakyThrows
@SuppressWarnings("unchecked")
public BukkitPlatform(@NotNull JavaPlugin plugin) {
this.plugin = plugin;
long time = System.currentTimeMillis();
modernOnlinePlayers = Bukkit.class.getMethod("getOnlinePlayers").getReturnType() == Collection.class;
String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
String[] array = CRAFTBUKKIT_PACKAGE.split("\\.");
serverPackage = array.length > 3 ? array[3] : null;
implementationProvider = findImplementationProvider();
logInfo(new TabTextComponent("Found NMS implementation: " + implementationProvider.getClass().getName(), TabTextColor.GRAY));
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
spigotTps = server.getClass().getField("recentTps");
Object server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = ((double[]) server.getClass().getField("recentTps").get(server));
} catch (ReflectiveOperationException ignored) {
//not spigot
}
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
PremiumVanishHook.setInstance(new BukkitPremiumVanishHook());
new BukkitPremiumVanishHook().register();
}
ComponentConverter.tryLoad();
PacketEntityView.tryLoad();
PingRetriever.tryLoad();
TabListBase.findInstance();
ScoreboardLoader.tryLoad();
if (BukkitReflection.getMinorVersion() >= 8) {
BukkitPipelineInjector.tryLoad();
}
BukkitUtils.sendCompatibilityMessage();
Bukkit.getConsoleSender().sendMessage("[TAB] " + EnumChatFormat.GRAY + "Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
commandMap = (SimpleCommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer());
knownCommands = (Map<String, Command>) ReflectionUtils.getField(SimpleCommandMap.class, "knownCommands").get(commandMap);
}
@Override
/**
* Finds implementation provider for current server software and version.
*
* @return Implementation provider for current server
* @throws IllegalStateException
* If no implementation was found
*/
@NotNull
public BossBarManagerImpl getBossBar() {
if (BukkitReflection.getMinorVersion() <= 8) return new WitherBossBar(plugin);
return new BossBarManagerImpl();
private ImplementationProvider findImplementationProvider() {
if (serverPackage != null) {
// Paper <1.20.5 or Spigot 1.x
try {
// Does not actually support flat 1.19, but whatever, no one is using it anyway
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + serverPackage + ".NMSImplementationProvider").getConstructor().newInstance();
} catch (ReflectiveOperationException ignored) {
throw new IllegalStateException("Implementation for version \"" + serverPackage + "\" was not found.");
}
}
Map<ProtocolVersion, String> spigotVersions = new LinkedHashMap<>();
spigotVersions.put(ProtocolVersion.V26_1, "v26_1");
Map<ProtocolVersion, String> paperVersions = new LinkedHashMap<>();
paperVersions.put(ProtocolVersion.V1_20_5, "paper_1_20_5");
paperVersions.put(ProtocolVersion.V1_20_6, "paper_1_20_5");
paperVersions.put(ProtocolVersion.V1_21, "paper_1_20_5");
paperVersions.put(ProtocolVersion.V1_21_1, "paper_1_20_5");
paperVersions.put(ProtocolVersion.V1_21_2, "paper_1_21_2");
paperVersions.put(ProtocolVersion.V1_21_3, "paper_1_21_2");
paperVersions.put(ProtocolVersion.V1_21_4, "paper_1_21_4");
paperVersions.put(ProtocolVersion.V1_21_5, "paper_1_21_4");
paperVersions.put(ProtocolVersion.V1_21_6, "paper_1_21_4");
paperVersions.put(ProtocolVersion.V1_21_7, "paper_1_21_4");
paperVersions.put(ProtocolVersion.V1_21_8, "paper_1_21_4");
paperVersions.put(ProtocolVersion.V1_21_9, "paper_1_21_9");
paperVersions.put(ProtocolVersion.V1_21_10, "paper_1_21_9");
paperVersions.put(ProtocolVersion.V1_21_11, "paper_1_21_11");
String software;
Map<ProtocolVersion, String> versions;
if (paperTps) { // This needs a better check
software = "Paper";
versions = paperVersions;
} else {
software = "Spigot";
versions = spigotVersions;
}
String serverVersionString = Bukkit.getBukkitVersion().split("-")[0];
String implementation = versions.get(serverVersion);
if (implementation == null) {
throw new IllegalStateException(String.format("No implementation is available for your server version (%s %s).", software, serverVersionString));
}
try {
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + implementation + ".NMSImplementationProvider").getConstructor().newInstance();
} catch (ClassNotFoundException e) {
throw new IllegalStateException(String.format("Your server version (%s %s) is marked as compatible, but the implementation does not exist. This is probably a bug.", software, serverVersionString), e);
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(String.format("Failed to initialize implementation for %s %s. This is probably a bug.", software, serverVersionString), e);
}
}
@Override
public void loadPlayers() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
}
}
@Override
public void registerPlaceholders() {
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) {
Chat chat = rspChat.getProvider();
int refresh = TAB.getInstance().getConfiguration().getPermissionRefreshInterval();
manager.registerPlayerPlaceholder("%vault-prefix%", refresh, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", refresh, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
}
}
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
p -> ((TabPlayer) p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
}
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return BukkitReflection.getMinorVersion() >= 8 &&
PacketEntityView.isAvailable() &&
BukkitPipelineInjector.isAvailable() ?
new BukkitNameTagX(plugin) : new NameTag();
return ReflectionUtils.classExists("io.netty.channel.Channel") ? new BukkitPipelineInjector() : null;
}
@Override
@@ -175,8 +216,8 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@Nullable
public TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
return new PerWorldPlayerList(plugin, this, configuration);
}
@Override
@@ -185,39 +226,41 @@ public class BukkitPlatform implements BackendPlatform {
registerDummyPlaceholder(identifier);
return;
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
int refresh = pl.getRefreshInterval(identifier);
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, (viewer, target) ->
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
} else if (identifier.startsWith("%sync:")) {
registerSyncPlaceholder(identifier, refresh);
registerSyncPlaceholder(identifier);
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh,
() -> PlaceholderAPI.setPlaceholders(null, identifier));
boolean stripLeadingAsterisk = identifier.startsWith("%server_tps");
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier,
() -> {
String out = PlaceholderAPI.setPlaceholders(null, identifier);
return stripLeadingAsterisk && out != null && out.startsWith("*") ? out.substring(1) : out;
});
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier,
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
}
}
/**
* Registers a sync placeholder with given identifier and refresh.
* Registers a sync placeholder with given identifier and automatically decided refresh.
*
* @param identifier
* Placeholder identifier
* @param refresh
* Placeholder refresh
*/
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
public void registerSyncPlaceholder(@NotNull String identifier) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
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);
long totalTime = System.nanoTime()-time;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
});
return null;
});
@@ -225,46 +268,37 @@ public class BukkitPlatform implements BackendPlatform {
@Override
public void logInfo(@NotNull TabComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message, true));
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message));
}
@Override
public void logWarn(@NotNull TabComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] [WARN] " + toBukkitFormat(message, true));
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0];
Bukkit.getConsoleSender().sendMessage("§c[TAB] [WARN] " + toBukkitFormat(message));
}
@Override
public void registerListener() {
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), plugin);
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(this), plugin);
}
@Override
public void registerCommand() {
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
PluginCommand command = Bukkit.getPluginCommand(getCommand());
if (command != null) {
BukkitTabCommand cmd = new BukkitTabCommand();
command.setExecutor(cmd);
command.setTabCompleter(cmd);
} else {
logWarn(new SimpleComponent("Failed to register command, is it defined in plugin.yml?"));
logWarn(new TabTextComponent("Failed to register command, is it defined in plugin.yml?", TabTextColor.RED));
}
}
@Override
public void startMetrics() {
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
() -> "1." + BukkitReflection.getMinorVersion() + ".x"));
metrics.addCustomChart(new SimplePie("tab_6_0_0_servers", serverVersion::getFriendlyName));
}
@Override
@@ -274,36 +308,96 @@ public class BukkitPlatform implements BackendPlatform {
}
@Override
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
if (ComponentConverter.INSTANCE != null) {
return ComponentConverter.INSTANCE.convert(component, modern);
} else {
return component;
}
@NotNull
public Object convertComponent(@NotNull TabComponent component) {
return implementationProvider.getComponentConverter().convert(component);
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
}
}
return new GroupManager("None", p -> TabConstants.NO_GROUP);
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
return implementationProvider.newScoreboard((BukkitTabPlayer) player);
}
@Override
@NotNull
public BossBar createBossBar(@NotNull TabPlayer player) {
//noinspection ConstantValue
if (AdventureBossBar.isAvailable() && Audience.class.isAssignableFrom(Player.class)) return new AdventureBossBar(player);
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
if (BukkitBossBar.isAvailable()) return new BukkitBossBar((BukkitTabPlayer) player);
// 1.9+ player on 1.8 server, handle using ViaVersion API
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_9.getNetworkId()) return new ViaBossBar((BukkitTabPlayer) player);
// 1.8- server and player, no implementation
return new DummyBossBar();
}
@Override
@NotNull
public TabList createTabList(@NotNull TabPlayer player) {
return implementationProvider.newTabList((BukkitTabPlayer) player);
}
@Override
public boolean supportsScoreboards() {
return true;
}
@Override
public boolean supportsListed() {
return serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId();
}
@Override
public boolean supportsListOrder() {
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
}
@Override
public boolean isSafeFromPacketEventsBug() {
return serverVersion.getMinorVersion() >= 13;
}
@Override
public void registerCustomCommand(@NotNull String commandName, @NotNull BiConsumer<TabPlayer, String[]> function) {
Command cmd = new BukkitCommand(commandName) {
@Override
public boolean execute(@NotNull CommandSender commandSender, @NotNull String alias, @NotNull String[] args) {
if (commandSender instanceof Player) {
TabPlayer p = TAB.getInstance().getPlayer(((Player) commandSender).getUniqueId());
if (p == null) return false; //player not loaded correctly
function.accept(p, args);
} else {
commandSender.sendMessage(toBukkitFormat(
TabComponent.fromColoredText(TAB.getInstance().getConfiguration().getMessages().getCommandOnlyFromGame())
));
}
return false;
}
};
commandMap.register(commandName, cmd);
customCommands.add(cmd);
}
@Override
public void unregisterAllCustomCommands() {
for (Command command : customCommands) {
knownCommands.remove(command.getName());
knownCommands.remove(command.getName() + ":" + command.getName());
command.unregister(commandMap);
}
}
@Override
@SneakyThrows
public double getTPS() {
if (paperTps) {
if (recentTps != null) {
return recentTps[0];
} else if (paperTps) {
return Bukkit.getTPS()[0];
} else if (spigotTps != null) {
return ((double[]) spigotTps.get(server))[0];
} else {
return -1;
}
@@ -328,54 +422,81 @@ public class BukkitPlatform implements BackendPlatform {
}
/**
* Runs an entity task that may or may not need to use its main thread.
*
* @param entity
* Entity to work with
* @param task
* Task to run
*/
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
if (BackendPlatform.super.canSee(viewer, target)) return true;
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
}
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* Converts component to string using bukkit RGB format if supported by the server.
* If not, closest legacy color is used instead.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
*/
@NotNull
public String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) {
if (component instanceof SimpleComponent) return component.toLegacyText();
StructuredComponent iComponent = (StructuredComponent) component;
public String toBukkitFormat(@NotNull TabComponent component) {
StringBuilder sb = new StringBuilder();
if (iComponent.getModifier().getColor() != null) {
if (serverVersion.supportsRGB() && rgbClient) {
String hexCode = iComponent.getModifier().getColor().getHexCode();
char c = EnumChatFormat.COLOR_CHAR;
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
if (component.getModifier().getColor() != null) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
String hexCode = component.getModifier().getColor().getHexCode();
sb.append('§').append("x").append('§').append(hexCode.charAt(0)).append('§').append(hexCode.charAt(1))
.append('§').append(hexCode.charAt(2)).append('§').append(hexCode.charAt(3))
.append('§').append(hexCode.charAt(4)).append('§').append(hexCode.charAt(5));
} else {
sb.append(iComponent.getModifier().getColor().getLegacyColor());
sb.append('§').append(component.getModifier().getColor().getLegacyColor().getCharacter());
}
}
sb.append(iComponent.getModifier().getMagicCodes());
sb.append(iComponent.getText());
for (StructuredComponent extra : iComponent.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
sb.append(component.getModifier().getMagicCodes());
if (component instanceof TabTextComponent) {
sb.append(((TabTextComponent) component).getText());
} else if (component instanceof TabTranslatableComponent) {
sb.append(((TabTranslatableComponent) component).getKey());
} else if (component instanceof TabKeybindComponent) {
sb.append(((TabKeybindComponent) component).getKeybind());
} else if (component instanceof TabObjectComponent) {
sb.append(component.toLegacyText());
} else {
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
}
for (TabComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra));
}
return sb.toString();
}
}
/**
* Returns online players from Bukkit API.
* This method may use reflections, because the return type changed in 1.7.10,
* and we want to avoid errors.
*
* @return Online players from Bukkit API.
*/
@SneakyThrows
@NotNull
public Collection<? extends Player> getOnlinePlayers() {
if (modernOnlinePlayers) {
return Bukkit.getOnlinePlayers();
}
return Arrays.asList((Player[]) Bukkit.class.getMethod("getOnlinePlayers").invoke(null));
}
@Override
@NotNull
public Object dump() {
Map<String, Object> map = new LinkedHashMap<>();
map.put("server-type", "Bukkit");
map.put("server-name", Bukkit.getName());
map.put("server-version", Bukkit.getBukkitVersion().split("-")[0]);
map.put("craftbukkit-package", serverPackage);
map.put("tab-version", ProjectVariables.PLUGIN_VERSION);
Map<String, Object> plugins = new LinkedHashMap<>();
for (Plugin p : Bukkit.getPluginManager().getPlugins()) {
plugins.put(p.getDescription().getName(), p.getDescription().getVersion());
}
map.put("plugins", plugins);
if (placeholderAPI) {
Map<String, String> expansions = new LinkedHashMap<>();
for (PlaceholderExpansion p : PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getExpansions()) {
expansions.put(p.getIdentifier(), p.getVersion());
}
map.put("placeholderapi-expansions", expansions);
}
return map;
}
}
@@ -1,13 +1,16 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
import me.neznamy.tab.shared.data.World;
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
@@ -15,7 +18,10 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Platform override for Folia.
@@ -36,40 +42,88 @@ public class FoliaPlatform extends BukkitPlatform {
public void loadPlayers() {
super.loadPlayers();
// Values are never updated in the API, warn users
logWarn(new SimpleComponent("Folia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20)."));
// Folia never calls PlayerChangedWorldEvent, this is a workaround
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100, "Folia compatibility", "Refreshing world", () -> {
TAB.getInstance().getCpu().getProcessingThread().repeatTask(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
String bukkitWorld = ((Player)player.getPlayer()).getWorld().getName();
if (!player.getWorld().equals(bukkitWorld)) {
TAB.getInstance().getFeatureManager().onWorldChange(player.getUniqueId(), bukkitWorld);
World actualWorld = World.byName(((Player) player.getPlayer()).getWorld().getName());
if (player.world != actualWorld) {
TAB.getInstance().getFeatureManager().onWorldChange(player.getUniqueId(), actualWorld);
PerWorldPlayerList pwp = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST);
if (pwp != null) {
runSync((Entity) player.getPlayer(), () -> pwp.onWorldChange(new PlayerChangedWorldEvent((Player) player.getPlayer(), ((Player) player.getPlayer()).getWorld())));
}
}
}
});
}, "Folia compatibility", "Refreshing world"), 100);
}
@Override
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
public void registerPlaceholders() {
super.registerPlaceholders();
DecimalFormat decimal2;
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
decimal2 = new DecimalFormat("#.##", symbols);
registerInternalSyncPlaceholder(TabConstants.Placeholder.MSPT, p -> decimal2.format(Bukkit.getAverageTickTime()));
registerInternalSyncPlaceholder(TabConstants.Placeholder.TPS, p -> decimal2.format(Math.min(20, Bukkit.getTPS()[0])));
}
@Override
public void registerSyncPlaceholder(@NotNull String identifier) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
long totalTime = System.nanoTime()-time;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
private void registerInternalSyncPlaceholder(@NonNull String identifier, @NonNull Function<TabPlayer, String> function) {
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = function.apply((TabPlayer) p);
long totalTime = System.nanoTime()-time;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
/**
* Overriding the method to fix initial error caused by ServerPlaceholder implementation trying to
* retrieve the value in constructor, but folia does not support that. Overriding this function to avoid the
* MSPT function being called in the wrong thread.
*
* @return -1
*/
@Override
public double getMSPT() {
return -1;
}
/**
* Overriding the method to fix initial error caused by ServerPlaceholder implementation trying to
* retrieve the value in constructor, but folia does not support that. Overriding this function to avoid the
* TPS function being called in the wrong thread.
*
* @return -1
*/
@Override
public double getTPS() {
return -1;
}
/**
* Runs task using player's entity scheduler. It's using reflection, because
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
@@ -89,9 +143,4 @@ public class FoliaPlatform extends BukkitPlatform {
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
.invoke(entityScheduler, getPlugin(), consumer, null);
}
@Override
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
runSync(entity, task);
}
}
@@ -0,0 +1,134 @@
package me.neznamy.tab.platforms.bukkit.provider;
import me.neznamy.tab.shared.chat.TabStyle;
import me.neznamy.tab.shared.chat.component.TabKeybindComponent;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.chat.component.TabTextComponent;
import me.neznamy.tab.shared.chat.component.TabTranslatableComponent;
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* Interface for converting TAB components into NMS components (1.7+).
*
* @param <T>
* NMS component type
*/
public abstract class ComponentConverter<T> {
/** Empty UUID for creating dummy profiles */
protected final UUID NIL_UUID = new UUID(0, 0);
/**
* Converts TAB component to NMS component.
*
* @param component
* Component to convert
* @return Converted component
*/
@NotNull
public T convert(@NotNull TabComponent component) {
// Component type
T nmsComponent;
if (component instanceof TabTextComponent) {
nmsComponent = newTextComponent(((TabTextComponent) component).getText());
} else if (component instanceof TabTranslatableComponent) {
nmsComponent = newTranslatableComponent(((TabTranslatableComponent) component).getKey());
} else if (component instanceof TabKeybindComponent) {
nmsComponent = newKeybindComponent(((TabKeybindComponent)component).getKeybind());
} else if (component instanceof TabObjectComponent) {
if ((((TabObjectComponent) component).getContents() instanceof TabAtlasSprite)) {
nmsComponent = newObjectComponent((TabAtlasSprite) ((TabObjectComponent) component).getContents());
} else if ((((TabObjectComponent) component).getContents() instanceof TabPlayerSprite)) {
nmsComponent = newObjectComponent((TabPlayerSprite) ((TabObjectComponent) component).getContents());
} else {
throw new IllegalArgumentException("Unexpected object component type: " + ((TabObjectComponent) component).getContents().getClass().getName());
}
} else {
throw new IllegalArgumentException("Unexpected component type: " + component.getClass().getName());
}
// Component style
applyStyle(nmsComponent, component.getModifier());
// Extra
for (TabComponent extra : component.getExtra()) {
addSibling(nmsComponent, convert(extra));
}
return nmsComponent;
}
/**
* Creates a new text component with given text.
*
* @param text
* Text to display
* @return Text component with given text
*/
@NotNull
public abstract T newTextComponent(@NotNull String text);
/**
* Creates a new translatable component with the given key.
*
* @param key
* Key to translate
* @return Translatable component with the given key
*/
@NotNull
public abstract T newTranslatableComponent(@NotNull String key);
/**
* Creates a new keybind component with given keybind.
*
* @param keybind
* Keybind to show
* @return Keybind component with given keybind
*/
@NotNull
public abstract T newKeybindComponent(@NotNull String keybind);
/**
* Creates a new object component with given atlas and sprite.
*
* @param sprite Sprite to use
* @return Object component with given atlas and sprite
*/
@NotNull
public abstract T newObjectComponent(@NotNull TabAtlasSprite sprite);
/**
* Creates a new head component with given skin.
*
* @param sprite
* Skin to use
* @return Head component with given skin
*/
@NotNull
public abstract T newObjectComponent(@NotNull TabPlayerSprite sprite);
/**
* Converts given chat modifier to minecraft style and applies it to the component.
*
* @param nmsComponent
* Component to apply style to
* @param style
* Style to convert and apply
*/
public abstract void applyStyle(@NotNull T nmsComponent, @NotNull TabStyle style);
/**
* Appends child to the given parent component.
*
* @param parent
* Parent to append the child to
* @param child
* Child component to append
*/
public abstract void addSibling(@NotNull T parent, @NotNull T child);
}
@@ -0,0 +1,71 @@
package me.neznamy.tab.platforms.bukkit.provider;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabListEntryTracker;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An interface to provide platform-specific implementations of various features in various Minecraft versions.
*/
public interface ImplementationProvider {
/**
* Creates a new instance of scoreboard for the given player.
*
* @param player
* Player to create scoreboard for
* @return Newly created scoreboard
*/
@NotNull
Scoreboard newScoreboard(@NotNull BukkitTabPlayer player);
/**
* Creates a new instance of tab list for the given player.
*
* @param player
* Player to create tab list for
* @return Newly created tab list
*/
@NotNull
TabList newTabList(@NotNull BukkitTabPlayer player);
/**
* Returns component converter to be used for converting TAB components to NMS components.
*
* @return Component converter
*/
@NotNull
ComponentConverter<?> getComponentConverter();
/**
* Gets player's channel. If not available, returns {@code null}.
*
* @param player
* Player to get channel of
* @return Player's channel or {@code null} if not supported
*/
@Nullable
Channel getChannel(@NotNull Player player);
/**
* Creates a new instance of tab list entry tracker.
*
* @return Newly created tab list entry tracker
*/
@NotNull
TabListEntryTracker newTabListEntryTracker();
/**
* Returns ping of the given player.
*
* @param player
* Player to get ping of
* @return Player's ping
*/
int getPing(@NotNull BukkitTabPlayer player);
}
@@ -1,77 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Dummy implementation if no scoreboard implementation is available.
*/
public class NullScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NullScoreboard(@NonNull BukkitTabPlayer player) {
super(player);
}
@Override
protected void setDisplaySlot0(int slot, @NonNull String objective) {
// Do nothing
}
@Override
protected void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
// Do nothing
}
@Override
protected void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void unregisterObjective0(@NonNull String objectiveName) {
// Do nothing
}
@Override
protected void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
// Do nothing
}
@Override
protected void unregisterTeam0(@NonNull String name) {
// Do nothing
}
@Override
protected void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
// Do nothing
}
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
/**
* Loader finding the best available Scoreboard implementation.
*/
public class ScoreboardLoader {
/** Instance function */
@Getter
@NotNull
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer, ?>> instance = NullScoreboard::new;
/**
* Tries to load Scoreboard packets.
*/
public static void tryLoad() {
if (PacketScoreboard.isAvailable()) {
instance = PacketScoreboard::new;
} else {
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", null,
"Scoreboard feature will not work",
"Belowname feature will not work",
"Player objective feature will not work",
"Scoreboard teams feature will not work (nametags & sorting)");
}
}
}
@@ -1,83 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Class storing NMS fields and methods of DisplayObjective packet.
*/
public class DisplayPacketData {
private final Class<?> DisplayObjectiveClass;
private final Constructor<?> newDisplayObjective;
private final Field DisplayObjective_OBJECTIVE_NAME;
private final Object[] displaySlots;
private final FunctionWithException<Object, Integer> packetToSlot;
/**
* Constructs new instance and loads all required NMS fields.
*
* @throws ReflectiveOperationException
* If something fails
*/
public DisplayPacketData() throws ReflectiveOperationException {
Class<?> ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
DisplayObjectiveClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
packetToSlot = packet -> ((Enum<?>)DisplayObjective_POSITION.get(packet)).ordinal();
} else {
displaySlots = new Object[]{0, 1, 2};
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
packetToSlot = DisplayObjective_POSITION::getInt;
}
}
/**
* Creates a packet for setting display slot.
*
* @param slot
* Objective slot
* @param objective
* Objective to display
* @return Packet for setting display slot
*/
@SneakyThrows
public Object setDisplaySlot(int slot, @NonNull Object objective) {
return newDisplayObjective.newInstance(displaySlots[slot], objective);
}
/**
* Checks if packet is display objective packet and forwards it to enabled features.
*
* @param player
* Player who received packet
* @param packet
* Received packet
*/
@SneakyThrows
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
if (DisplayObjectiveClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
}
}
@@ -1,262 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Scoreboard implementation which uses packets
* to send scoreboards to use the full potential on all versions
* and server software without any artificial limits.
*/
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
@Getter
private static boolean available;
@Getter
private static Exception exception;
static Class<?> Component;
static Class<?> Scoreboard;
static Class<?> ScoreboardObjective;
static Object emptyScoreboard;
static Class<?> NumberFormat;
private static Constructor<?> newFixedFormat;
// Objective packet
private static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
private static Field Objective_OBJECTIVE_NAME;
private static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
static Constructor<?> newScoreboardObjective;
private static Method ScoreboardObjective_setDisplayName;
private static Enum<?>[] healthDisplays;
static Object IScoreboardCriteria_dummy;
private static ScorePacketData scorePacketData;
@Getter private static TeamPacketData teamPacketData;
@Getter private static DisplayPacketData displayPacketData;
private static PacketSender packetSender;
private final Map<String, Object> teams = new HashMap<>();
static {
try {
int minorVersion = BukkitReflection.getMinorVersion();
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
"IScoreboardCriteria", // 1.5.1 - 1.16.5
"IObjective" // 1.5.0
);
ObjectivePacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
);
emptyScoreboard = Scoreboard.getConstructor().newInstance();
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
if (minorVersion >= 7) {
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
ComponentConverter.ensureAvailable();
}
if (minorVersion >= 8) {
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
"EnumScoreboardHealthDisplay");
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
if (minorVersion < 13) {
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
}
}
if (minorVersion < 13) {
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
}
if (BukkitReflection.is1_20_3Plus()) {
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
}
scorePacketData = new ScorePacketData();
teamPacketData = new TeamPacketData();
displayPacketData = new DisplayPacketData();
packetSender = new PacketSender();
available = true;
} catch (Exception e) {
exception = e;
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public PacketScoreboard(@NonNull BukkitTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot0(int slot, @NonNull String objective) {
packetSender.sendPacket(player.getPlayer(), displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
}
@SneakyThrows
private Object newObjectivePacket(int action, @NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
}
return packet;
}
@Override
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
Object team = teamPacketData.createTeam(name);
teams.put(name, team);
packetSender.sendPacket(player.getPlayer(), teamPacketData.registerTeam(team, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, players, options, color));
}
@Override
public void unregisterTeam0(@NonNull String name) {
packetSender.sendPacket(player.getPlayer(), teamPacketData.unregisterTeam(teams.remove(name)));
}
@Override
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
packetSender.sendPacket(player.getPlayer(), teamPacketData.updateTeam(teams.get(name), prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, options, color));
}
@Override
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
packetSender.sendPacket(player.getPlayer(), scorePacketData.setScore(objective, scoreHolder, score, displayName, toFixedFormat(numberFormat)));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
packetSender.sendPacket(player.getPlayer(), scorePacketData.removeScore(objective, scoreHolder));
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
displayPacketData.onPacketSend(player, packet);
if (ObjectivePacketClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
if (isAntiOverrideTeams()) teamPacketData.onPacketSend(player, packet);
}
/**
* Creates a new Scoreboard Objective with given parameters.
*
* @param objectiveName
* Objective name
* @param title
* Objective title
* @param renderType
* Render type
* @param numberFormat
* Default number format (1.20.3+)
* @return Created objective
*/
@SneakyThrows
public Object newObjective(@NonNull String objectiveName, @NonNull String title, int renderType,
@Nullable Object numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
// 1.20.3+
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType],
false, // Auto update
toFixedFormat(numberFormat)
);
}
if (BukkitReflection.getMinorVersion() >= 13) {
// 1.13 - 1.20.2
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType]
);
}
// 1.5 - 1.12.2
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
ScoreboardObjective_setDisplayName.invoke(objective, title);
return objective;
}
@NotNull
private Object toComponent(@NonNull String text) {
return TabComponent.optimized(text).convert(player.getVersion());
}
@Nullable
@SneakyThrows
private Object toFixedFormat(@Nullable Object numberFormat) {
if (numberFormat == null || newFixedFormat == null) return null;
return newFixedFormat.newInstance(numberFormat);
}
}
@@ -1,123 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.QuintFunction;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Optional;
/**
* Class containing data for scoreboard score set/reset packets.
*/
public class ScorePacketData {
private final QuintFunction<String, String, Integer, Object, Object, Object> setScore;
private final BiFunctionWithException<String, String, Object> removeScore;
/**
* Constructs new instance and loads all fields. Throws exception if something went wrong.
*
* @throws ReflectiveOperationException
* If something went wrong
*/
public ScorePacketData() throws ReflectiveOperationException {
Class<?> SetScorePacket = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
);
if (BukkitReflection.is1_20_3Plus()) {
QuintFunction<String, String, Integer, Object, Object, Object> setScore0;
try {
// 1.20.5+
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, Optional.class, Optional.class);
setScore0 = (objective, holder, score, displayName, numberFormat) ->
newSetScore.newInstance(holder, objective, score, Optional.ofNullable(displayName), Optional.ofNullable(numberFormat));
} catch (ReflectiveOperationException e) {
// 1.20.3 - 1.20.4
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, PacketScoreboard.Component, PacketScoreboard.NumberFormat);
setScore0 = (objective, holder, score, displayName, numberFormat) ->
newSetScore.newInstance(holder, objective, score, displayName, numberFormat);
}
setScore = setScore0;
Constructor<?> newResetScore = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
removeScore = (objective, holder) -> newResetScore.newInstance(holder, objective);
} else if (BukkitReflection.getMinorVersion() >= 13) {
Class<?> actionClass = BukkitReflection.getClass("server.ServerScoreboard$Method", "server.ScoreboardServer$Action", "ScoreboardServer$Action");
Constructor<?> newSetScore = SetScorePacket.getConstructor(actionClass, String.class, String.class, int.class);
Enum<?>[] scoreboardActions = (Enum<?>[]) actionClass.getMethod("values").invoke(null);
setScore = (objective, holder, score, displayName, numberFormat) -> newSetScore.newInstance(scoreboardActions[0], objective, holder, score);
removeScore = (objective, holder) -> newSetScore.newInstance(scoreboardActions[1], objective, holder, 0);
} else {
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
Constructor<?> newResetScore = SetScorePacket.getConstructor(String.class);
Field SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
Constructor<?> newScoreboardScore = ScoreboardScore.getConstructor(PacketScoreboard.Scoreboard, PacketScoreboard.ScoreboardObjective, String.class);
Constructor<?> newSetScore;
if (BukkitReflection.getMinorVersion() >= 8) {
newSetScore = SetScorePacket.getConstructor(ScoreboardScore);
} else {
newSetScore = SetScorePacket.getConstructor(ScoreboardScore, int.class);
}
setScore = (objective, holder, score, displayName, numberFormat) -> {
Object scoreboardScore = newScoreboardScore.newInstance(
PacketScoreboard.emptyScoreboard,
PacketScoreboard.newScoreboardObjective.newInstance(PacketScoreboard.emptyScoreboard, objective, PacketScoreboard.IScoreboardCriteria_dummy),
holder
);
Object packet;
if (BukkitReflection.getMinorVersion() >= 8) {
packet = newSetScore.newInstance(scoreboardScore);
} else {
packet = newSetScore.newInstance(scoreboardScore, Scoreboard.ScoreAction.CHANGE);
}
SetScorePacket_SCORE.set(packet, score);
return packet;
};
removeScore = (objective, holder) -> newResetScore.newInstance(holder);
}
}
/**
* Creates set score packet.
*
* @param objective
* Objective name
* @param scoreHolder
* Score holder
* @param score
* Score value
* @param displayName
* Score holder display name
* @param numberFormat
* Score value number format
* @return Set score packet with given parameters
*/
@SneakyThrows
public Object setScore(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
return setScore.apply(objective, scoreHolder, score, displayName, numberFormat);
}
/**
* Creates reset score packet.
*
* @param objective
* Objective name
* @param scoreHolder
* Score holder
* @return Reset score packet with given parameters.
*/
@SneakyThrows
public Object removeScore(@NonNull String objective, @NonNull String scoreHolder) {
return removeScore.apply(objective, scoreHolder);
}
}
@@ -1,312 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* Class storing all team related fields and methods.
*/
@SuppressWarnings("unchecked")
public class TeamPacketData {
/** First version with modern team data using components */
private final int MODERN_TEAM_DATA_VERSION = 13;
/** First version with static constructor-like methods */
private final int STATIC_CONSTRUCTOR_VERSION = 17;
private Class<?> Component;
private final Object emptyScoreboard;
@Getter private final Class<?> TeamPacketClass;
private Constructor<?> newTeamPacket;
private final Constructor<?> newScoreboardTeam;
private Method TeamPacketConstructor_of;
private Method TeamPacketConstructor_ofBoolean;
private final Field TeamPacket_NAME;
private final Field TeamPacket_ACTION;
private final Field TeamPacket_PLAYERS;
private final Method ScoreboardTeam_getPlayerNameSet;
private final Method ScoreboardTeam_setPrefix;
private final Method ScoreboardTeam_setSuffix;
private Method ScoreboardTeam_setColor;
private final Method ScoreboardTeam_setAllowFriendlyFire;
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private final Enum<?>[] chatFormats;
private BiConsumerWithException<Object, Scoreboard.NameVisibility> setVisibility = (team, visibility) -> {};
private BiConsumerWithException<Object, Scoreboard.CollisionRule> setCollision = (team, collision) -> {};
/**
* Constructs new instance and loads all required NMS classes, fields and methods.
* If anything fails, exception is thrown.
*
* @throws ReflectiveOperationException
* If anything fails
*/
public TeamPacketData() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
TeamPacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
);
emptyScoreboard = Scoreboard.getConstructor().newInstance();
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 7) {
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
}
if (minorVersion >= 8) loadVisibility(scoreboardTeam);
if (minorVersion >= 9) loadCollision(scoreboardTeam);
if (minorVersion >= MODERN_TEAM_DATA_VERSION) {
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
} else {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
}
if (minorVersion >= STATIC_CONSTRUCTOR_VERSION) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
}
@SneakyThrows
private void loadVisibility(@NonNull Class<?> scoreboardTeam) {
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
"world.scores.Team$Visibility", // Mojang mapped
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
"EnumNameTagVisibility" // Bukkit 1.8.0
);
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
Method setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
enumNameTagVisibility
);
setVisibility = (team, visibility) -> setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
}
@SneakyThrows
private void loadCollision(@NonNull Class<?> scoreboardTeam) {
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
Method setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
setCollision = (team, collision) -> setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
}
/**
* Creates team register packet with specified parameters.
*
* @param team
* Team to register
* @param prefix
* Team prefix for 1.12-
* @param prefixComponent
* Team prefix component for 1.13+
* @param suffix
* Team suffix for 1.12-
* @param suffixComponent
* Team suffix component for 1.13+
* @param visibility
* Nametag visibility rule
* @param collision
* Player collision rule
* @param players
* Team members
* @param options
* Team flags
* @param color
* Team color for 1.13+
* @return Register team packet with specified parameters
*/
@SneakyThrows
public Object registerTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
@NonNull String suffix, @Nullable Object suffixComponent,
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
((Collection<String>) ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
} else {
return newTeamPacket.newInstance(team, TeamAction.CREATE);
}
}
/**
* Creates unregister team packet with given team.
*
* @param team
* Team to unregister
* @return Packet for unregistering team
*/
@SneakyThrows
public Object unregisterTeam(@NonNull Object team) {
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
return TeamPacketConstructor_of.invoke(null, team);
} else {
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
}
}
/**
* Creates team update packet with specified parameters.
*
* @param team
* Team to update
* @param prefix
* Team prefix for 1.12-
* @param prefixComponent
* Team prefix component for 1.13+
* @param suffix
* Team suffix for 1.12-
* @param suffixComponent
* Team suffix component for 1.13+
* @param visibility
* Nametag visibility rule
* @param collision
* Player collision rule
* @param options
* Team flags
* @param color
* Team color for 1.13+
* @return Update team packet with specified parameters
*/
@SneakyThrows
public Object updateTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
@NonNull String suffix, @Nullable Object suffixComponent,
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
} else {
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
}
}
/**
* Updates team properties.
*
* @param team
* Team to update
* @param prefix
* Team prefix for 1.12-
* @param prefixComponent
* Team prefix component for 1.13+
* @param suffix
* Team suffix for 1.12-
* @param suffixComponent
* Team suffix component for 1.13+
* @param visibility
* Nametag visibility rule
* @param collision
* Player collision rule
* @param options
* Team flags
* @param color
* Team color for 1.13+
*/
@SneakyThrows
private void updateTeamData(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
@NonNull String suffix, @Nullable Object suffixComponent,
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= MODERN_TEAM_DATA_VERSION) {
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
ScoreboardTeam_setColor.invoke(team, chatFormats[color.ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
setVisibility.accept(team, visibility);
setCollision.accept(team, collision);
}
/**
* Creates a team with given name.
*
* @param name
* Team name
* @return Team with specified name
*/
@SneakyThrows
public Object createTeam(@NonNull String name) {
return newScoreboardTeam.newInstance(emptyScoreboard, name);
}
/**
* Checks if packet is team packet and removes all real players from team
* if sent by other plugins and anti-override is fully active on a player.
*
* @param player
* Player who received the packet
* @param packet
* Received packet
*/
@SneakyThrows
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
if (!TeamPacketClass.isInstance(packet)) return;
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.REMOVE || action == TeamAction.UPDATE) return;
TeamPacket_PLAYERS.set(packet, player.getScoreboard().onTeamPacket(
action, (String) TeamPacket_NAME.get(packet), (Collection<String>) TeamPacket_PLAYERS.get(packet)));
}
}
@@ -1,59 +0,0 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.TabComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* TabList handler using the almighty Bukkit API.
*/
public class BukkitTabList extends TabListBase<String> {
/**
* Constructs new instance with given player.
*
* @param player
* Player this tablist will belong to.
*/
public BukkitTabList(@NonNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
// Shrug
}
@Override
@SuppressWarnings("deprecation")
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
Player p = Bukkit.getPlayer(entry);
if (p == null) return;
p.setPlayerListName(displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
// Shrug
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
// Shrug
}
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, int latency, int gameMode, @Nullable String displayName) {
// Shrug
}
@Override
public String toComponent(@NonNull TabComponent component) {
return player.getPlatform().toBukkitFormat(component, true);
}
}
@@ -1,65 +0,0 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Header/footer sender that uses NMS to send packets. Available
* on all versions since 1.8 when the feature was added into the game.
*/
public class PacketHeaderFooter {
private final PacketSender packetSender = new PacketSender();
private final BiFunctionWithException<Object, Object, Object> createPacket;
/**
* Constructs new instance and loads all NMS content. Throws exception if something went wrong.
*
* @throws ReflectiveOperationException
* If something went wrong
*/
public PacketHeaderFooter() throws ReflectiveOperationException {
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
ComponentConverter.ensureAvailable();
if (BukkitReflection.getMinorVersion() >= 17) {
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor(Component, Component);
createPacket = newHeaderFooter::newInstance;
} else {
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor();
Field HEADER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(0);
Field FOOTER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(1);
createPacket = (header, footer) -> {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, header);
FOOTER.set(packet, footer);
return packet;
};
}
}
/**
* Sends header/footer to player.
*
* @param player
* Player to send header/footer to.
* @param header
* Header to use.
* @param footer
* Footer to use.
*/
@SneakyThrows
public void set(@NotNull BukkitTabPlayer player, @NotNull Object header, @NotNull Object footer) {
packetSender.sendPacket(player.getPlayer(), createPacket.apply(header, footer));
}
}
@@ -1,154 +0,0 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import com.mojang.authlib.GameProfile;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList handler for 1.19.3+ servers using packets.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketTabList1193 extends PacketTabList18 {
/** Map of actions to prevent creating new EnumSet on each packet send */
private static final Map<Action, EnumSet<?>> actionToEnumSet = new EnumMap<>(Action.class);
private static Enum actionAddPlayer;
private static Enum actionUpdateDisplayName;
private static Enum actionUpdateLatency;
private static Constructor<?> newRemovePacket;
private static Field PlayerInfoData_UUID;
private static Field PlayerInfoData_GameMode;
private static Field PlayerInfoData_Listed;
private static Field PlayerInfoData_RemoteChatSession;
/**
* Constructs new instance with given player.
*
* @param player
* Player this tablist will belong to.
*/
public PacketTabList1193(@NonNull BukkitTabPlayer player) {
super(player);
}
/**
* Attempts to load all required NMS classes, fields and methods.
* If anything fails, throws an exception.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public static void loadNew() throws ReflectiveOperationException {
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType", "world.level.EnumGamemode");
ActionClass = (Class<Enum>) BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a" // Bukkit
);
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket");
Class<?> playerInfoDataClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b" // Bukkit
);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
newRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket").getConstructor(List.class);
actionAddPlayer = Enum.valueOf(ActionClass, Action.ADD_PLAYER.name());
actionUpdateDisplayName = Enum.valueOf(ActionClass, Action.UPDATE_DISPLAY_NAME.name());
actionUpdateLatency = Enum.valueOf(ActionClass, Action.UPDATE_LATENCY.name());
Enum actionUpdateGameMode = Enum.valueOf(ActionClass, Action.UPDATE_GAME_MODE.name());
actionToEnumSet.put(Action.ADD_PLAYER, EnumSet.allOf(ActionClass));
actionToEnumSet.put(Action.UPDATE_GAME_MODE, EnumSet.of(actionUpdateGameMode));
actionToEnumSet.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(actionUpdateDisplayName));
actionToEnumSet.put(Action.UPDATE_LATENCY, EnumSet.of(actionUpdateLatency));
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
packetSender.sendPacket(player.getPlayer(), newRemovePacket.newInstance(Collections.singletonList(entry)));
}
@SneakyThrows
@NonNull
@Override
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
int latency, int gameMode, @Nullable Object displayName) {
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(
id,
action == Action.ADD_PLAYER ? createProfile(id, name, skin) : null,
true,
latency,
gameModes[gameMode],
displayName,
null
)));
return packet;
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (!(PlayerInfoClass.isInstance(packet))) return;
EnumSet<?> actions = (EnumSet<?>) ACTION.get(packet);
List<Object> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
boolean rewriteEntry = false;
UUID id = (UUID) PlayerInfoData_UUID.get(nmsData);
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
int latency = PlayerInfoData_Latency.getInt(nmsData);
if (actions.contains(actionUpdateDisplayName)) {
Object expectedName = getExpectedDisplayName(id);
if (expectedName != null && expectedName != displayName) {
displayName = expectedName;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionUpdateLatency)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionAddPlayer)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(rewriteEntry ? newPlayerInfoData.newInstance(
id,
profile,
PlayerInfoData_Listed.getBoolean(nmsData),
latency,
PlayerInfoData_GameMode.get(nmsData),
displayName,
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
}
if (rewritePacket) PLAYERS.set(packet, updatedList);
}
}
Loaded 100 of 711 files, more files were not shown because too many files have changed in this diff. Show more