Compare commits

...
253 Commits
Author SHA1 Message Date
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
416 changed files with 13443 additions and 8501 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
# About
This folder contains the plugin's description as advertised on various marketplaces which use various syntaxes
This folder contains the plugin's description as advertised on various marketplaces where each one uses a different syntax.
(bbcode, markdown).
While this is not an accurate folder to have this stored in (`.github`),
+22 -166
View File
@@ -2,12 +2,12 @@
TAB is an all-in-one plugin for displaying information in various places, which aims to outperform all similar plugins in terms of features, compatibility and performance.
The compact configuration allows you to get the plugin to work the way you want regardless of how simple or complex you want it to be or how experienced you are. The default configuration already contains useful values and examples to give you a better understanding and get you started quickly with instant results. The extensive [URL='https://github.com/NEZNAMY/TAB/wiki']wiki[/URL] answers any of your questions.
[IMG]https://img.shields.io/badge/Release-5.0.5-blue.svg[/IMG]
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg[/IMG]
[IMG]https://img.shields.io/badge/Java-8+-blue.svg[/IMG]
[URL='https://github.com/NEZNAMY/TAB'][IMG]https://img.shields.io/badge/GitHub-Source%20code-yellow.svg[/IMG][/URL]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Release.svg[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Minecraft.svg[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Java.svg[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_SourceCode.svg[/IMG]
[URL='https://github.com/NEZNAMY/TAB/issues'][IMG]https://img.shields.io/github/issues/neznamy/tab?label=Issues[/IMG][/URL]
[URL='https://github.com/NEZNAMY/TAB/wiki'][IMG]https://img.shields.io/badge/Documentation-Wiki-yellow.svg[/IMG][/URL]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Documentation.svg[/IMG]
[CENTER][B][SIZE=7]Discord?[/SIZE][/B][/CENTER]
Official support by me is no longer provided to free users due to limited time and extreme inefficiency (see [URL='https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services']provided services[/URL] for more info).
However, you can join this [URL='https://discord.gg/YPqXt63YQj']fresh new community discord[/URL] made by a nice person who promises to help everyone with all plugins, including TAB.
@@ -15,164 +15,20 @@ However, you can join this [URL='https://discord.gg/YPqXt63YQj']fresh new commun
Before you consider requiring assistance, check out the [URL='https://github.com/NEZNAMY/TAB/wiki']wiki[/URL] first. It will answer any questions you may have and is also constantly getting improved.
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Features.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_HeaderFooter.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_HeaderFooter.png[/IMG][/CENTER]
[LIST]
[*]Per-world / per-server support
[*]Per-group / per-player support
[*]Disable if a specified condition is met
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_NameTags.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_NameTags.png[/IMG][/CENTER]
[LIST]
[*]Configurable prefix and suffix
[*]Per-world / per-server support
[*]Per-group / per-player support
[*]Disable if a specified condition is met
[*]Make nametags completely invisible
[*]Control collision rule
[*]Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible
[*]Anti-override to prevent other plugins from overriding this one
[/LIST]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Sorting.png[/IMG]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Sorting.png[/IMG][/CENTER]
[LIST]
[*]Sorting by primary permission group
[*]Sorting by permission nodes
[*]Sorting by a numeric placeholder from lowest to highest or highest to lowest
[*]Alphabetically by a placeholder (A-Z or Z-A)
[*]Sorting by pre-defined placeholder values
[*]Combination of multiple sorting types
[*]Enable / disable case-sensitive sorting
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_TablistFormatting.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_TablistFormatting.png[/IMG][/CENTER]
[LIST]
[*]Configurable prefix, name and suffix
[*]Per-world / per-server support
[*]Per-group / per-player support
[*]Disable if a specified condition is met
[*]Anti-override to prevent other plugins from overriding this one
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PlayerlistObjective.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveNumber.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveHealth.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveFancy.png[/IMG][/CENTER]
[LIST]
[*]Display value of any numeric placeholder or hearts
[*]Display any text on 1.20.3+
[*]Disable if a specified condition is met
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Belowname.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BelownameComparison.png[/IMG][/CENTER]
[LIST]
[*]Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)
[*]Configurable text
[*]Disable if a specified condition is met
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Bossbar.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BossBar.png[/IMG][/CENTER]
[LIST]
[*]Configurable text, progress, color and style
[*]Define display condition to see a bossbar
[*]Announce defined bossbar for specified amount of time
[*]Configurable toggle command and message
[*]Option to remember toggle choice between logins
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_GlobalPlayerlist.png[/IMG][/CENTER]
[LIST]
[*]Display players from all servers on a proxy network in tablist
[*]Configurable server groups to share playerlist instead of with all servers
[*]Ability to create isolated servers
[*]Servers where people will see everyone
[*]Compatible with vanish plugins
[*]Option to display players from other servers as spectators
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Layout.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Layout.png[/IMG][/CENTER]
[LIST]
[*]Customize all 80 tablist slots
[*]Create static slots with any text and skin
[*]Create player groups based on conditions
[*]Multiple layouts based on conditions
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Scoreboard.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_ScoreboardComparison.png[/IMG][/CENTER]
[LIST]
[*]Display up to 15 lines
[*]Option to show 0 (or any other static number) in every line instead of 1-15
[*]Fully configurable text on right side of lines on 1.20.3+
[*]Displaying different scoreboards based on conditions (output of placeholder, permissions)
[*]Up to 64 characters per line for <1.13, unlimited for 1.13+ clients
[*]Customizable toggle command and message
[*]Option to remember toggle choice between logins
[*]Automatic detection of scoreboards from other plugins to hide own and show it again once other plugin hides its scoreboard
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PerWorldPlayerlist.png[/IMG][/CENTER]
[LIST]
[*]Only display players from current world in tablist
[*]Group worlds which should share players
[*]Worlds where players see everyone
[*]Option to see all players in all worlds for players with permission
[/LIST]
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_API.png[/IMG][/CENTER]
[LIST]
[*]Change player prefix/name/suffix in tablist
[*]Change header/footer
[*]Change nametag prefix / suffix
[*]Change player collision rule and nametag visibility
[*]Change sorting priority
[*]Create custom scoreboards, show different scoreboard, toggle scoreboard
[*]Create custom placeholders
[*]Create custom boss bars, toggle bossbar visibility, announce bossbar
[*]... and more!
[/LIST]
[LIST]
[*]Placeholder support
[LIST]
[*]Internal placeholders with customizable output
[*]Create and use custom animations
[*]All placeholders and animations are supported in all features
[*]Full PlaceholderAPI support including relational placeholders
[*]PlaceholderAPI support on BungeeCord
[/LIST]
[*]Placeholder output replacements
[LIST]
[*]Modify output of any placeholder, including PlaceholderAPI placeholders
[*]Replace exact text with another text
[*]Change output if it's in a number interval
[*]Set value if none of the above is met
[*]Use original placeholder's output in new one
[*]Support for nested placeholders
[*]Can be used in other plugins as well
[/LIST]
[*]RGB Support
[LIST]
[*]5 supported RGB patterns
[*]4 supported gradient patterns
[*]Automatically display closest color to legacy clients
[*]Configurable color for legacy clients
[*]Supported in all features [B]where possible[/B]
[/LIST]
[*]Conditional placeholders
[LIST]
[*]Conditions based on placeholder output (comparing numeric value, exact output)
[*]Conditions based on permission node
[*]Combine conditions with AND or OR
[*]Configurable output of both cases when condition is and isn't met
[*]Placeholder support in condition output
[/LIST]
[*]High performance
[LIST]
[*]Full control of CPU usage by optimizing configuration and disabling unwanted features
[*]Fully asynchronous - no impact on TPS
[*]Check plugin's CPU usage using /tab cpu
[/LIST]
[*]RedisBungee support
[*]MySQL support
[*]Fully client-sided, server-side stays unaffected (scoreboards with their teams and objectives, bossbar entities, armor stands)
[*]Disable vanilla feature making players in spectator gamemode appear on the bottom of tablist
[*]Possible errors moved into a separate file to not spam your console
[*]Yaml error assistant for more user-friendly messages and fix suggestions
[*]Many misconfiguration checks to help solve issues much faster
[/LIST]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_HeaderFooter.png[/IMG][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_NameTags.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Sorting.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_TablistFormatting.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PlayerlistObjective.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Belowname.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Bossbar.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_GlobalPlayerlist.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Layout.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Scoreboard.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PerWorldPlayerlist.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_API.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Placeholders.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PlaceholderOutputReplacements.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_RGB.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_ConditionalPlaceholders.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_HighPerformance.png[/IMG]
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_More.png[/IMG][/CENTER]
+24 -151
View File
@@ -4,12 +4,12 @@ The compact configuration allows you to get the plugin to work the way you want
The default configuration already contains useful values and examples to give you a better understanding and get you started quickly with instant results.
The extensive [wiki](https://github.com/NEZNAMY/TAB/wiki) answers any of your questions.
![https://img.shields.io/badge/Release-5.0.5-blue.svg](https://img.shields.io/badge/Release-5.0.5-blue.svg)
![https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg](https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg)
![https://img.shields.io/badge/Java-8+-blue.svg](https://img.shields.io/badge/Java-8+-blue.svg)
[![https://img.shields.io/badge/GitHub-Source%20code-yellow.svg](https://img.shields.io/badge/GitHub-Source%20code-yellow.svg)](https://github.com/NEZNAMY/TAB)
[![https://img.shields.io/github/issues/neznamy/tab?label=Issues](https://img.shields.io/github/issues/neznamy/tab?label=Issues)](https://github.com/NEZNAMY/TAB/issues)
[![https://img.shields.io/badge/Documentation-Wiki-yellow.svg](https://img.shields.io/badge/Documentation-Wiki-yellow.svg)](https://github.com/NEZNAMY/TAB/wiki)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Release.svg)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Minecraft.svg)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Java.svg)
[![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_SourceCode.svg)](https://github.com/NEZNAMY/TAB)
[![](https://img.shields.io/github/issues/neznamy/tab?label=Issues)](https://github.com/NEZNAMY/TAB/issues)
[![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Documentation.svg)](https://github.com/NEZNAMY/TAB/wiki)
# Discord?
Official support by me is no longer provided to free users due to limited time and extreme inefficiency (see [provided services]([https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services](https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services)) for more info).
@@ -18,148 +18,21 @@ However, you can join this [fresh new community discord]([https://discord.gg/YPq
Before you consider requiring assistance, check out the [wiki](https://github.com/NEZNAMY/TAB/wiki) first. It will answer any questions you may have and is also constantly getting improved.
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Features.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_HeaderFooter.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_HeaderFooter.png)
* Per-world / per-server support
* Per-group / per-player support
* Disable if a specified condition is met
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_NameTags.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_NameTags.png)
* Configurable prefix and suffix
* Per-world / per-server support
* Per-group / per-player support
* Disable if a specified condition is met
* Make nametags completely invisible
* Control collision rule
* Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible
* Anti-override to prevent other plugins from overriding this one
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Sorting.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Sorting.png)
* Sorting by primary permission group
* Sorting by permission nodes
* Sorting by a numeric placeholder from lowest to highest or highest to lowest
* Alphabetically by a placeholder (A-Z or Z-A)
* Sorting by pre-defined placeholder values
* Combination of multiple sorting types
* Enable / disable case-sensitive sorting
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_TablistFormatting.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_TablistFormatting.png)
* Configurable prefix, name and suffix
* Per-world / per-server support
* Per-group / per-player support
* Disable if a specified condition is met
* Anti-override to prevent other plugins from overriding this one
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PlayerlistObjective.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveNumber.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveHealth.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveFancy.png)
* Display value of any numeric placeholder or hearts
* Display any text on 1.20.3+
* Disable if a specified condition is met
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Belowname.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BelownameComparison.png)
* Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)
* Configurable text
* Disable if a specified condition is met
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Bossbar.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BossBar.png)
* Configurable text, progress, color and style
* Define display condition to see a bossbar
* Announce defined bossbar for specified amount of time
* Configurable toggle command and message
* Option to remember toggle choice between logins
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_GlobalPlayerlist.png)
* Display players from all servers on a proxy network in tablist
* Configurable server groups to share playerlist instead of with all servers
* Ability to create isolated servers
* Servers where people will see everyone
* Compatible with vanish plugins
* Option to display players from other servers as spectators
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Layout.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Layout.png)
* Customize all 80 tablist slots
* Create static slots with any text and skin
* Create player groups based on conditions
* Multiple layouts based on conditions
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Scoreboard.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_ScoreboardComparison.png)
* Display up to 15 lines
* Option to show 0 (or any other static number) in every line instead of 1-15
* Fully configurable text on the right side of lines on 1.20.3+
* Displaying different scoreboards based on conditions (output of placeholder, permissions)
* Up to 64 characters per line for <1.13, unlimited for 1.13+ clients
* Customizable toggle command and message
* Option to remember toggle choice between logins
* Automatic detection of scoreboards from other plugins to hide own and show it again once the other plugin hides its scoreboard
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PerWorldPlayerlist.png)
* Only display players from the same world in tablist
* Group worlds which should share players
* Worlds where players see everyone
* Option to see all players in all worlds for players with permission
![](https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_API.png)
* Change player prefix/name/suffix in tablist
* Change header/footer
* Change nametag prefix / suffix
* Change player collision rule and nametag visibility
* Change sorting priority
* Create custom scoreboards, show different scoreboard, toggle scoreboard
* Create custom placeholders
* Create custom boss bars, toggle bossbar visibility, announce bossbar
* ... and more!
.
.
.
.
.
Placeholder support
* Internal placeholders with customizable output
* Create and use custom animations
* All placeholders and animations are supported in all features
* Full [PlaceholderAPI]('https://www.spigotmc.org/resources/placeholderapi.6245/') support including [relational placeholders]('https://www.spigotmc.org/resources/placeholderapi.6245/update?update=167267')
* PlaceholderAPI support on BungeeCord / Velocity
Placeholder output replacements
* Modify output of any placeholder, including PlaceholderAPI placeholders
* Replace exact text with another text
* Change output if it's in a number interval
* Set value if none of the above is met
* Use original placeholder's output in new one
* Support for nested placeholders
* Can be used in other plugins as well
RGB Support
* 5 supported RGB patterns
* 3 supported gradient patterns
* Supported in all features **where possible**
Conditional placeholders
* Conditions based on placeholder output (comparing numeric value, exact output)
* Conditions based on permission node
* Combine conditions with `AND` or `OR`
* Configurable output of both cases when condition is and isn't met
* Placeholder support in conditions
High performance
* Full control of CPU usage by optimizing configuration and disabling unwanted features
* Fully asynchronous - no impact on TPS
* Check plugin's CPU usage using /tab cpu
RedisBungee support
MySQL support
Fully client-sided, server-side stays unaffected
Disable vanilla feature making players in spectator gamemode appear on the bottom of tablist
Possible errors moved into a separate file to not spam your console
Yaml error assistant for more user-friendly messages and fix suggestions
Many misconfiguration checks to help solve issues much faster
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_HeaderFooter.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_NameTags.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Sorting.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_TablistFormatting.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PlayerlistObjective.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Belowname.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Bossbar.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_GlobalPlayerlist.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Layout.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Scoreboard.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PerWorldPlayerlist.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_API.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Placeholders.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PlaceholderOutputReplacements.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_RGB.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_ConditionalPlaceholders.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_HighPerformance.png)
![](https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_More.png)
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="126" height="20" role="img" aria-label="Documentation: Wiki"><title>Documentation: Wiki</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="126" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="93" height="20" fill="#555"/><rect x="93" width="33" height="20" fill="#dfb317"/><rect width="126" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="830">Documentation</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="830">Documentation</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">Wiki</text><text x="1085" y="140" transform="scale(.1)" fill="#fff" textLength="230">Wiki</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" role="img" aria-label="Java: 8+"><title>Java: 8+</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="60" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="25" height="20" fill="#007ec6"/><rect width="60" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">Java</text><text x="185" y="140" transform="scale(.1)" fill="#fff" textLength="250">Java</text><text aria-hidden="true" x="465" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="150">8+</text><text x="465" y="140" transform="scale(.1)" fill="#fff" textLength="150">8+</text></g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="20" role="img" aria-label="Minecraft: 1.5 - 1.21.5"><title>Minecraft: 1.5 - 1.21.5</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="138" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="77" height="20" fill="#007ec6"/><rect width="138" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">Minecraft</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">Minecraft</text><text aria-hidden="true" x="985" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">1.5 - 1.21.5</text><text x="985" y="140" transform="scale(.1)" fill="#fff" textLength="670">1.5 - 1.21.5</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 5.2.1"><title>Release: 5.2.1</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">5.2.1</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">5.2.1</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="GitHub: Source code"><title>GitHub: Source code</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="124" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="77" height="20" fill="#dfb317"/><rect width="124" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">GitHub</text><text x="245" y="140" transform="scale(.1)" fill="#fff" textLength="370">GitHub</text><text aria-hidden="true" x="845" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">Source code</text><text x="845" y="140" transform="scale(.1)" fill="#fff" textLength="670">Source code</text></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@@ -0,0 +1,288 @@
from PIL import Image, ImageDraw, ImageFont
import requests
line_spacing = 10
padding = 10
class Text:
def __init__(self, text, color="black", font=ImageFont.truetype("arial.ttf", 20)):
self.text = text
self.color = color
self.font = font
class ImageGenerator:
def __init__(self, overlay_text, second_images, output_path, texts):
self.overlay_text = overlay_text
self.second_images = second_images
self.output_path = output_path
self.texts = texts
self.header_template = Image.open("Template_Small.png")
self.example_images = [Image.open(path) for path in second_images]
def calculate_total_height(self):
total_text_height = 0
for text in self.texts:
text_width, text_height = ImageDraw.Draw(self.header_template).textbbox((0, 0), text.text, font=text.font)[2:]
total_text_height += text_height + line_spacing
total_second_images_height = sum([img.height+padding for img in self.example_images])
return self.header_template.height + total_second_images_height + total_text_height + (3 * padding)
def draw_rounded_border(self, final_img, draw, radius, color, width):
draw.rounded_rectangle([(0, 0), (final_img.width - 1, final_img.height - 1)], radius=radius, outline=color, width=width)
def paste_top_image(self, final_img, font):
header_with_text = self.header_template.copy()
draw = ImageDraw.Draw(header_with_text)
text_width, text_height = draw.textbbox((0, 0), self.overlay_text, font=font)[2:]
text_x = (header_with_text.width - text_width) // 2 # Center text horizontally
text_y = 20 # Padding from the top
draw.text((text_x, text_y), self.overlay_text, fill="white", font=font)
final_img.paste(header_with_text, (padding, padding))
def paste_examples(self, final_img):
y_offset = self.header_template.height + 2 * padding
for img2 in self.example_images:
img2_x = (final_img.width - img2.width) // 2 # Center horizontally
final_img.paste(img2, (img2_x, y_offset))
y_offset += img2.height + padding
return y_offset
def paste_text(self, final_draw, y_offset):
y_offset += padding # Extra padding before text
for text in self.texts:
fancy_line = f"• {text.text}" # Add bullet point
final_draw.text((padding, y_offset), fancy_line, fill=text.color, font=text.font)
y_offset += ImageDraw.Draw(self.header_template).textbbox((0, 0), fancy_line, font=text.font)[3] + line_spacing # Add space between lines
def save(self, final_img):
final_img.save(self.output_path)
def create_image(overlay_text, second_images, output_path, texts):
generator = ImageGenerator(overlay_text, second_images, output_path, texts)
final_img = Image.new("RGB", (generator.header_template.width + 2 * padding, generator.calculate_total_height()), "white")
final_draw = ImageDraw.Draw(final_img)
# Rounded border
generator.draw_rounded_border(final_img, final_draw, 15, (39, 0, 114), 5)
# Template with text
generator.paste_top_image(final_img, ImageFont.truetype("arial.ttf", 60))
# Examples
y_offset = generator.paste_examples(final_img)
# Text
generator.paste_text(final_draw, y_offset)
# Save
generator.save(final_img)
def download_image(url, save_path):
try:
response = requests.get(url, stream=True)
response.raise_for_status()
with open(save_path, "wb") as file:
for chunk in response.iter_content(1024):
file.write(chunk)
except requests.RequestException as e:
print(f"Error downloading image: {e}")
if __name__ == "__main__":
create_image("Header & Footer", ["Example_HeaderFooter.png"], "Generated_HeaderFooter.png",
[
Text("Per-world / per-server support"),
Text("Per-group / per-player support"),
Text("Disable if a specified condition is met")
]
)
create_image("NameTags", ["Example_NameTags.png"], "Generated_NameTags.png",
[
Text("Configurable prefix and suffix"),
Text("Per-world / per-server support"),
Text("Per-group / per-player support"),
Text("Disable if a specified condition is met"),
Text("Make nametags completely invisible"),
Text("Control collision rule"),
Text("Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible"),
Text("Anti-override to prevent other plugins from overriding this one")
]
)
create_image("Sorting", ["Example_Sorting.png"], "Generated_Sorting.png",
[
Text("Sorting by primary permission group"),
Text("Sorting by permission nodes"),
Text("Sorting by a numeric placeholder from lowest to highest or highest to lowest"),
Text("Alphabetically by a placeholder (A-Z or Z-A)"),
Text("Sorting by pre-defined placeholder values"),
Text("Combination of multiple sorting types"),
Text("Enable / disable case-sensitive sorting")
]
)
create_image("Tablist Formatting", ["Example_TablistFormatting.png"], "Generated_TablistFormatting.png",
[
Text("Configurable prefix, name and suffix"),
Text("Per-world / per-server support"),
Text("Per-group / per-player support"),
Text("Disable if a specified condition is met"),
Text("Anti-override to prevent other plugins from overriding this one")
]
)
create_image("Playerlist Objective",
[
"Example_PlayerlistObjectiveNumber.png",
"Example_PlayerlistObjectiveHealth.png",
"Example_PlayerlistObjectiveFancy.png"
],
"Generated_PlayerlistObjective.png",
[
Text("Display value of any numeric placeholder or hearts"),
Text("Display any text on 1.20.3+"),
Text("Disable if a specified condition is met")
]
)
create_image("Belowname", ["Example_BelownameComparison.png"], "Generated_Belowname.png",
[
Text("Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)"),
Text("Configurable text"),
Text("Disable if a specified condition is met")
]
)
create_image("Bossbar", ["Example_BossBar.png"], "Generated_Bossbar.png",
[
Text("Configurable text, progress, color and style"),
Text("Define display condition to see a bossbar"),
Text("Announce defined bossbar for specified amount of time"),
Text("Configurable toggle command and message"),
Text("Option to remember toggle choice between logins")
]
)
create_image("Global Playerlist", [], "Generated_GlobalPlayerlist.png",
[
Text("Display players from all servers on a proxy network in tablist"),
Text("Configurable server groups to share playerlist instead of with all servers"),
Text("Ability to create isolated servers"),
Text("Servers where people will see everyone"),
Text("Compatible with vanish plugins"),
Text("Option to display players from other servers as spectators")
]
)
create_image("Layout", ["Example_Layout.png"], "Generated_Layout.png",
[
Text("Customize all 80 tablist slots"),
Text("Create static slots with any text and skin"),
Text("Create player groups based on conditions"),
Text("Multiple layouts based on conditions")
]
)
create_image("Scoreboard", ["Example_ScoreboardComparison.png"], "Generated_Scoreboard.png",
[
Text("Display up to 15 lines"),
Text("Option to show 0 (or any other static number) in every line instead of 1-15"),
Text("Fully configurable text on right side of lines on 1.20.3+"),
Text("Displaying different scoreboards based on conditions (output of placeholder, permissions)"),
Text("Up to 64 characters per line for <1.13, unlimited for 1.13+ clients"),
Text("Customizable toggle command and message"),
Text("Option to remember toggle choice between logins"),
Text("Automatic detection of scoreboards from other plugins to hide own and show it again once other plugin hides its scoreboard")
]
)
create_image("Per world Playerlist", [], "Generated_PerWorldPlayerlist.png",
[
Text("Only display players from current world in tablist"),
Text("Group worlds which should share players"),
Text("Worlds where players see everyone"),
Text("Option to see all players in all worlds for players with permission")
]
)
create_image("API", [], "Generated_API.png",
[
Text("Tablist formatting - Change player prefix/name/suffix"),
Text("Header / Footer - Change values"),
Text("Teams - Change nametag prefix / suffix, change visibility, collision rule"),
Text("Sorting - Change sorting priority"),
Text("Scoreboard - Create custom scoreboards, show different scoreboard, toggle scoreboard, announce scoreboard"),
Text("Bossbar - Create custom boss bars, toggle bossbar visibility, announce/send bossbar"),
Text("Layout - Create custom layouts, send existing layouts"),
Text("Placeholders - Create custom placeholders")
]
)
create_image("Placeholders", [], "Generated_Placeholders.png",
[
Text("Internal placeholders with customizable output"),
Text("Create and use custom animations"),
Text("All placeholders and animations are supported in all features"),
Text("Full PlaceholderAPI support including relational placeholders"),
Text("PlaceholderAPI support on BungeeCord / Velocity"),
Text("Conditional placeholders")
]
)
create_image("Conditional placeholders", [], "Generated_ConditionalPlaceholders.png",
[
Text("Conditions based on placeholder output (comparing numeric value, exact output)"),
Text("Conditions based on permission node"),
Text("Combine conditions with AND or OR"),
Text("Configurable output of both cases when condition is and isn't met"),
Text("Placeholder support in condition output")
]
)
create_image("Placeholder replacements", [], "Generated_PlaceholderOutputReplacements.png",
[
Text("Modify output of any placeholder, including PlaceholderAPI placeholders"),
Text("Replace exact text with another text"),
Text("Change output if it's in a number interval"),
Text("Set value if none of the above is met"),
Text("Use original placeholder's output in new one"),
Text("Support for nested placeholders"),
Text("Can be used in other plugins as well")
]
)
create_image("RGB Support", [], "Generated_RGB.png",
[
Text("5 supported RGB patterns"),
Text("3 supported gradient patterns"),
Text("Automatically display closest color to legacy clients"),
Text("Supported in all features where possible")
]
)
create_image("High Performance", [], "Generated_HighPerformance.png",
[
Text("Full control of CPU usage by optimizing configuration and disabling unwanted features"),
Text("Fully asynchronous - no impact on TPS"),
Text("Check plugin's CPU usage using /tab cpu"),
Text("Constant improvements to reduce usage"),
Text("Handle thousands of players")
]
)
create_image("And more!", [], "Generated_More.png",
[
Text("RedisBungee support for multiple proxies"),
Text("MySQL support to store groups/users"),
Text("Potential errors logged into a file instead of spamming the console"),
Text("Yaml error assistant for more user-friendly messages and fix suggestions"),
Text("Many misconfiguration checks to help solve issues much faster")
]
)
release = "5.2.1"
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
minecraft = "1.5 - 1.21.5".replace(" ", "%20").replace("-", "--")
download_image(f"https://img.shields.io/badge/Minecraft-{minecraft}-blue.svg", "Badge_Minecraft.svg")
java = "8+"
download_image(f"https://img.shields.io/badge/Java-{java}-blue.svg", "Badge_Java.svg")
download_image(f"https://img.shields.io/badge/GitHub-Source%20code-yellow.svg", "Badge_SourceCode.svg")
download_image(f"https://img.shields.io/badge/Documentation-Wiki-yellow.svg", "Badge_Documentation.svg")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

+1 -1
View File
@@ -14,11 +14,11 @@
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
* [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer)
* [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout)
* [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support)
* [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags)
* [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist)
* [Ping spoof](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Ping-Spoof)
* [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
* [RedisBungee support](https://github.com/NEZNAMY/TAB/wiki/Feature-info:-RedisBungee-support)
* [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard)
* [Sorting in tablist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist)
* [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix)
+8 -10
View File
@@ -11,24 +11,24 @@ This is a collection of random information about the plugin not necessarily limi
* [Debug config option](#debug-config-option)
# Vanish detection
Although TAB does not manage vanishing players in any way,
it checks vanish status for features to be properly compatible.
TAB is not a vanish plugin and does not manage vanishing players in any way, therefore, is not responsible for removing vanished players from the tablist.
However, it checks vanish status for features to be properly compatible.
At the moment, vanishing detection is used for:
* `%vanished%` placeholder (used for internal functionality, but can be used to display vanish status if one wants to)
* `%online%`, `%serveronline%`, `%worldonline%` and `%staffonline%` will not count vanished players
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) makes vanish plugins unable to remove players on other servers from tablist, vanish check will make TAB remove vanished players.
* Hide vanished players from [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout).
* Unregister teams of vanished players to players who cannot see them to prevent third party client exploits from seeing vanished players.
* [Unregister teams of vanished players](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags#additional-note-6---vanish-hook) to players who cannot see them to prevent third party client exploits from seeing vanished players.
Permission `tab.seevanished` allows players to:
* See vanished players in [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist).
* See vanished players in [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout).
* Not have teams of vanished players unregistered, which would make them not appear sorted properly and have no nametag prefix/suffix.
* Not have [teams of vanished players unregistered](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags#additional-note-6---vanish-hook), which would make them not appear sorted properly and have no nametag prefix/suffix.
For a player to be detected, player has to have `vanished` metadata flag set to `true`.
Most, (if not all) vanish plugins do this, including, but not limited to CMI,
Essentials, SuperVanish, PremiumVanish and probably more.
If TAB is installed on BungeeCord,
If TAB is installed on proxy,
[Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) must be installed on backend servers
to forward vanish status to the proxy.
@@ -71,10 +71,8 @@ You can check what version the plugin thinks player has by using any of the foll
* Set `debug: true` in config and check console messages on player join, it will send a message containing game version ViaVersion API has returned
Player game version detection may fail. Here are a few possible scenarios why:
* ViaVersion is installed on BungeeCord
* And TAB on BungeeCord as well - ViaVersion makes BungeeCord API return version of the backend server player is connected to instead of player's actual version
* And TAB is on backend servers - There is no ViaVersion to hook into, therefore, there is no suspicion of player not using server's version and not having a way to verify it
* ViaVersion API returns `-1` - There is currently no known cause when or why this happens. However, when this happens, TAB prints a console warn notifying this has happened. Plugin will assume player is using the same version as the server in such cases.
* ViaVersion is installed on proxy and TAB is on backend servers - There is no ViaVersion to hook into, therefore, there is no suspicion of player not using server's version and not having a way to verify it.
* ViaVersion API returns `-1` - This was an issue in the past and hasn't been reported in years now.
When a player's game version is not detected correctly, player's experience will be very limited, see below.
@@ -86,7 +84,7 @@ You will not experience all of these, only those that affect the version differe
| Change of logic | Negative side effect if version is not detected correctly |
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for 1.7 players | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being sent into the tablist of the player, breaking it as the layout is not designed support 1.7 |
| use alternate way of sorting [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries for 1.19.3 - 1.21.1 as the optimal sorting way cannot be used | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being out of order for 1.19.3 - 1.21.1 <br />Layout entries appearing in chat complete for <1.19.3 even if they don't have to |
| Use alternate way of sorting [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries for 1.19.3 - 1.21.1 as the optimal sorting way cannot be used | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being out of order for 1.19.3 - 1.21.1 <br />Layout entries appearing in chat complete for <1.19.3 even if they don't have to |
| Show 1.9+ bossbar on 1.8 servers for 1.9+ players using ViaVersion API | No 1.9+ bossbar for 1.9+ players on 1.8 server |
| Disabling [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) for 1.7 and lower due to a 16-character limit total to take advantage of the mechanic where nametags are displayed in tablist, which extends the limit to up to 48 characters (16 prefix, 16 name, 16 suffix) | Trying to fit the entire formatting into 16 characters, resulting in massive cutting and nametag feature not working |
| Play around <1.13 limitations of [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | Scoreboard lines limited to ~26 characters, even for 1.13+ clients <br />Scoreboard lines limited to 14 characters for < 1.13 clients |
+20 -11
View File
@@ -8,22 +8,23 @@ Commands have required permissions associated with them.
# Content
* [Configuration](#configuration-commands)
* [/tab \<player/group/playeruuid\> \<name\> \<property\> [value] [options]](#tab-playergroupplayeruuid-name-property-value-options)
* [/tab \<player/group/playeruuid\> \<name\> \<property\> \[value\] \[options\]](#tab-playergroupplayeruuid-name-property-value-options)
* [/tab \<player/group/playeruuid\> \<name\> remove](#tab-playergroupplayeruuid-name-remove)
* [/tab reload](#tab-reload)
* [/tab debug [player]](#tab-debug-player)
* [/tab debug \[player\]](#tab-debug-player)
* [/tab group \<group\>](#tab-group-group)
* [/tab groups](#tab-groups)
* [Bossbar](#bossbar)
* [/tab bossbar send \<name\> \[player\]](#tab-bossbar-show-name-player)
* [/tab bossbar [on/off/toggle] [player] [options]](#tab-bossbar-onofftoggle-player-options)
* [/tab bossbar \[on/off/toggle\] \[player\] \[options\]](#tab-bossbar-onofftoggle-player-options)
* [/tab bossbar announce \<name\> \<length\>](#tab-bossbar-announce-name-duration)
* [Scoreboard](#scoreboard)
* [/tab scoreboard show \<name\> \[player\]](#tab-scoreboard-show-name-player)
* [/tab scoreboard [on/off/toggle] [player] [options]](#tab-scoreboard-onofftoggle-player-options)
* [/tab scoreboard \[on/off/toggle\] \[player\] \[options\]](#tab-scoreboard-onofftoggle-player-options)
* [/tab scoreboard announce \<name\> \<length\>](#tab-scoreboard-announce-name-duration)
* [Nametags / Teams](#nametags--teams)
* [/tab nametag toggle](#tab-nametag-toggle)
* [/tab nametag <show/hide/toggle> \[player\] \[viewer\] \[options\]](#tab-nametag-showhidetoggle-player-viewer-options)
* [/tab nametag <showview/hideview/toggleview> \[viewer\] \[options\]](#tab-nametag-showviewhideviewtoggleview-viewer-options)
* [/tab setcollision <player> <true/false>](#tab-setcollision-player-truefalse)
* [MySQL](#mysql)
* [/tab mysql upload](#tab-mysql-upload)
@@ -34,7 +35,7 @@ Commands have required permissions associated with them.
* [Additional permissions](#additional-permissions)
# Configuration commands
## /tab \<player/group/playeruuid\> \<name\> \<property\> [value] [options]
## /tab \<player/group/playeruuid\> \<name\> \<property\> \[value\] \[options\]
* **Permission:** `tab.change.<property>`
* **Description:** Changes a property of a group/player to the given value. No value argument will result in that property being deleted.
@@ -83,7 +84,7 @@ Commands have required permissions associated with them.
* **Permission:** `tab.scoreboard.show` for showing to yourself, `tab.scoreboard.show.other` for showing to others.
* **Description:** Shows the scoreboard with the given `name`, either to yourself if no `player` was given, or to the given `player`.
## /tab scoreboard [on/off/toggle] [player] [options]
## /tab scoreboard \[on/off/toggle\] \[player\] \[options\]
* **Permission:** `tab.scoreboard.toggle` for toggling for yourself, `tab.scoreboard.toggle.other` for toggling for others.
* **Description:** Shows / hides / toggles scoreboard of specified player. If no player was given, command affects the sender.
* **Options:**
@@ -98,7 +99,7 @@ Commands have required permissions associated with them.
* **Permission:** `tab.bossbar.show` for showing to yourself, `tab.bossbar.show.other` for showing to others.
* **Description:** Shows the bossbar with the given `name`, either to yourself if no `player` was given, or to the given `player`.
## /tab bossbar [on/off/toggle] [player] [options]
## /tab bossbar \[on/off/toggle\] \[player\] \[options\]
* **Permission:** `tab.bossbar.toggle` for toggling for yourself, `tab.bossbar.toggle.other` for toggling for others.
* **Description:** Shows / hides / toggles bossbar of specified player. If no player was given, command affects the sender.
* **Options:**
@@ -109,9 +110,17 @@ Commands have required permissions associated with them.
* **Description:** Shows the bossbar with the given `name` to every player on the server for the given `duration`, in seconds.
# Nametags / Teams
## /tab nametag toggle
* **Permission:** `tab.nametag.toggle`
* **Description:** Toggles nametag visibility on all players for player who ran the command. Running the command again will make nametags visible again.
## /tab nametag <show/hide/toggle> \[player\] \[viewer\] \[options\]
* **Permission:** `tab.nametag.visibility` (`tab.nametag.visibility.other` for toggling for other players)
* **Description:** Shows / hides / toggles nametag of a specified player. If viewer is specified, view is only affected for the viewer.
* **Options:**
* `-s` for silent toggling (no chat message for affected player)
## /tab nametag <showview/hideview/toggleview> \[viewer\] \[options\]
* **Permission:** `tab.nametag.view` (`tab.nametag.view.other` for toggling for other players)
* **Description:** Shows / hides / toggles nametag VIEW a specified player.
* **Options:**
* `-s` for silent toggling (no chat message for affected player)
## /tab setcollision \<player\> \<true|false\>
* **Permission:** `tab.setcollision`
+42 -29
View File
@@ -1,5 +1,6 @@
# Content
* [Requirements](#requirements)
* [Java](#java)
* [Supported server software and versions](#supported-server-software-and-versions)
* [Supported features per platform](#supported-features-per-platform)
* [Plugin hooks](#plugin-hooks)
@@ -7,13 +8,19 @@
* [Bukkit](#bukkit)
* [BungeeCord](#bungeecord)
* [Fabric](#fabric)
* [Forge / NeoForge](#forge--neoforge)
* [Sponge](#sponge)
* [Velocity](#velocity)
* [Compatibility issues](#compatibility-issues)
# Requirements
TAB does not depend on any other plugins.
It is compiled with Java 8 and therefore supports any version of 8 and above.
TAB does not depend on any other plugins. The experience can however be enhanced by installing other plugins, see below.
## Java
TAB's shared module is compiled with Java 8. For the platforms themselves, TAB does have a higher requirement, however, it does not require a Java version higher than the server software itself does. Specifically:
* Sponge, Fabric, Forge and NeoForge require Java 21
* Velocity requires Java 17
* Bukkit and BungeeCord are ok with Java 8
# Supported server software and versions
<table>
@@ -28,42 +35,44 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
<tr>
<td rowspan=2>Vanilla</td>
<td rowspan=1><a href="https://getbukkit.org/">Bukkit</a> (+forge hybrids)</td>
<td>✔ (1.5 - 1.21.4)</td>
<td>✔ (1.5 - 1.21.5)</td>
</tr>
<tr>
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
<td>✔ (1.9<sup>1</sup> - 1.21.4)</td>
<td>✔ (1.20.6 - 1.21.5)</td>
</tr>
</tbody>
<tbody>
<tr>
<td rowspan=2>Modded</td>
<td rowspan=3>Modded</td>
<td rowspan=1><a href="https://fabricmc.net">Fabric</a></td>
<td>✔ (1.14 - 1.21.4)</td>
<td>✔ (1.21.4 - 1.21.5)</td>
</tr>
<tr>
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
<td>❌</td>
<td>✔ (1.21.4 - 1.21.5)</td>
</tr>
<tr>
<td rowspan=1><a href="https://neoforged.net/">NeoForge</a></td>
<td>✔ (1.21.4 - 1.21.5)</td>
</tr>
</tbody>
<tbody>
<tr>
<td rowspan=2>Proxies</td>
<td rowspan=1><a href="https://ci.md-5.net/job/BungeeCord/">BungeeCord</a></td>
<td>✔ (latest only<sup>2</sup>)</td>
<td>✔ (latest only<sup>1</sup>)</td>
</tr>
<tr>
<td rowspan=1><a href="https://www.velocitypowered.com/">Velocity</a></td>
<td>✔ (latest only<sup>2</sup>)</td>
<td>✔ (latest only<sup>1</sup>)</td>
</tr>
</tbody>
</table>
<sup>1</sup> Due to a bug in Sponge 7, it cannot coexist in the same jar with classes compiled with Java higher than 8, which is required by some other platforms. To get the plugin to run on Sponge 7, you will need to do one of the following:
* Compile the plugin yourself and use the jar from `sponge7/build/libs`, which only contains sponge support instead of the universal jar from `jar/build/libs`.
* Unzip the plugin jar, go to `me/neznamy/tab/platforms/` and delete everything except `sponge7`.
Modded platforms only support the latest MC version(s). For older MC versions try [modrinth](https://modrinth.com/plugin/tab-was-taken)'s version download filter.
<sup>2</sup> Latest only doesn't mean only the latest build will work,
<sup>1</sup> Latest only doesn't mean only the latest build will work,
it means the plugin was made to be compatible with the latest version/build.
Since breaking changes don't happen too often, it means a wide range of versions is usually supported.
When a breaking change occurs, the plugin is updated to support the new version,
@@ -72,19 +81,19 @@ Since proxies support all client versions, there is never a reason to stay outda
so you can always safely update to new version/build of your proxy software if the plugin requires it.
# Supported features per platform
| Feature \ Platform | Bukkit / Hybrid | Sponge | Fabric | BungeeCord | Velocity |
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
| [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [BossBar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Global Playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) | ❌ | ❌ | ❌ | ✔ | ✔ |
| [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist) | ✔ |  ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) | ✔ | ❌ | ❌ | ❌ | ❌ |
| [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Tablist names](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) | ✔ | ✔ | ✔ | ✔ | ✔ |
| Feature \ Platform | Bukkit / Hybrid | Sponge | Fabric / Forge / NeoForge | BungeeCord | Velocity |
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------|---------------------------|------------|----------------------------------------------------------------------------|
| [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [BossBar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Global Playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist) | ✔ |  ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) | ✔ | ❌ | ❌ | ❌ | ❌ |
| [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
| [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix) | ✔ | ✔ | ✔ | ✔ | ✔ |
| [Tablist names](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) | ✔ | ✔ | ✔ | ✔ | ✔ |
✔ = Fully functional
 ❗ = NameTags: Anti-override missing, Scoreboard: [compatibility with other plugins](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard#compatibility-with-other-plugins) missing.
@@ -112,7 +121,11 @@ Supporting PremiumVanish's vanishing levels instead of using a basic compatibili
Communicating with other proxies to properly display visuals on players on another proxy.
## Fabric
[**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels.
[**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels.
[**placeholder-api**](https://modrinth.com/mod/placeholder-api) - Displaying placeholders from that mod and offering TAB's placeholders into it.
## Forge / NeoForge
[**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Permission checks with a string instead of using vanilla OP levels.
## Sponge
*None*
@@ -130,8 +143,8 @@ Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-ob
* **[Tablisknu](https://forums.skunity.com/resources/tablisknu.727/)** (skript addon) prevents TAB from assigning teams (sorting & nametags).
* **SkBee** (skript addon) sends empty scoreboard, causing TAB's to not show sometimes.
* **Waterfall**'s `disable_tab_list_rewrite: true` **may** cause tablist to use offline UUIDs while TAB expects online uuids, causing various problems (most notably tablist formatting not working). Checking for this option is not an option either, because tablist rewrite might still be enabled despite being disabled (don't ask how, I have no idea). Set the option to `false` if you are experiencing issues.
* **ViaVersion on BungeeCord and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on BungeeCord instead.
* **ViaVersion**'s `hide-scoreboard-numbers` config option makes [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)'s `value` and [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)'s `value` not visible for 1.20.3+ clients on <1.20.3 server when enabled. When using this setup, you'll need to keep the option disabled (sadly scoreboard numbers will not be hidden, solving this would require an implementation on ViaVersion's side).
* **ViaVersion on proxy and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on the proxy as well.
* **Nexo**'s `hide_scoreboard_numbers` config option (may apply to ItemsAdder and Oraxen as well) makes [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)'s `value` and [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)'s `value` not visible. When using these features, you'll need to keep the option disabled.
* **Custom clients / resource packs** - Unofficially modified minecraft clients often tend to break things. Just Lunar client has tons of bugs that can be reproduced with TAB. Resource packs may also contain modifications you are not aware of, making things not look the way you want them to. If you are experiencing any visual issue and are using a custom client or resource pack, try it with a clean vanilla client. If it works there, it's an issue with the client / resource pack and TAB cannot do anything about it.
For example, here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
* They add their icon to players in tablist, but don't widen the entries. This results in player names overlapping with latency bar. You can avoid this by configuring some spaces in tabsuffix.
+28 -15
View File
@@ -1,4 +1,5 @@
# Page content
* [About](#about)
* [Adding the dependency](#adding-the-dependency)
* [Getting started](#getting-started)
* [Lifespan of API calls](#lifespan-of-api-calls)
@@ -13,22 +14,31 @@
* [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting#api)
* [Placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#api)
# About
This page explains how to import TAB's API into your project and together with other pages everything you can do with it.
Before starting please seriously reconsider if you really need this and cannot already achieve it using existing features (such as registering placeholders into PlaceholderAPI instead of TAB, using display conditions for scoreboards, ...). If not, consider making a request.
# Adding the dependency
For Maven users, you can add the API dependency like this:
```xml
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>TAB-API</artifactId>
<version>5.0.5</version>
<scope>provided</scope>
</dependency>
<dependencies>
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>TAB-API</artifactId>
<version>5.2.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
```
Or, for Gradle users, here are a few examples:
@@ -40,7 +50,7 @@ repositories {
}
dependencies {
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.5'
compileOnly 'com.github.NEZNAMY:TAB-API:5.2.1'
}
```
@@ -51,15 +61,18 @@ repositories {
}
dependencies {
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.5")
compileOnly("com.github.NEZNAMY", "TAB-API", "5.2.1")
}
```
**WARNING: DO NOT SHADE THE API IN TO YOUR JAR! DO NOT RELOCATE THE API FROM ITS ORIGINAL PACKAGE!**
If you relocate, the API that you are calling will also get relocated in your code, meaning that you will be trying to call the relocated API. If you shade the API in, your code could load that bundled API, causing class path conflicts.
> [!WARNING]
> DO NOT SHADE THE API IN TO YOUR JAR! DO NOT RELOCATE THE API FROM ITS ORIGINAL PACKAGE!**
> If you relocate, the API that you are calling will also get relocated in your code, meaning that you will be trying to call the relocated API. If you shade the API in, your code could load that bundled API, causing class path conflicts.
Ensure that you are using **`<scope>provided</scope>` for Maven**, or **`compileOnly` for Gradle**.
> [!WARNING]
> If you are on Paper and your plugin is defined as a paper plugin (paper-plugin.yml), TAB's classes may not be visible to the class loader that loaded your plugin. To avoid this issue, do not use paper-plugin.yml in your plugin, just plugin.yml.
# Getting started
First, you need to get an instance of the API.
You can do this with `TabAPI.getInstance()`.
+6 -4
View File
@@ -101,10 +101,12 @@ bossbar:
`/tab bossbar announce <name> <time>`
`name` is name of bossbar defined in config.yml under **bossbar** section, `time` is length of display time in seconds.
When using a bossbar announcement, you have a new placeholder available:
`%countdown%` that shows remaining time of the announcement in seconds.
You can further use this placeholder in progress, for example,
using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_countdown}/<total time>*100%`.
When using a bossbar announcement, you have new placeholders available:
* `%bossbar_announce_time_left_<bossbar>%` - Returns remaining time of the announcement in seconds.
* `%bossbar_announce_time_total_<bossbar>%` - Returns total time of the announcement in seconds (the inputted number).
You can further use these placeholders in progress, for example,
using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_bossbar_announce_time_left_<bossbar>}/{tab_placeholder_bossbar_announce_time_total_<bossbar>}*100%`.
## Additional settings
| Option name | Default value | Description |
@@ -25,44 +25,36 @@ They have 2 main uses in the plugin:
# Condition types
## Number comparations
`>=`: greater than or equal to
Example: `%ping%>=100` will pass if the player's ping is greater than or equal to `100`.
`>`: greater than
Example: `%ping%>100` will pass if the player's ping is greater than `100`.
`<=`: less than or equal to
Example: `%ping%<=100` will pass if the player's ping is less than or equal to `100`.
`<`: less than
Example: `%ping%<100` will pass if the player's ping is less than `100`.
| Operation | Description | Example |
|-----------|--------------------------|--------------------------------------------------------------------------------|
| `>=` | Greater than or equal to | `%ping%>=100` will pass if the player's ping is greater than or equal to `100` |
| `>` | Greater than | `%ping%>100` will pass if the player's ping is greater than `100` |
| `<=` | Less than or equal to | `%ping%<=100` will pass if the player's ping is less than or equal to `100` |
| `<` | Less than | `%ping%<100` will pass if the player's ping is less than `100` |
## Text operations
`=`: equal to (must match exactly)
Example: `%world%=world` will pass if player is in world `world`.
| Operation | Description | Example |
|-----------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| `=` | Equal to | `%world%=world` will pass if player is in world `world` |
| `!=` | Not equal to | `%world%!=world` will pass if player is in any world except `world` |
| `<-` | Contains (left side for full text, right side text to contain) | `%world%<-lobby-` will pass if player is in any world that contains `lobby-` (such as `lobby-1` etc.) |
| `\|-` | Starts with (left side for full text, right side text to start with) | `%world%\|-lobby-` will pass if player is in any world that starts with `lobby-` (such as `lobby-1` etc.) |
| `-\|` | Ends with (left side for full text, right side text to end with) | `%world%-\|nether` will pass if player is in any world that ends with `nether` (such as `world_nether` etc.) |
`!=`: not equal to (opposite of the above)
Example: `%world%!=world` will pass if player is in any world except `world`.
> [!NOTE]
> For `=` and `!=` you can check for empty output of a placeholder using `%my_placeholder%=` and `%my_placeholder%!=`.
**Note 1**: For `=` and `!=` you can check for empty output of a placeholder using `%my_placeholder%=` and `%my_placeholder%!=`.
`<-`: contains (left side for full text, right side text to contain)
Example: `%world%<-lobby-` will pass if player is in any world that contains `lobby-` (such as `lobby-1` etc.).
`|-`: starts with (left side for full text, right side text to start with)
Example: `%world%|-lobby-` will pass if player is in any world that starts with `lobby-` (such as `lobby-1` etc.).
`-|`: ends with (left side for full text, right side text to end with)
Example: `%world%-|nether` will pass if player is in any world that ends with `nether` (such as `world_nether` etc.).
**Note 2**: For string operators, the text must match placeholder's output exactly, including color codes.
> [!NOTE]
> For string operations, the text must match placeholder's output exactly, including color codes.
If you are using [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements),
condition must contain the altered output.
To see the exact output of a placeholder including color codes, use `/tab parse <player> <placeholder>`.
## Permission
`permission:<value>`: permission requirement
Example: `permission:my.permission` will pass if player has `my.permission` permission.
| Operation | Description | Example |
|-----------------------|---------------------------------|----------------------------------------------------------------------------------------------|
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
| `!permission:<value>` | Negative permission requirement | `!permission:my.permission` will pass if player **does not have** `my.permission` permission |
# Multiple condition requirements
Each condition has a `conditions` parameter, which a list of conditions. If you define more than 1 condition, you must specify the condition type.
@@ -166,7 +158,7 @@ Refresh intervals of conditions are not directly configurable.
They are based on placeholders used inside (subconditions, yes/no values).
Permission checks count as 1000ms.
To configure refresh intervals of placeholders,
check out the [Optimization guide](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---placeholder-refresh-intervals).
check out the [Optimization guide](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals).
# Examples
## Example 1 - Chaining conditional placeholders
+11 -11
View File
@@ -7,19 +7,19 @@
* [Compatibility with vanish plugins](#compatibility-with-vanish-plugins)
# About
This feature allows you to see players from other servers under a bungeecord network, instead of only seeing players on the same server.
To use this feature, you need [TAB installed on bungeecord](https://github.com/NEZNAMY/TAB/wiki/Installation#bungeecord). This option is not available on bukkit and will not be added.
This feature allows you to see players from other servers, instead of only seeing players on the same server.
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) for proper linking of servers. On proxy installation, this is not required.
# Configuration
| Option name | Default value | Description |
|----------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | false | Enables / Disables the feature |
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to 0 (5 bars). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
| Option name | Default value | Description |
|----------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | false | Enables / Disables the feature |
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to `0` (![image](https://user-images.githubusercontent.com/6338394/179717531-3c6409b6-6bf8-41c1-a150-ce0ed615e5a5.png)). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
## Grouping players only from certain servers
In case you want multiple servers to share playerlist,
+1 -1
View File
@@ -67,7 +67,7 @@ header-footer:
> [!NOTE]
> To make per-world work on proxy installation,
> install the [TAB-Bridge](https://www.mc-market.org/resources/21641) plugin on your backend servers.
> install the [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) plugin on your backend servers.
## Per-group / per-player
**groups.yml**
+2 -6
View File
@@ -18,7 +18,7 @@
* [Example 1 - Per-server columns](#example-1---per-server-columns)
# About
This feature allows you to customize all 80 tablist slots. Displaying less than 4 columns is currently not supported. This feature can be enabled and configured in **config.yml** file under **layout** section.
This feature allows you to customize all 80 tablist slots. Displaying less than 4 columns is currently not supported ([here's why](https://gist.github.com/NEZNAMY/3dfcbf7d44283735d3c18266a2851651)). This feature can be enabled and configured in **config.yml** file under **layout** section.
This feature is only available for versions **1.8** and up due to massive tablist changes, which would make 1.7- compatibility require a complete rewrite of the functionality and could still cause all kinds of visual issues, including, but not limited to compatibility with other plugins adding/removing players from the tablist.
@@ -170,10 +170,6 @@ Layout feature is capable of working with all online players connected to the se
From 1.19.3 until 1.21.1 (inclusive), entries will also appear in chat complete.
The mechanic used to hide them on <1.19.3 can no longer be used since 1.19.3.
1.21.2 has added a new way of sorting players, which is being taken advantage of to restore empty chat complete.
> [!NOTE]
> To hide entries from chat complete on 1.21.2+,
the server also has to be 1.21.2+ so TAB can send the new content to the players
(having TAB installed on proxy counts as using the latest version).
## Additional note 4 - Per world playerlist incompatibility
Layout works by adding 80 fake players into the tablist, pushing real player entries out of view.
@@ -241,7 +237,7 @@ as a result, you won't see the second layers of skins.
This can only be fixed by actually letting the real entries display in the tablist,
which would require a rework of the feature, which is not planned.
**1.21.4 has added a new tablist option called "show hats", which layout sets to true.
They will be visible for 1.21.4+ players on 1.21.4+ servers (using a proxy counts as being latest).**
They will be visible for 1.21.4+ players.**
# Examples
## Example 1 - Per-server columns
@@ -0,0 +1,39 @@
# Enabling
Find this section in your config:
```
proxy-support:
enabled: true
# Supported types: PLUGIN, REDIS, RABBITMQ
type: PLUGIN
plugin:
# Compatible plugins: RedisBungee
# If enabled and compatible plugin is found, hook is enabled to work with proxied players
name: RedisBungee
redis:
url: 'redis://:password@localhost:6379/0'
rabbitmq:
exchange: 'plugin'
url: 'amqp://guest:guest@localhost:5672/%2F'
```
Pick your desired way and configure it. I don't know anything about redis or rabbitmq, so good luck.
> [!IMPORTANT]
> **BACKEND INSTALLATION ONLY**
> You should also add this into your config and configure it:
> ```
> server-name: "server"
> ```
> Ideally, name it the same as servers in your BungeeCord / Velocity config. This value will be used for getting player's server name. Aside from %server% placeholder, it will be used for internal logic when checking for player's server. One of them is global playerlist not adding/removing players who are on the same server in the tablist, as backend servers already do this (and duplicating actions would cause problems).
> **As a result, not configuring this value will result in everyone's server being "N/A", therefore global playerlist will think everyone is on the same server and not add anyone into the tablist, effectively making the feature not work.**
Supported plugins:
* [RedisBungee](https://github.com/ProxioDev/RedisBungee)
# Supported features
* [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
* [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist)
* [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting)
* [Playerlist Objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
The hook does not and will never support [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout), which would require drastic changes to the feature itself and wouldn't be worth it, since the amount of visible players is usually even lower due to fixed slots and RedisBungee is used on networks with many players, so there will be enough players to work with anyway.
+15 -9
View File
@@ -14,6 +14,7 @@
* [Additional note 3 - Changing name itself](#additional-note-3---changing-name-itself)
* [Additional note 4 - F1 view](#additional-note-4---f1-view)
* [Additional note 5 - Transparent players](#additional-note-5---transparent-players)
* [Additional note 6 - Vanish hook](#additional-note-6---vanish-hook)
* [API](#api)
* [Changing prefix and suffix](#changing-prefix-and-suffix)
* [Collision](#collision)
@@ -138,7 +139,7 @@ _DEFAULT_:
```
# Limitations
* [1.5 - 1.12.2] Prefix/suffix length is limited to 16 characters (including color codes).
* [1.5 - 1.12.2] Prefix/suffix length is limited to 16 characters (including color codes). Any characters beyond that will be cut to prevent players getting disconnected.
* [1.13+] The name can only have one code. That is either color or magic code (such as &4 or &l), but not both.
* The name cannot be effectively changed and the plugin doesn't offer it.
* Name does not support RGB codes. Any used RGB colors will be rounded to the nearest legacy code.
@@ -156,12 +157,9 @@ To avoid it, make their names not match any online player and use holograms to d
Since 1.9 teams affect tamed animals as well, displaying prefix/suffix on them as well and if nametags are set to be invisible, they will be completely invisible as well. If you want to avoid it, install [this plugin](https://www.spigotmc.org/resources/109466/).
## Additional note 3 - Changing name itself
Teams do not allow to change the nametag name itself.
Changing name is a complicated process which is slightly out of scope of the plugin as well,
this is what nick plugins are about.
On top of that, it's a pain
requiring a ton of different packets which change their structure every version and with how many versions the plugin supports,
it doesn't sound like fun doing.
Teams do not allow to change the nametag name itself. The only way to achieve this goal is to send & modify packets to replace the player's actual profile name with a fake one. This is very complicated and can have a lot of side effects. This is out of scope of TAB and is in fact something nick plugins are trying to achieve. As such, **TAB does not offer this**.
Once you find a nick plugin that supports this, you will need to [make sure TAB properly detects this](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins#proper-compatibility-with-plugins-that-change-profile-name) to make name-bound features (such as nametags) work properly for nicked players.
## Additional note 4 - F1 view
Using teams causes player nametags to remain visible when using F1 view.
@@ -183,6 +181,13 @@ scoreboard-teams:
```
In config.yml. Keep in mind, you'll not be able to use any team features if you do so.
# Additional note 6 - Vanish hook
When a player is [vanished](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection), their team is unregistered for all other players who do **not** have `tab.seevanished` permission.
The purpose of this is to avoid players figuring out a staff member is still online but just vanished by "checking" registered teams and their members. The easiest way of "exploiting" this is to have a 3rd party client spawn a player entity with name of a chosen player (staff member) and seeing if their name is formatted (= team is registered = player is online) or not (offline).
For this reason, make sure you give all of your staff members who can see vanished players the `tab.seevanished` permission, otherwise the vanished player will appear on top of the tablist (players without team are above players with team) and their nametag will not be formatted.
# API
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
@@ -192,7 +197,8 @@ To access this feature, you'll need to obtain `NameTagManager` instance. Get it
To set the values for the respective formatting, use the following:
* `NameTagManager#setPrefix(TabPlayer, String)`
* `NameTagManager#setSuffix(TabPlayer, String)`
To reset them, set values to `null`.
To reset them, set values to `null`.
To get custom values previously set using the API (they will return `null` if no custom value is set):
* `NameTagManager#getCustomPrefix(TabPlayer)`
@@ -209,8 +215,8 @@ To get the original value set by the plugin based on configuration:
> use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
## Collision
* `NameTagManager#getCollisionRule(TabPlayer)` - Returns forced collision rule using the API, `null` if no value was forced and player follows the configuration.
* `NameTagManager#setCollisionRule(TabPlayer, Boolean)` - Forces collision rule to the player. Use `null` to reset value and make it follow configuration again.
* `NameTagManager#getCollisionRule(TabPlayer)` - Returns forced collision rule using the API, `null` if no value was forced and player follows the configuration.
## Manipulating visibility
* `NameTagManager#hideNametag(TabPlayer)` - Hides player's nametag from all players
@@ -18,12 +18,14 @@ Since 1.20.3, it can display any text.
This feature can be configured in **config.yml** under **playerlist-objective** section.
# Configuration
| Option name | Default value | Description |
|-------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | true | Enables / Disables the feature |
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. Setting value to `%health%`, `%player_health%` or `%player_health_rounded%` will result in the display type being changed to hearts instead of number. If you want to display health but as a number and not hearts, set value to `0%health%` for example. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
| Option name | Default value | Description |
|-------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | true | Enables / Disables the feature |
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
| title | "TAB" | Title to send. Only visible on Bedrock Edition. |
| render-type | INTEGER | Render type of the value. Supported values are `INTEGER` and `HEARTS` |
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
# Limitations
* [1.5 - 1.20.2] Only 2 display types are supported - yellow number and hearts.
+32 -9
View File
@@ -21,6 +21,7 @@
* [Example 1 - Per-world scoreboards](#example-1---per-world-scoreboards)
* [Example 2 - Periodical scoreboard switching](#example-2---periodical-scoreboard-switching)
* [Example 3 - Per-version scoreboards](#example-3---per-version-scoreboards)
* [Example 4 - Conditional lines](#example-4---conditional-lines)
# About
Scoreboard objective with SIDEBAR display slot.
@@ -51,10 +52,6 @@ Will give you the following:
![image](https://github.com/NEZNAMY/TAB/assets/6338394/785e4e66-935c-4233-8a16-f3e34386cfd0)
The right side text will not be visible at 1.20.2 and lower, and numbers will be displayed instead.
To properly see the right side, not only the client has to be 1.20.3+, but the server as well,
since the plugin cannot send a packet that does not exist (on older versions).
Proxies always include new content and therefore count as the latest version.
This can be used to send new content to new players while having an outdated server version.
### Alignment
The left side of the text is automatically aligned to the left,
@@ -113,15 +110,16 @@ time - number of seconds to display the scoreboard for
| toggle-command | /sb | Command that can be used to toggle scoreboard for the player running the command. Players need `tab.scoreboard.toggle` permission to use it. <br />**Note:** This command will not appear in command auto-complete, because it's not a real registered command. Registered commands must be defined in plugin jar directly and cannot be dynamic. If you want to solve this, you can try getting a dummy private plugin made which registers that command. |
| remember-toggle-choice | false | When enabled, toggling decision is saved into a file to remember it even after reloads/restarts/reconnects |
| hidden-by-default | false | If enabled, scoreboard will automatically be hidden on join until toggle command is used to show it. |
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. |
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. Will not be visible for 1.20.3+ players, instead, you can configure any text to show using `\|\|text` in the lines (scroll up for more info). |
| static-number | 0 | If `use-numbers` is disabled, this is number to be in all lines. |
| delay-on-join-milliseconds | 0 | Delay in milliseconds to send scoreboard after joining. |
# Limitations
* [1.5 - 1.12.2] The title is limited to 32 characters (including color codes).
* [1.5 - 1.12.2] Line length is limited to 28 characters (including color codes) (68 on lines with static text / [Longer lines](#longer-lines)).
* [1.5 - 1.12.2] Line length is limited to 28 characters (including color codes) (64 on lines with static text / [Longer lines](#longer-lines)).
* [1.5 - 1.20.2] The red numbers on the right really cannot be removed from the plugin side (a client modification / resource pack is necessary).
* The client only displays up to 15 lines. If a plugin sends more, only the top 15 scores will be displayed. Changing this would require a client modification.
* The client only displays up to 15 lines. If a plugin sends more, only the top 15 scores will be displayed. Changing this would require a client modification (on versions below 1.8 the scoreboard won't appear at all if more than 15 lines are displayed).
* [Bedrock] Scoreboard lines may be cut off and show `...`, this can be bypassed by using [GeyserOptionalPack](https://github.com/GeyserMC/GeyserOptionalPack)
# Longer lines
To make sure the scoreboard never flickers, it's only using prefix/suffix components to display text.
@@ -285,7 +283,7 @@ We don't need to define display condition for the second scoreboard,
since if animation is not on the first frame, it has to be on the second one.
If using more than 2, you will need to define display condition for all of them (except the last one).
# Example 3 - Per-version scoreboards
## Example 3 - Per-version scoreboards
1.20.3 has added new features to scoreboards, which are not visible to older clients. If you want to use new features for new players while still displaying things properly for older players, you'll need to create 2 different scoreboards.
You'll find 2 placeholders useful for this:
* `%player-version-id%` - Returns network ID of player's protocol version. Unlike with version names, you can perform numerical comparisons with these. You can find all version IDs [here](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java). For example, 1.20.3 is 765.
@@ -315,4 +313,29 @@ Let's go ahead and set it up (with proper spacing):
- "* &bRank&7: &f%group%"
- "* &bPing&7: &f%ping%&8ms"
- "* &bWorld&7: &f%world%"
```
```
## Example 4 - Conditional lines
Sometimes you may want a dynamic line that only shows sometimes. TAB is already made to hide lines if they only consist of a placeholder that returned empty value. However, you may want to add more text around it, such as
```
- "Faction: %factionsuuid_faction_name%"
```
Now, even if the placeholder returns empty string, text `Faction: ` will remain, and as such, the line will stay. In order to avoid this, create a [conditional placeholder](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders). If the placeholder returned empty value, show empty value. Otherwise, show result of the placeholder with the static text:
```
conditions:
faction:
conditions:
- '%factionsuuid_faction_name%='
yes: "" # No faction, return empty string to hide the line
no: "Faction: %factionsuuid_faction_name%" # Player has a faction, show the static text before it as well
```
> [!WARNING]
> DO NOT JUST RANDOMLY PASTE THIS ENTIRE "CONDITIONS" SECTION INTO YOUR CONFIG!
> INSTEAD, EDIT YOUR EXISTING CONDITIONS SECTION TO PREVENT HAVING THE SECTION TWICE,
> HAVING SECOND ONE COMPLETELY OVERRIDE THE FIRST ONE!
Now, configure your scoreboard line as:
```
- "%condition:faction%"
```
and it will only display if the placeholder returned non-empty value.
@@ -1,13 +0,0 @@
When a plugin for linking proxies is detected, TAB automatically enables its hook to add support for it.
No configuration is necessary.
Supported redis plugins:
* [RedisBungee](https://github.com/ProxioDev/RedisBungee)
Supported features:
* [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
* [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist)
* [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting)
* [Playerlist Objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
The hook does not and will never support [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout), which would require drastic changes to the feature itself and wouldn't be worth it, since the amount of visible players is usually even lower due to fixed slots and RedisBungee is used on networks with many players, so there will be enough players to work with anyway.
+9
View File
@@ -0,0 +1,9 @@
# About
A collection of things people have requested that I might look into implementing when I get extremely bored sometime next decade.
* Less than 4 columns in Layout
* `invisible-nametags` supporting conditions like `enable-collision` does
* Relational placeholder support in sorting
* Conditional header/footer
* Relational conditions for layout player groups
* 1.21.2+ sorting support
* disable-condition support in multi-server-support
+2 -2
View File
@@ -24,7 +24,7 @@ See [Citizens FAQ](https://wiki.citizensnpcs.co/Frequently_Asked_Questions#Why_a
## #2 - How to make TAB work with LuckPerms?
* If you want to take prefixes/suffixes, check [this guide](https://github.com/NEZNAMY/TAB/wiki/Mini-guides-collection#taking-prefixessuffixes-from-permission-plugin) and use `%luckperms-prefix%` & `%luckperms-suffix%`.
* Sorting:
First, [Configure weights correctly in LuckPerms](https://github.com/NEZNAMY/TAB/wiki/How-to-setup-weights-priorities#luckperms). Then, you have 2 options:
First, configure weights correctly in LuckPerms. Then, you have 2 options:
* Configure [sorting by groups](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#groups) (recommended).
* Sorting by weights directly: `PLACEHOLDER_HIGH_TO_LOW:%luckperms_highest_group_weight%`.
@@ -75,7 +75,7 @@ Most common reasons for a placeholder to not work include:
* Using % symbol by itself, which breaks placeholder starts and ends and therefore breaks all placeholders after it (use %% to display the symbol)
* Trying to use a PlaceholderAPI placeholder without downloading its expansion or not having PlaceholderAPI installed at all
* Trying to use [TAB's internal bukkit-only placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#bukkit-only) on BungeeCord
* Trying to use [PlaceholderAPI placeholders on BungeeCord](https://github.com/NEZNAMY/TAB/wiki/How-to-set-up-PlaceholderAPI-support-on-bungeecord) without installing [bridge plugin](https://www.mc-market.org/resources/20631/)
* Trying to use [PlaceholderAPI placeholders on BungeeCord](https://github.com/NEZNAMY/TAB/wiki/How-to-set-up-PlaceholderAPI-support-on-bungeecord) without installing [bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)
The full list of reasons can be found on the [Placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#placeholder-is-not-working) page.
+1 -1
View File
@@ -1,5 +1,5 @@
# About the wiki
The wiki explains the functionality of the plugin as of version **5.0.5**.
The wiki explains the functionality of the plugin as of version **5.2.1**.
If your config looks different or is missing some features, you are using an old version of the plugin.
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
Wiki for older plugin versions is not available.
@@ -42,7 +42,13 @@ Most notably, it breaks all name-bound features,
especially [nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) and [sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist).
For that reason, TAB must detect name change.
This is done automatically on Bukkit, BungeeCord and Fabric.
This detection is not available on Sponge and Velocity.
**This detection is not available on Sponge and Velocity**.
> [!WARNING]
> If you have TAB installed on backend server and a proxy plugin changes nickname, TAB is not able to detect this change, since the modified packets never actually go through the backend server where TAB is installed.
> [!TIP]
> If automatic detection is not working for you (either you use TAB on Sponge / Velocity, or use proxy plugin for changing names while TAB is on backend), you can use [this API method](https://github.com/NEZNAMY/TAB/blob/master/api/src/main/java/me/neznamy/tab/api/TabPlayer.java#L88) to let TAB know player's name changed.
In order for TAB to properly detect name change, player UUID must remain the same,
otherwise it's a random entry with a random uuid and random name, which TAB isn't able to match with actual players.
@@ -3,6 +3,7 @@
* [Notepad++](#notepad-1)
* [Sublime Text 3](#sublime-text-3)
* [WinSCP](#winscp)
* [VSCode](#vscode)
***
# Notepad
@@ -25,4 +26,9 @@ Open the file > Click encoding > Click UTF-8 (if that didn't work, use Convert t
# WinSCP
Open the file in WinSCP and check if it is in UTF-8 encoding already (1). If it is not, click on ENCODING at the top (2), select UTF-8 (3) and then save the file. You can now edit the file as you like and paste in characters.
![](https://i.ibb.co/nRxDmHk/Win-SCP-Xlxw-Qfft-Cb.png)
![](https://i.ibb.co/dP77yzp/Win-SCP-UXWc4-Xdp2-N.png)
![](https://i.ibb.co/dP77yzp/Win-SCP-UXWc4-Xdp2-N.png)
# VSCode
At the bottom right of VSCode, you can change the current file's encoding. It'll open a pop-up where you can select UTF-8
![](https://github.com/user-attachments/assets/3ca885ee-24fb-47a5-863b-46b69df42e3a)
![](https://github.com/user-attachments/assets/ceab40b0-d875-4292-9d75-414557c7d4f8)
+21 -1
View File
@@ -7,6 +7,8 @@
* [Fonts](#fonts)
* [Usage](#usage-1)
* [Compatibility with RGB and gradients](#compatibility-with-rgb-and-gradients)
* [Tips & Tricks](#tips--tricks)
* [Tip 1 - Manually defining legacy color](#tip-1---manually-defining-legacy-color)
# Introduction
As of 1.16, mojang introduced RGB color support.
@@ -36,7 +38,7 @@ You can use a tool like [this one](https://htmlcolorcodes.com/) to easily find s
## Magic codes
Just like with legacy colors, use color first, then magic codes (in any order). For example with **bold**: `#00FFFF&lText`.
For gradients, put the magic codes at the beginning of text. For example `<#00FF00>&lBold gradient text</#FF00FF>`.
For gradients, put the magic codes inside the text. Example: `<#00FF00>&lBold gradient text</#FF00FF>`.
# Invalid code handling
In case you accidentally use invalid hex color code, the plugin will simply ignore it and keep it in raw format.
@@ -70,3 +72,21 @@ When trying to use both font and RGB or gradients, font must be outside and RGB/
**Result**
![image](https://github.com/NEZNAMY/TAB/assets/6338394/81da5bc4-1920-4dad-bcf5-002b5de741f8)
# Tips & Tricks
## Tip 1 - Manually defining legacy color
For features that don't depend on any other players (header/footer, bossbar, scoreboard), you can easily specify legacy color instead of automatic rounding for <1.16 players. This can be done using [conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders).
Check for `%player-version-id%`, which returns network protocol id of the version, which can be easily compared to. 1.16's version is `735`. Final condition may look like this:
```
conditions:
rgb:
conditions:
- '%player-version-id%>=735'
yes: "#00FF00 RGB text"
no: "&a Legacy text"
```
Then, use this condition with `%condition:rgb%`.
This will not work in nametags and tablist formatting, because this would parse the condition for target player, not the viewer. To achieve this, you'll need a custom-made [relational placeholder](https://github.com/NEZNAMY/TAB/wiki/Placeholders#relational-placeholders) that checks for viewer's version.
+1 -2
View File
@@ -33,8 +33,7 @@ install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI
> With proxy installation, use **/btab** command instead of **/tab**.
Advantages of proxy installation compared to installing on all backend servers instead:
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
* Can use new content for new clients even if a backend server version is old (1.16+ RGB codes, 1.20.3+ scoreboard features)
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) without requiring Redis set up and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
* Avoid [header/footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer#additional-note-1---not-resetting-on-server-switch) and [bossbar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar#additional-note-2---not-hiding-on-server-switch) not disappearing on server switch
* Configuration files in a single place for easier editing / no need to use MySQL for syncing data
+11 -53
View File
@@ -6,12 +6,10 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
* [#5 - [All platforms] Disable anti-override](#5---all-platforms-disable-anti-override)
* [#6 - [All platforms] Disable tablist name formatting](#6---all-platforms-disable-tablist-name-formatting)
* [#7 - [All platforms] Increase permission refresh interval](#7---all-platforms-increase-permission-refresh-interval)
* [#8 - [BungeeCord / Velocity] Disable TAB expansion](#8---bungeecord--velocity-disable-tab-expansion)
* [#9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#9---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
* [#10 - [BungeeCord] Disable ByteBuf deserialization](#10---bungeecord-disable-bytebuf-deserialization)
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
* [#7 - [BungeeCord / Velocity] Disable TAB expansion](#7---bungeecord--velocity-disable-tab-expansion)
* [#8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#8---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
# #1 - [All platforms] Static text instead of placeholders
Static text doesn't need to be refreshed periodically, resulting in better performance.
@@ -23,11 +21,11 @@ instead of using placeholders to take those values from your permission plugin.
# #2 - [All platforms] Placeholder refresh intervals
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
This only works for PlaceholderAPI placeholders. Internal ones have an interval hardcoded already with values I found the most appropriate.
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval`.
To begin, find this part in your config
```yml
placeholderapi-refresh-intervals:
placeholder-refresh-intervals:
default-refresh-interval: 500
"%server_uptime%": 1000
"%server_tps_1_colored%": 1000
@@ -56,27 +54,7 @@ This is because they work by spamming updates every time an animation switches t
When animations are not used, no updates need to be sent (unless another placeholder changes value),
not causing extra CPU usage.
# #5 - [All platforms] Disable anti-override
By default, TAB prevents other plugins from overriding it.
This is because many server owners are not competent enough to configure their plugins correctly,
such as disabling CMI collision handling which breaks nametags or even disabling team handling in paper.
This obviously causes higher CPU usage.
TAB logs all override attempts of teams into [anti-override.log](https://github.com/NEZNAMY/TAB/wiki/Action-logging#anti-overridelog) file.
If you didn't get the file, it looks like you configured your plugins correctly.
In that case, you can disable anti-override for teams in TAB.
```
scoreboard-teams:
anti-override: false
```
Tablist formatting is trickier, as override attempts are not logged because it would have a lot of false positives.
You will need to try and see.
```
tablist-name-formatting:
anti-override: false
```
# #6 - [All platforms] Disable tablist name formatting
# #5 - [All platforms] Disable tablist name formatting
As we already know from [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics#nametag-format-in-tablist) page,
the nametag format appears in tablist if tablist name is not defined.
You can take advantage of this to effectively disable one entire feature and its cpu usage,
@@ -87,12 +65,11 @@ However, you can only do this if:
Whether you have another plugin attempting to handle them or not may not be easy to identify with a lot of plugins and not enough time spent configuring them. In that case, your best choice is to try it and see. If tablist names break, it means you have another plugin and cannot take advantage of this optimization option.
# #7 - [All platforms] Increase permission refresh interval
# #6 - [All platforms] Increase permission refresh interval
Plugin needs to constantly check permissions for:
* Permission checks in conditions
* Permission checks if sorting by permissions
* Group retrieving from permission plugin (for sorting / per-group properties)
* Prefix/suffix placeholders taking data from your permission plugin
default value
```
@@ -102,7 +79,7 @@ Refreshes them every second.
If you don't need any permission/group changes to take effect within a second,
you can increase this value for better performance.
# #8 - [BungeeCord / Velocity] Disable TAB expansion
# #7 - [BungeeCord / Velocity] Disable TAB expansion
To maximize the performance of TAB's response to PlaceholderAPI request,
values are tracked in advance and then quickly returned.
This process takes resources and is especially heavy on proxy installation,
@@ -113,24 +90,5 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
placeholders:
register-tab-expansion: false
```
# #9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
# #10 - [BungeeCord] Disable ByteBuf deserialization
In order for anti-override to work properly, some packets must be manually deserialized due to BungeeCord not doing it. If you don't need it, you can disable those functions, which will also disable the deserialization completely. To do so, you must disable 2 functions (only having 1 disabled is not enough):
#1 - Anti-override for teams
```
scoreboard-teams:
anti-override: false
```
Whether you need anti-override or not depends on the configuration of your plugins.
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
If you don't, you don't need it.
If you do, you'll need to do some configuring.
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
are a few examples of software and their team names, which you may find helpful.
#2 - Scoreboard detection of other plugins
TAB checks for packets sent by other plugins, and if another plugin sends a scoreboard,
TAB hides its own and resends it after the other plugin removes it.
This check cannot be disabled if the scoreboard is enabled.
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
+28 -26
View File
@@ -28,39 +28,41 @@ Basic placeholders provided by the plugin. Most of these placeholders were added
Placeholders must be refreshed.
All internal placeholders have a manually defined refresh interval,
which I found the most optimal and cannot be changed.
which I found the most optimal. You can [override the refresh intervals](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals) if you want to.
Time is in milliseconds.
It is used for placeholders which are in fact constants (such as %player%),
`-1` is used for placeholders which are in fact constants (such as %player%),
or fire an event each time they change, which can be listened to (such as %world%) and value updated.
Refresh interval for conditions is set to be equal to the fastest refreshing placeholder used inside each condition.
## Universal
These placeholders are available on all platforms.
| Identifier | Refresh interval | Description |
|---------------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
| %world% | -1 | Name of world where player is |
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
| %player% | -1 | Player's name |
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
| %memory-used% | 200 | Used RAM of server in MB |
| %memory-max% | -1 | Total RAM of server in MB |
| %memory-used-gb% | 200 | Used RAM of server in GB |
| %memory-max-gb% | -1 | Total RAM of server in GB |
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
| Identifier | Refresh interval | Description |
|-------------------------------------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
| %world% | -1 | Name of world where player is |
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
| %player% | -1 | Player's name |
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
| %memory-used% | 200 | Used RAM of server in MB |
| %memory-max% | -1 | Total RAM of server in MB |
| %memory-used-gb% | 200 | Used RAM of server in GB |
| %memory-max-gb% | -1 | Total RAM of server in GB |
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
| %bossbar_announce_time_total_\<bossbar\>% | -1 | Total time (inputted value) of an announced bossbar. |
| %bossbar_announce_time_left_\<bossbar\>% | 100 | Remaining time of an announced bossbar. |
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
## Backend only
These placeholders only work when TAB is installed on a backend server (Bukkit / Sponge / Fabric), they don't work when TAB is on BungeeCord. If you have TAB installed on BungeeCord and wish to use these, use their [PlaceholderAPI](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide) alternative with [Bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) installed for PlaceholderAPI support on BungeeCord.
+4 -4
View File
@@ -1,12 +1,12 @@
# About
When TAB is installed on BungeeCord, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on BungeeCord.
When TAB is installed on BungeeCord / Velocity, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on a proxy.
You can download it from [GitHub](https://github.com/NEZNAMY/TAB-Bridge/releases) or [Spigot](https://www.spigotmc.org/resources/83966).
# Installation
Installation is as simple as it can be.
Put it into the plugins folder of your backend servers.
Bridge is NOT a BungeeCord plugin!
Bridge is NOT a proxy plugin!
Bridge itself has no config file, as everything is configured in the main plugin.
# Features
@@ -20,7 +20,7 @@ Bridge itself has no config file, as everything is configured in the main plugin
* Forward player's gamemode for spectator fix feature to work
# Compatibility
Just like with Minecraft, when there is a change in the communication protocol,
Just like with Minecraft, when there is a change in the communication protocol between TAB <-> TAB-Bridge,
older software will not be compatible with the new one.
You should always be running the latest versions of both TAB and Bridge.
+1 -1
View File
@@ -6,7 +6,7 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
# Download
### Releases
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken), [BuiltByBit](https://builtbybit.com/resources/20631) or [CurseForge](https://www.curseforge.com/minecraft/mc-mods/tab).
### Dev builds
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
@@ -201,15 +201,37 @@ public interface NameTagManager {
/**
* 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,
+3 -3
View File
@@ -3,7 +3,7 @@ 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.8-SNAPSHOT")
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
implementation("io.freefair.gradle:lombok-plugin:8.13")
implementation("dev.architectury.loom:dev.architectury.loom.gradle.plugin:1.10-SNAPSHOT")
}
+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()
}
@@ -1,6 +1,10 @@
plugins {
id("tab.standard-conventions")
id("com.github.johnrengelman.shadow")
id("com.gradleup.shadow")
}
tasks.withType<Zip>().configureEach {
isZip64 = true
}
tasks {
@@ -11,5 +15,10 @@ tasks {
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")
}
}
+15 -8
View File
@@ -4,26 +4,33 @@ plugins {
allprojects {
group = "me.neznamy"
version = "5.0.6"
version = "5.2.2"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
ext.set("author", "NEZNAMY")
ext.set("credits", "Joseph T. McQuigg (JT122406)")
}
val platforms = setOf(
projects.bukkit,
projects.bukkit.paper,
projects.bukkit.paper1205,
projects.bukkit.paper1212,
projects.bukkit.paper1214,
projects.bukkit.v18R3,
projects.bukkit.v112R1,
projects.bukkit.v116R3,
projects.bukkit.v117R1,
projects.bukkit.v118R2,
projects.bukkit.v119R1,
projects.bungeecord,
projects.component,
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.sponge,
projects.fabric,
projects.fabric.v1144,
projects.fabric.v1182,
projects.fabric.v1203,
projects.fabric.v1213
projects.neoforge,
projects.forge
).map { it.dependencyProject }
val special = setOf(
+2
View File
@@ -15,4 +15,6 @@ dependencies {
exclude("net.md-5", "bungeecord-chat")
}
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
compileOnly("com.viaversion:viaversion-common:5.2.1")
}
@@ -1,52 +0,0 @@
package me.neznamy.tab.platforms.paper;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.component.KeybindComponent;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.chat.component.TextComponent;
import me.neznamy.tab.shared.chat.component.TranslatableComponent;
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 for versions 1.21.4+.
*/
@SuppressWarnings("unused") // Used via reflection
public class PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Component convert(@NotNull TabComponent component) {
// Component type
MutableComponent nmsComponent = switch (component) {
case TextComponent text -> Component.literal(text.getText());
case TranslatableComponent translatable -> Component.translatable(translatable.getKey());
case KeybindComponent keybind -> Component.keybind(keybind.getKeybind());
default -> throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
};
// Component style
ChatModifier modifier = component.getModifier();
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?
nmsComponent.setStyle(style);
// Extra
for (TabComponent extra : component.getExtra()) {
nmsComponent.append(convert(extra));
}
return nmsComponent;
}
}
@@ -1,17 +0,0 @@
package me.neznamy.tab.platforms.paper;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
/**
* Loader for 1.20.5+ Paper to speed up functions by using direct NMS code instead of reflection.
*/
@SuppressWarnings("unused") // Via reflection
public class PaperLoader {
/** Channel getter using direct NMS code */
public static final FunctionWithException<BukkitTabPlayer, Channel> getChannel =
player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
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,49 @@
package me.neznamy.tab.platforms.paper_1_20_5;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier 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 Object parent, @NotNull Object child) {
((MutableComponent)parent).append((Component) child);
}
}
@@ -0,0 +1,58 @@
package me.neznamy.tab.platforms.paper_1_20_5;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for versions 1.20.5 - 1.21.1.
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new PaperComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new PaperPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
PaperPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
PaperPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new PaperPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,177 @@
package me.neznamy.tab.platforms.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 PaperPacketScoreboard 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);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(), objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, scoreboard.onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
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;
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public PaperPacketScoreboard(@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().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
}
/**
* 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,194 @@
package me.neznamy.tab.platforms.paper_1_20_5;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
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.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class PaperPacketTabList 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 Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, TrackedTabList<BukkitTabPlayer> tabList) {
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();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = tabList.getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public PaperPacketTabList(@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 setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@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
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
}
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);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
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,52 @@
package me.neznamy.tab.platforms.paper_1_21_2;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
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 PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier 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 Object parent, @NotNull Object child) {
((MutableComponent)parent).append((Component) child);
}
}
@@ -0,0 +1,58 @@
package me.neznamy.tab.platforms.paper_1_21_2;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.2 - 1.21.3.
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new PaperComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new PaperPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
PaperPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
PaperPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new PaperPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,177 @@
package me.neznamy.tab.platforms.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 PaperPacketScoreboard 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);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(), objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, scoreboard.onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
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;
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public PaperPacketScoreboard(@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().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
}
/**
* 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,196 @@
package me.neznamy.tab.platforms.paper_1_21_2;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
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.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class PaperPacketTabList 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 Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, TrackedTabList<BukkitTabPlayer> tabList) {
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();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = tabList.getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public PaperPacketTabList(@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 setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@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
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
}
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);
}
}
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.8"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
}
repositories {
@@ -0,0 +1,50 @@
package me.neznamy.tab.platforms.paper_1_21_4;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier 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 Object parent, @NotNull Object child) {
((MutableComponent)parent).append((Component) child);
}
}
@@ -0,0 +1,58 @@
package me.neznamy.tab.platforms.paper_1_21_4;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.4 - 1.21.6.
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new PaperComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new PaperPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
PaperPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
PaperPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new PaperPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,177 @@
package me.neznamy.tab.platforms.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 PaperPacketScoreboard 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);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(), objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, scoreboard.onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
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;
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public PaperPacketScoreboard(@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().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
}
/**
* 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,198 @@
package me.neznamy.tab.platforms.paper_1_21_4;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
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.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class PaperPacketTabList 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);
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, TrackedTabList<BukkitTabPlayer> tabList) {
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();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = tabList.getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public PaperPacketTabList(@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 setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@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
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
}
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);
}
}
@@ -58,8 +58,9 @@ public class BukkitEventListener implements EventListener<Player>, Listener {
* @param e
* Command event
*/
@EventHandler
@EventHandler(priority = EventPriority.HIGH) // HIGH sounds about fair
public void onCommand(PlayerCommandPreprocessEvent e) {
if (e.isCancelled()) return;
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
}
@@ -1,39 +1,17 @@
package me.neznamy.tab.platforms.bukkit;
import io.netty.channel.Channel;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.ProtocolVersion;
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.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.EnumSet;
/**
* Pipeline injection for Bukkit 1.8+.
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
ProtocolVersion.V1_20_5,
ProtocolVersion.V1_20_6,
ProtocolVersion.V1_21,
ProtocolVersion.V1_21_1,
ProtocolVersion.V1_21_2,
ProtocolVersion.V1_21_3,
ProtocolVersion.V1_21_4
);
/** Function for getting player's channel */
@Setter
private static FunctionWithException<BukkitTabPlayer, Channel> getChannel;
/**
* Constructs new instance
*/
@@ -41,54 +19,10 @@ 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.
*
* @param serverVersion
* Server version
*/
@SuppressWarnings("unchecked")
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
try {
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
getChannel = (FunctionWithException<BukkitTabPlayer, Channel>) Class.forName("me.neznamy.tab.platforms.paper.PaperLoader").getDeclaredField("getChannel").get(null);
return;
}
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");
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(player.getHandle())));
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "network channel injection", null,
"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
@NotNull
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
return getChannel.apply((BukkitTabPlayer) player);
return ((BukkitPlatform)player.getPlatform()).getServerImplementationProvider().getChannelFunction().apply((BukkitTabPlayer) player);
}
}
@@ -1,12 +1,12 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Arrays;
@@ -23,16 +23,13 @@ public class BukkitReflection {
public static final Method CraftPlayer_getHandle = getHandle();
/** Server version data */
@Getter
private static final ServerVersion serverVersion = detectServerVersion();
/** 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");
/** Flag determining whether the server version is at least 1.21.4 or not */
@Getter
private static final boolean is1_21_4Plus = serverVersion.minorVersion >= 21 &&
@@ -45,36 +42,19 @@ public class BukkitReflection {
}
private static ServerVersion detectServerVersion() {
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
String[] array = CRAFTBUKKIT_PACKAGE.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);
}
return new ServerVersion(Integer.parseInt(array[3].split("_")[1]), array[3]);
} else {
// Paper without CB relocation
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
return new ServerVersion(Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]), null);
}
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.
* Returns class with given potential names in same order. It takes packaged class names
* without "net.minecraft." prefix.
*
* @param names
* possible class names
@@ -86,7 +66,7 @@ public class BukkitReflection {
public static Class<?> getClass(@NotNull String... names) {
for (String name : names) {
try {
return serverVersion.getClass.apply(name);
return Class.forName("net.minecraft." + name);
} catch (ClassNotFoundException | NullPointerException ignored) {
// not the first class name in array
}
@@ -99,9 +79,11 @@ public class BukkitReflection {
*/
@RequiredArgsConstructor
@Getter
private static class ServerVersion {
public static class ServerVersion {
private final FunctionWithException<String, Class<?>> getClass;
private final int minorVersion;
@Nullable
private final String serverPackage;
}
}
@@ -3,7 +3,7 @@ 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.component.TabComponent;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@@ -2,14 +2,10 @@ package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
@@ -52,7 +48,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public int getPing() {
return PingRetriever.getPing(this);
return getPlatform().getServerImplementationProvider().getPing(this);
}
@Override
@@ -70,12 +66,6 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return LibsDisguisesHook.isDisguised(this);
}
@Override
@Nullable
public TabList.Skin getSkin() {
return ((TabListBase<?>)getTabList()).getSkin();
}
@Override
@NotNull
public Player getPlayer() {
@@ -1,86 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
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("§c[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("§c[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,11 +1,13 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.component.TabComponent;
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;
@@ -17,8 +19,12 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
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 = org.bukkit.boss.BarStyle.values();
private static final org.bukkit.boss.BarStyle[] styles = available ? org.bukkit.boss.BarStyle.values() : null;
/** Player this handler belongs to */
@NotNull
@@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor;
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.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
import org.jetbrains.annotations.NotNull;
@@ -1,6 +1,6 @@
package me.neznamy.tab.platforms.bukkit.features;
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;
@@ -26,6 +26,10 @@ import java.util.Map.Entry;
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Reference to platform */
@NotNull
private final BukkitPlatform platform;
/** Config options */
@NotNull
private final PerWorldPlayerListConfiguration configuration;
@@ -35,25 +39,28 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
*
* @param plugin
* Plugin instance to register events
* @param platform
* Platform reference
* @param configuration
* Feature configuration
*/
public PerWorldPlayerList(@NotNull JavaPlugin plugin, @NotNull PerWorldPlayerListConfiguration configuration) {
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()) {
for (Player p : platform.getOnlinePlayers()) {
for (Player pl : platform.getOnlinePlayers()) {
p.showPlayer(pl);
}
}
@@ -95,7 +102,7 @@ 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);
@@ -1,32 +0,0 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Header/footer sender using Bukkit API. Despite this feature getting
* added into the game in 1.8, Bukkit team took 4 entire years to add
* it to the API, which eventually made it there in 1.13. <p>
* The input is just a String, meaning Bukkit will have to deserialize it
* into components, which is a massive performance loss. It also does not
* seem to support fonts.
*/
public class BukkitHeaderFooter extends HeaderFooter {
/** Flag tracking whether this class can be used on current server */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class);
@Override
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
player.getPlayer().setPlayerListHeaderFooter(
player.getPlatform().toBukkitFormat(header),
player.getPlatform().toBukkitFormat(footer)
);
}
}
@@ -1,60 +0,0 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.component.TabComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A class responsible for sending header/footer to players on request.
*/
public abstract class HeaderFooter {
/** Instance of the class, null if no implementation is available */
@Getter
@Nullable
private static HeaderFooter instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
instance = findInstance0();
}
@Nullable
private static HeaderFooter findInstance0() {
try {
return new PacketHeaderFooter();
} catch (Exception e) {
if (PaperHeaderFooter.isAvailable()) {
BukkitUtils.compatibilityError(e, "sending Header/Footer", "Paper API");
return new PaperHeaderFooter();
}
if (BukkitHeaderFooter.isAvailable()) {
BukkitUtils.compatibilityError(e, "sending Header/Footer", "Bukkit API",
"Header/Footer having drastically increased CPU usage",
"Header/Footer not supporting fonts (1.16+)");
return new BukkitHeaderFooter();
} else {
BukkitUtils.compatibilityError(e, "sending Header/Footer", null,
"Header/Footer feature not working");
return null;
}
}
}
/**
* Sends header/footer to player.
*
* @param player
* Player to send header/footer to.
* @param header
* Header to use.
* @param footer
* Footer to use.
*/
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer);
}
Loaded 100 of 416 files, more files were not shown because too many files have changed in this diff. Show more