Compare commits

...
444 Commits
Author SHA1 Message Date
NEZNAMY 27bae7fdbd 3.3.0 2023-02-21 17:03:39 +01:00
NEZNAMY 174b4214de Bukkit - Fix error from previous commits 2023-02-21 13:52:21 +01:00
NEZNAMY 4ac524840c Remove non-existing branch from build.yml 2023-02-20 20:42:48 +01:00
NEZNAMY dd4ce4e2c3 Remove redundant library (gson) 2023-02-20 17:34:38 +01:00
NEZNAMY 2a03d69628 Add @SuppressWarnings annotation to a method 2023-02-20 17:33:44 +01:00
NEZNAMY c28252e6ce RGB - Fix redundant color codes in conversion to legacy, causing issues with limits (#827) 2023-02-20 17:02:20 +01:00
NEZNAMY da1768d833 Further code cleanup 2023-02-20 16:43:49 +01:00
NEZNAMY b58c3e6b47 Fix typos 2023-02-20 12:11:58 +01:00
NEZNAMY c7575bdc8f Remove redundant throws and empty method 2023-02-20 12:00:01 +01:00
NEZNAMY e275d22061 Bukkit - Fix startup on <1.8 2023-02-20 00:35:50 +01:00
NEZNAMY b5600ef22a Fix merge conflict 2023-02-20 00:29:05 +01:00
NEZNAMY b8e14c9256 Merge branch 'master' of https://github.com/NEZNAMY/TAB
 Conflicts:
	krypton/src/main/kotlin/me/neznamy/tab/platforms/krypton/KryptonPlaceholderRegistry.kt
2023-02-19 23:58:11 +01:00
NEZNAMY 1af5b9a308 Bukkit - Further split NMS loading and packet creation 2023-02-19 23:58:01 +01:00
Callum Seabrook 2dd719c3a5 Added Spark placeholder support back to Krypton 2023-02-19 21:08:04 +00:00
NEZNAMY 45d94694cd Bukkit - Split NMS fields and initialization into multiple classes 2023-02-19 21:15:10 +01:00
NEZNAMY 3ce8c6a192 Code style - Extract duplicated code 2023-02-19 17:41:06 +01:00
NEZNAMY 7deb86f105 Fix typo 2023-02-19 17:28:25 +01:00
NEZNAMY d9b4f6a43f Bukkit - Move data watcher serializer initialization 2023-02-19 17:15:58 +01:00
NEZNAMY 54a7fb8721 Code style - Extract duplicated code for permission plugin detection 2023-02-19 16:08:02 +01:00
NEZNAMY d1dcb2f437 Merge remote-tracking branch 'origin/master' 2023-02-18 17:35:03 +01:00
NEZNAMY a92bab617a Unlimited nametags - Fix error caused by wrong method being called 2023-02-18 17:34:52 +01:00
Callum Seabrook 57e34bc595 Updated to latest Krypton and added TAB prefix to console 2023-02-15 23:58:25 +00:00
Callum Seabrook 953baa273c Build Krypton targeting Java 8 2023-02-15 20:23:39 +00:00
NEZNAMY c403cb3187 Fix startup after recode 2023-02-15 21:03:19 +01:00
NEZNAMY ffd6dc5bb9 Disable krypton compilation to fix Sponge 7 startup (due to a Sponge 7 bug) 2023-02-15 20:53:06 +01:00
NEZNAMY 0f6504a3dc Remove unused local variable 2023-02-15 20:28:55 +01:00
NEZNAMY f376fea598 Formatting 2023-02-15 20:26:50 +01:00
NEZNAMY 4896cdff91 Further extend lombok usage 2023-02-15 19:17:29 +01:00
NEZNAMY b4c87431bc Scoreboard - Fix placeholders rarely not refreshing in title 2023-02-15 19:16:29 +01:00
NEZNAMY 1cffbab97d Remove plugin message text from /tab cpu due to being impossible to read 2023-02-15 15:14:30 +01:00
NEZNAMY 9b0711075a Code style - Use constants in more places 2023-02-15 15:06:15 +01:00
NEZNAMY 17a8e902eb Velocity - Use data folder given by the platform instead of manually defining it 2023-02-15 15:01:16 +01:00
Callum Seabrook 4bdb5ba245 Use more constants for plugin info 2023-02-15 12:16:48 +00:00
NEZNAMY fd7748d280 Merge remote-tracking branch 'origin/master' 2023-02-15 12:51:36 +01:00
NEZNAMY 317b0292ff Fix error on sponge (and redundancy on bukkit) 2023-02-15 12:51:19 +01:00
Callum Seabrook b87be649bc Made ARMOR_STAND_ID constant and removed packet builder singleton for Sponge 2023-02-15 11:51:09 +00:00
Callum Seabrook e2dda54011 Added sneaking support for unlimited nametags Sponge 2023-02-15 11:43:05 +00:00
NEZNAMY d17518c39b Sponge7 - Fix broken scoreboards 2023-02-14 23:19:33 +01:00
NEZNAMY 48541dd0bd Placeholders - Fix NPE 2023-02-14 22:29:28 +01:00
NEZNAMY b83b023fee Sponge8 - Add ViaVersion hook 2023-02-14 22:29:13 +01:00
NEZNAMY 60e0c78033 Add Sponge7 module 2023-02-14 22:28:32 +01:00
NEZNAMY a395f7f01f Unlimited NameTags - Fix entity type detection from previous commits 2023-02-14 18:46:17 +01:00
NEZNAMY 09c36ef69e Relocate yamlassist as well 2023-02-14 18:28:45 +01:00
NEZNAMY 9d5fa685a0 Rename folder as well 2023-02-14 18:25:08 +01:00
NEZNAMY 6794f593fa Rename sponge module to sponge8 2023-02-14 17:39:41 +01:00
NEZNAMY 5e9965276f Code style - Change how packet builder is initialized in Platform class 2023-02-14 16:54:58 +01:00
NEZNAMY 1473038955 Code style - Fix redundant imports, throws, extract constants, grammar 2023-02-14 16:39:21 +01:00
Callum Seabrook 2041ca57f3 Finished Sponge support 2023-02-14 14:38:09 +00:00
NEZNAMY 098782d820 3.3.0-SNAPSHOT 2023-02-14 12:53:10 +01:00
NEZNAMY 87e9a6defd 3.2.5 2023-02-13 16:40:36 +01:00
NEZNAMY 3d896ff828 More abstraction of unlimited nametag mode (also a small fix from previous commit) 2023-02-13 14:25:38 +01:00
NEZNAMY bf93240888 Unlimited NameTags - Make armor stands visible for spectators (#785) 2023-02-13 13:57:49 +01:00
NEZNAMY 0b0e30c3c9 Disable sponge module until compilation is fixed 2023-02-13 12:54:05 +01:00
NEZNAMY 84d3816bd1 Fix NPE 2023-02-12 12:51:37 +01:00
NEZNAMY 04ff606947 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-02-12 12:29:06 +01:00
NEZNAMY 1cb8e6e238 Almost finish Sponge 8 2023-02-12 12:29:01 +01:00
NEZNAMY 4a2226170d Merge pull request #819 from Tanguygab/master
added support for *server or *world (just like server* and world*)
2023-02-12 12:27:03 +01:00
Tanguygab 1469124cfc added support for *server or *world (just like server* and world*) 2023-02-12 11:41:11 +01:00
NEZNAMY 0826095e73 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-02-11 15:34:05 +01:00
NEZNAMY 1bd698adde Code style - Create custom cache object to avoid code duplication 2023-02-11 14:33:31 +00:00
Callum Seabrook 1058d269ff Start of rewrite of Sponge for v8 over v7 2023-02-11 14:13:05 +00:00
NEZNAMY bacd87b78e Code style - Create custom cache object to avoid code duplication 2023-02-11 13:08:20 +01:00
Callum Seabrook 5befbe8780 Updated Sponge platform 2023-02-10 23:11:38 +00:00
Callum Seabrook e159b2adae Finished Sponge implementation (mostly) 2023-02-10 23:08:21 +00:00
Callum Seabrook 52f7418853 The start of Sponge 2023-02-10 23:08:21 +00:00
Callum Seabrook 76061f7ccd Set up Sponge skeleton project 2023-02-10 23:08:21 +00:00
NEZNAMY 7d39e21eae Fix mistake after recode 2023-02-11 00:07:10 +01:00
NEZNAMY fee3723474 Code style - More abstraction of features and feature loading 2023-02-10 23:06:52 +01:00
NEZNAMY 2f0e4c6888 Merge remote-tracking branch 'origin/master' 2023-02-10 12:58:49 +01:00
NEZNAMY b04cb6d3c5 Config converter - Reverse old config detection (#762) 2023-02-10 12:58:07 +01:00
Callum Seabrook a9228a698a Fixed NPE being thrown when player info data name is null for Krypton 2023-02-09 16:18:13 +00:00
NEZNAMY 27102495d8 Code style - Use lombok in more locations 2023-02-08 00:49:48 +01:00
NEZNAMY d1bf96ab33 Code style - Use lombok for getters/setters 2023-02-07 23:14:02 +01:00
NEZNAMY 3360dbb468 Update README.md 2023-02-07 20:21:14 +01:00
NEZNAMY d224e7584e Create CONTRIBUTING.md 2023-02-07 20:20:31 +01:00
Callum Seabrook 6373540076 Updated Krypton platform and removed Indra 2023-02-07 16:56:58 +00:00
NEZNAMY 5f78aca997 Hide proxy-only methods into proxy player object 2023-02-07 15:33:24 +01:00
NEZNAMY 145d944d33 Bukkit - Remove unused (and incorrectly implemented) code 2023-02-07 15:26:01 +01:00
NEZNAMY ac91940e3b Velocity - Send packet using wanted player uuid instead of random if entry does not exist (apparently it goes through even if not registered) 2023-01-31 21:36:26 +01:00
NEZNAMY ef8452be73 Code style - Simplify BungeeCord info packet read/write based on client version 2023-01-31 18:23:24 +01:00
NEZNAMY 47e0810697 Placeholders - Remove %essentialsnick% placeholder due to being pretty redundant (keeping empty implementation so users don't get it suddenly broken) 2023-01-31 17:11:45 +01:00
NEZNAMY 1f30f225c7 Code style - Use reflection for PremiumVanish API instead of direct code with dependencies 2023-01-31 16:17:33 +01:00
NEZNAMY fcbfd60ddb Code style - Call method directly when command sender is console 2023-01-31 15:55:28 +01:00
NEZNAMY 46f9033713 Code style - Add final modifier to variable 2023-01-31 15:54:48 +01:00
NEZNAMY 7e65018a0e Velocity - Switch to non-deprecated method 2023-01-31 15:51:28 +01:00
NEZNAMY 9ef3485968 Code style - Create a method for convenience to avoid duplicated code 2023-01-27 15:52:38 +01:00
NEZNAMY 257f8f1145 Commands - Remove properly length from debug output 2023-01-27 15:24:26 +01:00
NEZNAMY 8d65cbb0e5 3.2.5-SNAPSHOT 2023-01-26 10:35:08 +01:00
NEZNAMY 20d221e3ca Merge pull request #806 from NotAlexNoyle/proposal
Propose adopting open source license
2023-01-25 12:17:56 +01:00
NEZNAMY 94072acf10 3.2.4 2023-01-25 10:39:44 +01:00
NEZNAMY 0422cc2597 Update issue templates 2023-01-24 14:00:13 +01:00
NEZNAMY 58f61e645b 3.2.4-SNAPSHOT 2023-01-24 10:45:11 +01:00
NEZNAMY 5f416bd9e5 Unlimited NameTags - Avoid Bukkit API NPE (#800, #804) 2023-01-24 10:44:35 +01:00
NotAlexNoyle 64528c913c Propose adopting open source license 2023-01-23 22:33:21 -05:00
NEZNAMY 357e7c6a49 Revert "Remove redundant try/catch"
This reverts commit 1777d8bf01.
2023-01-23 14:39:54 +01:00
NEZNAMY 81dd202053 3.2.3 2023-01-21 14:58:34 +01:00
NEZNAMY 189797aa73 Bukkit - Use the other constructor for player info packet which is cleaner (also #797) 2023-01-21 00:18:19 +01:00
NEZNAMY 1ae2ef7cad Fix compilation 2023-01-21 00:17:02 +01:00
NEZNAMY de14e14d33 Revert "Placeholders - Add timeout for placeholders to return value, causing TAB to freeze"
This reverts commit 37caed3123.
2023-01-19 22:30:09 +01:00
NEZNAMY 19892c14c1 Revert "Placeholders - Increase placeholder retrieve timeout to 500ms (#792)"
This reverts commit 6d85875fde.
2023-01-19 22:29:59 +01:00
NEZNAMY 2e00c4c002 Revert "NameTags - Fix error that doesn't make sense (#794)"
This reverts commit 12f5dd52b4.
2023-01-19 22:29:52 +01:00
NEZNAMY 97aa367629 Revert "Fix thread pool not being single threaded (maybe #781?)"
This reverts commit 87cc9a030b.
2023-01-19 22:28:22 +01:00
NEZNAMY 87cc9a030b Fix thread pool not being single threaded (maybe #781?) 2023-01-19 22:01:11 +01:00
NEZNAMY 8ba83d1cb1 BossBar - Fix errors when progress is below 0 or above 100 2023-01-19 16:59:05 +01:00
NEZNAMY 9f0e60bd47 Per world PlayerList - Remove redundant task schedule to main thread 2023-01-19 16:58:45 +01:00
NEZNAMY 93b2a25b2e Merge pull request #795 from Chris6ix/update-actions
Update Actions
2023-01-17 22:01:37 +01:00
Chris 4242d7a839 Remove Test Build Trigger 2023-01-17 21:33:33 +01:00
Chris 0ddb99bd46 Trigger Test Build 2023-01-17 21:29:34 +01:00
Chris 969672e3ad Update Actions 2023-01-17 21:26:40 +01:00
NEZNAMY 2423fbb150 Update README.md 2023-01-17 20:28:47 +01:00
NEZNAMY a99928dbf5 Placeholders - Colorize output of relational placeholders as well 2023-01-17 20:25:06 +01:00
NEZNAMY 12f5dd52b4 NameTags - Fix error that doesn't make sense (#794) 2023-01-17 15:38:20 +01:00
NEZNAMY 2bd105446e Bukkit - Properly send RGB messages into console (only for Paper+, Spigot doesn't support RGB at all) 2023-01-17 14:11:51 +01:00
NEZNAMY 6d85875fde Placeholders - Increase placeholder retrieve timeout to 500ms (#792) 2023-01-16 18:49:23 +01:00
NEZNAMY 42810aa490 3.2.3-SNAPSHOT 2023-01-16 18:47:42 +01:00
NEZNAMY bd31521bd3 3.2.2 2023-01-15 12:02:05 +01:00
NEZNAMY ec0deb8378 Yellow Number - Disable the feature for Bedrock players 2023-01-12 21:57:56 +01:00
NEZNAMY ae66b6e9a1 RGB - Fix MiniMessage->Bukkit conversion being after Bukkit->TAB, making it not translate anymore 2023-01-12 14:55:41 +01:00
NEZNAMY 3b86becebd Header/Footer - Do not add &r after \n anymore 2023-01-12 14:53:38 +01:00
NEZNAMY 4a8d86825b Configuration - Small changes to default configuration files 2023-01-10 18:56:34 +01:00
NEZNAMY 54a3bd81b4 Global PlayerList - Increase player removal prevention delay to 2000ms from 500ms 2023-01-10 16:49:16 +01:00
NEZNAMY b6768cca99 CPU manager - Add try/catch for task scheduling (#781) 2023-01-10 16:48:28 +01:00
NEZNAMY 60c414deed Bukkit - Remove redundant methods in nms loader 2023-01-05 16:48:16 +01:00
NEZNAMY 009283a83f Update README.md 2023-01-05 16:04:38 +01:00
Callum Seabrook 4a430b97df Merge pull request #779 from JT122406/master
Updates
2023-01-04 22:50:08 +00:00
NEZNAMY 722333da2e Fix RejectedExecutionException (#769) 2023-01-04 19:15:56 +01:00
NEZNAMY 1215af5262 Bukkit - Add 1.5.0 support 2023-01-04 18:28:24 +01:00
NEZNAMY 254088c37b Bukkit - Repackage nms loaders (also fix startup on Thermos) 2023-01-04 18:14:39 +01:00
J.T. McQuiggandCallum Seabrook cb2ce4acca Update gradle/libs.versions.toml
Co-authored-by: Callum Seabrook <callum.seabrook@prevarinite.com>
2023-01-04 11:20:55 -05:00
J.T.McQuigg 872adca8fd Updates 2023-01-03 23:09:35 -05:00
J.T.McQuigg f37cc0bdd8 Gradle 7.6 2023-01-03 23:02:04 -05:00
NEZNAMY 4063578ac3 Code style - Remove duplicate .toLowerCase() 2023-01-02 20:45:23 +01:00
NEZNAMY 2528de7eeb Code style - Use built-in String#trim function 2023-01-02 20:25:11 +01:00
NEZNAMY 37caed3123 Placeholders - Add timeout for placeholders to return value, causing TAB to freeze 2022-12-23 14:49:31 +01:00
NEZNAMY 27fe9dfb12 Commands - Make player command update player instantly if uuid is used instead of player name 2022-12-21 22:00:58 +01:00
NEZNAMY 0169cbd4d2 Bukkit - Fix wrong method taken sometimes, causing player kick with unlimited nametag mode 2022-12-20 15:08:18 +01:00
NEZNAMY ed760aec08 Aligned PlayerList - Fix rare error that makes no sense 2022-12-20 15:04:48 +01:00
NEZNAMY 858cbcefeb Code style - Add toString override 2022-12-20 15:04:21 +01:00
NEZNAMY 8a87d581a9 Scoreboard - Do not send packets to players who cannot see that specific scoreboard 2022-12-18 17:16:26 +01:00
NEZNAMY 6d642f4e51 NameTags - Fix players getting unregistered with wrong team name 2022-12-17 19:12:00 +01:00
NEZNAMY aca63640bc Attempt to fix rare NPE 2022-12-17 19:08:32 +01:00
NEZNAMY 1845a35315 3.2.2-SNAPSHOT 2022-12-16 22:25:39 +01:00
NEZNAMY cab5f89a95 Remove redundant check 2022-12-16 22:03:21 +01:00
NEZNAMY 1777d8bf01 Remove redundant try/catch 2022-12-16 21:14:47 +01:00
NEZNAMY 91b0bfaa9d Configuration - Simplify file copying, since default files already are encoded with UTF-8 encoding 2022-12-16 15:04:52 +01:00
NEZNAMY f1cf994d10 3.2.1 2022-12-16 11:27:48 +01:00
NEZNAMY 69957be933 Pet name fix - Fix excessive CPU usage caused by heavy operation and not caching the result 2022-12-15 00:22:23 +01:00
NEZNAMY b78eb5efb6 Bukkit - Remove forgotten debug stack trace printing 2022-12-15 00:21:28 +01:00
NEZNAMY 3577ad1964 Bukkit - Fix NPE in packet creation on <1.13 (#763) 2022-12-14 18:02:04 +01:00
NEZNAMY 4510491177 3.2.0 2022-12-12 09:46:21 +01:00
NEZNAMY 01b3a12377 1.19.3 support 2022-12-12 09:46:12 +01:00
NEZNAMY 64ae9863fb Bukkit - Drop old event API system 2022-12-10 12:27:51 +01:00
NEZNAMY ecd315a407 Sorting - Fix error from previous commit (.toLowerCase made string longer) 2022-12-08 11:20:58 +01:00
NEZNAMY 678eed288e Conditions - Add OR type for anonymous conditions using "|" symbol 2022-12-06 10:32:41 +01:00
NEZNAMY 9510455cb1 Conditions - Fix anonymous conditions not updating from previous commit 2022-12-06 10:31:29 +01:00
NEZNAMY e925ce23d0 Conditions - Fix nested conditions not working if they are defined below the condition using them 2022-12-06 09:51:16 +01:00
NEZNAMY c68cad6a1e Global PlayerList - Fix feature breaking nick/skin changes (minecraft mechanic changed in 1.19) (#752) 2022-12-06 09:50:00 +01:00
NEZNAMY 01d6eda4fb Configuration - Use luckperms placeholders instead of vault in default configuration (and make them return empty value if LP is not found) 2022-12-04 14:19:21 +01:00
NEZNAMY 73e7119bbc Configuration - add hidden server-name option for %server% value (useful with MySQL) 2022-12-04 11:49:59 +01:00
NEZNAMY cea781382a Sorting - Fix wrong team name being unregistered (#727) 2022-12-03 21:18:38 +01:00
NEZNAMY cf25b84e86 Commands - Fix debug command showing sorting is disabled even if layout is enabled for sorting 2022-12-03 21:16:29 +01:00
NEZNAMY 86e1d22390 Sorting - Only use 1 character for sorting by groups, permissions and predefined values; Improve debug output of sorting results 2022-12-03 12:16:38 +01:00
NEZNAMY 1625be111f Global PlayerList - Make server names in server groups and spy-servers not case-sensitive 2022-12-02 23:18:40 +01:00
NEZNAMY 00cda3b2aa Placeholders - Load all used placeholders on join in placeholder manager to make cpu usage appear under that "feature" instead of a feature actually using the placeholders 2022-12-01 22:46:40 +01:00
NEZNAMY 85a00a4436 Code style - Extract minimum placeholder refresh interval into an editable constant 2022-12-01 22:36:33 +01:00
NEZNAMY 37aec1a497 Global PlayerList - Add %playerlist-group_<group>% placeholder for online count in each group 2022-12-01 22:32:43 +01:00
NEZNAMY ef637616ee Placeholders - Improve error message 2022-11-27 17:13:24 +01:00
NEZNAMY af3b5067fb BossBar - Ignore players in disabled worlds/servers in announce command (#748) 2022-11-25 11:23:48 +01:00
NEZNAMY 53e5b15f96 Tab Expansion - Fix error in nested placeholder parsing 2022-11-24 11:01:55 +01:00
NEZNAMY 6a3afae607 Implement empty method 2022-11-16 12:24:03 +01:00
NEZNAMY 2e05c7d9d1 Another attempt for #727 2022-11-15 19:15:49 +01:00
NEZNAMY fb698a04b7 Configuration - Merge default config.yml files into one 2022-11-12 19:11:36 +01:00
NEZNAMY 52bdf30ddb Merge remote-tracking branch 'origin/master' 2022-11-09 22:34:22 +01:00
NEZNAMY a92f07aa40 Tab Expansion - Resend values to bridge on server switch (#740) 2022-11-09 22:34:09 +01:00
NEZNAMY 003ff7f99d Merge pull request #739 from CoachYT1/patch-1
bump org.yaml:snakeyaml to 1.33
2022-11-09 21:59:08 +01:00
NEZNAMY 47b9806ae7 Tab Expansion - Parse nested placeholders in replace function 2022-11-07 22:26:17 +01:00
CoachYT d2a7c65e08 bump org.yaml:snakeyaml to 1.33
snakeyaml 1.30 is affected by 5 vulnerabilities and should be upgraded to the latest version
2022-11-07 17:53:24 +01:00
NEZNAMY e232336a5f Add check for modifying non-existing teams (#727) 2022-11-06 22:42:23 +01:00
NEZNAMY 8816c75212 BungeeCord - Fix crash on 1.7 caused by unregistering non-existing objectives (login packet cleared them) (#727) 2022-11-04 13:31:34 +01:00
NEZNAMY f9d54751ba Fix rare ConcurrentModificationException 2022-11-04 12:53:00 +01:00
NEZNAMY 43d41ee761 BungeeCord - Improve unsupported BungeeCord build message 2022-11-01 17:55:43 +01:00
NEZNAMY 4512ba8d5e NameTags - Print a console warn if both unlimited nametags and invisible nametags are enabled 2022-10-30 17:56:00 +01:00
NEZNAMY 8b55bca564 Remove redundant variable 2022-10-30 13:40:32 +01:00
NEZNAMY d8d54970d3 Code style - Clean up vanish check code 2022-10-30 13:29:45 +01:00
NEZNAMY 15ea4482f9 Bukkit - Drop direct Essentials hook for vanishing as it supports metadata 2022-10-30 13:27:24 +01:00
NEZNAMY 87f9f38eac Update issue templates 2022-10-29 18:35:11 +02:00
NEZNAMY dba559f041 Layout - Fix startup error caused by invalid number as slot 2022-10-29 12:17:04 +02:00
NEZNAMY c610b6f323 PlayerList - Fix name applying on server switch when pipeline injection is disabled 2022-10-26 17:36:08 +02:00
NEZNAMY a5fb900e19 API - Add back TabPlayer#getTeamName() 2022-10-24 14:05:39 +02:00
NEZNAMY 09d6e455f5 Layout - Implement a fix for compatibility with plugins which add empty player name into their 2022-10-21 19:55:59 +02:00
NEZNAMY e68097b0f4 Layout - Fix startup error if slots is a single number instead of interval 2022-10-16 15:32:26 +02:00
NEZNAMY 0744fed953 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-10-16 14:40:42 +02:00
NEZNAMY de0adf4067 MySQL - Attempt to fix startup error (#717) 2022-10-16 13:12:54 +02:00
NEZNAMY 76bf2b3efc Update links in README 2022-10-16 13:11:56 +02:00
NEZNAMY c089badeb4 Configuration - Remove owner and admin settings from default configuration 2022-10-15 19:15:38 +02:00
NEZNAMY b3b99d366a Placeholders - Fix relational placeholders updating causing normal placeholders to not refresh 2022-10-14 19:28:05 +02:00
NEZNAMY 4a36486f65 Bukkit - Fix startup on 1.8 2022-10-08 12:41:05 +02:00
NEZNAMY 75126d00d9 BossBar - Fix countdown placeholder not activating on usage in PlaceholderAPI 2022-10-06 16:43:06 +02:00
NEZNAMY 17ebf95d5b BossBar - Fix announcement bars disappearing on refresh 2022-10-06 16:42:32 +02:00
NEZNAMY c764420cfe Code Style - Simplify field value assignment 2022-10-06 15:33:50 +02:00
Callum Seabrook 28f05fc2af Changed publishing artifact IDs to begin with "tab-" 2022-10-06 12:02:28 +01:00
Callum Seabrook a420892887 Fixed publishing ID 2022-10-06 11:54:06 +01:00
NEZNAMY d910dc5913 Header / Footer - Prioritize world over server in config.yml as well 2022-10-06 11:11:00 +02:00
NEZNAMY c9c1cbc349 Layout - Fix anti-override for playerlist names being disabled even when no layout is visible 2022-09-26 11:02:14 +02:00
NEZNAMY fd4704f75e 3.1.5 2022-09-20 15:46:28 +02:00
NEZNAMY 5400ca637f Global PlayerList - Do not fill display name for 1.7 players 2022-09-19 18:23:16 +02:00
NEZNAMY 79d2610117 Global PlayerList - Fix server switch when pipeline injection is disabled 2022-09-17 18:38:06 +02:00
NEZNAMY 6a51f4cb9d BungeeCord - Fix 1.7 players not being processed fully on server switch (reapplying scoreboards) 2022-09-13 18:33:52 +02:00
NEZNAMY c190776665 Scoreboard - Fix show command enabling scoreboard for players who disabled it using toggle command 2022-09-13 18:32:54 +02:00
NEZNAMY 3765a1232e Merge pull request #697 from JT122406/master
Updates and Velocity Deprecation Fix
2022-08-27 19:48:00 +02:00
J.T. McQuigg 28866eba21 More Updates and Build Fix 2022-08-27 13:07:28 -04:00
J.T. McQuigg 08adee3dc3 Recomment this out for PR 2022-08-27 11:57:34 -04:00
J.T. McQuigg 8a1adaf2cd More Updates 2022-08-27 11:57:08 -04:00
J.T. McQuigg fdb9accaa6 Updates and don't use Deprecation in Velocity module 2022-08-27 11:42:15 -04:00
NEZNAMY 4f7656748b Merge pull request #695 from Tanguygab/master
added StartWith `|-` and EndsWith `-|` condition types
2022-08-25 22:25:48 +02:00
Tanguygab 03e4495ac7 added |- and -| conditions 2022-08-25 15:15:21 +02:00
NEZNAMY 01af5cb9e1 Placeholders - Fix nested placeholders not being applied when updating from nested?!! (#694) 2022-08-24 16:52:01 +02:00
NEZNAMY 8d49f32df2 Code style - Change the way API instance is set 2022-08-22 20:30:16 +02:00
NEZNAMY 8a955b3bc8 Javadoc - Further improve documentation 2022-08-22 20:27:20 +02:00
NEZNAMY ead40cb413 Merge pull request #691 from Tanguygab/master
Commands - Added target and -s arguments for /tab nametag preview/toggle (#690)
2022-08-22 16:22:32 +02:00
Tanguygab 5e07f78d4f updated messages.yml 2022-08-22 16:20:43 +02:00
Tanguygab c7d98795bc Revert "* for all players in /tab scoreboard show"
This reverts commit e4f33b58f7.
2022-08-22 16:13:46 +02:00
Tanguygab 9f637e3d6a added target and -s arguments for /tab nametag preview/toggle 2022-08-22 16:09:23 +02:00
Tanguygab 2d5f074c78 Merge remote-tracking branch 'upstream/master' 2022-08-22 15:35:23 +02:00
NEZNAMY 3be6ba484d API - Fix show_item hover event on 1.16+ 2022-08-18 22:36:43 +02:00
NEZNAMY d9d74e7a32 Placeholders - Fix NPE in %essentialsnick% event listener due to Essentials bug (#684) 2022-08-18 12:46:54 +02:00
NEZNAMY 887306a5ed Bukkit - Include 1.19.2 in supported version range 2022-08-16 01:25:15 +02:00
NEZNAMY 4b2542eb4f Global Playerlist - Add config option to disable (and enable) profile key filling (#680) 2022-08-15 13:28:56 +02:00
NEZNAMY 66bd3baa3e Layout - Add config option to show vanished players in layout 2022-08-14 15:41:19 +02:00
NEZNAMY bf32004f8f Layout - Do not format original entries in any way (can cause spaces) 2022-08-13 17:50:28 +02:00
NEZNAMY bfb2df9c95 Commands - Show storage type in debug output 2022-08-13 17:44:46 +02:00
Callum Seabrook ddeb05e408 Merge pull request #662 from JT122406/Updates
Update Gradle Wrapper to 7.5
2022-08-12 16:58:38 +01:00
Callum Seabrook 9312d61258 Fix some more BS 2022-08-12 16:49:30 +01:00
Callum Seabrook d44909bc42 Fixed some BS 2022-08-12 16:41:26 +01:00
Callum Seabrook 1d77df335c Switched to Indra for publishing and improved code reuse in build scripts 2022-08-12 16:29:12 +01:00
NEZNAMY a268dc25f4 BungeeCord - Use direct code for proxy module again 2022-08-12 15:46:14 +02:00
NEZNAMY 8c5696faa0 Bukkit - Register universal placeholders after bukkit ones to fix reference issue with nested placeholders 2022-08-12 15:05:40 +02:00
NEZNAMY 94f3ccfb7d Javadoc - Further improve documentation 2022-08-12 15:04:49 +02:00
NEZNAMY 576329214d Add 1.19.2 to known versions 2022-08-11 17:57:49 +02:00
NEZNAMY 5c13c2e225 Velocity - Remove join processing delay since the required delay is already included elsewhere 2022-08-11 17:49:42 +02:00
NEZNAMY 1070f85617 Placeholders - Retry updateValue if player is not loaded yet (might fix #564) 2022-08-11 17:48:26 +02:00
NEZNAMY 7c9dc87fcb NameTags - Make canSeeFriendlyInvisibles flag configurable 2022-08-09 14:12:14 +02:00
NEZNAMY 363e58bf91 API - Expose another method 2022-08-04 17:06:30 +02:00
NEZNAMY daca3f5b71 3.1.4 2022-08-04 15:43:53 +02:00
NEZNAMY a7d2ec084b Use profile key in player info packet (#673) 2022-08-03 12:57:48 +02:00
NEZNAMY 167f8cd0ff BungeeCord - Remove Waterfall tablist rewrite hook since the option doesn't work as I expected 2022-08-03 11:14:10 +02:00
NEZNAMY 170307f45f Catch everything 2022-07-29 15:49:59 +02:00
NEZNAMY 2f36377780 Revert "Placeholder output replacements - Apply again after applying nested placeholders"
This reverts commit 2706cd5881.
2022-07-29 12:48:13 +02:00
NEZNAMY 48c565d85d 3.1.3 2022-07-28 17:59:15 +02:00
NEZNAMY 86b3cc4044 Sorting - Fix startup error caused by misconfiguration (#664) 2022-07-28 11:40:42 +02:00
NEZNAMY 6a2d5091b8 Bukkit - Add 1.19.1 support 2022-07-28 11:30:34 +02:00
J.T. McQuigg dc09e77190 Update Gradle Wrapper to 7.5 2022-07-26 17:16:56 -04:00
NEZNAMY 4fc139c0c6 Remove synchronized lock from sendCustomPacket method 2022-07-26 21:57:36 +02:00
NEZNAMY e5d800f469 API - Expose a few more methods 2022-07-26 21:56:16 +02:00
NEZNAMY 4879ac28b5 PingSpoof - Add Layout compatibility 2022-07-25 15:34:33 +02:00
NEZNAMY bd0badab48 Feature management - Don't deserialize objective packets if no feature is listening to them 2022-07-24 21:20:55 +02:00
NEZNAMY 5575a26d3b Placeholders - Use event instead of refresh task for %essentialsnick% placeholder 2022-07-24 20:27:43 +02:00
NEZNAMY d8cbe96949 BossBar - Fix issues with hidden-by-default enabled 2022-07-24 17:55:04 +02:00
NEZNAMY cfb506a7ff BossBar - Make Wither's size (nearly) zero on 1.8 (#654) 2022-07-24 13:28:18 +02:00
Tanguygab d5b0ff7f40 Merge remote-tracking branch 'upstream/master' 2022-07-24 13:11:11 +02:00
NEZNAMY 31d259210c Bukkit - Add support for mojang mapped jars 2022-07-21 12:56:23 +02:00
NEZNAMY 39112b8d41 Tab expansion - Add %tab_nametag_visibility% and rename %tab_ntpreview% to %tab_nametag_preview% 2022-07-20 00:14:38 +02:00
NEZNAMY 4c92286ebb Thread management - Increase max thread pool size to 20 2022-07-19 14:02:44 +02:00
NEZNAMY a317aeba85 Plugin message handling - Fix ClassCastException when a placeholder is externally registered as a server placeholder 2022-07-19 13:30:32 +02:00
NEZNAMY dcaf17294e Aligned PlayerList - Fix commit ebd67dd - Do not save config if no changes were made 2022-07-19 12:00:16 +02:00
NEZNAMY 0bc734689a Unlimited Nametags - Fix per-world formatting not applying on world switch 2022-07-19 00:22:11 +02:00
NEZNAMY d0c2c00cc9 BungeeCord - Respect Waterfall's tablist rewrite config option (#652) 2022-07-18 19:25:07 +02:00
NEZNAMY fd02328af9 Aligned PlayerList - Update widths.txt for 1.19 2022-07-18 14:55:05 +02:00
NEZNAMY 3b7de31324 Aligned PlayerList - Add a subcommand (disabled by default) for mass-verifying character widths 2022-07-18 14:52:44 +02:00
NEZNAMY ebd67dd025 Aligned PlayerList - Remove custom widths from config that match default value 2022-07-17 21:30:56 +02:00
NEZNAMY 4557a39ae2 RGB - Fix &r getting removed 2022-07-17 21:29:59 +02:00
NEZNAMY ce1eb299d3 Conditions - Fix conditions being 1 loop behind nested placeholders 2022-07-16 22:58:37 +02:00
Callum Seabrook 20ab3f0a72 Disabled JavaDoc generation for all but API and added property to enable for API 2022-07-16 20:11:58 +01:00
NEZNAMY d61f90c25c Code style - use api module instead of shared where possible 2022-07-16 20:56:59 +02:00
NEZNAMY 90474e7592 Layout - Fix startup error when a layout is missing groups section 2022-07-16 19:15:18 +02:00
NEZNAMY a2e28003bc Code style - Move TabConstants class to api module 2022-07-16 19:13:31 +02:00
NEZNAMY 6e409c3dcd Thread management - Use scheduled executor for repeating and delayed tasks 2022-07-16 18:39:13 +02:00
NEZNAMY 85a699c8de Commands - Only show announcement bars in auto-complete for bossbar announcement command 2022-07-16 18:33:57 +02:00
NEZNAMY a788d1fb23 Commands - Add missing permission check when toggling scoreboard using player argument 2022-07-16 14:14:35 +02:00
NEZNAMY ab5b72375c Code style - Remove redundant suppression and cast 2022-07-13 16:42:36 +02:00
NEZNAMY 3a023c51b5 Catch NoClassDefFoundError which is rarely thrown on reloads and leads to memory leaks 2022-07-13 16:24:17 +02:00
NEZNAMY 0ad91bcafe Sorting - Fix space in sorting types causing it to not work 2022-07-13 15:25:30 +02:00
NEZNAMY f41ea3bcab Unlimited Nametags - Improve heights to match vanilla & remove config option for space between lines, since original value was found 2022-07-13 14:57:07 +02:00
NEZNAMY 327dc4b416 Unlimited Nametags - Fix sneaking not being detected immediately on reload 2022-07-13 14:55:39 +02:00
NEZNAMY 27e7779456 Unlimited Nametags - Do not spawn for <1.8 players 2022-07-13 14:02:33 +02:00
NEZNAMY a7b2f5bb22 BungeeCord - Fix NPE on <1.8 clients 2022-07-13 13:32:53 +02:00
NEZNAMY 19fcb7c946 Layout - Do not send latency update packets to <1.8 clients 2022-07-13 13:32:23 +02:00
NEZNAMY 2706cd5881 Placeholder output replacements - Apply again after applying nested placeholders 2022-07-13 12:17:39 +02:00
NEZNAMY 4e2abd9c5b Bukkit - Fix startup on 1.7.x and 1.8.0 2022-07-13 12:04:21 +02:00
NEZNAMY 51ba2fa80a Placeholder output replacements - Fix exact match detection when input is not colored (new placeholderapi version) 2022-07-11 00:28:14 +02:00
NEZNAMY fd60cd0a21 Change the way the module is disabled 2022-07-06 22:00:52 +02:00
NEZNAMY 5025372c05 Revert "Code style - Remove unused method" 2022-07-06 21:15:54 +02:00
NEZNAMY 4806bd075f Configuration - Fix NPE when disabled world/server is null 2022-07-05 19:09:56 +02:00
NEZNAMY 370d17757a Add poor and unreliable MiniMessage support 2022-07-04 18:06:19 +02:00
NEZNAMY a648d7e75f Scoreboard - Fix NPE when using show command from console without player argument (#639) 2022-07-04 14:05:44 +02:00
NEZNAMY 518e1c5310 AlignedPlayerList - Fix rare NPE caused by multi-thread access 2022-07-04 13:46:29 +02:00
NEZNAMY 11539d8290 Commands - Allow removing per-world/server values 2022-07-04 13:44:08 +02:00
Tanguygab e4f33b58f7 * for all players in /tab scoreboard show 2022-07-04 13:34:04 +02:00
NEZNAMY 073caa9515 Compilation - Bump Authlib version 2022-07-04 13:33:00 +02:00
NEZNAMY 2b06b11916 Scoreboard - Fix same lines of text not appearing with use-numbers true 2022-06-24 21:16:45 +02:00
NEZNAMY 032b0f8a41 Configuration - Fix NPE when copying bukkit config to bungeecord 2022-06-19 14:05:59 +02:00
NEZNAMY 9391363f9b Unlimited Nametags - Disable on boat with chest as well (#625) 2022-06-19 12:36:35 +02:00
NEZNAMY 467fb2e9c7 Bukkit - Use reflection for LibsDisguises hook 2022-06-18 16:12:53 +02:00
NEZNAMY 257893b8de Downgrade dependency to fix compilation 2022-06-18 16:12:09 +02:00
NEZNAMY e919839de5 Merge pull request #623 from JT122406/Updates
Repo Bumps
2022-06-18 14:12:41 +02:00
J.T. McQuigg 77d372cbee Use new RedisBungee 2022-06-18 07:42:38 -04:00
J.T. McQuigg 03e9facaf7 Repo Bumps 2022-06-18 07:35:45 -04:00
NEZNAMY f3e459d911 Revert "BungeeCord - Retry plugin message processing if player isn't loaded for any reason (#564)"
This reverts commit dffac5deb8.
2022-06-17 14:15:39 +02:00
NEZNAMY 36feff0e04 Sorting - Fix off-by-1 possibly causing sorting to not work correctly for players without sorting group/permission 2022-06-16 18:53:42 +02:00
NEZNAMY 0320140a1b BungeeCord - Fix vanish/invisible status not resetting on server switch 2022-06-12 14:16:20 +02:00
NEZNAMY 4fb1b1c9f9 3.1.2 2022-06-11 18:01:17 +02:00
NEZNAMY 37f3896a41 Update to 1.19 2022-06-11 18:01:08 +02:00
NEZNAMY e1c152d95d Add missing change in previous commit 2022-06-11 18:00:08 +02:00
NEZNAMY dffac5deb8 BungeeCord - Retry plugin message processing if player isn't loaded for any reason (#564) 2022-06-11 17:57:18 +02:00
NEZNAMY 8aa3bbfad3 Scoreboard - Fix RGB codes not translating into legacy correctly in long lines 2022-06-09 23:10:44 +02:00
NEZNAMY 5bb4006e55 Scoreboard - Fix additional empty lines when use-numbers is enabled 2022-06-08 00:46:32 +02:00
NEZNAMY e06a406682 Clean up repositories 2022-06-08 00:45:21 +02:00
NEZNAMY a19719be4b Configuration - Remove deleted placeholder from default config 2022-06-03 23:47:41 +02:00
NEZNAMY 35856a9f3d Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-06-02 15:50:46 +02:00
NEZNAMY cf1d5d5ee2 Code style - Remove unused method 2022-06-01 20:23:16 +02:00
NEZNAMY f69a845e51 Bukkit - Implement chat message sending on <1.7 2022-05-31 12:21:07 +02:00
NEZNAMY db67e93bb8 Disable compilation of a module 2022-05-31 11:51:21 +02:00
NEZNAMY 313f089222 Aligned Suffix - Fix memory leak 2022-05-20 01:10:00 +02:00
NEZNAMY 458c08ded7 Tab expansion - Slightly optimize placeholder retrieving 2022-05-18 01:39:20 +02:00
NEZNAMY fe6cc7417f Placeholders - Fix refresh interval forwarding to bridge (#597) (2nd attempt) 2022-05-17 19:00:23 +02:00
NEZNAMY 181c94a808 Code style - Extract strings into constants 2022-05-13 00:42:30 +02:00
NEZNAMY 9ff6f02d8d Code style - Remove unused code and other minor changes 2022-05-12 22:49:41 +02:00
NEZNAMY 3cf74516d9 Bukkit - Remove unused data watcher registries 2022-05-12 22:25:18 +02:00
NEZNAMY 31f5502c16 BungeeCord - Delete TAB's bungeecord events (to make users switch to universal ones) 2022-05-12 22:04:37 +02:00
NEZNAMY 671d60bd72 API - Throw user-friendly exception if player is not loaded yet (#585) 2022-05-10 18:32:09 +02:00
NEZNAMY 294e866501 Placeholders - Fix refresh interval forwarding to bridge (#597) 2022-05-10 18:21:13 +02:00
NEZNAMY 96789100e3 Bukkit - Wrap incoming components instead of deserializing and then serializing them 2022-05-10 13:22:27 +02:00
NEZNAMY ea934fc7fb GroupManager - Add null check for player group from permission plugin 2022-05-08 12:48:06 +02:00
NEZNAMY d529b7b340 Sorting - Allow placeholders with ":" inside to work properly in PLACEHOLDER sorting type 2022-05-08 12:47:40 +02:00
NEZNAMY 092ca7f9f8 Aligned Suffix - Assume players can see themselves when vanished 2022-05-03 18:03:29 +02:00
NEZNAMY 79f4bdb869 Unlimited Nametags - Do not delay respawn by 1 tick anymore to fix OOM 2022-05-01 13:07:42 +02:00
NEZNAMY b0d2d0f7dc NickCompatibility - Clean up code 2022-04-30 20:01:40 +02:00
NEZNAMY cdf58c1dfe Teams - set canSeeFriendlyInvisibles to true 2022-04-30 18:21:46 +02:00
NEZNAMY 4680de6022 Feature management - Add try/catch for each feature in join processing (#560) 2022-04-30 17:22:03 +02:00
NEZNAMY e8f61181b6 Error logging - Log (critical) errors into console even when error file is full 2022-04-30 17:20:24 +02:00
NEZNAMY 75e9e007d0 Fix disabled worlds not loading correctly (from #6f07cf4) 2022-04-28 17:27:23 +02:00
NEZNAMY 3574ef7c0b Belowname - Do not update score when entering disabled world 2022-04-28 12:20:54 +02:00
NEZNAMY 86158036d2 Commands - Make permission group assigning more obvious in debug output 2022-04-28 11:55:21 +02:00
NEZNAMY e73beb47a3 API - Fix scoreboard Line#setText not working 2022-04-26 23:23:42 +02:00
NEZNAMY 0db5f1dd73 Scoreboard - Fix NPE when a line is null 2022-04-23 14:18:30 +02:00
NEZNAMY 6dcdc2aa58 PetFix - Fix attack packet cancellation 2022-04-20 10:46:08 +02:00
NEZNAMY 64601137e8 Scoreboard - Show empty lines when defined as empty, only hide when placeholder output is empty 2022-04-15 00:07:44 +02:00
NEZNAMY af7697b670 BungeeCord - Fixed NPE from moving to reflection 2022-04-14 23:24:45 +02:00
NEZNAMY 9e4f46088b Commands - Do not force refresh players who are not loaded yet (#560) 2022-04-12 11:21:06 +02:00
NEZNAMY 91e71a9b9c Layout - Fixed NPE when player had no layout visible 2022-04-10 23:32:13 +02:00
NEZNAMY 1198ffb847 BungeeCord - Use reflection for proxy module access due to the repository no longer being available 2022-04-10 23:31:51 +02:00
NEZNAMY 72116ecd0c Javadoc - Further improve documentation 2022-04-10 21:08:19 +02:00
NEZNAMY 04ebb7123d Code style - Move NMS object declaration and initialization 2022-04-10 20:28:29 +02:00
NEZNAMY 43096beafe Bukkit - Use ConsoleCommandSender for colored messages (#555) 2022-04-09 18:27:37 +02:00
NEZNAMY 2a08c62456 Tab expansion - Fix server placeholders not loading on join 2022-04-07 15:44:58 +02:00
NEZNAMY b62d74c3fa Code style - Remove unnecessary reflection 2022-04-06 20:38:03 +02:00
NEZNAMY 652fb5a6ad Bukkit - Switch from Paper to Purpur API 2022-04-06 19:11:19 +02:00
NEZNAMY d1f25cc19f Bukkit - Disable checking for plugins with compatibility issue 2022-04-06 18:25:12 +02:00
NEZNAMY dc6a9d9761 Fix layout startup 2022-04-06 13:44:16 +02:00
NEZNAMY 838d725715 Code style - Removed redundant static modifiers 2022-04-06 13:31:31 +02:00
NEZNAMY c45acb39ca Code style - Clean up variable initialization 2022-04-06 13:10:11 +02:00
NEZNAMY 957edf2a52 Javadoc - Fixed compilation warn 2022-04-06 12:44:54 +02:00
NEZNAMY d969c6ed0e Code style - Change way of checking if server is proxy 2022-04-06 12:41:20 +02:00
NEZNAMY dd6fca961c Code style - Clean up configuration loading 2022-04-06 12:34:39 +02:00
NEZNAMY e88a6df456 Config - remove hidden unregister-before-register option and remove the function entirely. Hopefully with improved checks this should not be necessary anymore. It caused client errors, as well as #549 2022-04-06 11:35:03 +02:00
NEZNAMY 3a4530c999 RGB - Finish #552 - Only replace clean gradients in property initialization 2022-04-05 23:24:37 +02:00
NEZNAMY b623dba40a Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-04-05 23:20:13 +02:00
NEZNAMY e2fd945109 Merge pull request #552 from lucypoulton/feat/kyori-rgb-format
Add support for Kyori MiniMessage RGB format
2022-04-05 23:19:52 +02:00
NEZNAMY e6dc3cda31 Unlimited nametags - Manually destroy armor stands on world switch 2022-04-05 20:21:36 +02:00
Lucy Poulton 18d8ff127a add support for kyori minimessage rgb format 2022-04-05 14:25:22 +01:00
NEZNAMY 4a9526451f Code style - Remove redundant "triggerMode" flag from Placeholder class due being redundant. Setting refresh to -1 has the same effect. 2022-04-04 19:03:51 +02:00
NEZNAMY 669d89c870 Code style - Move tab expansion code to interface 2022-04-04 18:15:06 +02:00
NEZNAMY 39161f2af5 Nametags - Reduced CPU usage of anti-override 2022-04-04 16:02:32 +02:00
NEZNAMY be8c33e3fc Velocity - Delay join event processing to avoid unformatted tablist names 2022-04-03 14:08:19 +02:00
NEZNAMY 7c1a27f9df Javadoc - Further improve documentation 2022-04-02 19:07:18 +02:00
NEZNAMY 342cc93dbf Code style - Clean up code 2022-04-02 13:17:15 +02:00
NEZNAMY 6f07cf439b Code style - Clean up loading of disabled servers/worlds 2022-04-01 23:40:04 +02:00
NEZNAMY 1d4d00b69b Code style - Clean up code with usage of @Inject 2022-04-01 23:21:30 +02:00
NEZNAMY 54cb3e4d54 3.1.1 2022-04-01 10:02:26 +02:00
NEZNAMY b0fc2caef9 RedisBungee - Detect player name change from other proxies 2022-03-31 21:54:51 +02:00
NEZNAMY 1221509d24 MySQL - Do not lowercase _DEFAULT_ group name, which caused it to not load (#546) (for real this time) 2022-03-31 20:52:12 +02:00
NEZNAMY 17f5f60504 MySQL - Do not lowercase _DEFAULT_ group name, which caused it to not load (#546) 2022-03-31 17:02:12 +02:00
NEZNAMY a8b0b488c6 Layout - Implement latency updating for player slots (#545) 2022-03-31 15:45:14 +02:00
NEZNAMY 6d922d5b70 Unlimited nametags - Fix NPE when player moves too quickly after joining 2022-03-31 14:50:54 +02:00
NEZNAMY 1ab62a33e3 Placeholders - Improve refresh task interval adaptation (#543) 2022-03-31 10:01:10 +02:00
NEZNAMY f9540a1e78 Tab Expansion - Fix placeholders not loading on player join 2022-03-29 11:17:04 +02:00
NEZNAMY a720bf2c8b Unlimited nametags - Do not allow preview command when armor stands are disabled for player 2022-03-28 21:25:42 +02:00
NEZNAMY c143eab6fb Global playerlist - Fix player possibly being removed from playerlist after reconnecting rapidly (#539) 2022-03-28 20:57:47 +02:00
NEZNAMY 1afb20af8b Code style - Avoid reassigning parameters 2022-03-28 12:57:31 +02:00
NEZNAMY c6f27c839d Code style - Document empty method body 2022-03-28 12:49:37 +02:00
NEZNAMY 742f1708c5 Code style - Remove unused import 2022-03-28 12:47:19 +02:00
NEZNAMY ac9601a48b Unlimited nametags - Fix nametag toggle command not hiding vanilla name on players with disabled armor stands via API 2022-03-28 12:13:23 +02:00
NEZNAMY 4cd109a5fc Javadoc - Update style and fix alignment after replacing tabs with spaces 2022-03-26 23:13:19 +01:00
NEZNAMY 078554a8b0 RedisBungee - Fix /btab reload removing players from tablist when global playerlist is disabled 2022-03-25 14:46:59 +01:00
NEZNAMY 464e2f4f64 RedisBungee - Fix duplicated team names through proxies 2022-03-25 14:46:22 +01:00
NEZNAMY b84f55aa18 RedisBungee - Fix errors and issues when unlimited nametag mode is enabled 2022-03-25 14:45:55 +01:00
NEZNAMY be2f780bfa Drop LuckPerms event hook and go back to periodic task. Too many issues related to events. 2022-03-25 10:27:36 +01:00
NEZNAMY 262d8493a6 fix NPE 2022-03-25 09:33:32 +01:00
NEZNAMY 24e12e871b finish #534 2022-03-24 22:33:18 +01:00
NEZNAMY a2ee75339c 3.1.1-pre2 2022-03-24 22:20:25 +01:00
NEZNAMY 9c28c4d63c add auto complete for group names in group command 2022-03-22 22:12:55 +01:00
NEZNAMY 7d57da1424 fix disableArmorStands method not destroying armor stands 2022-03-22 13:06:28 +01:00
NEZNAMY b77b20ba4d expose placeholder output getters in the API 2022-03-22 11:54:30 +01:00
NEZNAMY 57ae58f093 call super.onQuit as it should 2022-03-21 20:26:56 +01:00
NEZNAMY b29c878688 Merge remote-tracking branch 'origin/master' 2022-03-21 08:51:52 +01:00
NEZNAMY 5a5a6c76c1 another banger that took a week to fix 2022-03-21 08:51:32 +01:00
NEZNAMY f6a53f537c fix NPE 2022-03-21 08:46:02 +01:00
NEZNAMY d86ed37208 allow negative values in replacement intervals using ~ 2022-03-20 17:55:17 +01:00
NEZNAMY 92b945f28d Merge pull request #534 from itsmemac/master
fix nametag preview help text
2022-03-20 15:03:07 +01:00
Mac cb164f0055 fix nametag preview help text
it seems new update `/tab ntpreview` has been replaced with `/tab nametag preview`
Help text was not updated
2022-03-20 19:31:38 +05:30
NEZNAMY acfe37c1b5 fix NPE 2022-03-20 12:50:30 +01:00
NEZNAMY 44ded26df4 change method call order in which groups are refreshed 2022-03-19 16:13:39 +01:00
NEZNAMY 6dbd92a010 remove players from map on quit 2022-03-19 16:12:06 +01:00
NEZNAMY f729ca0ad2 fix NPE 2022-03-19 16:11:45 +01:00
NEZNAMY 065490c077 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-03-18 17:12:20 +01:00
NEZNAMY da6425dd93 Merge pull request #530 from CoachYT1/master
Update dependencies & GitHub actions
2022-03-15 08:25:12 +01:00
CoachYT 250dcbc676 Update build.yml 2022-03-14 12:18:41 +01:00
CoachYT de4ca4a88f Update libs.versions.toml 2022-03-14 12:15:58 +01:00
NEZNAMY 723a0c890e fix NPE 2022-03-13 22:36:37 +01:00
NEZNAMY 7085e7df20 do not forward group if assigning by permissions 2022-03-13 22:36:25 +01:00
NEZNAMY 09496d60f3 add argument check 2022-03-13 13:19:05 +01:00
NEZNAMY bf6bc26a5c add check into API for all the users who shade API into their projects 2022-03-13 13:16:20 +01:00
NEZNAMY a7217eae15 fix placeholders used only in tab expansion not refreshing 2022-03-13 13:12:37 +01:00
NEZNAMY 2a532ef9b7 update README.md 2022-03-13 13:11:37 +01:00
NEZNAMY c304ded924 avoid thread spam from LP events 2022-03-11 15:06:13 +01:00
NEZNAMY 4d8c47a505 #517 - make group names not case-sensitive when connected to MySQL 2022-03-09 10:22:51 +01:00
NEZNAMY d3e0ccbf34 forward nametag preview on server switch 2022-03-09 09:08:49 +01:00
NEZNAMY 3c82b46148 fix updating of invalid score on server switch when pipeline injection is disabled 2022-03-08 23:09:29 +01:00
NEZNAMY 07d7f2cc17 catch StackOverflowError as well 2022-03-08 21:50:10 +01:00
NEZNAMY 54d4f09ab1 fix StackOverflowError when using placeholder identifier inside its own replacements (will act the same as %value%) 2022-03-08 21:45:11 +01:00
NEZNAMY e8c698b5e8 #517 - fix mysql header/footer download/upload 2022-03-07 22:14:49 +01:00
NEZNAMY b096906ffd 3.1.1-pre1 2022-03-07 18:59:40 +01:00
NEZNAMY 5a9cec12fa WeakHashMap isn't so weak after all (fixed memory leak) 2022-03-07 18:59:22 +01:00
NEZNAMY a2ed71751f fix NPE 2022-03-07 18:58:10 +01:00
NEZNAMY 5a3a2df920 simplify 2022-03-07 15:50:24 +01:00
NEZNAMY 9d07e669ea merge ifs 2022-03-07 15:50:12 +01:00
NEZNAMY aa18f1cc35 remove unnecessary full path 2022-03-07 14:03:04 +01:00
NEZNAMY 668d78ea64 remove redundant constructor 2022-03-07 13:59:38 +01:00
NEZNAMY 6f62581d6e print OOM into file instead of throwing into console 2022-03-07 12:53:12 +01:00
NEZNAMY 06686ada5c add synchronized lock to printError method 2022-03-07 12:21:47 +01:00
NEZNAMY 4380482606 remove direct reference to player channel on bukkit 2022-03-07 12:21:24 +01:00
NEZNAMY d5112b3d32 fix group refreshing introduced in previous commit which in the end didn't even fix the issue it was supposed to 2022-03-06 20:26:49 +01:00
NEZNAMY ef67506640 fix error caused by concurrent access 2022-03-06 12:01:50 +01:00
NEZNAMY c2ea4d2793 #516 - skin signature can be null 2022-03-05 16:29:16 +01:00
316 changed files with 12866 additions and 11210 deletions

No files matched your search

+5 -3
View File
@@ -1,6 +1,6 @@
name: Bug Report
description: Unwanted behavior that does not match configuration.
labels: ["Bug"]
description: Plugin does not work as described.
labels: ["Type: Bug"]
body:
- type: input
attributes:
@@ -45,7 +45,7 @@ body:
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide, such as screenshots.
description: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
validations:
required: false
- type: checkboxes
@@ -57,5 +57,7 @@ body:
required: true
- label: I have read the wiki to make sure it's not an issue with configuration
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
+3 -1
View File
@@ -1,6 +1,6 @@
name: Error
description: Error message with a stack trace in console or error file.
labels: ["Error"]
labels: ["Type: Error"]
body:
- type: input
attributes:
@@ -51,5 +51,7 @@ body:
required: true
- label: I have included a paste of the error
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
+1 -1
View File
@@ -1,6 +1,6 @@
name: Wiki change
description: Change to text on a wiki page or adding a new section.
labels: ["Wiki"]
labels: ["Type: Wiki change"]
body:
- type: input
attributes:
+6 -6
View File
@@ -3,7 +3,6 @@ on:
push:
branches:
- master
- v3
pull_request:
types: [opened, synchronize, reopened]
jobs:
@@ -11,21 +10,22 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v1
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
with:
distribution: 'temurin'
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v1
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle
uses: actions/cache@v2
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
path: |
~/.gradle/caches
@@ -38,7 +38,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
run: ./gradlew clean build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.3.1
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Jar
path: jar/build/libs/TAB-**.jar
+10
View File
@@ -0,0 +1,10 @@
If this plugin has helped you in any way, and you would like to return something back
to make the plugin even better, there is a lot of ways to contribute:
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report (typically providing correct steps to reproduce that actually work).
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
+174
View File
@@ -0,0 +1,174 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
+10 -24
View File
@@ -6,8 +6,8 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
# Distribution
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
version anymore. Only one version of the plugin exists now, which contains all implemented features
and is available to everyone for free. However, official support on discord is only provided to
those who have purchased the plugin.
and is available to everyone for free. However, customer support is only provided to
customers who have purchased the plugin.
# Compiling
Compilation requires JDK 8 and up.
@@ -15,18 +15,10 @@ To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
# Links
The precompiled JAR can be downloaded for free from:
* Releases: https://github.com/NEZNAMY/TAB/releases
* MC-Market: https://www.mc-market.org/resources/20631/
Supporting my work:
* MC-Market: https://www.mc-market.org/resources/14009/
* Polymart: https://polymart.org/resource/484
* Donation link: https://paypal.me/neznamy1
Other links:
* Discord: https://discord.gg/EaSvdk6
* Wiki: https://github.com/NEZNAMY/TAB/wiki
Download the plugin for free: [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[BuiltByBit](https://builtbybit.com/resources/20631/), [Modrinth](https://modrinth.com/plugin/tab-was-taken)
Support my work & receive customer support: [BuiltByBit](https://builtbybit.com/resources/14009/), [Polymart](https://polymart.org/resource/484)
Other links: [Discord support for customers](https://discord.gg/EaSvdk6), [Wiki](https://github.com/NEZNAMY/TAB/wiki)
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
@@ -34,15 +26,9 @@ In other words, they **are not available for**:
* **Support** - support is provided on discord for buyers, where roles are given to verified users
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
* **Feature requests** - closed indefinitely.
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
You will be provided with a much faster and higher quality service.**
# Contributing
If this plugin has helped you in any way, and you would like to return something back
to make the plugin even better, there is a lot of ways to contribute:
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report (typically providing correct steps to reproduce that actually work).
* Test **experimental builds**, which can be found in [Github Actions](https://github.com/NEZNAMY/TAB/actions) or in a form of pre-releases on discord
for problems of any kind including bugs. This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
* [Buy a copy](https://www.mc-market.org/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
+20 -1
View File
@@ -1,3 +1,7 @@
plugins {
id("net.kyori.blossom") version "1.2.0"
}
dependencies {
compileOnlyApi(libs.netty)
api(libs.jsonSimple) {
@@ -5,5 +9,20 @@ dependencies {
}
compileOnlyApi(libs.snakeyaml)
api(libs.yamlAssist)
compileOnlyApi(libs.gson)
compileOnlyApi(libs.adventureMiniMessage)
compileOnlyApi(libs.adventureApi)
compileOnlyApi(libs.adventureLegacy)
}
blossom {
replaceToken("@name@", rootProject.name)
replaceToken("@id@", rootProject.ext.get("id")!!.toString())
replaceToken("@version@", project.version)
replaceToken("@description@", project.description)
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
}
tasks.javadoc {
enabled = project.hasProperty("enable-javadoc")
}
@@ -1,28 +1,37 @@
package me.neznamy.tab.api;
/**
* Interface representing an armor stand attached to player
* for displaying text in game.
*/
public interface ArmorStand {
/**
* Returns true if offset is static, false if not
* @return true if static, false if dynamic
*
* @return true if static, false if dynamic
*/
boolean hasStaticOffset();
boolean isStaticOffset();
/**
* Changes offset of the armor stand
* @param offset - new offset
*
* @param offset
* new offset
*/
void setOffset(double offset);
/**
* Returns offset of the armor stand
* @return offset from base
*
* @return offset from base
*/
double getOffset();
/**
* Returns property for armor stand's name
* @return property for armor stand's name
*
* @return property for armor stand's name
*/
Property getProperty();
@@ -33,14 +42,17 @@ public interface ArmorStand {
/**
* Teleports armor stand to owner's current location for specified player
* @param viewer - player to send packet to
*
* @param viewer
* player to send packet to
*/
void teleport(TabPlayer viewer);
/**
* Changes sneaking flag and sends packets to everyone in range
* @param sneaking
* new sneaking status
*
* @param sneaking
* new sneaking status
*/
void sneak(boolean sneaking);
@@ -51,7 +63,9 @@ public interface ArmorStand {
/**
* DeSpawns armor stand for specified player
* @param viewer - player to send packet to
*
* @param viewer
* player to send packet to
*/
void destroy(TabPlayer viewer);
@@ -62,21 +76,33 @@ public interface ArmorStand {
/**
* Updates visibility if needed
* @param force - if refresh should be forced
*
* @param force
* if refresh should be forced
*/
void updateVisibility(boolean force);
/**
* Returns entity ID of this armor stand
* @return entity ID of this armor stand
*
* @return entity ID of this armor stand
*/
int getEntityId();
/**
* Spawns armor stand for specified player
* @param viewer - player to spawn for
*
* @param viewer
* player to spawn for
*/
void spawn(TabPlayer viewer);
/**
* Sends respawn packets to specified player to make position
* change instant instead of teleport animation
*
* @param viewer
* Player to send armor stand respawn packets to
*/
void respawn(TabPlayer viewer);
}
@@ -10,5 +10,11 @@ public interface ArmorStandManager {
*/
void destroy();
/**
* Refreshes text of all armor stands for all nearby players
*
* @param force
* If refresh should be force despite no update seemingly being needed
*/
void refresh(boolean force);
}
@@ -1,35 +1,116 @@
package me.neznamy.tab.api;
import java.util.UUID;
/**
* Feature manager allows registration and work with features.
*/
public interface FeatureManager {
/**
* Registers a feature, which will start receiving events
*
* @param name - name of feature
* @param feature - the handler
* @param name
* name of feature
* @param feature
* the handler
*/
void registerFeature(String name, TabFeature feature);
/**
* Unregisters feature making it no longer receive events. This does not run unload method nor cancel
* tasks created by the feature
* @param name - feature name defined in registerFeature
*
* @param name
* feature name defined in registerFeature
*/
void unregisterFeature(String name);
/**
* Returns whether a feature with said name is registered or not
*
* @param name - name of feature defined in registerFeature method
* @return true if feature exists, false if not
* @param name
* name of feature defined in registerFeature method
* @return true if feature exists, false if not
*/
boolean isFeatureEnabled(String name);
/**
* Returns feature handler by its name
*
* @param name - name of feature defined in registerFeature method
* @return the feature or null if feature does not exist
* @param name
* name of feature defined in registerFeature method
* @return the feature or null if feature does not exist
*/
TabFeature getFeature(String name);
/**
* Calls onQuit(TabPlayer) to all enabled features
*
* @param disconnectedPlayer
* player who disconnected
*/
void onQuit(TabPlayer disconnectedPlayer);
/**
* Calls onJoin(TabPlayer) to all enabled features
*
* @param connectedPlayer
* player who connected
*/
void onJoin(TabPlayer connectedPlayer);
/**
* Calls onServerChange(TabPlayer, String, String) to all enabled features
*
* @param playerUUID
* player who switched server
* @param to
* name of the new server
*/
void onServerChange(UUID playerUUID, String to);
/**
* Calls onWorldChange(TabPlayer, String, String) to all enabled features
*
* @param playerUUID
* player who switched world
* @param to
* name of the new world
*/
void onWorldChange(UUID playerUUID, String to);
/**
* Calls onCommand(TabPlayer, String) to all enabled features
*
* @param sender
* command sender
* @param command
* command line including /
* @return {@code true} if some feature marked the command for cancel, {@code false} if not
*/
boolean onCommand(TabPlayer sender, String command);
/**
* Internal function that marks objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markObjective();
/**
* Internal function that marks display objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markDisplayObjective();
/**
* Returns array of all currently enabled features
*
* @return array of all currently enabled features
*/
TabFeature[] getValues();
}
@@ -1,16 +1,82 @@
package me.neznamy.tab.api;
/**
* Interface allowing to send header/footer to players.
* <p>
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
* This requires the Header/footer feature to be enabled in config, otherwise
* the method will return {@code null}.
*/
public interface HeaderFooterManager {
/**
* Sets raw value of header to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
*
* @param player
* Player to set header for
* @param header
* Raw header value
*/
void setHeader(TabPlayer player, String header);
/**
* Sets raw value of footer to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
*
* @param player
* Player to set header for
* @param footer
* Raw footer value
*/
void setFooter(TabPlayer player, String footer);
/**
* Sets raw value of header and footer to specified values. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
*
* @param player
* Player to set header for
* @param header
* Raw header value
* @param footer
* Raw footer value
*/
void setHeaderAndFooter(TabPlayer player, String header, String footer);
/**
* Resets header value previously set using {@link #setHeader(TabPlayer, String)}
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
* logic using configuration files.
*
* @param player
* Player to reset header for
*/
void resetHeader(TabPlayer player);
/**
* Resets footer value previously set using {@link #setFooter(TabPlayer, String)}
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
* logic using configuration files.
*
* @param player
* Player to reset header for
*/
void resetFooter(TabPlayer player);
/**
* Resets values previously set using {@link #setHeader(TabPlayer, String)},
* {@link #setFooter(TabPlayer, String)} or {@link #setHeaderAndFooter(TabPlayer, String, String)}
* and restores internal logic using configuration files.
*
* @param player
* Player to reset header and footer for
*/
void resetHeaderAndFooter(TabPlayer player);
}
@@ -1,54 +1,68 @@
package me.neznamy.tab.api;
/**
* An interface representing a property assigned to a player,
* which may contain placeholders and can be changed using the API.
*/
public interface Property {
/**
* Returns original raw value ignoring API calls
* @return original raw value
*
* @return original raw value
*/
String getOriginalRawValue();
/**
* Temporarily overrides current raw value with an API call
* @param temporaryValue - temporary value to be assigned
*
* @param temporaryValue
* temporary value to be assigned
*/
void setTemporaryValue(String temporaryValue);
/**
* Returns current temporary value
* @return temporary value or null if not set
*
* @return temporary value or null if not set
*/
String getTemporaryValue();
/**
* Returns temporary value (via API) if present, raw value otherwise
* @return current raw value
*
* @return current raw value
*/
String getCurrentRawValue();
/**
* Returns last known value
* @return last known value
*
* @return last known value
*/
String get();
/**
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns whether value changed or not
* @return if updating changed value or not
*
* @return if updating changed value or not
*/
boolean update();
/**
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns it.
* Equal to calling update() and then get().
* @return updated value
*
* @return updated value
*/
String updateAndGet();
/**
* Returns value for defined viewer by applying relational placeholders to last known value
* @param viewer - the viewer
* @return format for the viewer
*
* @param viewer
* the viewer
* @return format for the viewer
*/
String getFormat(TabPlayer viewer);
}
@@ -1,30 +1,147 @@
package me.neznamy.tab.api;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
/**
* Interface representing user or group configuration.
* Implementation is either using file or MySQL, if it's enabled.
*/
public interface PropertyConfiguration {
void setProperty(String name, String property, String server, String world, String value);
/**
* Sets property value of group or user to specified value. If {@code world} or
* {@code server} is not {@code null}, value will be inserted as a per-world or
* per-server setting. If both {@code world} and {@code server} are {@code null},
* value is inserted as a global setting.
*
* @param groupOrUser
* Name of group or user, depending on what this instance handles
* @param property
* Name of property to set
* @param server
* Name of server to apply setting to, {@code null} for global setting
* @param world
* Name of world to apply setting to, {@code null} for global setting
* @param value
* Value of the property
*/
void setProperty(String groupOrUser, String property, String server, String world, String value);
String[] getProperty(String name, String property, String server, String world);
/**
* Gets property of group or user. If {@code server} or {@code world}
* are not {@code null}, per-server / per-world settings are checked first
* to try to find a match. If no match is found, global value is returned.
* Returned value is an array with 2 elements, with first argument being value
* and second being source. If nothing is found, empty array is returned.
*
* @param groupOrUser
* Name of group or user, depending on what this instance handles
* @param property
* Name of property to get
* @param server
* Server the player is currently in, to allow check for per-server settings
* @param world
* World the player is currently in, to allow check for per-world settings
* @return Array with 2 elements with value being first, source second if found,
* empty array if nothing was found.
*/
String[] getProperty(String groupOrUser, String property, String server, String world);
void remove(String name);
/**
* Removes all data applied to specified group or user.
*
* @param groupOrUser
* Name of group or user, depending on what this instance handles
*/
void remove(String groupOrUser);
Map<String, String> getGlobalSettings(String name);
/**
* Returns map of global settings applied to specified group or user.
* Map key is property name, value is property value.
*
* @param groupOrUser
* Name of group or user, depending on what this instance handles
* @return Map of global settings of specified group or user
*/
Map<String, Object> getGlobalSettings(String groupOrUser);
Map<String, Map<String, String>> getPerWorldSettings(String name);
/**
* Returns map of per-world settings of specified group or user.
* Map key is world name, value is property name-value map in that world.
*
* @param groupOrUser
* Name of group or user, depending on what this instance handles
* @return Map of per-world settings of specified group or user
*/
Map<String, Map<String, Object>> getPerWorldSettings(String groupOrUser);
Map<String, Map<String, String>> getPerServerSettings(String name);
/**
* Returns map of per-server settings of specified group or user.
* Map key is server name, value is property name-value map in that server.
*
* @param groupOrUser
* Name of group or user, depending on what this instance handles
* @return Map of per-server settings of specified group or user
*/
Map<String, Map<String, Object>> getPerServerSettings(String groupOrUser);
/**
* Returns set of all groups or users that have anything configured,
* either globally or in some world or server.
*
* @return Set of all entries with something configured
*/
Set<String> getAllEntries();
default Map<String, Map<String, String>> convertMap(Map<String, Map<String, Map<String, String>>> map, String key) {
Map<String, Map<String, String>> converted = new HashMap<>();
for (Map.Entry<String, Map<String, Map<String, String>>> entry : map.entrySet()) {
converted.put(entry.getKey(), entry.getValue().get(key));
/**
* Converts per-world or per-server map into a per-world or per-server map
* with values only belonging to entered group or user. In other words, extracts
* settings belonging to group or user from the map.
*
* @param map
* Original per-world or per-server setting map
* @param groupOrUser
* Name of group or user, depending on what this instance handles
* @return Converted map only containing data of specified group or user
*/
default Map<String, Map<String, Object>> convertMap(Map<String, Map<String, Map<String, Object>>> map, String groupOrUser) {
Map<String, Map<String, Object>> converted = new HashMap<>();
for (Map.Entry<String, Map<String, Map<String, Object>>> entry : map.entrySet()) {
converted.put(entry.getKey(), entry.getValue().get(groupOrUser));
}
return converted;
}
/**
* Converts object into string. For most objects, {@link Object#toString()} is called.
* For lists, entries are connected with {@code "\n"}.
*
* @param obj
* Object to convert
* @return Converted string
*/
@SuppressWarnings("unchecked")
default String toString(Object obj) {
if (obj instanceof List) {
return ((List<Object>)obj).stream().map(Object::toString).collect(Collectors.joining("\n"));
}
return obj.toString();
}
/**
* Converts string into object. If string contains {@code "\n"},
* a list is created using {@code "\n"} as a separator. Otherwise,
* inserted string is returned.
*
* @param string
* String to convert
* @return Converted object
*/
default Object fromString(String string) {
if (string != null && string.contains("\n")) {
return Arrays.asList(string.split("\n"));
}
return string;
}
}
@@ -1,5 +1,8 @@
package me.neznamy.tab.api;
import lombok.Getter;
import lombok.Setter;
/**
* Enum class representing all possibly used protocol versions
*/
@@ -8,6 +11,10 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_19_3 (761),
V1_19_2 (760),
V1_19_1 (760),
V1_19 (759),
V1_18_2 (758),
V1_18_1 (757),
V1_18 (757),
@@ -65,20 +72,25 @@ public enum ProtocolVersion {
/** Value array to iterate over to avoid array creations on each call */
public static final ProtocolVersion[] VALUES = values();
//version's network id found at https://wiki.vg/Protocol_version_numbers
private final int networkId;
/** Version's network id found at https://wiki.vg/Protocol_version_numbers */
@Getter private final int networkId;
//minor version, such as 16
private int minorVersion;
/** Version's minor version, such as 16 for 1.16.x. Allowing override to
* set minor version of UNKNOWN_SERVER_VERSION value to value from package to fix compatibility
* with server forks that set bukkit version field value to "Unknown".
*/
@Getter @Setter private int minorVersion;
//friendly name displayed in %player-version% placeholder
private final String friendlyName;
/** Version's friendly name displayed in %player-version% placeholder */
@Getter private final String friendlyName;
/**
* Constructs new instance with given network id
* @param networkId - network id of this version
*
* @param networkId
* network id of this version
*/
ProtocolVersion(int networkId){
ProtocolVersion(int networkId) {
this.networkId = networkId;
this.minorVersion = Integer.parseInt(toString().split("_")[1]);
this.friendlyName = toString().substring(1).replace("_", ".");
@@ -86,53 +98,22 @@ public enum ProtocolVersion {
/**
* Constructs new instance with given friendly name
* @param friendlyName - friendly name to display
*
* @param friendlyName
* friendly name to display
*/
ProtocolVersion(String friendlyName){
ProtocolVersion(String friendlyName) {
this.networkId = 999;
this.minorVersion = 18;
this.friendlyName = friendlyName;
}
/**
* Returns the version's network id
* @return version's network id
*/
public int getNetworkId() {
return networkId;
}
/**
* Returns user-friendly name of the version (such as 1.16.2 instead of V1_16_2)
* @return user-friendly name of the version
*/
public String getFriendlyName() {
return friendlyName;
}
/**
* Returns minor version of this release, such as 8 for 1.8.x or 13 for 1.13.x
* @return version's minor version
*/
public int getMinorVersion() {
return minorVersion;
}
/**
* Overrides minor version of the version instance. This is used internally to
* set minor version of UNKNOWN_SERVER_VERSION value to value from package to fix compatibility
* with server forks that set bukkit version field value to "Unknown".
* @param minorVersion
* minor version to change value to
*/
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
}
/**
* Returns enum constant of entered version or UNKNOWN_SERVER_VERSION if unknown version
* @param friendlyName - friendly name of the version
* @return version or UNKNOWN_SERVER_VERSION if version is unknown
*
* @param friendlyName
* friendly name of the version
* @return version or UNKNOWN_SERVER_VERSION if version is unknown
*/
public static ProtocolVersion fromFriendlyName(String friendlyName) {
if (friendlyName.startsWith("1.8")) return V1_8;
@@ -145,8 +126,10 @@ public enum ProtocolVersion {
/**
* Returns version from given network id
* @param networkId - network id of protocol version
* @return version from given network id
*
* @param networkId
* network id of protocol version
* @return version from given network id
*/
public static ProtocolVersion fromNetworkId(int networkId) {
for (ProtocolVersion v : VALUES) {
+121 -21
View File
@@ -15,103 +15,203 @@ import me.neznamy.tab.api.team.TeamManager;
*/
public abstract class TabAPI {
/** Instance of the API */
private static TabAPI instance;
public static TabAPI getInstance() {
return instance;
/**
* Constructs new instance and sets it as new API instance
*/
public TabAPI() {
instance = this;
}
public static void setInstance(TabAPI instance) {
TabAPI.instance = instance;
/**
* Returns API instance. If instance was not set by the plugin, throws
* {@code IllegalStateException}. This is usually caused by shading the API
* into own project, which is not allowed. Another option is calling the method
* before plugin was able to load.
*
* @return API instance
* @throws IllegalStateException
* If instance is {@code null}
*/
public static TabAPI getInstance() {
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
"plugin loads after TAB, add it as a depend or softdepend of your plugin.\n" +
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
"to not include them, such as scope provided for maven compilation.");
return instance;
}
/**
* Returns player object from given UUID
* @return player object from given UUID
* @param id - Player UUID
*
* @param id
* Player UUID
* @return player object from given UUID
*/
public abstract TabPlayer getPlayer(UUID id);
/**
* Returns player object from given name
* @return player object from given name
* @param name - Player name
*
* @param name
* Player name
* @return player object from given name
*/
public abstract TabPlayer getPlayer(String name);
/**
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
* @return collection of online players
*
* @return collection of online players
*/
public abstract TabPlayer[] getOnlinePlayers();
/**
* Return BossBar manager instance if the feature is enabled. Returns null otherwise.
* @return BossBar manager
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return BossBar manager
*/
public abstract BossBarManager getBossBarManager();
/**
* Returns scoreboard manager instance if the feature is enabled. Returns null otherwise.
* @return scoreboard manager
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return scoreboard manager
*/
public abstract ScoreboardManager getScoreboardManager();
/**
* Returns team manager instance if the feature is enabled, false otherwise
* @return team manager
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return team manager
*/
public abstract TeamManager getTeamManager();
/**
* Returns header/footer manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return Header/footer manager
*/
public abstract HeaderFooterManager getHeaderFooterManager();
/**
* Returns PlaceholderManager instance
* @return PlaceholderManager instance
*
* @return PlaceholderManager instance
*/
public abstract PlaceholderManager getPlaceholderManager();
/**
* Returns feature manager instance
* @return feature manager instance
*
* @return feature manager instance
*/
public abstract FeatureManager getFeatureManager();
/**
* Returns Tablist name format manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return Tablist name format manager
*/
public abstract TablistFormatManager getTablistFormatManager();
/**
* Gets the event bus for registering listeners for TAB events.
*
* @return the event bus
* @return the event bus
*/
public abstract EventBus getEventBus();
/**
* Returns server version. On proxy installation returns PROXY.
* @return server version
*
* @return server version
*/
public abstract ProtocolVersion getServerVersion();
/**
* Prints message into console
* @param message - message to print
* @param translateColors - true if colors should be translated, false if not
*
* @param message
* message to print
* @param translateColors
* {@code true} if colors should be translated, {@code false} if not
*/
public abstract void sendConsoleMessage(String message, boolean translateColors);
/**
* Returns TAB's Thread manager, which allows task submitting
*
* @return ThreadManager instance
*/
public abstract ThreadManager getThreadManager();
/**
* Returns TAB's cache file used to store player toggle data
*
* @return TAB's player cache file
*/
public abstract ConfigurationFile getPlayerCache();
/**
* Returns TAB's config.yml file
*
* @return config.yml file
*/
public abstract ConfigurationFile getConfig();
/**
* Returns TAB's group configuration
*
* @return TAB's group configuration
*/
public abstract PropertyConfiguration getGroups();
/**
* Returns TAB's user configuration
*
* @return TAB's user configuration
*/
public abstract PropertyConfiguration getUsers();
/**
* Sends a debug message into console if the option
* is enabled in config.
*
* @param message
* Message to send
*/
public abstract void debug(String message);
/**
* Logs an error into errors.log file
*
* @param message
* Error message
* @param t
* Thrown error
*/
public abstract void logError(String message, Throwable t);
/**
* Sets name of file with syntax error, which prevented
* the plugin from enabling. Internal use only.
*
* @param file
* Name of file with syntax error
*/
public abstract void setBrokenFile(String file);
/**
* Returns {@code true} if the plugin is currently disabled due to an error,
* either a yaml syntax error or an internal startup error. Returns {@code false}
* if the plugin is properly running.
*
* @return {@code true} if disabled, {@code false} if running correctly
*/
public abstract boolean isPluginDisabled();
}
@@ -1,25 +1,32 @@
package me.neznamy.tab.shared;
package me.neznamy.tab.api;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
/**
* A class containing various constants used everywhere in the plugin
* to allow easier overview and modification to prevent inconsistencies.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class TabConstants {
public static final String PLUGIN_VERSION = "@plugin_version@";
public static final String DEFAULT_GROUP = "NONE";
public static final String PLUGIN_MESSAGE_CHANNEL_NAME = "tab:bridge-2";
public static final String REDIS_CHANNEL_NAME = "TAB";
public static final String PLUGIN_NAME = "@name@";
public static final String PLUGIN_ID = "@id@";
public static final String PLUGIN_VERSION = "@version@";
public static final String PLUGIN_DESCRIPTION = "@description@";
public static final String PLUGIN_WEBSITE = "@website@";
/**
* Private constructor to prevent the class from being instantiated
*/
private TabConstants() {}
public static final String NO_GROUP = "NONE";
public static final String DEFAULT_GROUP = "_DEFAULT_";
public static final String PLUGIN_MESSAGE_CHANNEL_NAME = "tab:bridge-2";
public static final String REDIS_CHANNEL_NAME = PLUGIN_NAME;
public static final String PIPELINE_HANDLER_NAME = PLUGIN_NAME;
/**
* Feature sub-category explaining why / when a certain feature
* needed CPU time to process tasks.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class CpuUsageCategory {
public static final String PLAYER_JOIN = "Player Join";
@@ -31,8 +38,6 @@ public class TabConstants {
public static final String PLAYER_RESPAWN = "Player Respawn";
public static final String PLUGIN_MESSAGE = "PluginMessageEvent";
public static final String REDIS_BUNGEE_MESSAGE = "PubSubMessageEvent";
public static final String LUCKPERMS_USER_RECALCULATE_EVENT = "UserDataRecalculateEvent";
public static final String LUCKPERMS_GROUP_RECALCULATE_EVENT = "GroupDataRecalculateEvent";
public static final String VANISH_CHANGE = "Vanish status change";
public static final String ANTI_OVERRIDE = "Anti override";
@@ -49,23 +54,19 @@ public class TabConstants {
public static final String RAW_PACKET_OUT = "Packet reading (out)";
public static final String PLACEHOLDER_REFRESHING = "Refreshing placeholders";
public static final String GROUP_REFRESHING = "Refreshing groups";
public static final String V1_8_0_BUG_COMPENSATION = "Compensating for 1.8.0 bugs";
public static final String REFRESHING_NAME_TAG_VISIBILITY = "Refreshing NameTag visibility";
public static final String SCOREBOARD_PACKET_CHECK = "Checking for other plugins";
public static final String PROCESSING_PLAYER_MOVEMENT = "Processing player movement";
public static final String TELEPORTING_WITHER = "Teleporting wither";
/**
* Private constructor to prevent the class from being instantiated
*/
private CpuUsageCategory() {}
}
/**
* Information why a specific feature sent a packet
* and what the packet sent is supposed to do.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class PacketCategory {
public static final String BOSSBAR_COLOR_STYLE = "BossBar (Color and style)";
@@ -78,11 +79,14 @@ public class TabConstants {
public static final String LAYOUT_FIXED_SLOTS = "Layout (Fixed slots)";
public static final String LAYOUT_PLAYER_SLOTS = "Layout (Player slots)";
public static final String LAYOUT_LATENCY = "Layout (Updating latency)";
public static final String NAMETAGS_TEAM_REGISTER = "NameTags (Team register)";
public static final String NAMETAGS_TEAM_UNREGISTER = "NameTags (Team unregister)";
public static final String NAMETAGS_TEAM_UPDATE = "NameTags (Team update)";
public static final String SORTING_CHANGING_TEAM_NAME = "Sorting (Changing team name)";
public static final String SCOREBOARD_TITLE = "Scoreboard (Title)";
public static final String SCOREBOARD_LINES = "Scoreboard (Lines)";
@@ -92,16 +96,12 @@ public class TabConstants {
public static final String UNLIMITED_NAMETAGS_TELEPORT = "Unlimited NameTags (Teleporting)";
public static final String UNLIMITED_NAMETAGS_SNEAK = "Unlimited NameTags (Sneaking)";
public static final String UNLIMITED_NAMETAGS_METADATA = "Unlimited NameTags (Metadata)";
/**
* Private constructor to prevent the class from being instantiated
*/
private PacketCategory() {}
}
/**
* Permission nodes used by the plugin
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Permission {
public static final String COMMAND_ALL = "tab.admin";
@@ -116,7 +116,11 @@ public class TabConstants {
public static final String COMMAND_MYSQL_DOWNLOAD = "tab.mysql.download";
public static final String COMMAND_MYSQL_UPLOAD = "tab.mysql.upload";
public static final String COMMAND_NAMETAG_TOGGLE = "tab.nametag.toggle";
public static final String COMMAND_NAMETAG_TOGGLE_OTHER = "tab.nametag.toggle.other";
public static final String COMMAND_NAMETAG_PREVIEW = "tab.nametag.preview";
public static final String COMMAND_NAMETAG_PREVIEW_OTHER = "tab.nametag.preview.other";
public static final String COMMAND_PARSE = "tab.parse";
public static final String COMMAND_SCOREBOARD_ANNOUNCE = "tab.announce.scoreboard";
public static final String COMMAND_SCOREBOARD_TOGGLE = "tab.scoreboard.toggle";
@@ -138,17 +142,13 @@ public class TabConstants {
public static final String TEST_PERMISSION = "tab.testpermission";
public static final String GROUP_PREFIX = "tab.group.";
/**
* Private constructor to prevent the class from being instantiated
*/
private Permission(){}
}
/**
* Internal property names used to store text
* under a specific key
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Property {
public static final String HEADER = "header";
@@ -173,11 +173,6 @@ public class TabConstants {
public static final String YELLOW_NUMBER = "yellow-number";
/**
* Private constructor to prevent the class from being instantiated
*/
private Property() {}
public static String bossbarTitle(String name) {
return "bossbar-title-" + name;
}
@@ -211,6 +206,7 @@ public class TabConstants {
* Feature names used to register features under in the
* feature manager which they can be retrieved by.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Feature {
//universal features
@@ -225,6 +221,7 @@ public class TabConstants {
public static final String PING_SPOOF = "PingSpoof";
public static final String SORTING = "sorting";
public static final String LAYOUT = "layout";
public static final String LAYOUT_LATENCY = "layout-latency";
public static final String NICK_COMPATIBILITY = "nick";
public static final String PIPELINE_INJECTION = "injection";
public static final String BOSS_BAR = "BossBar";
@@ -237,6 +234,7 @@ public class TabConstants {
public static final String UNLIMITED_NAME_TAGS = "NameTagX";
public static final String UNLIMITED_NAME_TAGS_PACKET_LISTENER = "nametagx-packet";
public static final String UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER = "nametagx-vehicle";
public static final String UNLIMITED_NAME_TAGS_GAMEMODE_LISTENER = "nametagx-gamemode";
//Bukkit only
public static final String PER_WORLD_PLAYER_LIST = "PerWorldPlayerList";
@@ -250,15 +248,14 @@ public class TabConstants {
public static final String PACKET_SERIALIZING = "Packet serializing";
public static final String PACKET_DESERIALIZING = "Packet deserializing";
/**
* Private constructor to prevent the class from being instantiated
*/
private Feature(){}
public static String scoreboardLine(String line) {
return "scoreboard-" + line;
}
public static String scoreboardLine(String scoreboard, int index) {
return "scoreboard-score-" + scoreboard + "-" + index;
}
public static String layout(String name) {
return "layout-" + name;
}
@@ -278,9 +275,96 @@ public class TabConstants {
public static String bossBarColorStyle(String name) {
return "BossBar-color-style-" + name;
}
}
public static String scoreboardLine(String scoreboard, int index) {
return "scoreboard-score-" + scoreboard + "-" + index;
/**
* All internal placeholders offered by the plugin
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Placeholder {
// Universal
public static final String DISPLAY_NAME = "%displayname%";
public static final String GROUP = "%group%";
public static final String VANISHED = "%vanished%";
public static final String WORLD = "%world%";
public static final String WORLD_ONLINE = "%worldonline%";
public static final String SERVER = "%server%";
public static final String SERVER_ONLINE = "%serveronline%";
public static final String PLAYER = "%player%";
public static final String TIME = "%time%";
public static final String DATE = "%date%";
public static final String PING = "%ping%";
public static final String PLAYER_VERSION = "%player-version%";
public static final String PLAYER_VERSION_ID = "%player-version-id%";
public static final String MEMORY_USED = "%memory-used%";
public static final String MEMORY_MAX = "%memory-max%";
public static final String MEMORY_USED_GB = "%memory-used-gb%";
public static final String MEMORY_MAX_GB = "%memory-max-gb%";
public static final String ONLINE = "%online%";
public static final String STAFF_ONLINE = "%staffonline%";
public static final String NON_STAFF_ONLINE = "%nonstaffonline%";
public static final String LUCKPERMS_PREFIX = "%luckperms-prefix%";
public static final String LUCKPERMS_SUFFIX = "%luckperms-suffix%";
public static final String COUNTDOWN = "%countdown%";
// Placeholders for internal use
public static final String COLLISION = "%collision%";
public static final String INVISIBLE = "%invisible%";
public static final String VEHICLE = "%vehicle%";
// Bukkit only
public static final String TPS = "%tps%";
public static final String MSPT = "%mspt%";
public static final String AFK = "%afk%";
public static final String ESSENTIALS_NICK = "%essentialsnick%";
public static final String VAULT_PREFIX = "%vault-prefix%";
public static final String VAULT_SUFFIX = "%vault-suffix%";
public static final String HEALTH = "%health%";
public static final int MINIMUM_REFRESH_INTERVAL = 50;
public static String condition(String name) {
return "%condition:" + name + "%";
}
public static String animation(String name) {
return "%animation:" + name + "%";
}
public static String globalPlayerListGroup(String group) {
return "%playerlist-group_" + group + "%";
}
}
/**
* All plugins TAB hooks into
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class Plugin {
public static final String LUCKPERMS = "LuckPerms";
public static final String VAULT = "Vault";
public static final String PLACEHOLDER_API = "PlaceholderAPI";
public static final String LIBS_DISGUISES = "LibsDisguises";
public static final String VIAVERSION = "ViaVersion";
public static final String PROTOCOL_SUPPORT = "ProtocolSupport";
public static final String ESSENTIALS = "Essentials";
public static final String REDIS_BUNGEE = "RedisBungee";
public static final String PREMIUM_VANISH = "PremiumVanish";
}
/**
* TAB's custom metrics charts
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class MetricsChart {
public static final String PERMISSION_SYSTEM = "permission_system";
public static final String GLOBAL_PLAYER_LIST_ENABLED = "global_playerlist_enabled";
public static final String PLACEHOLDER_API = "placeholderapi";
public static final String SERVER_VERSION = "server_version";
public static final String UNLIMITED_NAME_TAG_MODE_ENABLED = "unlimited_nametag_mode_enabled";
}
}
@@ -2,42 +2,58 @@ package me.neznamy.tab.api;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
/**
* Abstract class representing a core feature of the plugin.
* <p>
* It receives all kinds of events and can react to them.
*/
public abstract class TabFeature {
private final String featureName;
private final String refreshDisplayName;
protected final String[] disabledServers;
private final boolean serverWhitelistMode;
protected final String[] disabledWorlds;
private final boolean worldWhitelistMode;
/** Feature's name displayed in /tab cpu */
@Getter private final String featureName;
/** Feature's function name displayed in place of refreshing in /tab cpu */
@Getter private final String refreshDisplayName;
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
/** Flag tracking whether disabled server list is a whitelist or blacklist */
private boolean serverWhitelistMode = false;
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledWorlds = new String[0];
/** Flag tracking whether disabled world list is a whitelist or blacklist */
private boolean worldWhitelistMode = false;
/** Players located in currently disabled worlds / servers */
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
/**
* List of methods overridden (implemented) by each feature.
* If a method is not overridden, it is not called at all.
* This avoids a massive spam for every single method
* in every single feature in /tab cpu output
*/
private final List<String> methodOverrides = new ArrayList<>();
/**
* Constructs new instance with given parameters and loads method overrides
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
*/
protected TabFeature(String featureName, String refreshDisplayName) {
this(featureName, refreshDisplayName, null, null);
}
protected TabFeature(String featureName, String refreshDisplayName, List<String> disabledServers, List<String> disabledWorlds) {
this.featureName = featureName;
this.refreshDisplayName = refreshDisplayName;
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
} else {
this.disabledServers = new String[0];
serverWhitelistMode = false;
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
} else {
this.disabledWorlds = new String[0];
worldWhitelistMode = false;
}
try {
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onCommand");
@@ -49,12 +65,16 @@ public abstract class TabFeature {
methodOverrides.add("onWorldChange");
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onServerChange");
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class)
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onDisplayObjective");
TabAPI.getInstance().getFeatureManager().markDisplayObjective();
}
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onLoginPacket");
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class)
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onObjective");
TabAPI.getInstance().getFeatureManager().markObjective();
}
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPlayerInfo");
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
@@ -71,7 +91,34 @@ public abstract class TabFeature {
}
/**
* Loads all players and sends packets
* Constructs new instance with given parameters and loads method overrides.
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(String featureName, String refreshDisplayName, String configSection) {
this(featureName, refreshDisplayName);
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
}
}
/**
* Loads all online players and sends packets
*/
public void load() {
//empty by default
@@ -85,10 +132,13 @@ public abstract class TabFeature {
}
/**
* Processes command from player
* @param sender - command sender
* @param message - command line
* @return true if event should be cancelled, false if not
* Processes command from player. This is typically a toggle command.
*
* @param sender
* command sender
* @param message
* command line
* @return {@code true} if event should be cancelled, {@code true} if not
*/
public boolean onCommand(TabPlayer sender, String message) {
return false;
@@ -96,7 +146,9 @@ public abstract class TabFeature {
/**
* Processes join event
* @param connectedPlayer - player who connected
*
* @param connectedPlayer
* player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
//empty by default
@@ -104,16 +156,23 @@ public abstract class TabFeature {
/**
* Processes quit event
* @param disconnectedPlayer - player who disconnected
*
* @param disconnectedPlayer
* player who disconnected
*/
public void onQuit(TabPlayer disconnectedPlayer) {
//empty by default
}
/**
* Processes world switch
* @param changed - player who switched world
* @param from - world player changed from
* @param to - world player changed to
*
* @param changed
* player who switched world
* @param from
* world player changed from
* @param to
* world player changed to
*/
public void onWorldChange(TabPlayer changed, String from, String to) {
//empty by default
@@ -121,9 +180,13 @@ public abstract class TabFeature {
/**
* Processes server switch
* @param changed - player who switched server
* @param from - server player changed from
* @param to - server player changed to
*
* @param changed
* player who switched server
* @param from
* server player changed from
* @param to
* server player changed to
*/
public void onServerChange(TabPlayer changed, String from, String to) {
//empty by default
@@ -131,8 +194,11 @@ public abstract class TabFeature {
/**
* Processes the packet send and returns true if packet should be cancelled
* @param receiver - player receiving packet
* @param packet - received packet
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
//empty by default
@@ -140,7 +206,9 @@ public abstract class TabFeature {
/**
* Processes login packet, only available on BungeeCord
* @param packetReceiver - player receiving client reset packet
*
* @param packetReceiver
* player receiving client reset packet
*/
public void onLoginPacket(TabPlayer packetReceiver) {
//empty by default
@@ -148,8 +216,11 @@ public abstract class TabFeature {
/**
* Processes the packet send
* @param receiver - player receiving packet
* @param packet - received packet
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
//empty by default
@@ -157,8 +228,11 @@ public abstract class TabFeature {
/**
* Processes the packet send and possibly modifies it
* @param receiver - player receiving packet
* @param info - received packet
*
* @param receiver
* player receiving packet
* @param info
* received packet
*/
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
//empty by default
@@ -166,11 +240,14 @@ public abstract class TabFeature {
/**
* Processes raw packet sent by client
* @param sender - packet sender
* @param packet - packet received
* @return true if false should be cancelled, false if not
* @throws ReflectiveOperationException
* if thrown by reflective operation
*
* @param sender
* packet sender
* @param packet
* packet received
* @return true if false should be cancelled, false if not
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
return false;
@@ -178,10 +255,13 @@ public abstract class TabFeature {
/**
* Processes raw packet sent to client
* @param receiver - packet receiver
* @param packet - the packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*
* @param receiver
* packet receiver
* @param packet
* the packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
//empty by default
@@ -189,20 +269,31 @@ public abstract class TabFeature {
/**
* Performs refresh of specified player
* @param refreshed - player to refresh
* @param force - if refresh should be forced despite refresh seemingly not needed
*
* @param refreshed
* player to refresh
* @param force
* if refresh should be forced despite refresh seemingly not needed
*/
public void refresh(TabPlayer refreshed, boolean force) {
//empty by default
}
public void onVanishStatusChange(TabPlayer player){
/**
* Processes vanish status change of player
*
* @param player
* Player who changed vanish status
*/
public void onVanishStatusChange(TabPlayer player) {
//empty by default
}
/**
* Registers this feature as one using specified placeholders
* @param placeholders - placeholders to add as used in this feature
*
* @param placeholders
* placeholders to add as used in this feature
*/
public void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
@@ -210,10 +301,13 @@ public abstract class TabFeature {
}
/**
* Returns true if world or server is disabled, false if not
* @param world - world to check
* @param server - server to check
* @return true if feature should be disabled, false if not
* Returns {@code true} if world or server is disabled, {@code false} if not
*
* @param server
* server to check
* @param world
* world to check
* @return {@code true} if feature should be disabled, {@code false} if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
@@ -224,6 +318,17 @@ public abstract class TabFeature {
return contains;
}
/**
* Returns {@code true} if list contains the specified element or element
* ends with {@code "*"} and element meeting that requirement is present,
* {@code false} otherwise.
*
* @param list
* List to check
* @param element
* Element to find
* @return {@code true} if element was found, {@code false} if not
*/
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
@@ -237,31 +342,50 @@ public abstract class TabFeature {
}
/**
* Returns name of the feature displayed in /tab cpu.
* @return name of the feature displayed in /tab cpu
* Returns {@code true} if method with specified name is overridden
* in the implementation, {@code false} if not
*
* @param method
* Method to check
* @return {@code true} if overridden, {@code false} if not
*/
public String getFeatureName() {
return featureName;
}
public boolean overridesMethod(String method) {
return methodOverrides.contains(method);
}
/**
* Returns {@code true} if player is currently located in a server
* or world, which is marked as disabled, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if player is in disabled server / world,
* {@code false} if not
*/
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
/**
* Adds specified player into list of players in disabled
* servers / worlds.
*
* @param p
* Player to add
*/
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
/**
* Removes specified player from list of players in disabled
* servers / worlds
*
* @param p
* Player to check
*/
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
public String getRefreshDisplayName() {
return refreshDisplayName;
}
}
@@ -19,225 +19,288 @@ public interface TabPlayer {
/**
* Returns player's name
* @return Player's name
*
* @return Player's name
*/
String getName();
/**
* Returns player's UUID
* @return Player's UUID
*
* @return Player's UUID
*/
UUID getUniqueId();
/**
* Returns player's uuid used in TabList. This may only be different from real uuid if
* TAB is installed on velocity with some specific velocity setup
* @return player's uuid in TabList
* TAB is installed on a proxy which does not rewrite UUIDs.
*
* @return player's uuid in TabList
*/
UUID getTablistUUID();
UUID getTablistId();
/**
* Returns player's protocol version
* @return player's protocol version
*
* @return player's protocol version
*/
ProtocolVersion getVersion();
/**
* Returns platform-specific entity
* @return an instance of bukkit/bungee player depending on platform
*
* @return an instance of bukkit/bungee player depending on platform
*/
Object getPlayer();
/**
* Returns player's current world name (on BungeeCord this requires bridge installed)
* @return name of world where player is currently in, "N/A" on BungeeCord if bridge is not installed
*
* @return name of world where player is currently in, "N/A" on BungeeCord if bridge is not installed
*/
String getWorld();
/**
* Returns player's current server name on BungeeCord (on bukkit this returns "N/A")
* @return name of server where player is currently in, "N/A" on bukkit
*
* @return name of server where player is currently in, "N/A" on bukkit
*/
String getServer();
/**
* Performs platform-specific API call to check for permission and returns the result
* @param permission - the permission to check for
* @return - true if player has permission, false if not
*
* @param permission
* the permission to check for
* @return true if player has permission, false if not
*/
boolean hasPermission(String permission);
/**
* Sends the player a custom universal packet
* @param packet - packet to send
*
* @param packet
* packet to send
*/
void sendCustomPacket(TabPacket packet);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - packet to send
* @param feature - feature to increment sent packet counter of
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, TabFeature feature);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - packet to send
* @param feature - feature to increment sent packet counter of
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, String feature);
/**
* Sends the player a platform-specific packet
* @param packet - an instance of packet depending on platform
*
* @param packet
* an instance of packet depending on platform
*/
void sendPacket(Object packet);
/**
* Sends the player a platform-specific packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - an instance of packet depending on platform
* @param feature - feature to increment sent packet counter of
*/
void sendPacket(Object packet, TabFeature feature);
/**
* Sends the player a platform-specific packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - an instance of packet depending on platform
* @param feature - feature to increment sent packet counter of
*/
void sendPacket(Object packet, String feature);
/**
* Returns player's property by name
* @param name - name of property
* @return the property or null if not found
*
* @param name
* name of property
* @return the property or {@code null} if not found
*/
Property getProperty(String name);
/**
* Sends a message to the player
* @param message - message to be sent
* @param translateColors - whether colors should be translated or not
*
* @param message
* message to be sent
* @param translateColors
* whether colors should be translated or not
*/
void sendMessage(String message, boolean translateColors);
/**
* Sends specified component as a chat message
* @param message - message to send
*
* @param message
* message to send
*/
void sendMessage(IChatBaseComponent message);
/**
* Returns player's primary permission group
* @return player's group depending on configuration
*/
String getGroup();
/**
* Returns player's channel or null if server is 1.7 or older
* @return player's channel
* Returns player's netty channel. On servers running version 1.7 or older returns {@code null}.
*
* @return player's channel
*/
Channel getChannel();
/**
* Returns player's ping calculated by server
* @return player's ping
*
* @return player's ping
*/
int getPing();
/**
* Returns player's skin data
* @return player's skin
*
* @return player's skin
*/
Skin getSkin();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
* @return true if player is fully loaded, false otherwise
*
* @return {@code true} if player is fully loaded, {@code false} otherwise
*/
boolean isLoaded();
/**
* Sets property with specified name to new value. If property did not exist before, it is
* created and true is returned. If it existed, it is overridden and true is returned. False is returned otherwise.
* @param feature - feature using this property to get placeholders registered
* @param identifier - property name
* @param rawValue - new raw value
* @return true if value changed / did not exist, false if value did not change
* created and {@code true} is returned. If it existed, it is overridden and {@code true} is returned.
* {@code false} is returned otherwise.
*
* @param feature
* feature using this property to get placeholders registered
* @param identifier
* property name
* @param rawValue
* new raw value
* @return {@code true} if value changed / did not exist, {@code false} if value did not change
*/
boolean setProperty(TabFeature feature, String identifier, String rawValue);
/**
* Loads property from config using standard property loading algorithm
* @param property - property name to load
* @return true if value did not exist or changed, false otherwise
*
* @param property
* property name to load
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property);
/**
* Loads property from config using standard property loading algorithm. If the property is
* not set in config, sets it to ifNotSet value
* @param property - property name to load
* @param ifNotSet - value to use if property is not defined in config
* @return true if value did not exist or changed, false otherwise
* not set in config, {@code ifNotSet} value is used.
*
* @param property
* property name to load
* @param ifNotSet
* value to use if property is not defined in config
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
/**
* Returns name of player's scoreboard team or null if NameTag feature is disabled
* @return name of player's team
* Returns name of player's scoreboard team or {@code null} if NameTag feature is disabled
*
* @return name of player's team
*/
String getTeamName();
/**
* Returns user-friendly explanation of team name
* @return explanation behind team name
*/
String getTeamNameNote();
/**
* Returns true if player is disguised using LibsDisguises
* @return true if player is disguised, false if not
* Returns {@code true} if player is disguised using LibsDisguises, {@code false} if not
*
* @return {@code true} if player is disguised, {@code false} if not
*/
boolean isDisguised();
/**
* Returns true if player has invisibility potion, false if not. For bukkit, API is used, for BungeeCord
* bukkit bridge is used
* @return true if player has invisibility potion, false if not
* Returns {@code true} if player has invisibility potion, {@code false} if not.
* For bukkit, API is used, for BungeeCord, bridge is used.
*
* @return {@code true} if player has invisibility potion, {@code false} if not
*/
boolean hasInvisibilityPotion();
/**
* Currently only PV hook on BungeeCord for global PlayerList function. Looking to expand this
* in the future
* @return true if vanished with PV on BungeeCord
* Checks if player is vanished using any supported vanish plugin and returns the result.
*
* @return {@code true} if player is vanished, {@code false} if not
*/
boolean isVanished();
/**
* Returns true if player is online according to server
* @return true if online, false if not
* Calls platform-specific method and returns the result
*
* @return {@code true} if player is online, {@code false} if not
*/
boolean isOnline();
/**
* Returns GameMode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
* @return GameMode of the player
*
* @return GameMode of the player
*/
int getGamemode();
/**
* Returns {@code true} if this is a bedrock player, {@code false} if not
*
* @return {@code true} if player is a bedrock player, {@code false} if not
*/
boolean isBedrockPlayer();
/**
* Returns player's primary permission group. If group has been changed using
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
* detected by standard group assign logic
*
* @return player's primary permission group
*/
String getGroup();
/**
* Temporarily overrides player's group and applies all changes coming from new group.
* This includes all properties and sorting, if used.
*
* @param group
* New group to use
* @see #hasTemporaryGroup()
* @see #resetTemporaryGroup()
*/
void setTemporaryGroup(String group);
/**
* Returns temporary group applied to the player using {@link #setTemporaryGroup(String)}.
* If no group was set, returns {@code null}
*
* @return Temporary group assigned to player or {@code null} if not set
* @see #setTemporaryGroup(String)
* @see #resetTemporaryGroup()
*/
boolean hasTemporaryGroup();
/**
* Resets temporary group assigned using {@link #setTemporaryGroup(String)}.
* If no temporary group is set, doesn't do anything.
*
* @see #setTemporaryGroup(String)
* @see #hasTemporaryGroup()
*/
void resetTemporaryGroup();
/**
* Returns player's game profile name as seen by other players. By default,
* this returns player's name. If using a nickname / disguise plugin that changes
* game profile name for other players and change was successfully detected, returns
* that value.
*
* @return player's game profile name as seen by other players
*/
String getNickname();
}
@@ -1,29 +1,168 @@
package me.neznamy.tab.api;
/**
* An interface allowing to modify tablist display names
* of players.
* <p>
* Instance can be obtained using {@link TabAPI#getTablistFormatManager()}.
* This requires the feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
public interface TablistFormatManager {
/**
* Changes player's prefix to provided value. Supports placeholders,
* as well as any supported RGB formats.
*
* @param player
* player to change prefix of
* @param prefix
* new prefix value
* @see #resetPrefix(TabPlayer)
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void setPrefix(TabPlayer player, String prefix);
/**
* Changes player's name to provided value. Supports placeholders,
* as well as any supported RGB formats.
*
* @param player
* player to change prefix of
* @param customName
* new customName value
* @see #resetName(TabPlayer)
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
void setName(TabPlayer player, String customName);
/**
* Changes player's suffix to provided value. Supports placeholders,
* as well as any supported RGB formats.
*
* @param player
* player to change prefix of
* @param suffix
* new suffix value
* @see #resetSuffix(TabPlayer)
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void setSuffix(TabPlayer player, String suffix);
/**
* Resets player's prefix previously assigned using {@link #setPrefix(TabPlayer, String)}
* method. If nothing was set, this method will not do anything.
*
* @param player
* player to reset prefix of
* @see #setPrefix(TabPlayer, String)
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void resetPrefix(TabPlayer player);
/**
* Resets player's custom name previously assigned using {@link #setName(TabPlayer, String)}
* method. If nothing was set, this method will not do anything.
*
* @param player
* player to reset name of
* @see #setName(TabPlayer, String)
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
void resetName(TabPlayer player);
/**
* Resets player's suffix previously assigned using {@link #setSuffix(TabPlayer, String)}
* method. If nothing was set, this method will not do anything.
*
* @param player
* player to reset suffix of
* @see #setSuffix(TabPlayer, String)
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void resetSuffix(TabPlayer player);
/**
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
* If the method was not used, {@code null} is returned.
*
* @param player
* player to get custom prefix of
* @return player's custom prefix or {@code null} if not defined
* @see #setPrefix(TabPlayer, String)
* @see #resetPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
String getCustomPrefix(TabPlayer player);
/**
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
* If the method was not used, {@code null} is returned.
*
* @param player
* player to get custom name of
* @return player's custom name or {@code null} if not defined
* @see #setName(TabPlayer, String)
* @see #resetName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
String getCustomName(TabPlayer player);
/**
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
* If the method was not used, {@code null} is returned.
*
* @param player
* player to get custom suffix of
* @return player's custom suffix or {@code null} if not defined
* @see #setSuffix(TabPlayer, String)
* @see #resetSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
String getCustomSuffix(TabPlayer player);
/**
* Returns player's original prefix applied using plugin's internal logic.
* This value is not affected by API calls in any way.
*
* @param player
* player to get original prefix of
* @return player's original prefix
* @see #setPrefix(TabPlayer, String)
* @see #resetPrefix(TabPlayer)
* @see #getCustomPrefix(TabPlayer)
*/
String getOriginalPrefix(TabPlayer player);
/**
* Returns player's original name applied using plugin's internal logic.
* This value is not affected by API calls in any way.
*
* @param player
* player to get original name of
* @return player's original name
* @see #setName(TabPlayer, String)
* @see #resetName(TabPlayer)
* @see #getCustomName(TabPlayer)
*/
String getOriginalName(TabPlayer player);
/**
* Returns player's original suffix applied using plugin's internal logic.
* This value is not affected by API calls in any way.
*
* @param player
* player to get original suffix of
* @return player's original suffix
* @see #setSuffix(TabPlayer, String)
* @see #resetSuffix(TabPlayer)
* @see #getCustomSuffix(TabPlayer)
*/
String getOriginalSuffix(TabPlayer player);
}
@@ -1,10 +1,14 @@
package me.neznamy.tab.api.bossbar;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
@AllArgsConstructor
public enum BarStyle {
PROGRESS("SOLID"),
@@ -14,23 +18,5 @@ public enum BarStyle {
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
private final String bukkitName;
/**
* Initializes enum constant with given bukkit name
*
* @param bukkitName
* Name of color to be returned in {@code getBukkitName()}
*/
BarStyle(String bukkitName){
this.bukkitName = bukkitName;
}
/**
* Returns name of this style in Bukkit API
* @return name of this style in Bukkit API
*/
public String getBukkitName() {
return bukkitName;
}
@Getter private final String bukkitName;
}
@@ -23,14 +23,16 @@ public interface BossBar {
* Returns name of this BossBar. If it was defined in config, returns
* the name specified in config. If it was made using the API, returns
* randomly generated ID given to this BossBar when creating.
* @return name of BossBar
*
* @return name of BossBar
*/
String getName();
/**
* Returns randomly generated uuid of this BossBar used internally to match
* the BossBar in client with packets or with Bukkit API BossBar instances.
* @return uuid of this BossBar
*
* @return uuid of this BossBar
*/
UUID getUniqueId();
@@ -43,8 +45,8 @@ public interface BossBar {
* <p>
* If specified title is equal to the current one, nothing happens.
*
* @param title
* New title to display in this BossBar
* @param title
* New title to display in this BossBar
*/
void setTitle(String title);
@@ -59,8 +61,8 @@ public interface BossBar {
* <p>
* If specified progress is equal to the current one, nothing happens.
*
* @param progress
* New progress to use in this BossBar
* @param progress
* New progress to use in this BossBar
*/
void setProgress(String progress);
@@ -68,8 +70,9 @@ public interface BossBar {
* Changes BossBar progress to specified value ranging from 0 to 100.
* <p>
* If specified progress is equal to the current one, nothing happens.
* @param progress
* New progress to use in this BossBar
*
* @param progress
* New progress to use in this BossBar
*/
void setProgress(float progress);
@@ -84,8 +87,8 @@ public interface BossBar {
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
* @param color
* New color to use in this BossBar
*/
void setColor(String color);
@@ -94,8 +97,8 @@ public interface BossBar {
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
* @param color
* New color to use in this BossBar
*/
void setColor(BarColor color);
@@ -110,8 +113,8 @@ public interface BossBar {
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
* @param style
* New style to use in this BossBar
*/
void setStyle(String style);
@@ -120,36 +123,40 @@ public interface BossBar {
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
* @param style
* New style to use in this BossBar
*/
void setStyle(BarStyle style);
/**
* Returns current title of the BossBar in raw format. If it contains placeholders,
* their raw identifiers are used in the result.
* @return title of the BossBar
*
* @return title of the BossBar
*/
String getTitle();
/**
* Returns progress of the BossBar as a string, which is either entered string
* containing placeholders or entered number converted to string
* @return entered progress as a string
*
* @return entered progress as a string
*/
String getProgress();
/**
* Returns color of the BossBar as a string, which is either entered string
* containing placeholders or entered enum value converted to string
* @return entered color as a string
*
* @return entered color as a string
*/
String getColor();
/**
* Returns style of the BossBar as a string, which is either entered string
* containing placeholders or entered enum value converted to string
* @return entered style as a string
*
* @return entered style as a string
*/
String getStyle();
@@ -158,8 +165,8 @@ public interface BossBar {
* <p>
* If the player already sees this BossBar, nothing happens.
*
* @param player
* Player to register this BossBar to
* @param player
* Player to register this BossBar to
*/
void addPlayer(TabPlayer player);
@@ -168,8 +175,8 @@ public interface BossBar {
* <p>
* If the player does not see this BossBar, nothing happens.
*
* @param player
* Player to unregister this BossBar from
* @param player
* Player to unregister this BossBar from
*/
void removePlayer(TabPlayer player);
@@ -179,15 +186,25 @@ public interface BossBar {
* The returned list can only be used to read. Writing
* will not work properly. For adding/removing players see {@link #addPlayer(TabPlayer)}
* and {@link #removePlayer(TabPlayer)}.
* @return List of players seeing this BossBar
*
* @return List of players seeing this BossBar
*/
List<TabPlayer> getPlayers();
/**
* Returns {@code true} if specified player can see this BossBar, {@code false} if not
* @param player
* Player to check for
* @return {@code true} if contains, {@code false} if not
*
* @param player
* Player to check for
* @return {@code true} if contains, {@code false} if not
*/
boolean containsPlayer(TabPlayer player);
/**
* Returns {@code true} if this bossbar in configured as announcement-only bar,
* {@code false} if not.
*
* @return {@code true} if bar is announcement only, {@code false} if not
*/
boolean isAnnouncementBar();
}
@@ -4,15 +4,16 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
/**
* An interface allowing work with BossBars such as creating, sending
* and toggling.
* <p>
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getBossBarManager()}.
* Instance can be obtained using {@link TabAPI#getBossBarManager()}.
* This requires the BossBar feature to be enabled in config, otherwise the method will
* return null.
* return {@code null}.
*/
public interface BossBarManager {
@@ -20,18 +21,15 @@ public interface BossBarManager {
* Creates BossBar with specified parameters and registers it to BossBar manager,
* allowing to retrieve it later.
*
* @param title
* Title to display, supports placeholders
*
* @param progress
* Progress to use. Must be between 0 and 100
*
* @param color
* Color to use
*
* @param style
* Style to use
* @return The newly created BossBar
* @param title
* Title to display, supports placeholders
* @param progress
* Progress to use. Must be between 0 and 100
* @param color
* Color to use
* @param style
* Style to use
* @return The newly created BossBar
*/
BossBar createBossBar(String title, float progress, BarColor color, BarStyle style);
@@ -39,21 +37,18 @@ public interface BossBarManager {
* Creates BossBar with specified parameters and registers it to BossBar manager,
* allowing to retrieve it later.
*
* @param title
* Title to display, supports placeholders
*
* @param progress
* Progress to use. Must be a string version of a number, or
* a placeholder that returns a number
*
* @param color
* Color to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
*
* @param style
* Style to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
* @return The newly created BossBar
* @param title
* Title to display, supports placeholders
* @param progress
* Progress to use. Must be a string version of a number, or
* a placeholder that returns a number
* @param color
* Color to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
* @param style
* Style to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
* @return The newly created BossBar
*/
BossBar createBossBar(String title, String progress, String color, String style);
@@ -63,9 +58,9 @@ public interface BossBarManager {
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
* Returns null if no such BossBar exists.
*
* @param name
* Name of registered BossBar
* @return BossBar with specified name
* @param name
* Name of registered BossBar
* @return BossBar with specified name
*/
BossBar getBossBar(String name);
@@ -74,9 +69,9 @@ public interface BossBarManager {
* BossBar is created and used in packets / to match which the one from Bukkit API.
* Returns null if no such BossBar exists.
*
* @param id
* UUID to return BossBar by
* @return BossBar with specified uuid
* @param id
* UUID to return BossBar by
* @return BossBar with specified uuid
*/
BossBar getBossBar(UUID id);
@@ -86,31 +81,31 @@ public interface BossBarManager {
* <p>
* Map key is BossBar's name available in {@link me.neznamy.tab.api.bossbar.BossBar#getName()},
* map value is the BossBar interface.
* @return all registered BossBars
*
* @return all registered BossBars
*/
Map<String, BossBar> getRegisteredBossBars();
/**
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
*
* @param player
* Player to toggle BossBar for
*
* @param sendToggleMessage
* {@code true} if toggle message should be sent, {@code false} if not
* @see #hasBossBarVisible(TabPlayer)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
* @param player
* Player to toggle BossBar for
* @param sendToggleMessage
* {@code true} if toggle message should be sent, {@code false} if not
* @see #hasBossBarVisible(TabPlayer)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
void toggleBossBar(TabPlayer player, boolean sendToggleMessage);
/**
* Returns true if player can see BossBars, false if toggled via command or API.
*
* @param player
* Player to check toggle status of
* @return {@code true} if visible, {@code false} if disabled (toggled)
* @see #toggleBossBar(TabPlayer, boolean)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
* @param player
* Player to check toggle status of
* @return {@code true} if visible, {@code false} if disabled (toggled)
* @see #toggleBossBar(TabPlayer, boolean)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
boolean hasBossBarVisible(TabPlayer player);
@@ -118,12 +113,12 @@ public interface BossBarManager {
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
* If visibility changed, toggle message is sent if {@code sendToggleMessage} is {@code true}.
*
* @param player
* Player to set BossBar visibility of
* @param visible
* {@code true} if BossBar should be visible, {@code false} if not
* @param sendToggleMessage
* {@code true} if toggle message should be sent if value changed, {@code false} if not
* @param player
* Player to set BossBar visibility of
* @param visible
* {@code true} if BossBar should be visible, {@code false} if not
* @param sendToggleMessage
* {@code true} if toggle message should be sent if value changed, {@code false} if not
* @see #toggleBossBar(TabPlayer, boolean)
* @see #hasBossBarVisible(TabPlayer)
*/
@@ -131,37 +126,40 @@ public interface BossBarManager {
/**
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
* @param player
* Player to show BossBar to
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #announceBossBar(String, int)
* @see #getAnnouncedBossBars()
*
* @param player
* Player to show BossBar to
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #announceBossBar(String, int)
* @see #getAnnouncedBossBars()
*/
void sendBossBarTemporarily(TabPlayer player, String bossBar, int duration);
/**
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #getAnnouncedBossBars()
*
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #getAnnouncedBossBars()
*/
void announceBossBar(String bossBar, int duration);
/**
* Returns list of BossBars which are currently being announced.
* @return list of currently active BossBar announcements
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #announceBossBar(String, int)
*
* @return list of currently active BossBar announcements
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #announceBossBar(String, int)
*/
List<BossBar> getAnnouncedBossBars();
}
@@ -1,49 +1,19 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
/**
* Class for click event action in chat component
*/
@Data
public class ChatClickable {
/** Click action */
private final EnumClickAction action;
@NonNull private final EnumClickAction action;
/** Click value */
private final String value;
/**
* Constructs new instance with given action and value.
* @param action
* click event action
* @param value
* click event value
* @throws IllegalArgumentException
* if {@code action} is null or {@code value} is null
*/
public ChatClickable(EnumClickAction action, String value) {
Preconditions.checkNotNull(action, "hover action");
Preconditions.checkNotNull(value, "hover value");
this.action = action;
this.value = value;
}
/**
* Returns click action defined in constructor
* @return click action
*/
public EnumClickAction getAction() {
return action;
}
/**
* Returns click action defined in constructor
* @return click action
*/
public String getValue() {
return value;
}
@NonNull private final String value;
/**
* Enum for all possible click actions
@@ -1,22 +1,22 @@
package me.neznamy.tab.api.chat;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import java.util.UUID;
import com.google.gson.JsonObject;
import me.neznamy.tab.api.util.Preconditions;
public class ChatComponentEntity extends IChatBaseComponent {
private final String type;
private final UUID id;
private final String name;
public ChatComponentEntity(String type, UUID id, String name) {
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(id, "id");
this.type = type;
this.id = id;
this.name = name;
private final String CONTENTS;
private final String VALUE_1_13;
private final String VALUE_1_12;
private final String VALUE_1_8;
public ChatComponentEntity(@NonNull String type, @NonNull UUID id, String name) {
this.CONTENTS = String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
this.VALUE_1_13 = String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name);
this.VALUE_1_12 = String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name);
this.VALUE_1_8 = String.format("{type:%s,id:%s,name:%s}", type, id, name);
}
@Override
@@ -26,16 +26,10 @@ public class ChatComponentEntity extends IChatBaseComponent {
@Override
public String toString() {
return String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
}
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("type", type);
json.addProperty("id", id.toString());
JsonObject json2 = new JsonObject();
json2.addProperty("text", name);
json.add("name", json2);
return json;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) return CONTENTS;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) return VALUE_1_13;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) return VALUE_1_12;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) return VALUE_1_8;
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
}
}
@@ -1,57 +1,32 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
/**
* Class for hover event action in chat component
*/
@Data
public class ChatHoverable {
/** Hover action */
private final EnumHoverAction action;
@NonNull private final EnumHoverAction action;
/** Hover value */
private final IChatBaseComponent value;
/**
* Constructs new instance with given action and value.
* @param action
* hover event action
* @param value
* hover event value
* @throws IllegalArgumentException
* if {@code action} is null or {@code value} is null
*/
public ChatHoverable(EnumHoverAction action, IChatBaseComponent value) {
Preconditions.checkNotNull(action, "hover action");
Preconditions.checkNotNull(value, "hover value");
this.action = action;
this.value = value;
}
/**
* Returns hover action defined in constructor
* @return hover action
*/
public EnumHoverAction getAction() {
return action;
}
/**
* Returns hover value defined in constructor
* @return hover value
*/
public IChatBaseComponent getValue() {
return value;
}
@NonNull private final IChatBaseComponent value;
/**
* Enum for all possible hover actions
*/
@AllArgsConstructor
public enum EnumHoverAction {
SHOW_TEXT,
SHOW_ITEM,
SHOW_ENTITY
SHOW_TEXT("contents"),
SHOW_ITEM("value"),
SHOW_ENTITY("contents");
@Getter private final String preferredKey;
}
}
@@ -1,37 +1,31 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import me.neznamy.tab.api.util.Preconditions;
import org.json.simple.JSONObject;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
import org.json.simple.JSONObject;
import java.util.UUID;
@Data @NoArgsConstructor
public class ChatModifier {
private TextColor color;
private Boolean bold;
private Boolean italic;
private Boolean underlined;
private Boolean strikethrough;
private Boolean obfuscated;
private ChatClickable clickEvent;
private ChatHoverable hoverEvent;
private String font;
private ProtocolVersion targetVersion;
public ChatModifier() {
}
public ChatModifier(ChatModifier modifier) {
Preconditions.checkNotNull(modifier, "modifier");
public ChatModifier(@NonNull ChatModifier modifier) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
this.bold = modifier.bold;
this.italic = modifier.italic;
@@ -44,140 +38,87 @@ public class ChatModifier {
this.targetVersion = modifier.targetVersion;
}
public TextColor getColor() {
return color;
}
public void setColor(TextColor color) {
this.color = color;
}
public Boolean getBold() {
return bold;
}
public void setBold(Boolean bold) {
this.bold = bold;
}
public Boolean getItalic() {
return italic;
}
public void setItalic(Boolean italic) {
this.italic = italic;
}
public Boolean getUnderlined() {
return underlined;
}
public void setUnderlined(Boolean underlined) {
this.underlined = underlined;
}
public Boolean getStrikethrough() {
return strikethrough;
}
public void setStrikethrough(Boolean strikethrough) {
this.strikethrough = strikethrough;
}
public Boolean getObfuscated() {
return obfuscated;
}
public void setObfuscated(Boolean obfuscated) {
this.obfuscated = obfuscated;
}
/**
* Returns true if bold is defined and set to true, false otherwise
* @return true if bold is defined and set to true, false otherwise
*
* @return true if bold is defined and set to true, false otherwise
*/
public boolean isBold(){
public boolean isBold() {
return Boolean.TRUE.equals(bold);
}
/**
* Returns true if italic is defined and set to true, false otherwise
* @return true if italic is defined and set to true, false otherwise
*
* @return true if italic is defined and set to true, false otherwise
*/
public boolean isItalic(){
public boolean isItalic() {
return Boolean.TRUE.equals(italic);
}
/**
* Returns true if underlined is defined and set to true, false otherwise
* @return true if underlined is defined and set to true, false otherwise
*
* @return true if underlined is defined and set to true, false otherwise
*/
public boolean isUnderlined(){
public boolean isUnderlined() {
return Boolean.TRUE.equals(underlined);
}
/**
* Returns true if strikethrough is defined and set to true, false otherwise
* @return true if strikethrough is defined and set to true, false otherwise
*
* @return true if strikethrough is defined and set to true, false otherwise
*/
public boolean isStrikethrough(){
public boolean isStrikethrough() {
return Boolean.TRUE.equals(strikethrough);
}
/**
* Returns true if obfuscation is defined and set to true, false otherwise
* @return true if obfuscation is defined and set to true, false otherwise
*
* @return true if obfuscation is defined and set to true, false otherwise
*/
public boolean isObfuscated(){
public boolean isObfuscated() {
return Boolean.TRUE.equals(obfuscated);
}
public ChatClickable getClickEvent() {
return clickEvent;
}
public void setClickEvent(ChatClickable clickEvent) {
this.clickEvent = clickEvent;
}
public ChatHoverable getHoverEvent() {
return hoverEvent;
}
public void setHoverEvent(ChatHoverable hoverEvent) {
this.hoverEvent = hoverEvent;
}
/**
* Sets click action to OPEN_URL and url to given value
* @param url - url to open
*
* @param url
* url to open
*/
public void onClickOpenUrl(String url) {
Preconditions.checkNotNull(url, "url");
public void onClickOpenUrl(@NonNull String url) {
clickEvent = new ChatClickable(EnumClickAction.OPEN_URL, url);
}
/**
* Sets click action to RUN_COMMAND and command to given value
* @param command - command to perform, might be without / to send a chat message
*
* @param command
* command to perform, might be without / to send a chat message
*/
public void onClickRunCommand(String command) {
Preconditions.checkNotNull(command, "command");
public void onClickRunCommand(@NonNull String command) {
clickEvent = new ChatClickable(EnumClickAction.RUN_COMMAND, command);
}
/**
* Sets click action to SUGGEST_COMMAND and command to given value
* @param command - command to suggest
*
* @param command
* command to suggest
*/
public void onClickSuggestCommand(String command) {
Preconditions.checkNotNull(command, "command");
public void onClickSuggestCommand(@NonNull String command) {
clickEvent = new ChatClickable(EnumClickAction.SUGGEST_COMMAND, command);
}
/**
* Sets click action to CHANGE_PAGE and page id to given value
* @param newPage - id of new page
*
* @param newPage
* id of new page
*/
public void onClickChangePage(int newPage) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() < 8) throw new UnsupportedOperationException("change_page click action is not supported on <1.8");
@@ -186,73 +127,56 @@ public class ChatModifier {
/**
* Sets click action to COPY_TO_CLIPBOARD and text to provided value
* @param text - text to copy to clipboard on click
*
* @param text
* text to copy to clipboard on click
*/
public void onClickCopyToClipBoard(String text) {
Preconditions.checkNotNull(text, "text");
public void onClickCopyToClipBoard(@NonNull String text) {
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
}
public void onClick(EnumClickAction action, String value) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(value, "value");
public void onClick(@NonNull EnumClickAction action, @NonNull String value) {
clickEvent = new ChatClickable(action, value);
}
/**
* Sets hover action to SHOW_TEXT and text to given value
* @param text - text to show
*
* @param text
* text to show
*/
public void onHoverShowText(IChatBaseComponent text) {
Preconditions.checkNotNull(text, "text");
public void onHoverShowText(@NonNull IChatBaseComponent text) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_TEXT, text);
}
/**
* Sets hover action to SHOW_ITEM and item to given value
* @param serializedItem - item to show
*
* @param serializedItem
* item to show
*/
public void onHoverShowItem(String serializedItem) {
Preconditions.checkNotNull(serializedItem, "item");
public void onHoverShowItem(@NonNull String serializedItem) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ITEM, new IChatBaseComponent(serializedItem));
}
/**
* Sets hover action to SHOW_ENTITY and entity data to given values
* @param type - entity type
* @param id - entity uuid
* @param name - entity custom name
*
* @param type
* entity type
* @param id
* entity uuid
* @param name
* entity custom name
*/
public void onHoverShowEntity(String type, UUID id, String name) {
Preconditions.checkNotNull(type, "type");
Preconditions.checkNotNull(id, "id");
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:%s,id:%s,name:%s}", type, id, name)));
} else {
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
}
public void onHoverShowEntity(@NonNull String type, @NonNull UUID id, String name) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
}
public void onHover(EnumHoverAction action, IChatBaseComponent value) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(value, "value");
public void onHover(@NonNull EnumHoverAction action, @NonNull IChatBaseComponent value) {
hoverEvent = new ChatHoverable(action, value);
}
public String getFont() {
return font;
}
public void setFont(String font) {
this.font = font;
}
@SuppressWarnings("unchecked")
public JSONObject serialize() {
JSONObject json = new JSONObject();
@@ -272,7 +196,7 @@ public class ChatModifier {
JSONObject hover = new JSONObject();
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hover.put("contents", hoverEvent.getValue());
hover.put(hoverEvent.getAction().getPreferredKey(), hoverEvent.getValue());
} else {
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
@@ -292,12 +216,4 @@ public class ChatModifier {
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
return builder.toString();
}
public void setTargetVersion(ProtocolVersion targetVersion) {
this.targetVersion = targetVersion;
}
public ProtocolVersion getTargetVersion() {
return targetVersion;
}
}
@@ -1,8 +1,10 @@
package me.neznamy.tab.api.chat;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
import org.jetbrains.annotations.NotNull;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
@@ -23,10 +25,11 @@ import java.util.List;
* will act like a normal chat component.
*/
@SuppressWarnings("unchecked")
@RequiredArgsConstructor
public class DeserializedChatComponent extends IChatBaseComponent {
/** The original serialized component string */
private final String json;
@NonNull private final String json;
/**
* Flag tracking whether this component was fully deserialized
@@ -41,17 +44,6 @@ public class DeserializedChatComponent extends IChatBaseComponent {
*/
private boolean modified;
/**
* Constructs new instance with given parameter
*
* @param json
* serialized component as string
*/
public DeserializedChatComponent(String json) {
Preconditions.checkNotNull(json, "json");
this.json = json;
}
@Override
public String toString() {
if (modified) return super.toString();
@@ -65,7 +57,7 @@ public class DeserializedChatComponent extends IChatBaseComponent {
}
@Override
public List<IChatBaseComponent> getExtra(){
public List<IChatBaseComponent> getExtra() {
if (!deserialized) deserialize();
return super.getExtra();
}
@@ -77,27 +69,27 @@ public class DeserializedChatComponent extends IChatBaseComponent {
}
@Override
public ChatModifier getModifier() {
public @NotNull ChatModifier getModifier() {
if (!deserialized) deserialize();
return super.getModifier();
}
@Override
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
if (!deserialized) deserialize();
modified = true;
return super.setExtra(components);
}
@Override
public void addExtra(IChatBaseComponent child) {
public void addExtra(@NotNull IChatBaseComponent child) {
if (!deserialized) deserialize();
modified = true;
super.addExtra(child);
}
@Override
public void setModifier(ChatModifier modifier) {
public void setModifier(@NotNull ChatModifier modifier) {
if (!deserialized) deserialize();
modified = true;
super.setModifier(modifier);
@@ -156,15 +148,13 @@ public class DeserializedChatComponent extends IChatBaseComponent {
/**
* Returns boolean value of requested key from map
*
* @param jsonObject
* map to get value from
* @param key
* name of key
* @return value from json object or null if not present
* @param jsonObject
* map to get value from
* @param key
* name of key
* @return value from json object or null if not present
*/
private static Boolean getBoolean(JSONObject jsonObject, String key) {
Preconditions.checkNotNull(jsonObject, "json object");
Preconditions.checkNotNull(key, "key");
private static Boolean getBoolean(@NonNull JSONObject jsonObject, @NonNull String key) {
String value = String.valueOf(jsonObject.getOrDefault(key, null));
return "null".equals(value) ? null : Boolean.parseBoolean(value);
}
@@ -1,5 +1,6 @@
package me.neznamy.tab.api.chat;
import lombok.Getter;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
/**
@@ -40,33 +41,34 @@ public enum EnumChatFormat {
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
/** Character representing the color or magic code */
private final char character;
@Getter private final char character;
/** Red value of this constant, 0 for magic codes */
private final short red;
@Getter private final short red;
/** Green value of this constant, 0 for magic codes */
private final short green;
@Getter private final short green;
/** Blue value of this constant, 0 for magic codes */
private final short blue;
@Getter private final short blue;
/** Color as 6-digit hex code, null for magic codes */
private final String hexCode;
@Getter private final String hexCode;
/** Color symbol followed by constant's character */
private final String chatFormat;
@Getter private final String format;
/**
* Constructs new color instance with given character and hex code
* @param character
* character which the color goes by
* @param hexCode
* 6-digit hex code of the color
*
* @param character
* character which the color goes by
* @param hexCode
* 6-digit hex code of the color
*/
EnumChatFormat(char character, String hexCode) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
this.format = String.valueOf(COLOR_CHAR) + character;
this.hexCode = hexCode;
int hexColor = Integer.parseInt(hexCode, 16);
red = (short) ((hexColor >> 16) & 0xFF);
@@ -76,47 +78,25 @@ public enum EnumChatFormat {
/**
* Constructs new magic code instance with given character
* @param character
* character representing the magic code
*
* @param character
* character representing the magic code
*/
EnumChatFormat(char character) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
this.format = String.valueOf(COLOR_CHAR) + character;
red = 0;
green = 0;
blue = 0;
hexCode = null;
}
/**
* Returns red value of this color code
* @return red value
*/
public short getRed() {
return red;
}
/**
* Returns green value of this color code
* @return green value
*/
public short getGreen() {
return green;
}
/**
* Returns blue value of this color code
* @return blue value
*/
public short getBlue() {
return blue;
}
/**
* Returns enum value based on provided character or null if character is not valid
* @param c
* color code character (0-9, a-f, k-o, r)
* @return instance from the character or null if character is not valid
*
* @param c
* color code character (0-9, a-f, k-o, r)
* @return instance from the character or null if character is not valid
*/
public static EnumChatFormat getByChar(char c) {
for (EnumChatFormat format : VALUES) {
@@ -128,9 +108,10 @@ public enum EnumChatFormat {
/**
* Returns enum value of last colors used in given string.
* If it's null, empty or does not contain color codes, WHITE is returned.
* @param string
* string to check last colors of
* @return last used color code in given string or WHITE if nothing is found
*
* @param string
* string to check last colors of
* @return last used color code in given string or WHITE if nothing is found
*/
public static EnumChatFormat lastColorsOf(String string) {
if (string == null || string.length() == 0) return EnumChatFormat.WHITE;
@@ -145,41 +126,18 @@ public enum EnumChatFormat {
return EnumChatFormat.WHITE;
}
/**
* Returns color char followed by color's character
* @return color char followed by color's character
*/
public String getFormat() {
return chatFormat;
}
/**
* Returns character representing this color
* @return character representing this color
*/
public char getCharacter() {
return character;
}
/**
* Returns hex code of this format, null if this is a magic code
* @return hex code of this format
*/
public String getHexCode() {
return hexCode;
}
/**
* Returns enum value with exact red, green and blue values or null if no match was found
* @param red
* exact red value
* @param green
* exact green value
* @param blue
* exact blue value
* @return enum value or null if no such combination exists
*
* @param red
* exact red value
* @param green
* exact green value
* @param blue
* exact blue value
* @return enum value or null if no such combination exists
*/
public static EnumChatFormat fromRGBExact(int red, int green, int blue){
public static EnumChatFormat fromRGBExact(int red, int green, int blue) {
for (EnumChatFormat format : VALUES) {
if (format.red == red && format.green == green && format.blue == blue) return format;
}
@@ -189,16 +147,17 @@ public enum EnumChatFormat {
/**
* Color translation method taken from bukkit, which converts '&amp;' symbol into
* the actual color character if followed by a valid color character.
* @param textToTranslate
* text to replace color symbol in
* @return colorized string from provided text
*
* @param textToTranslate
* text to replace color symbol in
* @return colorized string from provided text
*/
public static String color(String textToTranslate){
public static String color(String textToTranslate) {
if (textToTranslate == null) return null;
if (!textToTranslate.contains("&")) return textToTranslate;
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; i++) {
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx".indexOf(b[(i + 1)]) > -1)){
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx".indexOf(b[(i + 1)]) > -1)) {
b[i] = COLOR_CHAR;
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
}
@@ -208,9 +167,10 @@ public enum EnumChatFormat {
/**
* Turns back the color symbol into '&amp;' symbol in provided text.
* @param text
* text to revert colors in
* @return reverted text
*
* @param text
* text to revert colors in
* @return reverted text
*/
public static String decolor(String text) {
if (text == null) return null;
@@ -220,17 +180,18 @@ public enum EnumChatFormat {
/**
* Code taken from bukkit, which returns last color codes used in provided text.
* @param input
* text to get last colors from
* @return last colors used in provided text or empty string if nothing was found
*
* @param input
* text to get last colors from
* @return last colors used in provided text or empty string if nothing was found
*/
public static String getLastColors(String input) {
if (input == null) return "";
StringBuilder result = new StringBuilder();
int length = input.length();
for (int index = length - 1; index > -1; index--){
for (int index = length - 1; index > -1; index--) {
char section = input.charAt(index);
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)){
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)) {
char c = input.charAt(index + 1);
if ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".contains(String.valueOf(c))) {
result.insert(0, COLOR_CHAR);
@@ -1,9 +1,13 @@
package me.neznamy.tab.api.chat;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.api.util.ComponentCache;
import org.json.simple.JSONObject;
import java.util.*;
@@ -12,38 +16,37 @@ import java.util.*;
* A class representing the n.m.s.IChatBaseComponent class to make work with it much easier
*/
@SuppressWarnings("unchecked")
@NoArgsConstructor
public class IChatBaseComponent {
/**
* Component cache maps to avoid large memory allocations as well as
* higher CPU usage when using animations which send the same text on repeat.
*/
private static final Map<String, IChatBaseComponent> componentCache = new HashMap<>();
private static final Map<IChatBaseComponent, String> serializeCacheModern = new HashMap<>();
private static final Map<IChatBaseComponent, String> serializeCacheLegacy = new HashMap<>();
private static final ComponentCache<String, IChatBaseComponent> stringCache = new ComponentCache<>(10000, (text, clientVersion) -> {
return text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x") || text.contains("<") ?
IChatBaseComponent.fromColoredText(text) : //contains RGB colors
new IChatBaseComponent(text); //no RGB
});
private static final ComponentCache<IChatBaseComponent, String> serializeCache = new ComponentCache<>(10000,
(component, clientVersion) -> component.toString());
/** Text of the component */
private String text;
@Getter @Setter private String text;
/** Chat modifier containing color, magic codes, hover and click event */
private ChatModifier modifier = new ChatModifier();
@Getter @Setter @NonNull private ChatModifier modifier = new ChatModifier();
/** Extra components used in "extra" field */
private List<IChatBaseComponent> extra;
/**
* Constructs a new empty component
*/
public IChatBaseComponent() {
}
/**
* Constructs a new component which is a clone of provided component
* @param component
* component to clone
*
* @param component
* component to clone
*/
public IChatBaseComponent(IChatBaseComponent component) {
Preconditions.checkNotNull(component, "component");
public IChatBaseComponent(@NonNull IChatBaseComponent component) {
this.text = component.text;
this.modifier = new ChatModifier(component.modifier);
for (IChatBaseComponent child : component.getExtra()) {
@@ -53,8 +56,9 @@ public class IChatBaseComponent {
/**
* Constructs new instance with given text
* @param text
* text to display
*
* @param text
* text to display
*/
public IChatBaseComponent(String text) {
this.text = text;
@@ -62,22 +66,24 @@ public class IChatBaseComponent {
/**
* Returns list of extra components. If no extra components are defined, returns empty list.
* @return list of extra components
*
* @return list of extra components
*/
public List<IChatBaseComponent> getExtra(){
public List<IChatBaseComponent> getExtra() {
if (extra == null) return Collections.emptyList();
return extra;
}
/**
* Sets full list of extra components to given list. Does not allow empty list.
* @param components
* components to use as extra
* @return self
* @throws IllegalArgumentException
* if {@code components} is an empty list
*
* @param components
* components to use as extra
* @return self
* @throws IllegalArgumentException
* if {@code components} is an empty list
*/
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
this.extra = components;
return this;
@@ -85,57 +91,21 @@ public class IChatBaseComponent {
/**
* Appends provided component as extra component
* @param child
* component to append
*
* @param child
* component to append
*/
public void addExtra(IChatBaseComponent child) {
Preconditions.checkNotNull(child, "extra");
public void addExtra(@NonNull IChatBaseComponent child) {
if (extra == null) extra = new ArrayList<>();
extra.add(child);
}
/**
* Returns text of this component
* @return text of this component
*/
public String getText() {
return text;
}
/**
* Returns chat modifier of this component
* @return chat modifier of this component
*/
public ChatModifier getModifier() {
return modifier;
}
/**
* Sets modifier to provided value
* @param modifier
* modifier to set value to
* @throws IllegalArgumentException
* if {@code modifier} is null
*/
public void setModifier(ChatModifier modifier) {
Preconditions.checkNotNull(modifier, "modifier");
this.modifier = modifier;
}
/**
* Changes text of this component
* @param text
* text to show
*/
public void setText(String text) {
this.text = text;
}
/**
* Deserializes string and returns created component. If provided string is null, returns null.
* @param json
* serialized string
* @return Deserialized component or null if input is null
*
* @param json
* serialized string
* @return Deserialized component or null if input is null
*/
public static IChatBaseComponent deserialize(String json) {
if (json == null) return null;
@@ -146,8 +116,9 @@ public class IChatBaseComponent {
* Converts the component to a string representing the serialized component.
* This method is only used internally by json library since it's missing
* protocol version field used by the method.
* @return serialized component in string form
* @see #toString(ProtocolVersion)
*
* @return serialized component in string form
* @see #toString(ProtocolVersion)
*/
@Override
public String toString() {
@@ -162,9 +133,10 @@ public class IChatBaseComponent {
/**
* Serializes this component with colors based on client version.
* If client version is &lt;1.16, HEX colors will be converted to legacy colors.
* @param clientVersion
* client version to adapt component for
* @return serialized string
*
* @param clientVersion
* client version to adapt component for
* @return serialized string
*/
public String toString(ProtocolVersion clientVersion) {
return toString(clientVersion, false);
@@ -173,11 +145,12 @@ public class IChatBaseComponent {
/**
* Serializes this component with colors based on client version.
* If client version is &lt;1.16, HEX colors will be converted to legacy colors.
* @param clientVersion
* client version to adapt component for
* @param sendTranslatableIfEmpty
* whether empty translatable should be sent if text is empty or not
* @return serialized string
*
* @param clientVersion
* client version to adapt component for
* @param sendTranslatableIfEmpty
* whether empty translatable should be sent if text is empty or not
* @return serialized string
*/
public String toString(ProtocolVersion clientVersion, boolean sendTranslatableIfEmpty) {
if (extra == null) {
@@ -194,36 +167,24 @@ public class IChatBaseComponent {
for (IChatBaseComponent child : getExtra()) {
child.modifier.setTargetVersion(clientVersion);
}
String string;
if (clientVersion.getMinorVersion() >= 16) {
if (serializeCacheModern.containsKey(this)) return serializeCacheModern.get(this);
string = toString();
if (serializeCacheModern.size() > 10000) serializeCacheModern.clear();
serializeCacheModern.put(this, string);
} else {
if (serializeCacheLegacy.containsKey(this)) return serializeCacheLegacy.get(this);
string = toString();
if (serializeCacheLegacy.size() > 10000) serializeCacheLegacy.clear();
serializeCacheLegacy.put(this, string);
}
return string;
return serializeCache.get(this, clientVersion);
}
/**
* Returns organized component from colored text
* @param originalText
* text to convert
* @return organized component from colored text
*
* @param originalText
* text to convert
* @return organized component from colored text
*/
public static IChatBaseComponent fromColoredText(String originalText){
Preconditions.checkNotNull(originalText, "text");
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), false);
public static IChatBaseComponent fromColoredText(@NonNull String originalText) {
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
List<IChatBaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
IChatBaseComponent component = new IChatBaseComponent();
for (int i = 0; i < text.length(); i++){
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == EnumChatFormat.COLOR_CHAR){
if (c == EnumChatFormat.COLOR_CHAR) {
i++;
if (i >= text.length()) {
break;
@@ -233,7 +194,7 @@ public class IChatBaseComponent {
c = (char)(c + ' ');
}
EnumChatFormat format = EnumChatFormat.getByChar(c);
if (format != null){
if (format != null) {
if (builder.length() > 0) {
component.setText(builder.toString());
components.add(component);
@@ -241,7 +202,7 @@ public class IChatBaseComponent {
component.text = null;
builder = new StringBuilder();
}
switch (format){
switch (format) {
case BOLD:
component.modifier.setBold(true);
break;
@@ -267,7 +228,7 @@ public class IChatBaseComponent {
break;
}
}
} else if (c == '#' && text.length() > i+6){
} else if (c == '#' && text.length() > i+6) {
String hex = text.substring(i+1, i+7);
if (RGBUtils.getInstance().isHexCode(hex)) {
TextColor color;
@@ -278,7 +239,7 @@ public class IChatBaseComponent {
color = new TextColor(hex);
i += 6;
}
if (builder.length() > 0){
if (builder.length() > 0) {
component.setText(builder.toString());
components.add(component);
builder = new StringBuilder();
@@ -299,11 +260,12 @@ public class IChatBaseComponent {
/**
* Returns true if text contains legacy color request at defined RGB index start
* @param text
* text to check
* @param i
* current index start
* @return true if legacy color is defined, false if not
*
* @param text
* text to check
* @param i
* current index start
* @return true if legacy color is defined, false if not
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
@@ -312,7 +274,8 @@ public class IChatBaseComponent {
/**
* Converts this component into a simple text with legacy colors (the closest match if color is set to RGB)
* @return The simple text format
*
* @return The simple text format
*/
public String toLegacyText() {
StringBuilder builder = new StringBuilder();
@@ -323,11 +286,12 @@ public class IChatBaseComponent {
/**
* Appends text to string builder, might also add color and magic codes if they are different
* from previous component in chain.
* @param builder
* builder to append text to
* @param previousFormatting
* colors and magic codes in previous component
* @return wew formatting, might be identical to previous one
*
* @param builder
* builder to append text to
* @param previousFormatting
* colors and magic codes in previous component
* @return new formatting, might be identical to previous one
*/
private String append(StringBuilder builder, String previousFormatting) {
String formatting = previousFormatting;
@@ -347,7 +311,8 @@ public class IChatBaseComponent {
/**
* Returns colors and magic codes of this component
* @return used colors and magic codes
*
* @return used colors and magic codes
*/
private String getFormatting() {
StringBuilder builder = new StringBuilder();
@@ -365,7 +330,8 @@ public class IChatBaseComponent {
/**
* Returns raw text without colors, only works correctly when component is organized
* @return raw text in this component and all child components
*
* @return raw text in this component and all child components
*/
public String toRawText() {
StringBuilder builder = new StringBuilder();
@@ -378,7 +344,8 @@ public class IChatBaseComponent {
/**
* Converts the component into flat text with used colors (including rgb) and magic codes
* @return converted text
*
* @return converted text
*/
public String toFlatText() {
StringBuilder builder = new StringBuilder();
@@ -395,23 +362,12 @@ public class IChatBaseComponent {
* Returns the most optimized component based on text. Returns null if text is null,
* organized component if RGB colors are used or simple component with only text field
* containing the whole text when no RGB colors are used
* @param text
* text to create component from
* @return The most performance-optimized component based on text
*
* @param text
* text to create component from
* @return The most performance-optimized component based on text
*/
public static IChatBaseComponent optimizedComponent(String text){
if (text == null) return null;
if (componentCache.containsKey(text)) return componentCache.get(text);
IChatBaseComponent component;
if (text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x")){
//contains RGB colors
component = IChatBaseComponent.fromColoredText(text);
} else {
//no RGB
component = new IChatBaseComponent(text);
}
if (componentCache.size() > 10000) componentCache.clear();
componentCache.put(text, component);
return component;
public static IChatBaseComponent optimizedComponent(String text) {
return stringCache.get(text, null);
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.api.chat;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
@@ -24,18 +26,17 @@ public class TextColor {
* Boolean value whether the legacy color was forced with constructor or should be
* automatically assigned as closest color.
* This value is used in gradients when converting text for legacy players. */
private boolean legacyColorForced;
@Getter private boolean legacyColorForced;
/**
* Constructs new instance as a clone of the provided color.
*
* @param color
* color to create a clone of
* @throws IllegalArgumentException
* if color is {@code null}
* @param color
* color to create a clone of
* @throws IllegalArgumentException
* if color is {@code null}
*/
public TextColor(TextColor color) {
Preconditions.checkNotNull(color, "color");
public TextColor(@NonNull TextColor color) {
rgb = color.rgb;
legacyColor = color.legacyColor;
hexCode = color.hexCode;
@@ -45,29 +46,26 @@ public class TextColor {
/**
* Constructs new instance from provided 6-digit hex code string
*
* @param hexCode
* a 6-digit combination of hex numbers as a string
* @throws IllegalArgumentException
* if hexCode is {@code null}
* @param hexCode
* a 6-digit combination of hex numbers as a string
* @throws IllegalArgumentException
* if hexCode is {@code null}
*/
public TextColor(String hexCode) {
Preconditions.checkNotNull(hexCode, "hex code");
public TextColor(@NonNull String hexCode) {
this.hexCode = hexCode;
}
/**
* Constructs new instance from provided 6-digit hex code and forced legacy color
*
* @param hexCode
* 6-digit combination of hex numbers as a string
* @param legacyColor
* color to use for legacy clients instead of using the closest legacy color
* @throws IllegalArgumentException
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
* @param hexCode
* 6-digit combination of hex numbers as a string
* @param legacyColor
* color to use for legacy clients instead of using the closest legacy color
* @throws IllegalArgumentException
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
*/
public TextColor(String hexCode, EnumChatFormat legacyColor) {
Preconditions.checkNotNull(hexCode, "hex code");
Preconditions.checkNotNull(legacyColor, "legacy color");
public TextColor(@NonNull String hexCode, EnumChatFormat legacyColor) {
this.hexCode = hexCode;
this.legacyColorForced = true;
this.legacyColor = legacyColor;
@@ -76,13 +74,12 @@ public class TextColor {
/**
* Constructs new instance from provided legacy color
*
* @param legacyColor
* legacy color to construct the instance from
* @throws IllegalArgumentException
* if {@code legacyColor} is {@code null}
* @param legacyColor
* legacy color to construct the instance from
* @throws IllegalArgumentException
* if {@code legacyColor} is {@code null}
*/
public TextColor(EnumChatFormat legacyColor) {
Preconditions.checkNotNull(legacyColor, "legacy color");
public TextColor(@NonNull EnumChatFormat legacyColor) {
this.rgb = (legacyColor.getRed() << 16) + (legacyColor.getGreen() << 8) + legacyColor.getBlue();
this.hexCode = legacyColor.getHexCode();
}
@@ -90,14 +87,14 @@ public class TextColor {
/**
* Constructs new instance with red, green and blue values
*
* @param red
* red value
* @param green
* green value
* @param blue
* blue value
* @throws IllegalArgumentException
* if {@code red}, {@code green} or {@code blue} is out of range ({@code 0-255})
* @param red
* red value
* @param green
* green value
* @param blue
* blue value
* @throws IllegalArgumentException
* if {@code red}, {@code green} or {@code blue} is out of range ({@code 0-255})
*/
public TextColor(int red, int green, int blue) {
Preconditions.checkRange(red, 0, 255, "red");
@@ -130,7 +127,8 @@ public class TextColor {
/**
* Returns {@code red} value
* @return red value
*
* @return red value
*/
public int getRed() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
@@ -139,7 +137,8 @@ public class TextColor {
/**
* Returns {@code green} value
* @return green value
*
* @return green value
*/
public int getGreen() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
@@ -148,7 +147,8 @@ public class TextColor {
/**
* Returns {@code blue} value
* @return blue value
*
* @return blue value
*/
public int getBlue() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
@@ -159,7 +159,8 @@ public class TextColor {
* Returns the closest legacy color of this color object.
* If the color was defined in constructor, it's returned.
* Otherwise, the closest color is calculated the then returned.
* @return closest legacy color
*
* @return closest legacy color
*/
public EnumChatFormat getLegacyColor() {
if (legacyColor == null) legacyColor = loadClosestColor();
@@ -168,7 +169,8 @@ public class TextColor {
/**
* Returns the rgb combination as a 6-digit hex code string
* @return the rgb combination as a 6-digit hex code string
*
* @return the rgb combination as a 6-digit hex code string
*/
public String getHexCode() {
if (hexCode == null) hexCode = String.format("%06X", rgb);
@@ -178,7 +180,8 @@ public class TextColor {
/**
* Converts the color into a valid color value used in color field in chat component.
* That is either 6-digit hex code prefixed with '#', or lowercase legacy color.
* @return the color serialized for use in chat component
*
* @return the color serialized for use in chat component
*/
public String toString() {
EnumChatFormat legacyEquivalent = EnumChatFormat.fromRGBExact(getRed(), getGreen(), getBlue());
@@ -189,22 +192,15 @@ public class TextColor {
return "#" + getHexCode();
}
/**
* Returns true if legacy color was forced with a constructor, false if not
* @return true if forced, false if not
*/
public boolean isLegacyColorForced() {
return legacyColorForced;
}
/**
* Reads the string as it appears in chat component and turns it into the color object.
* If the entered string is null, returns null.
* If it's prefixed with '#', it's considered as a hex code.
* Otherwise, it is considered being a lowercase legacy color.
* @param string
* string from color field in chat component
* @return An instance from specified string or null if string is null
*
* @param string
* string from color field in chat component
* @return An instance from specified string or null if string is null
*/
public static TextColor fromString(String string) {
if (string == null) return null;
@@ -0,0 +1,62 @@
package me.neznamy.tab.api.chat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A class extending IChatBaseComponent that holds the original value
* to avoid deserializing it, which is an unnecessary operation that
* slows down the performance, as well as makes code more complicated
* and open to problems.
*/
@AllArgsConstructor
public class WrappedChatComponent extends IChatBaseComponent {
/** Original NMS component */
@Getter private final Object originalComponent;
@Override
public String toString() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public String toString(ProtocolVersion clientVersion) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public List<IChatBaseComponent> getExtra() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public String getText() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public @NotNull ChatModifier getModifier() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public void addExtra(@NonNull IChatBaseComponent child) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public void setModifier(@NotNull ChatModifier modifier) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
}
@@ -1,20 +1,20 @@
package me.neznamy.tab.api.chat.rgb;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
import me.neznamy.tab.api.chat.rgb.format.BukkitFormat;
import me.neznamy.tab.api.chat.rgb.format.CMIFormat;
import me.neznamy.tab.api.chat.rgb.format.HtmlFormat;
import me.neznamy.tab.api.chat.rgb.format.RGBFormatter;
import me.neznamy.tab.api.chat.rgb.format.UnnamedFormat1;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.format.*;
import me.neznamy.tab.api.chat.rgb.gradient.CMIGradient;
import me.neznamy.tab.api.chat.rgb.gradient.CommonGradient;
import me.neznamy.tab.api.chat.rgb.gradient.GradientPattern;
import me.neznamy.tab.api.chat.rgb.gradient.KyoriGradient;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.api.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
@@ -22,7 +22,7 @@ import me.neznamy.tab.api.util.Preconditions;
public class RGBUtils {
/** Instance of the class */
private static final RGBUtils instance = new RGBUtils();
@Getter private static final RGBUtils instance = new RGBUtils();
/** Registered RGB formatters */
private final RGBFormatter[] formats;
@@ -40,12 +40,17 @@ public class RGBUtils {
* Constructs new instance and loads all RGB patterns and gradients
*/
public RGBUtils() {
formats = new RGBFormatter[] {
new BukkitFormat(),
new CMIFormat(),
new UnnamedFormat1(),
new HtmlFormat()
};
List<RGBFormatter> list = new ArrayList<>();
if (ReflectionUtils.classExists("net.kyori.adventure.text.minimessage.MiniMessage")) {
list.add(new MiniMessageFormat());
}
list.add(new BukkitFormat());
list.add(new CMIFormat());
list.add(new UnnamedFormat1());
list.add(new HtmlFormat());
list.add(new KyoriFormat());
formats = list.toArray(new RGBFormatter[0]);
gradients = new GradientPattern[] {
//{#RRGGBB>}text{#RRGGBB<}
new CMIGradient(),
@@ -61,30 +66,17 @@ public class RGBUtils {
};
}
/**
* Returns instance of this class
* @return instance
*/
public static RGBUtils getInstance() {
return instance;
}
/**
* Applies all RGB formats and gradients to text and returns it.
* This method is called on every property initialization to convert formats
* once instead of on every refresh, if possible. Because of that, parameter
* {@code ignorePlaceholders} exists to not break raw placeholder identifiers
* @param text
* original text
* @param ignorePlaceholders
* whether placeholders should be ignored or not
* @return text where everything is converted to #RRGGBB
*
* @param text
* original text
* @return text where everything is converted to #RRGGBB
*/
public String applyFormats(String text, boolean ignorePlaceholders) {
Preconditions.checkNotNull(text, "text");
public String applyFormats(@NonNull String text) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, ignorePlaceholders);
replaced = pattern.applyPattern(replaced, false);
}
for (RGBFormatter formatter : formats) {
replaced = formatter.reformat(replaced);
@@ -92,22 +84,39 @@ public class RGBUtils {
return replaced;
}
/**
* Applies all gradient formats to text and returns it. This only affects
* usage where no placeholder is used inside.
*
* @param text
* original text
* @return text where all gradients with static text are converted to #RRGGBB
*/
public String applyCleanGradients(@NonNull String text) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, true);
}
return replaced;
}
/**
* Converts TAB's RGB format (#RRGGBB) into bukkit one
* (&amp;x&amp;r&amp;r&amp;g&amp;g&amp;b&amp;b) for modern
* clients (1.16+), for legacy clients it will use the closest color.
* @param text
* text to convert
* @param rgbClient
* whether client accepts RGB or not
* @return converted text
*
* @param text
* text to convert
* @param rgbClient
* whether client accepts RGB or not
* @return converted text
*/
public String convertToBukkitFormat(String text, boolean rgbClient) {
if (text == null) return null;
if (!text.contains("#")) return text; //no rgb codes
if (rgbClient) {
//converting random formats to TAB one
String replaced = applyFormats(text, false);
String replaced = applyFormats(text);
for (Pattern p : new Pattern[]{tabPatternLegacy, tabPattern}) {
Matcher m = p.matcher(replaced);
while (m.find()) {
@@ -123,47 +132,28 @@ public class RGBUtils {
}
/**
* Converts all hex codes in given string to legacy codes
* @param text
* text to convert
* @return translated text
* Converts all hex codes in given string to legacy codes.
* Also removes redundant color codes caused by this operation
* to properly fit in limits.
*
* @param text
* text to convert
* @return translated text
*/
public String convertRGBtoLegacy(String text) {
if (text == null) return null;
if (!text.contains("#")) return EnumChatFormat.color(text);
String applied = applyFormats(text, false);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < applied.length(); i++){
char c = applied.charAt(i);
if (c == '#' && applied.length() > i+6) {
String hexCode = applied.substring(i+1, i+7);
if (isHexCode(hexCode)) {
if (containsLegacyCode(applied, i)) {
sb.append(new TextColor(hexCode, EnumChatFormat.getByChar(applied.charAt(i+8))).getLegacyColor().getFormat());
i += 8;
} else {
sb.append(new TextColor(hexCode).getLegacyColor().getFormat());
i += 6;
}
} else {
sb.append(c);
}
} else {
sb.append(c);
}
}
return sb.toString();
return IChatBaseComponent.fromColoredText(text).toLegacyText();
}
/**
* Returns true if entered string is a valid 6-digit combination of
* hexadecimal numbers, false if not
* @param string
* string to check
* @return {@code true} if valid, {@code false} if not
*
* @param string
* string to check
* @return {@code true} if valid, {@code false} if not
*/
public boolean isHexCode(String string) {
Preconditions.checkNotNull(string, "string");
public boolean isHexCode(@NonNull String string) {
if (string.length() != 6) return false;
for (int i=0; i<6; i++) {
char c = string.charAt(i);
@@ -174,11 +164,12 @@ public class RGBUtils {
/**
* Returns true if text contains legacy color request at defined RGB index start
* @param text
* text to check
* @param i
* current index start
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
*
* @param text
* text to check
* @param i
* current index start
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
@@ -4,7 +4,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for #<RRGGBB>
* Formatter for #&lt;RRGGBB&gt;
*/
public class HtmlFormat implements RGBFormatter {
@@ -0,0 +1,25 @@
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for &lt;#RRGGBB&gt;
*/
public class KyoriFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("<#[0-9a-fA-F]{6}>");
@Override
public String reformat(String text) {
if (!text.contains("<#")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -0,0 +1,20 @@
package me.neznamy.tab.api.chat.rgb.format;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
/**
* Call to MiniMessage API to reformat text to &amp;x&amp;R&amp;R&amp;G&amp;G&amp;B&amp;B
*/
public class MiniMessageFormat implements RGBFormatter {
@Override
public String reformat(String text) {
if (!text.contains("<")) return text;
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text));
} catch (Throwable ignored) {
return text;
}
}
}
@@ -7,8 +7,10 @@ public interface RGBFormatter {
/**
* Reformats RGB codes in provided text into #RRGGBB format
* @param text - text to format
* @return reformatted text
*
* @param text
* text to format
* @return reformatted text
*/
String reformat(String text);
}
@@ -3,9 +3,11 @@ package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
@AllArgsConstructor
public class CommonGradient implements GradientPattern {
private final Pattern pattern;
@@ -15,18 +17,7 @@ public class CommonGradient implements GradientPattern {
private final int startColorStart;
private final int messageStart;
private final int endColorStartSub;
public CommonGradient(Pattern pattern, Pattern legacyPattern, String containCheck, int legacyCharPosition,
int startColorStart, int messageStart, int endColorStartSub) {
this.pattern = pattern;
this.legacyPattern = legacyPattern;
this.containCheck = containCheck;
this.legacyCharPosition = legacyCharPosition;
this.startColorStart = startColorStart;
this.messageStart = messageStart;
this.endColorStartSub = endColorStartSub;
}
@Override
public String applyPattern(String text, boolean ignorePlaceholders) {
if (!text.contains(containCheck)) return text;
@@ -10,17 +10,23 @@ public interface GradientPattern {
/**
* Applies gradients in provided text and returns text using only #RRGGBB
* @param text - text to be reformatted
* @return reformatted text
*
* @param text
* text to be reformatted
* @return reformatted text
*/
String applyPattern(String text, boolean ignorePlaceholders);
/**
* Returns gradient text based on start color, text and end color
* @param start - start color
* @param text - text to be reformatted
* @param end - end color
* @return reformatted text
*
* @param start
* start color
* @param text
* text to be reformatted
* @param end
* end color
* @return reformatted text
*/
default String asGradient(TextColor start, String text, TextColor end) {
//lazy support for magic codes in gradients
@@ -7,8 +7,8 @@ import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
/**
* Gradient applier for &lt;gradient:#RRGGBB:#RRGGBB>Text&lt;/gradient>
* and &lt;gradient:#RRGGBB|L:#RRGGBB>Text&lt;/gradient>
* Gradient applier for &lt;gradient:#RRGGBB:#RRGGBB&gt;Text&lt;/gradient&gt;
* and &lt;gradient:#RRGGBB|L:#RRGGBB&gt;Text&lt;/gradient&gt;
*/
public class KyoriGradient implements GradientPattern {
@@ -1,14 +1,13 @@
package me.neznamy.tab.api.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.TabAPI;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -16,9 +15,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.util.Preconditions;
/**
* Abstract class for configuration file
*/
@@ -29,42 +25,32 @@ public abstract class ConfigurationFile {
protected List<String> header;
/** Configuration file content */
protected Map<String, Object> values;
@Getter @Setter protected Map<String, Object> values;
/** File to use */
protected final File file;
@Getter protected final File file;
/**
* Constructs new instance and attempts to load specified configuration file.
* If file does not exist, default file is copied from {@code source}.
*
* @param source
* Source to copy file from if it does not exist
* @param destination
* File destination to use
* @throws IllegalArgumentException
* if {@code destination} is null
* @throws IllegalStateException
* if file does not exist and source is null
* @throws IOException
* if I/O operation with the file unexpectedly fails
* @param source
* Source to copy file from if it does not exist
* @param destination
* File destination to use
* @throws IllegalArgumentException
* if {@code destination} is null
* @throws IllegalStateException
* if file does not exist and source is null
* @throws IOException
* if I/O operation with the file unexpectedly fails
*/
protected ConfigurationFile(InputStream source, File destination) throws IOException {
Preconditions.checkNotNull(destination, "destination");
protected ConfigurationFile(InputStream source, @NonNull File destination) throws IOException {
this.file = destination;
if (file.getParentFile() != null && !file.getParentFile().exists()) Files.createDirectories(file.getParentFile().toPath());
if (!file.exists() && source == null) throw new IllegalStateException("File does not exist and source is null");
if (file.createNewFile()) {
// Files.copy(source, file.toPath());
//avoiding MalformedInputException thrown on fabric due to file not having UTF8 encoding by default
//also automatically using the encoding so users don't have to worry about it anymore
try (BufferedReader reader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))){
String line;
while ((line = reader.readLine()) != null) {
writer.write(line + "\n");
}
}
if (!file.exists()) {
if (source == null) throw new IllegalStateException("File does not exist and source is null");
Files.copy(source, file.toPath());
}
detectHeader();
}
@@ -76,34 +62,23 @@ public abstract class ConfigurationFile {
/**
* Returns simple name of the file
* @return simple name of the file
*
* @return simple name of the file
*/
public String getName() {
return file.getName();
}
/**
* Returns the root value map
* @return the root value map
*/
public Map<String, Object> getValues(){
return values;
}
public void setValues(Map<String, Object> values) {
this.values = values;
}
/**
* Gets config option with specified path. If the option is not present and
* {@code defaultValue} is not {@code null}, value is inserted, {@link #save()}
* called and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file
*/
public Object getObject(String path, Object defaultValue) {
Object value = values;
@@ -126,9 +101,9 @@ public abstract class ConfigurationFile {
* Returns config option with specified path. If option is not present,
* {@code null} is returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from configuration file or null if not present
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from configuration file or null if not present
*/
public Object getObject(String path) {
return getObject(path, null);
@@ -149,11 +124,11 @@ public abstract class ConfigurationFile {
* Returns value from map without case sensitivity of the key. Returns
* {@code null} if no such key is found.
*
* @param map
* map to get value from
* @param key
* case-insensitive key name
* @return map value from case-insensitive key
* @param map
* map to get value from
* @param key
* case-insensitive key name
* @return map value from case-insensitive key
*/
private Object getIgnoreCase(Map<Object, Object> map, String key) {
for (Entry<Object, Object> entry : map.entrySet()) {
@@ -166,9 +141,9 @@ public abstract class ConfigurationFile {
* Returns config option with specified path as {@code String}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public String getString(String path) {
return getString(path, null);
@@ -179,11 +154,11 @@ public abstract class ConfigurationFile {
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code String}
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code String}
*/
public String getString(String path, String defaultValue) {
Object value = getObject(path, defaultValue);
@@ -195,9 +170,9 @@ public abstract class ConfigurationFile {
* Returns config option with specified path as {@code List<String>}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public List<String> getStringList(String path) {
return getStringList(path, null);
@@ -208,11 +183,11 @@ public abstract class ConfigurationFile {
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path of the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code List<String>}
* @param path
* Path of the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code List<String>}
*/
public List<String> getStringList(String path, List<String> defaultValue) {
Object value = getObject(path, defaultValue);
@@ -222,7 +197,7 @@ public abstract class ConfigurationFile {
}
List<String> fixedList = new ArrayList<>();
for (Object key : (List<Object>)value) {
fixedList.add(key.toString());
fixedList.add(String.valueOf(key));
}
return fixedList;
}
@@ -231,9 +206,9 @@ public abstract class ConfigurationFile {
* Returns config option with specified path as {@code Integer}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public Integer getInt(String path) {
return getInt(path, null);
@@ -244,11 +219,11 @@ public abstract class ConfigurationFile {
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Integer}
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Integer}
*/
public Integer getInt(String path, Integer defaultValue) {
Object value = getObject(path, defaultValue);
@@ -265,11 +240,11 @@ public abstract class ConfigurationFile {
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Boolean}
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Boolean}
*/
public boolean getBoolean(String path, boolean defaultValue) {
Object value = getObject(path, defaultValue);
@@ -282,11 +257,11 @@ public abstract class ConfigurationFile {
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Double}
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Double}
*/
public Double getDouble(String path, double defaultValue) {
Object value = getObject(path, defaultValue);
@@ -302,9 +277,9 @@ public abstract class ConfigurationFile {
* Returns config option with specified path as {@code Map<K, V>}. If the option
* is not present or value is not a {@code Map}, new empty {@code Map} is returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from configuration file as {@code Map<K, V>}
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from configuration file as {@code Map<K, V>}
*/
public <K, V> Map<K, V> getConfigurationSection(String path) {
if (path == null || path.length() == 0) return (Map<K, V>) values;
@@ -320,9 +295,9 @@ public abstract class ConfigurationFile {
* Returns {@code true} if the file has option with specified path, {@code false}
* if not.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return {@code true} if present, {@code false} if not
* @param path
* Path to the option with sections separated with "{@code .}"
* @return {@code true} if present, {@code false} if not
*/
public boolean hasConfigOption(String path) {
return getObject(path) != null;
@@ -331,10 +306,10 @@ public abstract class ConfigurationFile {
/**
* Sets value to the specified path and saves the file to disk by calling {@link #save()}.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param value
* Value to save
* @param path
* Path to the option with sections separated with "{@code .}"
* @param value
* Value to save
*/
public void set(String path, Object value) {
set(values, path, value);
@@ -345,13 +320,13 @@ public abstract class ConfigurationFile {
* Sets value to the specified map with specified path and value. This is an internal method
* that correctly creates maps to separate sections using "{@code .}".
*
* @param map
* Map to insert value to
* @param path
* Path to the option with sections separated with "{@code .}"
* @param value
* Value to save
* @return the first argument to allow chaining
* @param map
* Map to insert value to
* @param path
* Path to the option with sections separated with "{@code .}"
* @param value
* Value to save
* @return the first argument to allow chaining
*/
private Map<String, Object> set(Map<String, Object> map, String path, Object value) {
if (path.contains(".")) {
@@ -375,11 +350,11 @@ public abstract class ConfigurationFile {
* Returns the real key name without case sensitivity from map. If not found, inserted key
* is returned.
*
* @param map
* Map to check keys of
* @param key
* Key to find
* @return The real key name
* @param map
* Map to check keys of
* @param key
* Key to find
* @return The real key name
*/
private String getRealKey(Map<?, ?> map, String key) {
for (Object mapKey : map.keySet()) {
@@ -391,8 +366,8 @@ public abstract class ConfigurationFile {
/**
* Detects header of a file (first lines of file starting with #).
*
* @throws IOException
* if I/O operation fails
* @throws IOException
* if I/O operation fails
*/
private void detectHeader() throws IOException {
header = new ArrayList<>();
@@ -409,8 +384,8 @@ public abstract class ConfigurationFile {
* Inserts header back into file. This is required after calling {@link #save()}, because
* it destroys the header.
*
* @throws IOException
* if I/O operation fails
* @throws IOException
* if I/O operation fails
*/
public void fixHeader() throws IOException {
if (header == null) return;
@@ -421,8 +396,4 @@ public abstract class ConfigurationFile {
Files.write(file.toPath(), content);
}
}
public File getFile() {
return file;
}
}
@@ -36,18 +36,18 @@ public class YamlConfigurationFile extends ConfigurationFile {
* Constructs new instance and attempts to load specified configuration file.
* If file does not exist, default file is copied from {@code source}.
*
* @param source
* Source to copy file from if it does not exist
* @param destination
* File destination to use
* @throws IllegalArgumentException
* if {@code destination} is null
* @throws IllegalStateException
* if file does not exist and source is null
* @throws YAMLException
* if file has invalid YAML syntax
* @throws IOException
* if I/O operation with the file unexpectedly fails
* @param source
* Source to copy file from if it does not exist
* @param destination
* File destination to use
* @throws IllegalArgumentException
* if {@code destination} is null
* @throws IllegalStateException
* if file does not exist and source is null
* @throws YAMLException
* if file has invalid YAML syntax
* @throws IOException
* if I/O operation with the file unexpectedly fails
*/
public YamlConfigurationFile(InputStream source, File destination) throws YAMLException, IOException {
super(source, destination);
@@ -11,14 +11,15 @@ public interface PlayerLoadEvent extends TabEvent {
/**
* Gets the player that was loaded.
*
* @return the player that was loaded
* @return the player that was loaded
*/
TabPlayer getPlayer();
/**
* Returns true if player was loaded because they joined,
* false if the player was loaded because tab reload command was executed
* @return true if player joined, false if plugin was reloaded
*
* @return true if player joined, false if plugin was reloaded
*/
boolean isJoin();
}
@@ -6,11 +6,9 @@ public interface Placeholder {
String getIdentifier();
boolean isTriggerMode();
void enableTriggerMode();
void enableTriggerMode(Runnable onActivation, Runnable onDisable);
void unload();
boolean isUsed();
}
@@ -27,20 +27,27 @@ public interface PlaceholderManager {
/**
* Detects placeholders in text using %% pattern and returns list of all detected identifiers
* @param text - text to detect placeholders in
* @return list of detected identifiers
*
* @param text
* text to detect placeholders in
* @return list of detected identifiers
*/
List<String> detectPlaceholders(String text);
/**
* Adds placeholder to list of used placeholders and assigns this feature as using it,
* which will then receive refresh() if values changes
* @param identifier - placeholder identifier
* @param feature - feature using the placeholder
*
* @param identifier
* placeholder identifier
* @param feature
* feature using the placeholder
*/
void addUsedPlaceholder(String identifier, TabFeature feature);
String findReplacement(String placeholder, String output);
List<String> getUsedPlaceholders();
Placeholder getPlaceholder(String identifier);
}
@@ -8,11 +8,21 @@ public interface PlayerPlaceholder extends Placeholder {
/**
* Calls the placeholder request function and returns the output.
* If the placeholder thrown an exception, it is logged in {@code placeholder-errors.log}
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
* file and "ERROR" is returned.
* @param p
* player to get placeholder value for
* @return value placeholder returned or "ERROR" if it threw an error
*
* @param p
* player to get placeholder value for
* @return value placeholder returned or "ERROR" if it threw an error
*/
Object request(TabPlayer p);
/**
* Returns last known value for given player.
*
* @param player
* player to get last value for
* @return last known value for entered player
*/
String getLastValue(TabPlayer player);
}
@@ -6,5 +6,28 @@ public interface RelationalPlaceholder extends Placeholder {
void updateValue(TabPlayer viewer, TabPlayer target, Object value);
/**
* Calls the placeholder request function and returns the output.
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
* file and "ERROR" is returned.
*
* @param viewer
* player looking at output of the placeholder
* @param target
* player the placeholder is displayed on
* @return value placeholder returned or "ERROR" if it threw an error
*/
Object request(TabPlayer viewer, TabPlayer target);
/**
* Returns last known value for given players. First player is viewer,
* second player is target.
*
* @param viewer
* viewer of the placeholder
* @param target
* target who is the text displayed on
* @return last known value for entered player duo
*/
String getLastValue(TabPlayer viewer, TabPlayer target);
}
@@ -4,5 +4,19 @@ public interface ServerPlaceholder extends Placeholder {
void updateValue(Object value);
/**
* Calls the placeholder request function and returns the output.
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
* file and "ERROR" is returned.
*
* @return value placeholder returned or "ERROR" if it threw an error
*/
Object request();
/**
* Returns last known value of the placeholder.
*
* @return last known value
*/
String getLastValue();
}
@@ -1,14 +1,14 @@
package me.neznamy.tab.api.protocol;
import java.util.HashMap;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.BiFunctionWithException;
import me.neznamy.tab.api.util.Preconditions;
import java.util.HashMap;
import java.util.Map;
/**
* A class for packet building, methods are overridden in
@@ -40,30 +40,28 @@ public class PacketBuilder {
* Converts custom packet into platform-specific packet by calling a function from
* {@link #buildMap}.
*
* @param packet
* Packet to build
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if reflection fails
* @param packet
* Packet to build
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if reflection fails
*/
public Object build(TabPacket packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
Preconditions.checkNotNull(packet, "packet");
Preconditions.checkNotNull(clientVersion, "clientVersion");
public Object build(@NonNull TabPacket packet, @NonNull ProtocolVersion clientVersion) throws ReflectiveOperationException {
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
}
/**
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
@@ -72,28 +70,26 @@ public class PacketBuilder {
/**
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
@@ -102,75 +98,65 @@ public class PacketBuilder {
/**
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
return packet;
}
@@ -179,11 +165,12 @@ public class PacketBuilder {
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
* RGB codes are converted into legacy, since cutting is only needed for &lt;1.13.
* If {@code string} is {@code null}, empty string is returned.
* @param string
* String to cut
* @param length
* Length to cut to
* @return string cut to {@code length} characters
*
* @param string
* String to cut
* @param length
* Length to cut to
* @return string cut to {@code length} characters
*/
public String cutTo(String string, int length) {
if (string == null) return "";
@@ -201,17 +188,17 @@ public class PacketBuilder {
}
/**
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is cut to {@code length} characters if
* needed and returned.
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
if (text == null) return null;
@@ -226,13 +213,13 @@ public class PacketBuilder {
* Converts platform-specific instance of player info packet into
* {@link PacketPlayOutPlayerInfo} object.
*
* @param packet
* platform-specific info packet
* @param clientVersion
* Version of client receiving the packet
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* platform-specific info packet
* @param clientVersion
* Version of client receiving the packet
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return null;
@@ -242,11 +229,11 @@ public class PacketBuilder {
* Converts platform-specific instance of objective packet into
* {@link PacketPlayOutScoreboardObjective} object.
*
* @param packet
* platform-specific objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* platform-specific objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
return null;
@@ -256,11 +243,11 @@ public class PacketBuilder {
* Converts platform-specific instance of display objective packet into
* {@link PacketPlayOutScoreboardDisplayObjective} object.
*
* @param packet
* platform-specific display objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
* @param packet
* platform-specific display objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
return null;
@@ -1,21 +1,23 @@
package me.neznamy.tab.api.protocol;
import java.util.UUID;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.util.Preconditions;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private final Action action;
private Action action;
/** BossBar title */
private String name;
@@ -42,22 +44,18 @@ public class PacketPlayOutBoss implements TabPacket {
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param pct
* BossBar progress
* @param color
* BossBar color
* @param overlay
* BossBar style
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param pct
* BossBar progress
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, String name, float pct, BarColor color, BarStyle overlay) {
Preconditions.checkNotNull(id, "uuid");
Preconditions.checkNotNull(name, "title");
Preconditions.checkNotNull(color, "color");
Preconditions.checkNotNull(overlay, "style");
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name, float pct, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.ADD;
this.id = id;
this.name = name;
@@ -68,28 +66,26 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.REMOVE action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#REMOVE} action
*
* @param id
* BossBar uuid
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(UUID id) {
Preconditions.checkNotNull(id, "uuid");
public PacketPlayOutBoss(@NonNull UUID id) {
this.action = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PCT action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PCT} action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(UUID id, float pct) {
Preconditions.checkNotNull(id, "uuid");
public PacketPlayOutBoss(@NonNull UUID id, float pct) {
this.action = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
@@ -97,16 +93,14 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_NAME action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_NAME} action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(UUID id, String name) {
Preconditions.checkNotNull(id, "uuid");
Preconditions.checkNotNull(name, "title");
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name) {
this.action = Action.UPDATE_NAME;
this.id = id;
this.name = name;
@@ -114,19 +108,16 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_STYLE action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_STYLE} action
*
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, BarColor color, BarStyle overlay) {
Preconditions.checkNotNull(id, "uuid");
Preconditions.checkNotNull(color, "color");
Preconditions.checkNotNull(overlay, "style");
public PacketPlayOutBoss(@NonNull UUID id, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
@@ -135,17 +126,16 @@ public class PacketPlayOutBoss implements TabPacket {
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PROPERTIES action
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PROPERTIES} action
*
* @param darkenScreen
* Darken screen flag
* @param playMusic
* Play music flag
* @param createWorldFog
* Create fog flag
* @param darkenScreen
* Darken screen flag
* @param playMusic
* Play music flag
* @param createWorldFog
* Create fog flag
*/
public PacketPlayOutBoss(UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
Preconditions.checkNotNull(id, "uuid");
public PacketPlayOutBoss(@NonNull UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.action = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
@@ -159,9 +149,9 @@ public class PacketPlayOutBoss implements TabPacket {
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
* to the final value.
*
* @return the bitmask
* @return the bitmask
*/
public byte getFlags(){
public byte getFlags() {
byte value = 0;
if (darkenScreen) value += 1;
if (playMusic) value += 2;
@@ -169,114 +159,6 @@ public class PacketPlayOutBoss implements TabPacket {
return value;
}
@Override
public String toString() {
return String.format("PacketPlayOutBoss{id=%s,operation=%s,name=%s,pct=%s,color=%s,overlay=%s,darkenScreen=%s,playMusic=%s,createWorldFog=%s}",
id, action, name, pct, color, overlay, darkenScreen, playMusic, createWorldFog);
}
/**
* Returns {@link #color}
* @return color
*/
public BarColor getColor() {
return color;
}
/**
* Returns {@link #overlay}
* @return style
*/
public BarStyle getOverlay() {
return overlay;
}
/**
* Returns {@link #name}
* @return name
*/
public String getName() {
return name;
}
/**
* Returns {@link #id}
* @return id
*/
public UUID getId() {
return id;
}
/**
* Returns {@link #pct}
* @return progress
*/
public float getPct() {
return pct;
}
/**
* Returns {@link #action}
* @return packet action
*/
public Action getAction() {
return action;
}
/**
* Returns {@link #darkenScreen}
* @return darkenScreen
*/
public boolean isDarkenScreen() {
return darkenScreen;
}
/**
* Returns {@link #createWorldFog}
* @return createWorldFog
*/
public boolean isCreateWorldFog() {
return createWorldFog;
}
/**
* Returns {@link #playMusic}
* @return playMusic
*/
public boolean isPlayMusic() {
return playMusic;
}
/**
* Sets {@link #darkenScreen} to specified value
*
* @param darkenScreen
* Darken screen flag
*/
public void setDarkenScreen(boolean darkenScreen) {
this.darkenScreen = darkenScreen;
}
/**
* Sets {@link #createWorldFog} to specified value
*
* @param createWorldFog
* Create fog flag
*/
public void setCreateWorldFog(boolean createWorldFog) {
this.createWorldFog = createWorldFog;
}
/**
* Sets {@link #playMusic} to specified value
*
* @param playMusic
* Play music flag
*/
public void setPlayMusic(boolean playMusic) {
this.playMusic = playMusic;
}
/**
* An enum representing all valid boss packet actions.
* Calling ordinal() will return action's network ID.
@@ -1,54 +1,20 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
private final IChatBaseComponent message;
@NonNull private final IChatBaseComponent message;
/** Message position */
private final ChatMessageType type;
/**
* Constructs new instance with given parameters
*
* @param message
* Chat message to be sent
* @param type
* Message position
*/
public PacketPlayOutChat(IChatBaseComponent message, ChatMessageType type) {
Preconditions.checkNotNull(message, "message");
Preconditions.checkNotNull(type, "message type");
this.message = message;
this.type = type;
}
@Override
public String toString() {
return String.format("PacketPlayOutChat{message=%s,type=%s}", message, type);
}
/**
* Returns {@link #message}
* @return message
*/
public IChatBaseComponent getMessage() {
return message;
}
/**
* Returns {@link #type}
* @return type
*/
public ChatMessageType getType() {
return type;
}
@NonNull private final ChatMessageType type;
/**
* An enum representing positions of a chat message
@@ -1,19 +1,23 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet action */
private final EnumPlayerInfoAction action;
/** Packet actions */
private final EnumSet<EnumPlayerInfoAction> actions;
/** List of affected entries */
private final List<PlayerInfoData> entries;
@@ -21,59 +25,68 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(entries, "entries");
this.action = action;
this.entries = Arrays.asList(entries);
this(action, Arrays.asList(entries));
}
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(entries, "entries");
this.action = action;
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
actions = EnumSet.of(EnumPlayerInfoAction.ADD_PLAYER,
EnumPlayerInfoAction.INITIALIZE_CHAT,
EnumPlayerInfoAction.UPDATE_GAME_MODE,
EnumPlayerInfoAction.UPDATE_LISTED,
EnumPlayerInfoAction.UPDATE_LATENCY,
EnumPlayerInfoAction.UPDATE_DISPLAY_NAME);
} else {
this.actions = EnumSet.of(action);
}
this.entries = entries;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerInfo{action=%s,entries=%s}", action, entries);
}
/**
* Returns {@link #action}
* @return packet action
* Constructs new instance with given parameters.
*
* @param actions
* Packet actions
* @param entries
* Affected entries
*/
public EnumPlayerInfoAction getAction() {
return action;
}
/**
* Returns {@link #entries}
* @return affected entries
*/
public List<PlayerInfoData> getEntries() {
return entries;
public PacketPlayOutPlayerInfo(@NonNull EnumSet<EnumPlayerInfoAction> actions, @NonNull PlayerInfoData... entries) {
this.actions = actions;
this.entries = Arrays.asList(entries);
}
/**
* A subclass representing player list entry
*/
@Data @AllArgsConstructor
public static class PlayerInfoData {
/** Real name of affected player */
private String name;
/** Player UUID */
@NonNull private UUID uniqueId;
/** Player's skin, null for empty skin */
private Skin skin;
/** I have no idea what this is */
private boolean listed;
/** Latency */
private int latency;
@@ -86,215 +99,75 @@ public class PacketPlayOutPlayerInfo implements TabPacket {
*/
private IChatBaseComponent displayName;
/** Real name of affected player */
private String name;
/** Chat session ID */
private UUID chatSessionId;
/** Player UUID */
private UUID uniqueId;
/** Player's skin, null for empty skin */
private Skin skin;
/** Player's chat signing key */
private Object profilePublicKey;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.ADD_PLAYER action
* {@link EnumPlayerInfoAction#UPDATE_GAME_MODE} action
*
* @param name
* Player's name
* @param uniqueId
* Player's uuid
* @param skin
* Player's platform-specific skin object
* @param latency
* Player's ping
* @param gameMode
* Player's GameMode
* @param displayName
* Player's display name
* @param uniqueId
* Player's uuid
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(String name, UUID uniqueId, Skin skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName) {
Preconditions.checkNotNull(uniqueId, "uuid");
this.name = name;
this.uniqueId = uniqueId;
this.skin = skin;
this.latency = latency;
this.gameMode = gameMode;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_GAME_MODE action
*
* @param uniqueId
* Player's uuid
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(UUID uniqueId, EnumGamemode gameMode) {
Preconditions.checkNotNull(uniqueId, "uuid");
public PlayerInfoData(@NonNull UUID uniqueId, EnumGamemode gameMode) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_LATENCY action
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
*
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
*/
public PlayerInfoData(UUID uniqueId, int latency) {
Preconditions.checkNotNull(uniqueId, "uuid");
public PlayerInfoData(@NonNull UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_DISPLAY_NAME action
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
*
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
*/
public PlayerInfoData(UUID uniqueId, IChatBaseComponent displayName) {
Preconditions.checkNotNull(uniqueId, "uuid");
public PlayerInfoData(@NonNull UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction}.REMOVE_PLAYER action
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
*
* @param uniqueId
* Player's uuid
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(UUID uniqueId) {
public PlayerInfoData(@NonNull UUID uniqueId) {
this.uniqueId = uniqueId;
}
@Override
public String toString() {
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s}",
latency, gameMode, displayName, name, uniqueId, skin);
}
/**
* Returns {@link #latency}
* @return latency
*/
public int getLatency() {
return latency;
}
/**
* Sets {@link #latency} to specified value
* @param latency
* Latency to use
*/
public void setLatency(int latency) {
this.latency = latency;
}
/**
* Returns {@link #displayName}
* @return displayName
*/
public IChatBaseComponent getDisplayName() {
return displayName;
}
/**
* Sets {@link #displayName} to specified value
* @param displayName
* Display name to use
*/
public void setDisplayName(IChatBaseComponent displayName) {
this.displayName = displayName;
}
/**
* Returns {@link #uniqueId}
* @return uniqueId
*/
public UUID getUniqueId() {
return uniqueId;
}
/**
* Sets {@link #uniqueId} to specified value
* @param uniqueId
* UUID to use
*/
public void setUniqueId(UUID uniqueId) {
this.uniqueId = uniqueId;
}
/**
* Returns {@link #name}
* @return name
*/
public String getName() {
return name;
}
/**
* Sets {@link #name} to specified value
* @param name
* name to use
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns {@link #gameMode}
* @return gameMode
*/
public EnumGamemode getGameMode() {
return gameMode;
}
/**
* Sets {@link #gameMode} to specified value
* @param gameMode
* GameMode to use
*/
public void setGameMode(EnumGamemode gameMode) {
this.gameMode = gameMode;
}
/**
* Returns {@link #skin}
* @return skin
*/
public Skin getSkin() {
return skin;
}
/**
* Sets {@link #skin} to specified value
* @param skin
* Skin to use
*/
public void setSkin(Skin skin) {
this.skin = skin;
}
}
/**
* En enum representing packet action
* Calling ordinal() will return action's network ID.
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
@@ -1,68 +1,33 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
private final IChatBaseComponent header;
@NonNull private final IChatBaseComponent header;
/** TabList footer */
private final IChatBaseComponent footer;
@NonNull private final IChatBaseComponent footer;
/**
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
*
* @param header
* TabList header
* @param footer
* TabList footer
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(String header, String footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
public PacketPlayOutPlayerListHeaderFooter(@NonNull String header, @NonNull String footer) {
this.header = IChatBaseComponent.optimizedComponent(header);
this.footer = IChatBaseComponent.optimizedComponent(footer);
}
/**
* Constructs new instance with given parameters.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent header, IChatBaseComponent footer) {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
this.header = header;
this.footer = footer;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerListHeaderFooter{header=%s,footer=%s}", header, footer);
}
/**
* Returns {@link #header}
* @return header
*/
public IChatBaseComponent getHeader() {
return header;
}
/**
* Returns {@link #footer}
* @return footer
*/
public IChatBaseComponent getFooter() {
return footer;
}
}
@@ -1,10 +1,12 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
@@ -16,42 +18,5 @@ public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
private final int slot;
/** Up to 16 characters long objective name */
private final String objectiveName;
/**
* Constructs new instance with given parameters
* @param slot
* Display slot
* @param objectiveName
* Internal name of the objective
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardDisplayObjective(int slot, String objectiveName) {
Preconditions.checkNotNull(objectiveName, "objective name");
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.slot = slot;
this.objectiveName = objectiveName;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardDisplayObjective{slot=%s,objectiveName=%s}", slot, objectiveName);
}
/**
* Returns {@link #slot}
* @return slot
*/
public int getSlot() {
return slot;
}
/**
* Returns {@link #objectiveName}
* @return objectiveName
*/
public String getObjectiveName() {
return objectiveName;
}
@NonNull private final String objectiveName;
}
@@ -1,21 +1,16 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutScoreboardObjective implements TabPacket {
/** Up to 16 characters long objective name */
private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Packet action.
* 0 = register,
@@ -24,39 +19,24 @@ public class PacketPlayOutScoreboardObjective implements TabPacket {
*/
private final int action;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action (0 = add, 1 = remove, 2 = update title)
* @param objectiveName
* objective name, up to 16 characters long
* @param displayName - title
* @param renderType - display type
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(int action, String objectiveName, String displayName, EnumScoreboardHealthDisplay renderType) {
Preconditions.checkRange(action, 0, 2, "action");
Preconditions.checkNotNull(objectiveName, "objective name");
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
Preconditions.checkNotNull(renderType, "render type");
this.objectiveName = objectiveName;
this.displayName = displayName;
this.renderType = renderType;
this.action = action;
}
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Constructs new packet with given objective name and 1 (unregister) action.
*
* @param objectiveName
* objective name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
* @param objectiveName
* objective name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(String objectiveName) {
Preconditions.checkNotNull(objectiveName, "objective name");
public PacketPlayOutScoreboardObjective(@NonNull String objectiveName) {
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
@@ -64,44 +44,6 @@ public class PacketPlayOutScoreboardObjective implements TabPacket {
this.renderType = null;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardObjective{objectiveName=%s,displayName=%s,renderType=%s,method=%s}",
objectiveName, displayName, renderType, action);
}
/**
* Returns {@link #displayName}
* @return displayName
*/
public String getDisplayName() {
return displayName;
}
/**
* Returns {@link #objectiveName}
* @return objectiveName
*/
public String getObjectiveName() {
return objectiveName;
}
/**
* Returns {@link #renderType}
* @return renderType
*/
public EnumScoreboardHealthDisplay getRenderType() {
return renderType;
}
/**
* Returns {@link #action}
* @return packet action
*/
public int getAction() {
return action;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
@@ -1,87 +1,26 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.util.Preconditions;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
private final Action action;
@NonNull private final Action action;
/** Objective name */
private final String objectiveName;
@NonNull private final String objectiveName;
/** Affected player */
private final String player;
@NonNull private final String player;
/** Player's score */
private final int score;
/**
* Constructs new instance with given parameters
*
* @param action
* Packet action
* @param objectiveName
* Objective name
* @param player
* Affected player
* @param score
* Player's score
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardScore(Action action, String objectiveName, String player, int score) {
Preconditions.checkNotNull(action, "action");
Preconditions.checkNotNull(objectiveName, "objective name");
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
Preconditions.checkNotNull(player, "player");
this.action = action;
this.objectiveName = objectiveName;
this.player = player;
this.score = score;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardScore{action=%s,objectiveName=%s,player=%s,score=%s}",
action, objectiveName, player, score);
}
/**
* Returns {@link #action}
* @return packet action
*/
public Action getAction() {
return action;
}
/**
* Returns {@link #objectiveName}
* @return objective name
*/
public String getObjectiveName() {
return objectiveName;
}
/**
* Returns {@link #player}
* @return player
*/
public String getPlayer() {
return player;
}
/**
* Returns {@link #score}
* @return score
*/
public int getScore() {
return score;
}
/**
* An enum representing action types
*/
@@ -1,14 +1,17 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
@@ -18,12 +21,12 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
private final String name;
@NonNull private final String name;
/** Team prefix, limited to 16 characters, on <1.13 */
/** Team prefix, limited to 16 characters, on &lt;1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on <1.13 */
/** Team suffix, limited to 16 characters, on &lt;1.13 */
private String playerSuffix;
/**
@@ -46,10 +49,10 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Affected entities. For players, it is their name, for other entities their UUID
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
* Player name length is limited to 16 characters for &lt;1.8, 40 characters since 1.8.
* Entity UUID option was added in 1.8.
*/
private Collection<String> players = Collections.emptyList();
@NonNull private Collection<String> players = Collections.emptyList();
/**
* Packet action.
@@ -72,16 +75,15 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* Constructs new instance with given parameters. Private constructor used
* internally to validate team name and set action.
*
* @param action
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
* 3 = add entries, 4 = remove entries)
* @param name
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
* @param action
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
* 3 = add entries, 4 = remove entries)
* @param name
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
private PacketPlayOutScoreboardTeam(int action, String name) {
Preconditions.checkNotNull(name, "team name");
private PacketPlayOutScoreboardTeam(int action, @NonNull String name) {
Preconditions.checkMaxLength(name, 16, "team name");
this.action = action;
this.name = name;
@@ -90,30 +92,26 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Constructs new instance with given parameters and 0 (CREATE) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param players
* Affected entities
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param players
* Affected entities
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix, String visibility,
String collision, @NonNull Collection<String> players, int options) {
this(0, team);
Preconditions.checkNotNull(prefix, "prefix");
Preconditions.checkNotNull(suffix, "suffix");
Preconditions.checkNotNull(visibility, "visibility");
Preconditions.checkNotNull(collision, "collision");
Preconditions.checkNotNull(players, "players");
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
@@ -125,10 +123,10 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Constructs new instance with given parameters and 1 (REMOVE) action
*
* @param team
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
* @param team
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team) {
this(1, team);
@@ -137,28 +135,24 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, int options) {
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix,
String visibility, String collision, int options) {
this(2, team);
Preconditions.checkNotNull(prefix, "prefix");
Preconditions.checkNotNull(suffix, "suffix");
Preconditions.checkNotNull(visibility, "visibility");
Preconditions.checkNotNull(collision, "collision");
Preconditions.checkNotNull(players, "players");
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
@@ -170,107 +164,17 @@ public class PacketPlayOutScoreboardTeam implements TabPacket {
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
*
* @param team
* Team name, up to 16 characters long
* @param players
* Affected entities
* @param add
* {@code true} if players should be added, {@code false} if removed from team
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
* @param team
* Team name, up to 16 characters long
* @param players
* Affected entities
* @param add
* {@code true} if players should be added, {@code false} if removed from team
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, Collection<String> players, boolean add) {
public PacketPlayOutScoreboardTeam(String team, @NonNull Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
Preconditions.checkNotNull(players, "players");
this.players = players;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardTeam{name=%s,playerPrefix=%s,playerSuffix=%s,nameTagVisibility=%s,"
+ "collisionRule=%s,color=%s,players=%s,method=%s,options=%s}",
name, playerPrefix, playerSuffix, nameTagVisibility, collisionRule, color, players, action, options);
}
/**
* Returns {@link #name}
* @return team name
*/
public String getName() {
return name;
}
/**
* Returns {@link #playerPrefix}
* @return playerPrefix
*/
public String getPlayerPrefix() {
return playerPrefix;
}
/**
* Returns {@link #playerSuffix}
* @return playerSuffix
*/
public String getPlayerSuffix() {
return playerSuffix;
}
/**
* Returns {@link #nameTagVisibility}
* @return nameTagVisibility
*/
public String getNameTagVisibility() {
return nameTagVisibility;
}
/**
* Returns {@link #collisionRule}
* @return collisionRule
*/
public String getCollisionRule() {
return collisionRule;
}
/**
* Sets {@link #color} to specified color
*
* @param color
* color to use
*/
public void setColor(EnumChatFormat color) {
this.color = color;
}
/**
* Returns {@link #color}
* @return color
*/
public EnumChatFormat getColor() {
return color;
}
/**
* Returns {@link #players}
* @return players
*/
public Collection<String> getPlayers() {
return players;
}
/**
* Returns {@link #options}
* @return team options
*/
public int getOptions() {
return options;
}
/**
* Returns {@link #action}
* @return packet action
*/
public int getAction() {
return action;
}
}
@@ -1,54 +1,18 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.util.Preconditions;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
/**
* Class representing a minecraft skin as a value - signature pair.
*/
@Data @AllArgsConstructor
public class Skin {
/** Skin value */
private final String value;
@NonNull private final String value;
/** Skin signature */
/** Skin signature, can be null */
private final String signature;
/**
* Constructs new instance with given parameters
* @param value
* skin value
* @param signature
* skin signature
*/
public Skin(String value, String signature) {
Preconditions.checkNotNull(value, "value");
Preconditions.checkNotNull(signature, "signature");
this.value = value;
this.signature = signature;
}
/**
* Returns skin value
* @return skin value
*/
public String getValue() {
return value;
}
/**
* Returns skin signature
* @return skin signature
*/
public String getSignature() {
return signature;
}
/**
* Returns user-friendly representation of this object
* @return user-friendly representation of this object
*/
@Override
public String toString() {
return String.format("Skin{value=%s,signature=%s}", value, signature);
}
}
@@ -10,7 +10,8 @@ public interface TabPacket {
/**
* Returns user-friendly string representation of the packet
* @return user-friendly string representation of the packet
*
* @return user-friendly string representation of the packet
*/
String toString();
}
@@ -11,7 +11,7 @@ public interface Line {
* Returns configured raw text of this line. Placeholders
* remain in raw format.
*
* @return raw text of this line
* @return raw text of this line
*/
String getText();
@@ -22,8 +22,8 @@ public interface Line {
* keep placeholders up to date. If value is identical to previous
* one, nothing happens.
*
* @param text
* Raw text to display in the line
* @param text
* Raw text to display in the line
*/
void setText(String text);
}
@@ -15,7 +15,7 @@ public interface Scoreboard {
* scoreboards from config use name they were defined with. This value is
* used internally and in /tab announce scoreboard command.
*
* @return custom name of this scoreboard
* @return custom name of this scoreboard
*/
String getName();
@@ -23,8 +23,8 @@ public interface Scoreboard {
* Returns raw title of this scoreboard. Placeholders stay in their
* raw format.
*
* @return scoreboard title
* @see #setTitle(String)
* @return scoreboard title
* @see #setTitle(String)
*/
String getTitle();
@@ -39,9 +39,9 @@ public interface Scoreboard {
* <p>
* Calling this method with same title as before will not do anything.
*
* @param title
* New title to use with placeholder support
* @see #getTitle()
* @param title
* New title to use with placeholder support
* @see #getTitle()
*/
void setTitle(String title);
@@ -50,9 +50,9 @@ public interface Scoreboard {
* in game (first line is on top). This list should only be used for reading,
* for adding/removing lines see {@link #addLine(String)} and {@link #removeLine(int)}.
*
* @return list of lines in this scoreboard
* @see #addLine(String)
* @see #removeLine(int)
* @return list of lines in this scoreboard
* @see #addLine(String)
* @see #removeLine(int)
*/
List<Line> getLines();
@@ -60,10 +60,10 @@ public interface Scoreboard {
* Adds line with specified text on the bottom of scoreboard. Supports
* placeholders, which will automatically be refreshed.
*
* @param text
* Text to display
* @see #getLines()
* @see #removeLine(int)
* @param text
* Text to display
* @see #getLines()
* @see #removeLine(int)
*/
void addLine(String text);
@@ -71,12 +71,12 @@ public interface Scoreboard {
* Removes line with specified index. Index starts at {@code 0} and ends at
* {@link #getLines()}.size()-1.
*
* @param index
* Index of line to remove, starting at 0
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index >= {@link #getLines()}.size())
* @see #getLines()
* @see #addLine(String)
* @param index
* Index of line to remove, starting at 0
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index &gt;= {@link #getLines()}.size())
* @see #getLines()
* @see #addLine(String)
*/
void removeLine(int index);
@@ -29,15 +29,15 @@ public interface ScoreboardManager {
* Client can only see up to 15 lines at a time, however you can define
* more, if some of them only consist of placeholder that might be empty.
*
* @param name
* Internal name of the scoreboard
* @param title
* The scoreboard title
* @param lines
* Lines of text in scoreboard (supports placeholders)
* @return The new scoreboard with given parameters
* @throws NullPointerException
* if {@code name} or {@code lines} is {@code null}
* @param name
* Internal name of the scoreboard
* @param title
* The scoreboard title
* @param lines
* Lines of text in scoreboard (supports placeholders)
* @return The new scoreboard with given parameters
* @throws NullPointerException
* if {@code name} or {@code lines} is {@code null}
*/
Scoreboard createScoreboard(String name, String title, List<String> lines);
@@ -47,7 +47,7 @@ public interface ScoreboardManager {
* or using {@link #createScoreboard(String, String, List)}.
* Map value is reference to the actual scoreboard.
*
* @return map of registered scoreboards
* @return map of registered scoreboards
*/
Map<String, Scoreboard> getRegisteredScoreboards();
@@ -57,14 +57,14 @@ public interface ScoreboardManager {
* {@link #resetScoreboard(TabPlayer)}. If the player already sees
* this scoreboard, nothing will happen.
*
* @param player
* Player to send scoreboard to
* @param scoreboard
* Scoreboard to display
* @throws NullPointerException
* if {@code player} or {@code scoreboard} is {@code null}
* @see #hasCustomScoreboard(TabPlayer)
* @see #resetScoreboard(TabPlayer)
* @param player
* Player to send scoreboard to
* @param scoreboard
* Scoreboard to display
* @throws NullPointerException
* if {@code player} or {@code scoreboard} is {@code null}
* @see #hasCustomScoreboard(TabPlayer)
* @see #resetScoreboard(TabPlayer)
*/
void showScoreboard(TabPlayer player, Scoreboard scoreboard);
@@ -72,12 +72,12 @@ public interface ScoreboardManager {
* Returns {@code true} if player has custom scoreboard set using
* {@link #showScoreboard(TabPlayer, Scoreboard)}, {@code false} if not.
*
* @param player
* Player to check
* @return {@code true} if player has custom scoreboard set using API,
* {@code false} if not
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #resetScoreboard(TabPlayer)
* @param player
* Player to check
* @return {@code true} if player has custom scoreboard set using API,
* {@code false} if not
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #resetScoreboard(TabPlayer)
*/
boolean hasCustomScoreboard(TabPlayer player);
@@ -86,10 +86,10 @@ public interface ScoreboardManager {
* and re-enables internal display logic with conditions. If player does not
* have any forced scoreboard, nothing happens.
*
* @param player
* Player to hide custom scoreboard from
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #hasCustomScoreboard(TabPlayer)
* @param player
* Player to hide custom scoreboard from
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #hasCustomScoreboard(TabPlayer)
*/
void resetScoreboard(TabPlayer player);
@@ -99,11 +99,11 @@ public interface ScoreboardManager {
* {@link #toggleScoreboard(TabPlayer, boolean)} or
* {@link #setScoreboardVisible(TabPlayer, boolean, boolean)}.
*
* @param player
* Player to get visibility status of
* @return {@code true} if visible, {@code false} if disabled
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
* @see #toggleScoreboard(TabPlayer, boolean)
* @param player
* Player to get visibility status of
* @return {@code true} if visible, {@code false} if disabled
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
* @see #toggleScoreboard(TabPlayer, boolean)
*/
boolean hasScoreboardVisible(TabPlayer player);
@@ -113,14 +113,14 @@ public interface ScoreboardManager {
* visibility, scoreboard is toggled. If {@code sendToggleMessage} is {@code true},
* toggle message defined in configuration is sent.
*
* @param player
* Player to set visibility for
* @param visible
* New visibility status
* @param sendToggleMessage
* whether to send toggle message if status changed or not
* @see #toggleScoreboard(TabPlayer, boolean)
* @see #hasScoreboardVisible(TabPlayer)
* @param player
* Player to set visibility for
* @param visible
* New visibility status
* @param sendToggleMessage
* whether to send toggle message if status changed or not
* @see #toggleScoreboard(TabPlayer, boolean)
* @see #hasScoreboardVisible(TabPlayer)
*/
void setScoreboardVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
@@ -129,12 +129,12 @@ public interface ScoreboardManager {
* hides it. If hidden, shows it. If {@code sendToggleMessage} is {@code true},
* toggle message defined in configuration is sent.
*
* @param player
* Player to toggle scoreboard for
* @param sendToggleMessage
* Whether to send toggle message or not
* @see #hasScoreboardVisible(TabPlayer)
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
* @param player
* Player to toggle scoreboard for
* @param sendToggleMessage
* Whether to send toggle message or not
* @see #hasScoreboardVisible(TabPlayer)
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
*/
void toggleScoreboard(TabPlayer player, boolean sendToggleMessage);
@@ -143,13 +143,13 @@ public interface ScoreboardManager {
* time (in milliseconds). Scoreboard name is either defined in config, or
* through API in {@link #createScoreboard(String, String, List)}.
*
* @param scoreboard
* Scoreboard from config or registered via API
* @param duration
* Duration in milliseconds
* @throws IllegalArgumentException
* if no scoreboard was found with such name or {@code duration}
* is &lt; 0.
* @param scoreboard
* Scoreboard from config or registered via API
* @param duration
* Duration in milliseconds
* @throws IllegalArgumentException
* if no scoreboard was found with such name or {@code duration}
* is &lt; 0.
*/
void announceScoreboard(String scoreboard, int duration);
@@ -157,9 +157,10 @@ public interface ScoreboardManager {
* Returns player's currently displayed scoreboard. This can be either with
* configuration, overridden with commands or the API. Will return {@code null}
* if player does not see any scoreboard due to not meeting any display condition.
* @param player
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*
* @param player
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
Scoreboard getActiveScoreboard(TabPlayer player);
}
@@ -1,10 +0,0 @@
package me.neznamy.tab.api.task;
public interface RepeatingTask {
int getInterval();
void setInterval(int paramInt);
void cancel();
}
@@ -4,50 +4,93 @@ import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
/**
* Manager for scheduling and executing tasks in TAB's
* main thread to avoid concurrency issues.
*/
public interface ThreadManager {
/**
* Starts a task in new thread and measures how long it took to process
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
/**
* Starts a task in new thread and measures how long it took to process
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
/**
* Runs task in a new thread
* @param task - the task
*
* @param task
* the task
*/
Future<Void> runTask(Runnable task);
/**
* Starts a new task with defined repeat interval that measures cpu usage
* @param intervalMilliseconds - task interval
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*
* @param intervalMilliseconds
* task interval
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
RepeatingTask startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
Future<?> startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
RepeatingTask startRepeatingTask(int intervalMilliseconds, Runnable task);
/**
* Starts a new task with defined repeat interval
*
* @param intervalMilliseconds
* task interval
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingTask(int intervalMilliseconds, Runnable task);
/**
* Runs task with a delay and measures how long it took to process
* @param delayMilliseconds - how long to run the task after
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
* @return future allowing to cancel the task
*
* @param delayMilliseconds
* how long to run the task after
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Runs task with a delay
*
* @param delayMilliseconds
* how long to run the task after
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
}
@@ -6,92 +6,138 @@ public interface TeamManager {
/**
* Makes player's NameTag globally invisible
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*
* @param player
* player to hide nametag of
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void hideNametag(TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
* @param viewer - player to hide NameTag for
*
* @param player
* player to hide nametag of
* @param viewer
* player to hide NameTag for
*/
void hideNametag(TabPlayer player, TabPlayer viewer);
/**
* Makes player's NameTag visible again
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*
* @param player
* player to show nametag of
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void showNametag(TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
* @param viewer - player to show NameTag back for
*
* @param player
* player to show nametag of
* @param viewer
* player to show NameTag back for
*/
void showNametag(TabPlayer player, TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
* @return Whether player has hidden NameTag or not
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
*
* @param player
* player to check nametag visibility status of
* @return Whether player has hidden NameTag or not
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
*/
boolean hasHiddenNametag(TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
* @param viewer - player to check visibility status for
* @return true if hidden, false if not
*
* @param player
* player to check visibility status of
* @param viewer
* player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
*
* @param player
* player to pause team handling of
*/
void pauseTeamHandling(TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*
* @param player
* player to resume team handling of
*/
void resumeTeamHandling(TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
* it was resumed already using resumeTeamHandling
* @return true if paused, false if not
*
* @param player
* player to check handling status of
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(TabPlayer player);
/**
* Forces new team name for the player until this method is called again with null argument and
* performs all actions to change player's team name
* @param teamName - forced team name
*
* @param player
* player to set team name of
* @param teamName
* forced team name
*/
void forceTeamName(TabPlayer player, String teamName);
/**
* Returns forced team name of player or null if not forced
* @return forced team name of player or null if not forced
*
* @param player
* player to check forced team name of
* @return forced team name of player or null if not forced
*/
String getForcedTeamName(TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
* @param collision - forced collision rule
*
* @param player
* player to set collision rule of
* @param collision
* forced collision rule
*/
void setCollisionRule(TabPlayer player, Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
* @return forced value or null if not forced
*
* @param player
* player to get forced collision of
* @return forced value or null if not forced
*/
Boolean getCollisionRule(TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
* @param player - player to update team data of
*
* @param player
* player to update team data of
*/
void updateTeamData(TabPlayer player);
@@ -116,10 +162,10 @@ public interface TeamManager {
* On first call, nametags of all players will become invisible for specified
* player. On second call, they will become visible again.
*
* @param player
* player to toggle nametag visibility view for
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
* @param player
* player to toggle nametag visibility view for
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
@@ -128,9 +174,9 @@ public interface TeamManager {
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
* {@code false} if not.
*
* @param player
* player to check
* @return {@code true} if hidden, {@code false} if not
* @param player
* player to check
* @return {@code true} if hidden, {@code false} if not
*/
boolean hasHiddenNameTagVisibilityView(TabPlayer player);
}
@@ -2,17 +2,28 @@ package me.neznamy.tab.api.util;
/**
* An interface for BiFunction which can throw an exception.
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* call build method for packet classes, which may throw an
* exception due to reflection.
* @param <A>
* First function argument
* @param <B>
* Second function argument
* @param <C>
* Function return type
* @param <A>
* First function argument
* @param <B>
* Second function argument
* @param <C>
* Function return type
*/
public interface BiFunctionWithException<A, B, C> {
/**
* Runs the function and returns the result
*
* @param a
* First argument
* @param b
* Second argument
* @return Output from function
* @throws ReflectiveOperationException
* If reflective operation fails
*/
C apply(A a, B b) throws ReflectiveOperationException;
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.api.util;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
public class ComponentCache<K, V> {
private final int cacheSize;
private final BiFunctionWithException<K, ProtocolVersion, V> function;
private final Map<K, V> cacheModern = new HashMap<>();
private final Map<K, V> cacheLegacy = new HashMap<>();
public V get(K key, ProtocolVersion clientVersion) {
try {
if (key == null) return null;
Map<K, V> cache = clientVersion == null || clientVersion.getMinorVersion() >= 16 ? cacheModern : cacheLegacy;
if (cache.containsKey(key)) return cache.get(key);
V value = function.apply(key, clientVersion);
if (cache.size() > cacheSize) cache.clear();
cache.put(key, value);
return value;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,20 +1,64 @@
package me.neznamy.tab.api.util;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Class with utility methods used in the plugin
*/
public class Preconditions {
private Preconditions(){}
/**
* Private constructor preventing the class from being instantiated
*/
private Preconditions() {}
public static void checkNotNull(Object obj, String name){
if (obj == null) throw new IllegalArgumentException(name + " cannot be null");
}
public static void checkMaxLength(String string, int maxLength, String name) {
checkNotNull(string, name);
/**
* Checks length of String and throws {@code IllegalArgumentException} if it is
* longer than limit.
*
* @param string
* String to check
* @param maxLength
* Maximum length of the string
* @param name
* Name of the variable to use in exception message
* @throws IllegalArgumentException
* if {@code string} is {@code null} or longer than {@code maxLength} characters
*/
public static void checkMaxLength(@NonNull String string, int maxLength, String name) {
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
}
public static void checkRange(Number number, Number min, Number max, String variable){
/**
* Checks number range and throws {@code IllegalArgumentException} if number
* is out of range.
*
* @param number
* Number to check
* @param min
* Minimum allowed value
* @param max
* Maximum allowed value
* @param variable
* Variable name to use in exception message
* @throws IllegalArgumentException
* if {@code number} is out of range ({@code number < min || number > max})
*/
public static void checkRange(Number number, Number min, Number max, String variable) {
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + ")");
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + "), was " + number);
}
/**
* Checks if player is loaded and throws {@code IllegalStateException} if not.
*
* @param player
* Player to check
* @throws IllegalStateException
* If player is not loaded
*/
public static void checkLoaded(TabPlayer player) {
if (!player.isLoaded()) throw new IllegalStateException("Player is not loaded yet. Try again later.");
}
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.util;
/**
* Utility class storing methods working with reflection
*/
public class ReflectionUtils {
/**
* Returns {@code true} if class with given full name exists,
* {@code false} if not.
*
* @param path
* Full class path and name
* @return {@code true} if exists, {@code false} if not
*/
public static boolean classExists(String path) {
try {
Class.forName(path);
return true;
} catch (ClassNotFoundException | NullPointerException e) {
return false;
}
}
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.util;
/**
* Supplier that can throw an exception
*
* @param <T>
* Return class type
*/
public interface SupplierWithException<T> {
T get() throws Exception;
}
+9 -4
View File
@@ -2,11 +2,16 @@ plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
dependencies {
implementation(libs.plugin.shadow)
implementation(libs.plugin.lombok)
}
dependencies {
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.0")
compileOnly(files(libs::class.java.protectionDomain.codeSource.location))
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
+16
View File
@@ -0,0 +1,16 @@
rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
gradlePluginPortal()
mavenCentral()
}
// You're a great IDE IntelliJ, but sometimes you really annoy me. >:(
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
versionCatalogs {
register("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
@@ -2,6 +2,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins {
`java-library`
id("io.freefair.lombok")
}
tasks {
@@ -9,10 +10,13 @@ tasks {
filter<ReplaceTokens>("tokens" to mapOf(
"name" to rootProject.name,
"version" to project.version,
"description" to project.description
"description" to project.description,
"website" to rootProject.ext.get("website")!!.toString()
))
}
javadoc {
// This saves a decent bit of processing power on slow machines
enabled = false
options.encoding = Charsets.UTF_8.name()
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
}
@@ -9,5 +9,6 @@ tasks {
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.yamlassist")
}
}
@@ -1,9 +1,3 @@
import gradle.kotlin.dsl.accessors._4cbb0020bd75d48acc42cabded5fdbb7.publishing
import org.gradle.api.artifacts.repositories.PasswordCredentials
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.repositories
plugins {
id("tab.base-conventions")
`maven-publish`
+8 -3
View File
@@ -4,15 +4,20 @@ plugins {
allprojects {
group = "me.neznamy"
version = property("projectVersion") as String // from gradle.properties
version = "3.3.0"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
}
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
// projects.krypton,
projects.velocity
projects.krypton,
projects.velocity,
projects.sponge7,
projects.sponge8
).map { it.dependencyProject }
val special = setOf(
-3
View File
@@ -4,8 +4,5 @@ dependencies {
compileOnly(libs.bukkit)
compileOnly(libs.placeholderapi)
compileOnly(libs.vault)
compileOnly(libs.via)
compileOnly(libs.authlib)
compileOnly(libs.libsDisguises)
compileOnly(libs.essentials)
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -8,56 +10,63 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import me.neznamy.tab.shared.TAB;
/**
* The core for bukkit forwarding events into all enabled features
*/
@RequiredArgsConstructor
public class BukkitEventListener implements Listener {
/** Platform instance */
private final BukkitPlatform platform;
public BukkitEventListener(BukkitPlatform platform) {
this.platform = platform;
}
/**
* Listener to PlayerQuitEvent to remove player data and forward the event to features
* @param e quit event
*
* @param e
* quit event
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e){
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
public void onQuit(PlayerQuitEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
* Listener to PlayerJoinEvent to create player data and forward the event to features
* @param e join event
*
* @param e
* join event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent e) {
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
}
/**
* Listener to PlayerChangedWorldEvent to forward the event to features
* @param e world changed event
*
* @param e
* world changed event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e){
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
public void onWorldChange(PlayerChangedWorldEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
}
/**
* Listener to PlayerChangedWorldEvent to forward the event to features
* @param e command preprocess event
*
* @param e
* command preprocess event
*/
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (TAB.getInstance().isDisabled()) return;
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
if (TabAPI.getInstance().isPluginDisabled()) return;
if (TabAPI.getInstance().getFeatureManager().onCommand(
TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
}
}
@@ -1,91 +1,38 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.api.protocol.*;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatComponentEntity;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.TextColor;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import java.util.UUID;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitPacketBuilder extends PacketBuilder {
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
//entity type ids
private final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
private final Map<IChatBaseComponent, Object> componentCacheModern = new HashMap<>();
private final Map<IChatBaseComponent, Object> componentCacheLegacy = new HashMap<>();
private Object emptyScoreboard;
private Object dummyEntity;
/**
* Constructs new instance
*/
public BukkitPacketBuilder() {
if (nms.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
entityIds.put(EntityType.WITHER, 83);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8){
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
try {
emptyScoreboard = nms.newScoreboard.newInstance();
} catch (ReflectiveOperationException e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Failed to create instance of \"Scoreboard\""));
}
if (nms.getMinorVersion() >= 8) {
try {
dummyEntity = nms.newEntityArmorStand.newInstance(nms.World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
} catch (ReflectiveOperationException e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Failed to create instance of \"EntityArmorStand\""));
}
}
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> build((PacketPlayOutEntityMetadata)packet));
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> build((PacketPlayOutEntityTeleport)packet));
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> build((PacketPlayOutEntityDestroy)packet));
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> build((PacketPlayOutSpawnEntityLiving)packet));
{
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> ((PacketPlayOutEntityMetadata)packet).build());
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> ((PacketPlayOutEntityTeleport)packet).build());
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> ((PacketPlayOutEntityDestroy)packet).build());
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> ((PacketPlayOutSpawnEntityLiving)packet).build());
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> PacketPlayOutPlayerListHeaderFooterStorage.build((PacketPlayOutPlayerListHeaderFooter) packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> PacketPlayOutChatStorage.build((PacketPlayOutChat) packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> PacketPlayOutScoreboardObjectiveStorage.build((PacketPlayOutScoreboardObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> PacketPlayOutScoreboardDisplayObjectiveStorage.build((PacketPlayOutScoreboardDisplayObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> PacketPlayOutScoreboardTeamStorage.build((PacketPlayOutScoreboardTeam) packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> PacketPlayOutScoreboardScoreStorage.build((PacketPlayOutScoreboardScore) packet, version));
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
if (NMSStorage.getInstance().getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
//1.9+ server or client, handled by bukkit api or ViaVersion
return packet;
}
@@ -93,463 +40,59 @@ public class BukkitPacketBuilder extends PacketBuilder {
return buildBossPacketEntity(packet, clientVersion);
}
@Override
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
Object component = toNMSComponent(packet.getMessage(), clientVersion);
if (nms.getMinorVersion() >= 16) {
return nms.newPacketPlayOutChat.newInstance(component, nms.ChatMessageType_values[packet.getType().ordinal()], UUID.randomUUID());
}
if (nms.getMinorVersion() >= 12) {
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf((Class<Enum>) nms.ChatMessageType, packet.getType().toString()));
}
if (nms.getMinorVersion() >= 8) {
return nms.newPacketPlayOutChat.newInstance(component, (byte) packet.getType().ordinal());
}
if (nms.getMinorVersion() == 7) {
return nms.newPacketPlayOutChat.newInstance(component);
}
throw new IllegalStateException("Not supported on <1.7");
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(nms.EnumPlayerInfoAction_values[packet.getAction().ordinal()], Array.newInstance(nms.EntityPlayer, 0));
List<Object> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (nms.newPlayerInfoData.getParameterCount() == 5) {
parameters.add(nmsPacket);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : nms.EnumGamemode_values[nms.EnumGamemode_values.length-EnumGamemode.VALUES.length+data.getGameMode().ordinal()]); //not_set was removed in 1.17
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
}
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
return nmsPacket;
return PacketPlayOutPlayerInfoStorage.build(packet, clientVersion);
}
@Override
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
if (nms.newPacketPlayOutPlayerListHeaderFooter.getParameterCount() == 2) {
return nms.newPacketPlayOutPlayerListHeaderFooter.newInstance(toNMSComponent(packet.getHeader(), clientVersion), toNMSComponent(packet.getFooter(), clientVersion));
}
Object nmsPacket = nms.newPacketPlayOutPlayerListHeaderFooter.newInstance();
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_HEADER, toNMSComponent(packet.getHeader(), clientVersion));
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_FOOTER, toNMSComponent(packet.getFooter(), clientVersion));
return nmsPacket;
}
@Override
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return nms.newPacketPlayOutScoreboardDisplayObjective.newInstance(packet.getSlot(), newScoreboardObjective(packet.getObjectiveName()));
}
@Override
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
String displayName = clientVersion.getMinorVersion() < 13 ? cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
if (nms.getMinorVersion() >= 13) {
return nms.newPacketPlayOutScoreboardObjective.newInstance(nms.newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
packet.getRenderType() == null ? null : nms.EnumScoreboardHealthDisplay_values[packet.getRenderType().ordinal()]),
packet.getAction());
}
Object nmsPacket = nms.newPacketPlayOutScoreboardObjective.newInstance();
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME, packet.getObjectiveName());
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_DISPLAYNAME, displayName);
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_RENDERTYPE, Enum.valueOf((Class<Enum>) nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
}
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_METHOD, packet.getAction());
return nmsPacket;
}
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 13) {
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(nms.EnumScoreboardAction_values[packet.getAction().ordinal()], packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
}
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
return nms.newPacketPlayOutScoreboardScore_String.newInstance(packet.getPlayer());
}
Object score = nms.newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
nms.ScoreboardScore_setScore.invoke(score, packet.getScore());
if (nms.getMinorVersion() >= 8) {
return nms.newPacketPlayOutScoreboardScore.newInstance(score);
}
return nms.newPacketPlayOutScoreboardScore.newInstance(score, 0);
}
@Override
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.PacketPlayOutScoreboardTeam == null) return null; //fabric
Object team = nms.newScoreboardTeam.newInstance(emptyScoreboard, packet.getName());
String prefix = packet.getPlayerPrefix();
String suffix = packet.getPlayerSuffix();
if (clientVersion.getMinorVersion() < 13) {
prefix = cutTo(prefix, 16);
suffix = cutTo(suffix, 16);
}
((Collection<String>)nms.ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(packet.getPlayers());
nms.ScoreboardTeam_setAllowFriendlyFire.invoke(team, (packet.getOptions() & 0x1) > 0);
nms.ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (packet.getOptions() & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(packet, clientVersion, team, prefix, suffix);
} else {
createTeamLegacy(packet, team, prefix, suffix);
}
if (nms.getMinorVersion() >= 17) {
switch (packet.getAction()) {
case 0:
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, true);
case 1:
return nms.PacketPlayOutScoreboardTeam_of.invoke(null, team);
case 2:
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, false);
case 3:
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[0]);
case 4:
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[1]);
default:
throw new IllegalArgumentException("Invalid action: " + packet.getAction());
}
}
return nms.newPacketPlayOutScoreboardTeam.newInstance(team, packet.getAction());
}
/**
* Builds entity destroy packet with given parameter
* @return destroy packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutEntityDestroy packet) throws ReflectiveOperationException {
try {
return nms.newPacketPlayOutEntityDestroy.newInstance(new Object[]{packet.getEntities()});
} catch (IllegalArgumentException e) {
//1.17.0
return nms.newPacketPlayOutEntityDestroy.newInstance(packet.getEntities()[0]);
}
}
public Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), packet.getDataWatcher().toNMS(), true);
}
/**
* Builds entity spawn packet with given parameters
*
* @return entity spawn packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutSpawnEntityLiving packet) throws ReflectiveOperationException {
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance(dummyEntity);
} else {
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance();
}
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYID, packet.getEntityId());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, entityIds.get(packet.getEntityType()));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_YAW, (byte)(packet.getLocation().getYaw() * 256.0f / 360.0f));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_PITCH, (byte)(packet.getLocation().getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, packet.getDataWatcher().toNMS());
}
if (nms.getMinorVersion() >= 9) {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_UUID, packet.getUniqueId());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, packet.getLocation().getX());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, packet.getLocation().getY());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, packet.getLocation().getZ());
} else {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, floor(packet.getLocation().getX()*32));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, floor(packet.getLocation().getY()*32));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, floor(packet.getLocation().getZ()*32));
}
return nmsPacket;
}
/**
* Builds entity teleport packet with given parameters
* @return entity teleport packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutEntityTeleport packet) throws ReflectiveOperationException {
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance(dummyEntity);
} else {
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance();
}
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_ENTITYID, packet.getEntityId());
if (nms.getMinorVersion() >= 9) {
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, packet.getLocation().getX());
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, packet.getLocation().getY());
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, packet.getLocation().getZ());
} else {
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, floor(packet.getLocation().getX()*32));
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, floor(packet.getLocation().getY()*32));
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, floor(packet.getLocation().getZ()*32));
}
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_YAW, (byte) (packet.getLocation().getYaw()/360*256));
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_PITCH, (byte) (packet.getLocation().getPitch()/360*256));
return nmsPacket;
}
private void createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion, Object team, String prefix, String suffix) throws ReflectiveOperationException {
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
nms.ScoreboardTeam_setColor.invoke(team, nms.EnumChatFormat_values[format.ordinal()]);
nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
}
private void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix) throws ReflectiveOperationException {
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, prefix);
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, suffix);
if (nms.getMinorVersion() >= 8) nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
if (nms.getMinorVersion() >= 9) nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
List<PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
IChatBaseComponent listName = nmsComponent == null ? null : fromNMSComponent(nmsComponent);
Skin skin = null;
if (!profile.getProperties().get("textures").isEmpty()) {
Property pr = (Property) profile.getProperties().get("textures").iterator().next();
skin = new Skin(pr.getValue(), pr.getSignature());
}
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), skin,
(int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName));
}
return new PacketPlayOutPlayerInfo(action, listData);
return PacketPlayOutPlayerInfoStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardObjective(nms.PacketPlayOutScoreboardObjective_METHOD.getInt(nmsPacket),
(String) nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.get(nmsPacket), null,
PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER
);
return PacketPlayOutScoreboardObjectiveStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardDisplayObjective(
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.getInt(nmsPacket),
(String) nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.get(nmsPacket)
);
return PacketPlayOutScoreboardDisplayObjectiveStorage.read(nmsPacket);
}
/**
* Builds entity BossBar packet
* @param packet - packet to build
* @param clientVersion - client version
* @return entity BossBar packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
*
* @param packet
* packet to build
* @param clientVersion
* client version
* @return entity BossBar packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getAction() == Action.REMOVE) {
return build(new PacketPlayOutEntityDestroy(entityId));
return new PacketPlayOutEntityDestroy(entityId).build();
}
DataWatcher w = new DataWatcher();
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.helper().setHealth(health);
w.getHelper().setHealth(health);
}
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
w.helper().setCustomName(packet.getName(), clientVersion);
w.getHelper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getAction() == Action.ADD) {
w.helper().setEntityFlags((byte) 32);
return build(new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w));
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
return new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w).build();
} else {
return build(new PacketPlayOutEntityMetadata(entityId, w));
return new PacketPlayOutEntityMetadata(entityId, w).build();
}
}
/**
* A method taken from minecraft code used to convert double to int
* @param paramDouble double value
* @return int value
*/
private int floor(double paramDouble){
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
/**
* Converts minecraft IChatBaseComponent into TAB's component class. Currently, does not support show_item hover event on 1.16+.
* @param component - component to convert
* @return converted component
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private IChatBaseComponent fromNMSComponent(Object component) throws ReflectiveOperationException {
if (!nms.ChatComponentText.isInstance(component)) return null; //paper
IChatBaseComponent chat = new IChatBaseComponent((String) nms.ChatComponentText_text.get(component));
Object modifier = nms.ChatBaseComponent_modifier.get(component);
if (modifier != null) {
chat.getModifier().setColor(fromNMSColor(nms.ChatModifier_color.get(modifier)));
chat.getModifier().setBold((Boolean) nms.ChatModifier_bold.get(modifier));
chat.getModifier().setItalic((Boolean) nms.ChatModifier_italic.get(modifier));
chat.getModifier().setObfuscated((Boolean) nms.ChatModifier_obfuscated.get(modifier));
chat.getModifier().setStrikethrough((Boolean) nms.ChatModifier_strikethrough.get(modifier));
chat.getModifier().setUnderlined((Boolean) nms.ChatModifier_underlined.get(modifier));
Object clickEvent = nms.ChatModifier_clickEvent.get(modifier);
if (clickEvent != null) {
chat.getModifier().onClick(EnumClickAction.valueOf(nms.ChatClickable_action.get(clickEvent).toString().toUpperCase()), (String) nms.ChatClickable_value.get(clickEvent));
}
Object hoverEvent = nms.ChatModifier_hoverEvent.get(modifier);
if (hoverEvent != null) {
EnumHoverAction action;
IChatBaseComponent value;
if (nms.getMinorVersion() >= 16) {
//does not support show_item on 1.16+
JsonObject json = (JsonObject) nms.ChatHoverable_serialize.invoke(hoverEvent);
action = EnumHoverAction.valueOf(json.get("action").getAsString().toUpperCase());
value = IChatBaseComponent.deserialize(json.get("contents").getAsJsonObject().toString());
} else {
action = EnumHoverAction.valueOf(nms.ChatHoverable_getAction.invoke(hoverEvent).toString().toUpperCase());
value = fromNMSComponent(nms.ChatHoverable_getValue.invoke(hoverEvent));
}
chat.getModifier().onHover(action, value);
}
}
for (Object extra : (List<Object>) nms.ChatBaseComponent_extra.get(component)) {
chat.addExtra(fromNMSComponent(extra));
}
return chat;
}
private TextColor fromNMSColor(Object color) throws ReflectiveOperationException {
if (color == null) return null;
if (nms.getMinorVersion() >= 16) {
String name = (String) nms.ChatHexColor_name.get(color);
if (name != null) {
//legacy code
return new TextColor(EnumChatFormat.valueOf(name.toUpperCase(Locale.US)));
} else {
int rgb = (int) nms.ChatHexColor_rgb.get(color);
return new TextColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
} else {
return new TextColor(EnumChatFormat.valueOf(((Enum)color).name()));
}
}
/**
* Converts TAB's IChatBaseComponent into minecraft's component. Currently, does not support hover event.
* @param component - component to convert
* @param clientVersion - client version used to decide RGB conversion
* @return converted component
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
Object obj;
if (clientVersion.getMinorVersion() >= 16) {
if (componentCacheModern.containsKey(component)) return componentCacheModern.get(component);
obj = toNMSComponent0(component, clientVersion);
if (componentCacheModern.size() > 10000) componentCacheModern.clear();
componentCacheModern.put(component, obj);
} else {
if (componentCacheLegacy.containsKey(component)) return componentCacheLegacy.get(component);
obj = toNMSComponent0(component, clientVersion);
if (componentCacheLegacy.size() > 10000) componentCacheLegacy.clear();
componentCacheLegacy.put(component, obj);
}
return obj;
}
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
private Object toNMSComponent0(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (component == null) return null;
Object chat = nms.newChatComponentText.newInstance(component.getText());
Object modifier;
Object clickEvent = component.getModifier().getClickEvent() == null ? null : nms.newChatClickable.newInstance(Enum.valueOf((Class<Enum>) nms.EnumClickAction,
component.getModifier().getClickEvent().getAction().toString().toUpperCase()), component.getModifier().getClickEvent().getValue());
if (nms.getMinorVersion() >= 16) {
Object color = null;
if (component.getModifier().getColor() != null) {
if (clientVersion.getMinorVersion() >= 16) {
color = nms.ChatHexColor_ofInt.invoke(null, (component.getModifier().getColor().getRed() << 16) + (component.getModifier().getColor().getGreen() << 8) + component.getModifier().getColor().getBlue());
} else {
color = nms.ChatHexColor_ofString.invoke(null, component.getModifier().getColor().getLegacyColor().toString().toLowerCase());
}
}
Object hoverEvent = null;
if (component.getModifier().getHoverEvent() != null) {
Object nmsAction = nms.EnumHoverAction_a.invoke(null, component.getModifier().getHoverEvent().getAction().toString().toLowerCase());
switch (component.getModifier().getHoverEvent().getAction()) {
case SHOW_TEXT:
hoverEvent = nms.newChatHoverable.newInstance(nmsAction, toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion));
break;
case SHOW_ENTITY:
hoverEvent = nms.EnumHoverAction_fromJson.invoke(nmsAction, ((ChatComponentEntity) component.getModifier().getHoverEvent().getValue()).toJson());
break;
case SHOW_ITEM:
hoverEvent = nms.EnumHoverAction_fromLegacyComponent.invoke(nmsAction, toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion));
break;
default:
break;
}
}
modifier = nms.newChatModifier.newInstance(
color,
component.getModifier().getBold(),
component.getModifier().getItalic(),
component.getModifier().getUnderlined(),
component.getModifier().getStrikethrough(),
component.getModifier().getObfuscated(),
clickEvent, hoverEvent, null, null);
} else {
modifier = nms.newChatModifier.newInstance();
if (component.getModifier().getColor() != null) nms.setField(modifier, nms.ChatModifier_color, Enum.valueOf((Class<Enum>) nms.EnumChatFormat, component.getModifier().getColor().getLegacyColor().toString()));
nms.setField(modifier, nms.ChatModifier_bold, component.getModifier().getBold());
nms.setField(modifier, nms.ChatModifier_italic, component.getModifier().getItalic());
nms.setField(modifier, nms.ChatModifier_underlined, component.getModifier().getUnderlined());
nms.setField(modifier, nms.ChatModifier_strikethrough, component.getModifier().getStrikethrough());
nms.setField(modifier, nms.ChatModifier_obfuscated, component.getModifier().getObfuscated());
if (clickEvent != null) nms.setField(modifier, nms.ChatModifier_clickEvent, clickEvent);
if (component.getModifier().getHoverEvent() != null) {
nms.setField(modifier, nms.ChatModifier_hoverEvent, nms.newChatHoverable.newInstance(nms.EnumHoverAction_a.invoke(null,
component.getModifier().getHoverEvent().getAction().toString().toLowerCase()), toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion)));
}
}
nms.setField(chat, nms.ChatBaseComponent_modifier, modifier);
for (IChatBaseComponent extra : component.getExtra()) {
nms.ChatComponentText_addSibling.invoke(chat, toNMSComponent0(extra, clientVersion));
}
return chat;
}
private Object newScoreboardObjective(String objectiveName) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 13) {
return nms.newScoreboardObjective.newInstance(null, objectiveName, null, nms.newChatComponentText.newInstance(""), null);
}
return nms.newScoreboardObjective.newInstance(null, objectiveName, nms.IScoreboardCriteria_self.get(null));
}
}
@@ -7,55 +7,53 @@ import java.util.function.Function;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
/**
* Pipeline injection for bukkit
*/
public class BukkitPipelineInjector extends PipelineInjector {
//nms storage
/** NMS data storage */
private final NMSStorage nms = NMSStorage.getInstance();
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BukkitChannelDuplexHandler::new;
/**
* Constructs new instance
*/
public BukkitPipelineInjector(){
public BukkitPipelineInjector() {
super("packet_handler");
}
@Override
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return BukkitChannelDuplexHandler::new;
}
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
//injected player
/** Injected player */
private final TabPlayer player;
/**
* Constructs new instance with given player
* @param player - player to inject
*/
public BukkitChannelDuplexHandler(TabPlayer player) {
Preconditions.checkNotNull(player, "player");
this.player = player;
}
@Override
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
try {
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
super.channelRead(context, packet);
} catch (Exception e){
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
}
@@ -63,23 +61,27 @@ public class BukkitPipelineInjector extends PipelineInjector {
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (nms.PacketPlayOutPlayerInfo.isInstance(packet)) {
if (PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet) ||
(PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null &&
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam != null && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
if (antiOverrideTeams && PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet)) {
long time = System.nanoTime();
modifyPlayers(packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
super.write(context, packet, channelPromise);
return;
}
if (nms.PacketPlayOutScoreboardDisplayObjective.isInstance(packet)){
if (PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
}
if (nms.PacketPlayOutScoreboardObjective.isInstance(packet)) {
if (PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Exception e){
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
@@ -91,15 +93,18 @@ public class BukkitPipelineInjector extends PipelineInjector {
/**
* Removes all real players from team if packet does not come from TAB and reports this to override log
* @param packetPlayOutScoreboardTeam - team packet
* @throws ReflectiveOperationException
* nmsGameMode
*
* @param packetPlayOutScoreboardTeam
* team packet
* @throws ReflectiveOperationException
* nmsGameMode
*/
@SuppressWarnings("unchecked")
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
long time = System.nanoTime();
Collection<String> players = (Collection<String>) nms.PacketPlayOutScoreboardTeam_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) nms.PacketPlayOutScoreboardTeam_NAME.get(packetPlayOutScoreboardTeam);
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
@@ -109,17 +114,26 @@ public class BukkitPipelineInjector extends PipelineInjector {
newList.add(entry);
continue;
}
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(p.getTeamName())) {
logTeamOverride(teamName, p.getName(), p.getTeamName());
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
} else {
newList.add(entry);
}
}
nms.setField(packetPlayOutScoreboardTeam, nms.PacketPlayOutScoreboardTeam_PLAYERS, newList);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
/**
* Returns player with matching game profile name. This is different from
* real name when a nick plugin changing names of players is used. If no
* player was found, returns {@code null}.
*
* @param name
* Game profile name
* @return Player with matching game profile name
*/
private TabPlayer getPlayer(String name) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getNickname().equals(name)) return p;
@@ -1,50 +1,50 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.util.Locale;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import com.earth2me.essentials.Essentials;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* Bukkit registry to register bukkit-only and universal placeholders
*/
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
/** Number formatter for 2 decimal places */
private final NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.US);
/** Vault Chat hook */
private Chat chat;
private Object chat;
private final Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
/** NMS server to get TPS from on spigot */
private Object server;
/** TPS field*/
private Field recentTps;
private boolean paperTps;
private boolean paperMspt;
private Method playerIsAfk;
/** Detection for presence of Paper's TPS getter */
private Method paperTps;
/** Detection for presence of Paper's MSPT getter */
private Method paperMspt;
/** Detection for presence of Purpur's AFK getter */
private Method purpurIsAfk;
/**
* Constructs new instance with given parameter
* Constructs new instance and loads hooks
*/
public BukkitPlaceholderRegistry() {
numberFormat.setMaximumFractionDigits(2);
try {
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<?> rspChat = Bukkit.getServicesManager().getRegistration(Class.forName("net.milkbowl.vault.chat.Chat"));
if (rspChat != null) chat = rspChat.getProvider();
}
} catch (ClassNotFoundException e) {
//modded server without vault
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
@@ -52,73 +52,54 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
} catch (ReflectiveOperationException e) {
//not spigot
}
try {
Bukkit.class.getMethod("getTPS");
paperTps = true;
} catch (NoSuchMethodException e) {
//not paper
}
try {
Bukkit.class.getMethod("getAverageTickTime");
paperMspt = true;
} catch (NoSuchMethodException e) {
//not paper
}
try {
playerIsAfk = Player.class.getMethod("isAfk");
} catch (NoSuchMethodException e) {
//not purpur
}
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
try { purpurIsAfk = Player.class.getMethod("isAfk"); } catch (NoSuchMethodException ignored) {}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(PlaceholderManager manager) {
super.registerPlaceholders(manager);
manager.registerPlayerPlaceholder("%displayname%", 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps) {
manager.registerServerPlaceholder("%tps%", 1000, () -> formatTPS(Bukkit.getTPS()[0]));
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
} else if (recentTps != null) {
manager.registerServerPlaceholder("%tps%", 1000, () -> {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
try {
return formatTPS(((double[]) recentTps.get(server))[0]);
} catch (IllegalAccessException e) {
return "-1";
return -1;
}
});
} else {
manager.registerServerPlaceholder("%tps%", -1, () -> "-1").enableTriggerMode();
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
}
if (paperMspt) {
manager.registerServerPlaceholder("%mspt%", 1000, () -> numberFormat.format(Bukkit.getAverageTickTime()));
if (paperMspt != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
}
manager.registerPlayerPlaceholder("%afk%", 500, p -> {
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
if (playerIsAfk == null) return false;
try {
return playerIsAfk.invoke(p.getPlayer());
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get AFK status of " + p.getName() + " using Purpur", exception);
Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
manager.registerPlayerPlaceholder(TabConstants.Placeholder.AFK, 500, p -> {
if (essentials != null) {
try {
Object user = essentials.getClass().getMethod("getUser", UUID.class).invoke(essentials, p.getUniqueId());
if ((boolean) user.getClass().getMethod("isAfk").invoke(user)) return true;
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get AFK status of " + p.getName() + " using Essentials", e);
}
}
return false;
return purpurIsAfk != null && ((Player)p.getPlayer()).isAfk();
});
manager.registerPlayerPlaceholder("%essentialsnick%", 1000, p -> {
String nickname = null;
if (essentials != null)
nickname = ((Essentials)essentials).getUser(p.getUniqueId()).getNickname();
return nickname == null || nickname.length() == 0 ? p.getName() : nickname;
});
if (chat != null) {
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> ((Chat) chat).getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> ((Chat) chat).getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "").enableTriggerMode();
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "").enableTriggerMode();
}
manager.registerPlayerPlaceholder("%health%", 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
}
// Removed placeholder, keeping the implementation to avoid placeholder breaking for users on update
manager.registerPlayerPlaceholder(TabConstants.Placeholder.ESSENTIALS_NICK, -1, TabPlayer::getName);
private String formatTPS(double tps) {
return numberFormat.format(Math.min(20, tps));
if (chat != null) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, -1, () -> "");
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, -1, () -> "");
}
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
super.registerPlaceholders(manager);
}
}
@@ -1,22 +1,25 @@
package me.neznamy.tab.platforms.bukkit;
import com.earth2me.essentials.Essentials;
import com.viaversion.viaversion.api.Via;
import lombok.Getter;
import lombok.Setter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.event.TabLoadEvent;
import me.neznamy.tab.platforms.bukkit.event.TabPlayerLoadEvent;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.PetFix;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.Platform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.permission.LuckPerms;
@@ -29,109 +32,110 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
public class BukkitPlatform extends Platform {
public class BukkitPlatform extends BackendPlatform {
@Getter private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
@Getter private final BukkitPipelineInjector pipelineInjector = NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
/** Plugin instance for registering tasks and events */
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
private boolean libsDisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
private final Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API);
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
private Plugin viaVersion;
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport");
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
/**
* Constructs new instance with given plugin parameter
* @param plugin
* plugin instance
*
* @param plugin
* plugin instance
*/
public BukkitPlatform(JavaPlugin plugin) {
super(new BukkitPacketBuilder());
Preconditions.checkNotNull(plugin, "plugin");
this.plugin = plugin;
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VIAVERSION)) {
if (ReflectionUtils.classExists("com.viaversion.viaversion.api.Via")) {
viaVersion = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.VIAVERSION);
} else {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion(TabConstants.Plugin.VIAVERSION) + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
return new LuckPerms(getPluginVersion("LuckPerms"));
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LUCKPERMS)) {
return new LuckPerms(getPluginVersion(TabConstants.Plugin.LUCKPERMS));
} else if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider == null) return new None();
return new Vault(provider.getProvider(), getPluginVersion("Vault"));
return new Vault(provider.getProvider(), getPluginVersion(TabConstants.Plugin.VAULT));
} else {
return new None();
}
}
@Override
public void loadFeatures() {
if (Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
try {
Class.forName("com.viaversion.viaversion.api.Via");
viaVersion = Bukkit.getPluginManager().getPlugin("ViaVersion");
} catch (ClassNotFoundException e) {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion("ViaVersion") + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
if (Bukkit.getPluginManager().isPluginEnabled("Tablisknu")) {
TAB.getInstance().sendConsoleMessage("&cDetected plugin \"Tablisknu\", which causes TAB to not work properly. Consider removing the plugin.", true);
}
if (Bukkit.getPluginManager().isPluginEnabled("SkBee")) {
TAB.getInstance().sendConsoleMessage("&cDetected plugin \"SkBee\", which causes TAB's scoreboard to not show. Consider removing the plugin.", true);
}
TAB tab = TAB.getInstance();
if (tab.getConfiguration().isPipelineInjection())
tab.getFeatureManager().registerFeature(TabConstants.Feature.PIPELINE_INJECTION, new BukkitPipelineInjector());
new BukkitPlaceholderRegistry().registerPlaceholders(tab.getPlaceholderManager());
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.enabled", true)) {
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && tab.getServerVersion().getMinorVersion() >= 8) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, new BukkitNameTagX(plugin));
} else {
tab.getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
tab.loadUniversalFeatures();
if (tab.getConfiguration().getConfig().getBoolean("bossbar.enabled", false)) {
if (tab.getServerVersion().getMinorVersion() < 9) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new WitherBossBar(plugin));
} else {
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new BossBarManagerImpl());
}
}
if (tab.getServerVersion().getMinorVersion() >= 9 && tab.getConfiguration().getConfig().getBoolean("fix-pet-names.enabled", false))
tab.getFeatureManager().registerFeature(TabConstants.Feature.PET_FIX, new PetFix());
if (tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.enabled", false))
tab.getFeatureManager().registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, new PerWorldPlayerList(plugin));
if (placeholderAPI && tab.getConfiguration().getConfig().getBoolean("placeholders.register-tab-expansion", true)) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
TAB.getInstance().getPlaceholderManager().setTabExpansion(expansion);
}
for (Player p : getOnlinePlayers()) {
tab.addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
}
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public String getPluginVersion(String plugin) {
Preconditions.checkNotNull(plugin, "plugin");
Plugin pl = Bukkit.getPluginManager().getPlugin(plugin);
return pl == null ? null : pl.getDescription().getVersion();
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public NameTag getUnlimitedNametags() {
return new BukkitNameTagX(plugin);
}
@Override
public TabExpansion getTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return null;
}
@Override
public TabFeature getPetFix() {
return new PetFix();
}
@Override
public @Nullable TabFeature getPerWorldPlayerlist() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
* @return online players from Bukkit API
*
* @return online players from Bukkit API
*/
@SuppressWarnings("unchecked")
private Player[] getOnlinePlayers() {
@@ -152,7 +156,6 @@ public class BukkitPlatform extends Platform {
@Override
public void registerUnknownPlaceholder(String identifier) {
Preconditions.checkNotNull(identifier, "identifier");
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
if (identifier.startsWith("%rel_")) {
//relational placeholder
@@ -189,82 +192,37 @@ public class BukkitPlatform extends Platform {
}
}
@Override
public void callLoadEvent() {
Bukkit.getPluginManager().callEvent(new TabLoadEvent());
}
@Override
public void callLoadEvent(TabPlayer player) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> Bukkit.getPluginManager().callEvent(new TabPlayerLoadEvent(player)));
}
/**
* Returns status of LibsDisguises plugin presence
* @return {@code true} if plugin is enabled, {@code false} if not
*/
public boolean isLibsDisguisesEnabled() {
return libsDisguises;
}
/**
* Sets LibsDisguises presence status to provided value. This is used
* to disable LibsDisguises hook in case the plugin is not correctly loaded
* for any reason to avoid error spam in the hook.
* @param enabled
* New status of LibsDisguises presence
*/
public void setLibsDisguisesEnabled(boolean enabled) {
libsDisguises = enabled;
}
/**
* Returns Essentials' main class if the plugin is installed, {@code null} if not
* @return Essentials instance or {@code null} if plugin is not installed
*/
public Essentials getEssentials() {
return (Essentials) essentials;
}
@Override
public boolean isProxy() {
return false;
}
@Override
public String getConfigName() {
return "bukkitconfig.yml";
}
/**
* Gets protocol version of requested player and returns it.
* @param player
* Player to get protocol version of
* @return protocol version of the player
*
* @param player
* Player to get protocol version of
* @return protocol version of the player
*/
public int getProtocolVersion(Player player) {
if (protocolSupport){
if (protocolSupport) {
int version = getProtocolVersionPS(player);
//some PS versions return -1 on unsupported server versions instead of throwing exception
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
}
if (viaVersion != null) {
return getProtocolVersionVia(player, 0);
return getProtocolVersionVia(player.getUniqueId(), player.getName(), 0);
}
return TAB.getInstance().getServerVersion().getNetworkId();
}
/**
* Returns protocol version of requested player using ProtocolSupport
* @param player
* Player to get protocol version of
* @return protocol version of the player using ProtocolSupport
*
* @param player
* Player to get protocol version of
* @return protocol version of the player using ProtocolSupport
*/
private int getProtocolVersionPS(Player player){
private int getProtocolVersionPS(Player player) {
try {
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + player.getName() + " (online=" + player.isOnline() + ")");
return version;
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ProtocolSupport", player.getName()), e);
@@ -273,31 +231,20 @@ public class BukkitPlatform extends Platform {
}
/**
* Returns protocol version of requested player using ViaVersion
* @param player
* Player to get protocol version of
* @return protocol version of the player using ViaVersion
* Sends console message using ConsoleCommandSender, due to
* Paper not translating colors correctly in Logger messages
* and to allow RGB (at least on Paper, doesn't work on spigot)
*
* @param message
* Message to send
* @param translateColors
* Whether color codes should be translated or not
*/
private int getProtocolVersionVia(Player player, int retryLevel){
try {
if (retryLevel == 10) {
TAB.getInstance().debug("Failed to get protocol version of " + player.getName() + " after 10 retries");
return TAB.getInstance().getServerVersion().getNetworkId();
}
int version = Via.getAPI().getPlayerVersion(player.getUniqueId());
if (version == -1) {
if (!player.isOnline()) return TAB.getInstance().getServerVersion().getNetworkId();
Thread.sleep(5);
return getProtocolVersionVia(player, retryLevel + 1);
}
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
return version;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return -1;
} catch (Exception | LinkageError e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ViaVersion v%s", player.getName(), viaVersion.getDescription().getVersion()), e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
@Override
public void sendConsoleMessage(String message, boolean translateColors) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + (translateColors ?
EnumChatFormat.color(RGBUtils.getInstance().convertToBukkitFormat(message,
TAB.getInstance().getServerVersion().getMinorVersion() >= 16))
: message));
}
}
@@ -1,49 +1,51 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.SupplierWithException;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Main class for Bukkit platform
*/
public class Main extends JavaPlugin {
public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable(){
getLogger().info(EnumChatFormat.color("&7Server version: " + Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")"));
if (!isVersionSupported()){
public void onEnable() {
String version = Bukkit.getBukkitVersion().split("-")[0];
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
if (!isVersionSupported()) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
BukkitPlatform platform = new BukkitPlatform(this);
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]),
Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")", getDataFolder(), getLogger()));
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), version + " (" + serverPackage + ")", getDataFolder(), getLogger()));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
getLogger().info(EnumChatFormat.color("&cUnknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(platform), this);
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 5304);
metrics.addCustomChart(new SimplePie("unlimited_nametag_mode_enabled", () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie("placeholderapi", () -> Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie("permission_system", () -> TAB.getInstance().getGroupManager().getPlugin().getName()));
metrics.addCustomChart(new SimplePie("server_version", () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPlugin().getName()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
PluginCommand cmd = Bukkit.getPluginCommand("tab");
if (cmd == null) return;
TABCommand command = new TABCommand();
@@ -53,7 +55,7 @@ public class Main extends JavaPlugin {
@Override
public void onDisable() {
//null check due to L31 return making L34 not run
//null check due to compatibility check making instance not get set on unsupported versions
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
@@ -61,42 +63,58 @@ public class Main extends JavaPlugin {
* Initializes all used NMS classes, constructors, fields and methods.
* Returns {@code true} if everything went successfully and version is marked as compatible,
* {@code false} if anything went wrong or version is not marked as compatible.
* @return {@code true} if server version is compatible, {@code false} if not
*
* @return {@code true} if server version is compatible, {@code false} if not
*/
private boolean isVersionSupported(){
private boolean isVersionSupported() {
List<String> supportedVersions = Arrays.asList(
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1", "v1_18_R2");
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1",
"v1_18_R2", "v1_19_R1", "v1_19_R2");
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
long time = System.currentTimeMillis();
NMSStorage.setInstance(new NMSStorage());
NMSStorage.setInstance(getNMSLoader());
if (supportedVersions.contains(serverPackage)) {
getLogger().info(EnumChatFormat.color("&7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
return true;
} else {
getLogger().info(EnumChatFormat.color("&cNo compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.x - 1.18.2. Disabling just to stay safe."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.2 - 1.19.3. Disabling just to stay safe."));
}
} catch (Exception ex) {
} catch (IllegalStateException ex) {
if (supportedVersions.contains(serverPackage)) {
getLogger().log(Level.SEVERE, EnumChatFormat.color("&cYour server version is marked as compatible, but a compatibility issue was found. Please report the error below (include your server version & fork too)"), ex);
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report this issue (include your server version & fork too)"));
} else {
getLogger().info(EnumChatFormat.color("&cYour server version is completely unsupported. This plugin version only supports 1.5.x - 1.18.2. Disabling."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.2 - 1.19.3. Disabling."));
}
}
return false;
}
private NMSStorage getNMSLoader() {
List<SupplierWithException<NMSStorage>> loaders = new ArrayList<>();
loaders.add(BukkitLegacyNMSStorage::new);
loaders.add(BukkitModernNMSStorage::new);
loaders.add(MojangModernNMSStorage::new);
loaders.add(ThermosNMSStorage::new);
for (SupplierWithException<NMSStorage> loader : loaders) {
try {
return loader.get();
} catch (Exception ignored) {}
}
throw new IllegalStateException("Unsupported server version");
}
/**
* Command handler for /tab command
*/
public static class TABCommand implements CommandExecutor, TabCompleter {
private static class TABCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (TAB.getInstance().isDisabled()) {
if (TabAPI.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
}
@@ -1,33 +1,34 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.*;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import lombok.NonNull;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarFlag;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import com.earth2me.essentials.Essentials;
import com.mojang.authlib.GameProfile;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import me.libraryaddict.disguise.DisguiseAPI;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
@@ -48,26 +49,24 @@ public class BukkitTabPlayer extends ITabPlayer {
/**
* Constructs new instance with given bukkit player and protocol version
* @param p
* bukkit player
* @param protocolVersion
* Player's protocol network id
*
* @param p
* bukkit player
* @param protocolVersion
* Player's protocol network id
*/
public BukkitTabPlayer(Player p, int protocolVersion){
super(p, p.getUniqueId(), p.getName(), "N/A", p.getWorld().getName(), protocolVersion);
public BukkitTabPlayer(Player p, int protocolVersion) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion, true);
try {
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
if (NMSStorage.getInstance().CHANNEL != null)
channel = (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection or channel of " + p.getName(), e);
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection of " + p.getName(), e);
}
}
@Override
public boolean hasPermission(String permission) {
Preconditions.checkNotNull(permission, "permission");
public boolean hasPermission(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
@@ -96,6 +95,8 @@ public class BukkitTabPlayer extends ITabPlayer {
} else {
handleVia((PacketPlayOutBoss) nmsPacket);
}
} else if (nmsPacket instanceof PacketPlayOutChat) {
getPlayer().sendMessage(((PacketPlayOutChat) nmsPacket).getMessage().toLegacyText());
} else {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
@@ -109,8 +110,9 @@ public class BukkitTabPlayer extends ITabPlayer {
* Handles PacketPlayOutBoss packet send request using Bukkit API,
* since the API offers everything we need and makes us not need to
* deal with NMS code at all.
* @param packet
* packet request to handle using Bukkit API
*
* @param packet
* packet request to handle using Bukkit API
*/
private void handle(PacketPlayOutBoss packet) {
BossBar bar = bossBars.get(packet.getId());
@@ -157,8 +159,9 @@ public class BukkitTabPlayer extends ITabPlayer {
/**
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
* 1.8 servers using ViaVersion API instead of using Wither.
* @param packet
* packet request to handle using ViaVersion API
*
* @param packet
* packet request to handle using ViaVersion API
*/
private void handleVia(PacketPlayOutBoss packet) {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
@@ -202,12 +205,13 @@ public class BukkitTabPlayer extends ITabPlayer {
/**
* Sets BossBar flag to requested target value.
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
@@ -216,12 +220,13 @@ public class BukkitTabPlayer extends ITabPlayer {
/**
* Sets BossBar flag to requested target value.
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
@@ -237,8 +242,8 @@ public class BukkitTabPlayer extends ITabPlayer {
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
return DisguiseAPI.isDisguised(getPlayer());
} catch (LinkageError e) {
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, getPlayer());
} catch (LinkageError | ReflectiveOperationException e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
@@ -246,7 +251,6 @@ public class BukkitTabPlayer extends ITabPlayer {
}
}
@SuppressWarnings("unchecked")
@Override
public Skin getSkin() {
try {
@@ -272,10 +276,7 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public boolean isVanished() {
Essentials essentials = ((BukkitPlatform)TAB.getInstance().getPlatform()).getEssentials();
if (essentials != null && essentials.getUser(getUniqueId()).isVanished()) return true;
List<MetadataValue> metadata = getPlayer().getMetadata("vanished");
return !metadata.isEmpty() && metadata.get(0).asBoolean();
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
}
@SuppressWarnings("deprecation")
@@ -283,4 +284,15 @@ public class BukkitTabPlayer extends ITabPlayer {
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public Channel getChannel() {
try {
if (NMSStorage.getInstance().CHANNEL != null)
return (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + getName(), e);
}
return null;
}
}
@@ -1,25 +0,0 @@
package me.neznamy.tab.platforms.bukkit.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* Bukkit event that is called each time TAB fully loads. This includes server startup, reload,
* /tab reload and /plugman reload tab
*
* @deprecated for removal, use {@link me.neznamy.tab.api.event.plugin.TabLoadEvent}
*/
@Deprecated
public class TabLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@Override
public HandlerList getHandlers(){
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.bukkit.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import me.neznamy.tab.api.TabPlayer;
/**
* Bukkit event that is called when player is successfully loaded after joining. This also includes plugin reloading.
*
* @deprecated for removal, use {@link me.neznamy.tab.api.event.player.PlayerLoadEvent}
*/
@Deprecated
public class TabPlayerLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TabPlayer player;
public TabPlayerLoadEvent(TabPlayer player) {
this.player = player;
}
@Override
public HandlerList getHandlers(){
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
public TabPlayer getPlayer() {
return player;
}
}
@@ -1,10 +1,13 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.Getter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.TabExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -18,78 +21,43 @@ import java.util.WeakHashMap;
*/
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all placeholder values for all players */
/** Map holding all values for all players for easy and high-performance access */
private final Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
@Getter private final String author = "NEZNAMY";
@Getter private final String identifier = TabConstants.PLUGIN_ID;
@Getter private final String version = TabConstants.PLUGIN_VERSION;
@Override
public boolean persist(){
public boolean persist() {
return true;
}
@Override
public boolean canRegister(){
return true;
}
@Override
public @NotNull String getAuthor(){
return "NEZNAMY";
}
@Override
public @NotNull String getIdentifier(){
return "tab";
}
@Override
public @NotNull String getVersion() {
return TabConstants.PLUGIN_VERSION;
}
@Override
public String onPlaceholderRequest(Player player, @NotNull String identifier){
if (player == null) return "";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
public String onPlaceholderRequest(Player player, @NotNull String identifier) {
if (identifier.startsWith("replace_")) {
String placeholder = "%" + identifier.substring(8) + "%";
String output = PlaceholderAPI.setPlaceholders(player, placeholder);
return TAB.getInstance().getPlaceholderManager().findReplacement(placeholder, output);
String text = "%" + identifier.substring(8) + "%";
String textBefore;
do {
textBefore = text;
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
text = text.replace(placeholder, TabAPI.getInstance().getPlaceholderManager().findReplacement(placeholder,
PlaceholderAPI.setPlaceholders(player, placeholder)));
}
} while (!textBefore.equals(text));
return text;
}
return values.computeIfAbsent(p, pl -> new HashMap<>()).get(identifier);
if (identifier.startsWith("placeholder_")) {
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
}
if (player == null) return "<Player cannot be null>";
TabPlayer p = TabAPI.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
}
@Override
public void setScoreboardVisible(TabPlayer player, boolean visible) {
values.computeIfAbsent(player, p -> new HashMap<>()).put("scoreboard_visible", visible ? "Enabled" : "Disabled");
}
@Override
public void setScoreboardName(TabPlayer player, String name) {
values.computeIfAbsent(player, p -> new HashMap<>()).put("scoreboard_name", name);
}
@Override
public void setBossBarVisible(TabPlayer player, boolean visible) {
values.computeIfAbsent(player, p -> new HashMap<>()).put("bossbar_visible", visible ? "Enabled" : "Disabled");
}
@Override
public void setNameTagPreview(TabPlayer player, boolean previewing) {
values.computeIfAbsent(player, p -> new HashMap<>()).put("ntpreview", previewing ? "Enabled" : "Disabled");
}
@Override
public void setPlaceholderValue(TabPlayer player, String placeholder, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put("placeholder_" + placeholder.substring(1, placeholder.length()-1), value);
}
@Override
public void setPropertyValue(TabPlayer player, String property, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(property, value);
}
@Override
public void setRawPropertyValue(TabPlayer player, String property, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(property + "_raw", value);
public void setValue(TabPlayer player, String key, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
}
}
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.api.TabAPI;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -15,7 +16,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
/**
@@ -24,32 +25,30 @@ import me.neznamy.tab.shared.TAB;
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener {
//plugin instance
private final JavaPlugin plugin;
//config options
private final boolean allowBypass = TAB.getInstance().getConfiguration().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TAB.getInstance().getConfiguration().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/** Config options */
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/**
* Constructs new instance with given parameters and loads config options
* @param plugin - plugin instance
*
* @param plugin
* plugin instance
*/
public PerWorldPlayerList(JavaPlugin plugin) {
super("Per world PlayerList", null);
this.plugin = plugin;
TAB.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
Bukkit.getPluginManager().registerEvents(this, plugin);
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
}
@Override
public void load(){
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getOnlinePlayers().forEach(this::checkPlayer));
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
}
@Override
public void unload(){
public void unload() {
for (Player p : Bukkit.getOnlinePlayers()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
p.showPlayer(pl);
@@ -72,8 +71,16 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
* Performs visibility check on the player. Shows players this player should see and does not,
* hides players the player should not see, shows the player to those who should see player and hides
* from those, who should not see.
*
* @param p
* Player to update
*/
private void checkPlayer(Player p) {
for (Player all : Bukkit.getOnlinePlayers()){
for (Player all : Bukkit.getOnlinePlayers()) {
if (all == p) continue;
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
@@ -82,6 +89,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
}
}
/**
* Returns {@code true} if viewer should see target player, {@code false} if not.
* @param viewer
* Player viewing the TabList
* @param target
* Target displayed in the TabList
* @return {@code true} if viewer should see target, {@code false} if not.
*/
private boolean shouldSee(Player viewer, Player target) {
if (target == viewer) return true;
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
@@ -1,13 +1,19 @@
package me.neznamy.tab.platforms.bukkit.features;
import java.util.*;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.shared.TAB;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import java.util.WeakHashMap;
/**
* A feature to disable minecraft 1.9+ feature making tamed animals
@@ -19,13 +25,13 @@ import me.neznamy.tab.shared.TAB;
*/
public class PetFix extends TabFeature {
//nms storage
/** NMS Storage reference for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
//DataWatcher position of pet owner field
/** DataWatcher position of pet owner field */
private final int petOwnerPosition = getPetOwnerPosition();
//logger of last interacts to prevent feature not working on 1.16
/** Logger of last interacts to prevent feature not working on 1.16 */
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
/**
@@ -45,16 +51,17 @@ public class PetFix extends TabFeature {
*/
public PetFix() {
super("Pet name fix", null);
TAB.getInstance().debug("Loaded PetFix feature");
TabAPI.getInstance().debug("Loaded PetFix feature");
}
/**
* Returns position of pet owner field based on server version
* @return position of pet owner field based on server version
*
* @return position of pet owner field based on server version
*/
private int getPetOwnerPosition() {
if (nms.getMinorVersion() >= 17) {
//1.17.x, 1.18.x
//1.17.x, 1.18.x, 1.19.x
return 18;
} else if (nms.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
@@ -72,25 +79,31 @@ public class PetFix extends TabFeature {
}
/**
* Cancels a packet if previous one arrived with no delay to prevent double toggle on 1.16
* @throws ReflectiveOperationException
* if thrown by reflective operation
* Cancels a packet if previous one arrived with no delay to prevent double toggle since 1.16
*
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (nms.PacketPlayInUseEntity.isInstance(packet)) {
if (lastInteractFix.containsKey(sender) && (System.currentTimeMillis() - lastInteractFix.get(sender) < INTERACT_COOLDOWN)) {
if (nms.PacketPlayInUseEntity.isInstance(packet) && isInteract(nms.PacketPlayInUseEntity_ACTION.get(packet))) {
if (System.currentTimeMillis() - lastInteractFix.getOrDefault(sender, 0L) < INTERACT_COOLDOWN) {
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
return true;
}
if (isInteract(nms.PacketPlayInUseEntity_ACTION.get(packet))) {
//this is the first packet, saving player so the next packet can be cancelled
lastInteractFix.put(sender, System.currentTimeMillis());
}
//this is the first packet, saving player so the next packet can be cancelled
lastInteractFix.put(sender, System.currentTimeMillis());
}
return false;
}
/**
* Checks if the provided entity use action is INTERACT or not.
*
* @param action
* Action to check
* @return {@code true} if action is INTERACT, {@code false} if not.
*/
private boolean isInteract(Object action) {
if (nms.getMinorVersion() >= 17) {
return nms.PacketPlayInUseEntity$d.isInstance(action);
@@ -101,22 +114,31 @@ public class PetFix extends TabFeature {
/**
* Removes pet owner field from DataWatcher
* @throws ReflectiveOperationException
* if thrown by reflective operation
*
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (nms.PacketPlayOutEntityMetadata.isInstance(packet)) {
if (PacketPlayOutEntityMetadata.CLASS.isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) nms.PacketPlayOutEntityMetadata_LIST.get(packet);
List<Object> items = (List<Object>) PacketPlayOutEntityMetadata.LIST.get(packet);
if (items == null) return;
try {
for (Object item : items) {
if (item == null) continue;
if (nms.DataWatcherObject_SLOT.getInt(nms.DataWatcherItem_TYPE.get(item)) == petOwnerPosition) {
Object value = nms.DataWatcherItem_VALUE.get(item);
if (value instanceof java.util.Optional || value instanceof com.google.common.base.Optional) {
int slot;
Object value;
if (nms.is1_19_3Plus()) {
slot = DataWatcher.DataValue_POSITION.getInt(item);
value = DataWatcher.DataValue_VALUE.get(item);
} else {
slot = DataWatcherObject.SLOT.getInt(DataWatcherItem.TYPE.get(item));
value = DataWatcherItem.VALUE.get(item);
}
if (slot == petOwnerPosition) {
if (value instanceof Optional || value instanceof com.google.common.base.Optional) {
removedEntry = item;
}
}
@@ -126,13 +148,13 @@ public class PetFix extends TabFeature {
onPacketSend(receiver, packet);
}
if (removedEntry != null) items.remove(removedEntry);
} else if (nms.PacketPlayOutSpawnEntityLiving.isInstance(packet) && nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER != null) {
} else if (PacketPlayOutSpawnEntityLiving.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLiving.DATA_WATCHER != null) {
//<1.15
DataWatcher watcher = DataWatcher.fromNMS(nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.get(packet));
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLiving.DATA_WATCHER.get(packet));
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
if (petOwner != null && (petOwner.getValue() instanceof java.util.Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
if (petOwner != null && (petOwner.getValue() instanceof Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
watcher.removeValue(petOwnerPosition);
nms.setField(packet, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, watcher.toNMS());
PacketPlayOutSpawnEntityLiving.DATA_WATCHER.set(packet, watcher.build());
}
}
}
@@ -1,6 +1,5 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.api.util.Preconditions;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -13,7 +12,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
@@ -22,15 +21,16 @@ import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
*/
public class WitherBossBar extends BossBarManagerImpl implements Listener {
//distance of wither in blocks
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/**
* Constructs a new instance of the class
* @param plugin - plugin instance
* Constructs a new instance of the class and registers events and task
*
* @param plugin
* plugin instance
*/
public WitherBossBar(JavaPlugin plugin) {
Preconditions.checkNotNull(plugin, "plugin");
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
@@ -44,6 +44,9 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
teleport();
}
/**
* Updates Wither's location for all online players
*/
private void teleport() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
@@ -1,108 +1,52 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
/**
* A class representing an armor stand attached to a player (if the feature is enabled)
* A class representing an armor stand attached to a player
*/
public class BukkitArmorStand implements ArmorStand {
public class BukkitArmorStand extends BackendArmorStand {
//entity id counter to pick unique entity IDs
private static int idCounter = 2000000000;
//NameTag feature
private final BukkitNameTagX manager = (BukkitNameTagX) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
private final BukkitArmorStandManager asm;
//owner of the armor stand
private final TabPlayer owner;
//instance of Bukkit player
/** Owner as a Bukkit player */
private final Player player;
//offset in blocks, 0 for original height
private double yOffset;
/** Entity destroy packet */
protected final TabPacket destroyPacket = new PacketPlayOutEntityDestroy(entityId);
//entity ID of this armor stand
private final int entityId = idCounter++;
//unique ID of this armor stand
private final UUID uuid = UUID.randomUUID();
//sneaking flag of armor stands
private boolean sneaking;
//armor stand visibility
private boolean visible;
//property dedicated to this armor stand
private final Property property;
//if offset is static or dynamic based on other armor stands
private final boolean staticOffset;
//entity destroy packet
private final PacketPlayOutEntityDestroy destroyPacket = new PacketPlayOutEntityDestroy(entityId);
public BukkitArmorStand(BukkitArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
this.asm = asm;
this.owner = owner;
this.staticOffset = staticOffset;
/**
* Constructs new instance with given parameters.
*
* @param asm
* Armor stand manager which this armor stand belongs to
* @param owner
* Owner of the armor stand
* @param propertyName
* Name of refresh property to use
* @param yOffset
* Offset in blocks
* @param staticOffset
* {@code true} if offset is static, {@code false} if not
*/
public BukkitArmorStand(BackendNameTagX feature, BackendArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
super(feature, asm, owner, propertyName, yOffset, staticOffset);
player = (Player) owner.getPlayer();
this.yOffset = yOffset;
this.property = owner.getProperty(propertyName);
visible = getVisibility();
}
@Override
public void refresh() {
visible = getVisibility();
updateMetadata();
}
@Override
public Property getProperty() {
return property;
}
@Override
public boolean hasStaticOffset() {
return staticOffset;
}
@Override
public double getOffset() {
return yOffset;
}
@Override
public void setOffset(double offset) {
if (yOffset == offset) return;
yOffset = offset;
for (TabPlayer all : asm.getNearbyPlayers()) {
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_OFFSET_CHANGE);
}
sneaking = player.isSneaking();
}
@Override
@@ -112,223 +56,100 @@ public class BukkitArmorStand implements ArmorStand {
}
}
@Override
public void destroy() {
for (TabPlayer all : asm.getNearbyPlayers()) all.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
@Override
public void destroy(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
@Override
public void teleport() {
for (TabPlayer all : asm.getNearbyPlayers()) {
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
}
@Override
public void teleport(TabPlayer viewer) {
if (!asm.isNearby(viewer) && viewer != owner) {
asm.spawn(viewer);
} else {
viewer.sendCustomPacket(getTeleportPacket(viewer), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
}
@Override
public void sneak(boolean sneaking) {
if (this.sneaking == sneaking) return; //idk
this.sneaking = sneaking;
for (TabPlayer viewer : asm.getNearbyPlayers()) {
if (viewer.getVersion().getMinorVersion() == 14 && !manager.isArmorStandsAlwaysVisible()) {
//1.14.x client sided bug, de-spawning completely
if (sneaking) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SNEAK);
} else {
spawn(viewer);
}
} else {
//respawning so there's no animation and it's instant
respawn(viewer);
}
}
}
@Override
public void updateVisibility(boolean force) {
boolean visibility = getVisibility();
if (visible != visibility || force) {
refresh();
}
}
@Override
public int getEntityId() {
return entityId;
}
/**
* Returns teleport packet for specified viewer
* @param viewer - player to get location for
* @return teleport packet
* Returns location where armor stand should be at time of calling.
* This takes into account everything that affects height, including
* viewer's game version.
*
* @param viewer
* Player looking at the armor stand
* @return Location where armor stand should be for specified viewer
*/
public PacketPlayOutEntityTeleport getTeleportPacket(TabPlayer viewer) {
return new PacketPlayOutEntityTeleport(entityId, getArmorStandLocationFor(viewer));
}
public Location getLocation(TabPlayer viewer) {
Location loc = player.getLocation();
double y = loc.getY();
/**
* Updates armor stand's metadata
*/
public void updateMetadata() {
for (TabPlayer viewer : asm.getNearbyPlayers()) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
}
/**
* Returns general visibility rule for everyone with limited info
* @return true if armor stand should be visible, false if not
*/
public boolean getVisibility() {
if (manager.isArmorStandsAlwaysVisible()) return true;
if (owner.isDisguised() || manager.getVehicleManager().isOnBoat(owner)) return false;
return !owner.hasInvisibilityPotion() && owner.getGamemode() != 3 && !manager.hasHiddenNametag(owner) && property.get().length() > 0;
}
/**
* Returns general location where armor stand should be at time of calling
* @return Location where armor stand should be for everyone
*/
public Location getLocation() {
double x = player.getLocation().getX();
double y = getY() + yOffset + 2;
double z = player.getLocation().getZ();
if (player.isSleeping()) {
y -= 1.76;
} else {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
y -= (sneaking ? 0.45 : 0.18);
} else {
y -= (sneaking ? 0.30 : 0.18);
}
}
return new Location(null,x,y,z);
}
/**
* Returns Y where player is based on player's vehicle due to bukkit API bug
* @return correct player's Y
*/
protected double getY() {
//1.14+ server sided bug
Entity vehicle = player.getVehicle();
if (vehicle != null) {
if (vehicle.getType().toString().contains("HORSE")) { //covering all 3 horse types
return vehicle.getLocation().getY() + 0.85;
if (vehicle.getType().toString().contains("horse")) { //covering all 3 horse types
y = vehicle.getLocation().getY() + 0.85;
}
if (vehicle.getType().toString().equals("DONKEY")) { //1.11+
return vehicle.getLocation().getY() + 0.525;
if (vehicle.getType().toString().equals("donkey")) { //1.11+
y = vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
return vehicle.getLocation().getY() + 0.325;
y = vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("STRIDER")) { //1.16+
return vehicle.getLocation().getY() + 1.15;
if (vehicle.getType().toString().equals("strider")) { //1.16+
y = vehicle.getLocation().getY() + 1.15;
}
}
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
return player.getLocation().getY()-1.22;
}
return player.getLocation().getY();
}
private boolean isSwimming() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
}
/**
* Creates data watcher with specified display name for viewer
* @param displayName - armor stand name
* @param viewer - player to apply checks against
* @return DataWatcher for viewer
*/
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
byte flag = 32; //invisible
if (sneaking) flag += (byte)2;
datawatcher.helper().setEntityFlags(flag);
datawatcher.helper().setCustomName(displayName, viewer.getVersion());
boolean visibility;
if (isNameVisiblyEmpty(displayName) || !((Player) viewer.getPlayer()).canSee(player) ||
manager.hasHiddenNametag(owner, viewer) || manager.hasHiddenNameTagVisibilityView(viewer)) {
visibility = false;
} else {
visibility = visible;
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
y = loc.getY()-1.22;
}
}
datawatcher.helper().setCustomNameVisible(visibility);
y += getYAdd(player.isSleeping(), sneaking, viewer);
return new Location(null, loc.getX(), y, loc.getZ());
}
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x()) datawatcher.helper().setArmorStandFlags((byte)16);
return datawatcher;
@Override
public void updateMetadata(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
@Override
public void sendTeleportPacket(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityTeleport(entityId, getLocation(viewer)),
TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
/**
* Returns true if display name is in fact empty, for example only containing color codes
* @param displayName - string to check
* @return true if it's empty, false if not
* Returns {@code true} if owner is swimming, {@code false} if not
* @return {@code true} if owner is swimming, {@code false} if not
*/
private boolean isNameVisiblyEmpty(String displayName) {
if (displayName.length() == 0) return true;
if (!displayName.startsWith(EnumChatFormat.COLOR_STRING) && !displayName.startsWith("&") && !displayName.startsWith("#")) return false;
String text = IChatBaseComponent.fromColoredText(displayName).toRawText();
if (text.contains(" ")) text = text.replace(" ", "");
return text.length() == 0;
private boolean isSwimming() {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
}
/**
* Returns list of packets to send to make armor stand spawn with metadata
* @param viewer - viewer to apply relational placeholders for
* @return List of packets that spawn the armor stand
*/
public TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = getVisibility();
private TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = calculateVisibility();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), null),
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), dataWatcher),
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), dataWatcher),
};
}
}
/**
* Returns location where armor stand should be for specified viewer
* @param viewer - player to get location for
* @return location of armor stand
* Creates data watcher with specified display name for viewer
*
* @param displayName
* armor stand name
* @param viewer
* player to apply checks against
* @return DataWatcher for viewer
*/
public Location getArmorStandLocationFor(TabPlayer viewer) {
return viewer.getVersion().getMinorVersion() == 8 && !manager.isMarkerFor18x() ? getLocation().clone().add(0,-2,0) : getLocation();
}
@Override
public void respawn(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
Runnable spawn = () -> spawn(viewer);
if (viewer.getVersion().getMinorVersion() == 8) {
//1.8.0 client sided bug
TAB.getInstance().getCPUManager().runTaskLater(50, manager, TabConstants.CpuUsageCategory.V1_8_0_BUG_COMPENSATION, spawn);
} else {
spawn.run();
}
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags((byte) (sneaking ? 34 : 32));
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(!shouldBeInvisibleFor(viewer, displayName) && visible);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x())
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
}
@@ -1,161 +1,154 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.nametags.unlimited.NameTagX;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
/**
* The core class for unlimited NameTag mode
*/
public class BukkitNameTagX extends NameTagX {
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
//bukkit event listener
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/** Bukkit event listener */
private final EventListener eventListener = new EventListener(this);
private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
/**
* Constructs new instance with given parameters and loads config options
* @param plugin - plugin instance
* Constructs new instance with given parameter, loads config options, registers events
* and registers sub-features.
*
* @param plugin
* plugin instance
*/
public BukkitNameTagX(JavaPlugin plugin) {
super(BukkitArmorStandManager::new);
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_PACKET_LISTENER, new PacketListener(this));
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER, vehicleManager);
}
@Override
public void load() {
super.load();
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(all)) continue;
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, all);
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.PacketPlayInUseEntity_ENTITY.set(packet, getEntityId(attacked));
}
}
startVisibilityRefreshTask();
return false;
}
private void startVisibilityRefreshTask() {
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(500, this, TabConstants.CpuUsageCategory.REFRESHING_NAME_TAG_VISIBILITY, () -> {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(p)) continue;
getArmorStandManager(p).updateVisibility(false);
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleport.CLASS.isInstance(packet)) {
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleport.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroy.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroy.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy(receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy(receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroy.ENTITIES.get(packet));
}
});
}
@Override
public void unload() {
super.unload();
HandlerList.unregisterAll(eventListener);
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
super.onJoin(connectedPlayer);
if (isPlayerDisabled(connectedPlayer)) return;
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, connectedPlayer);
spawnArmorStands(connectedPlayer, viewer);
}
}
@Override
public boolean isOnBoat(TabPlayer player) {
return vehicleManager != null && vehicleManager.isOnBoat(player);
}
private void spawnArmorStands(TabPlayer viewer, TabPlayer target) {
if (target == viewer || isPlayerDisabled(target)) return;
if (((Player) viewer.getPlayer()).getWorld() != ((Player) target.getPlayer()).getWorld()) return;
if (getDistance(viewer, target) <= 48) {
if (((Player)viewer.getPlayer()).canSee((Player)target.getPlayer()) && !target.isVanished()) getArmorStandManager(target).spawn(viewer);
}
}
@Override
public void setNameTagPreview(TabPlayer player, boolean status) {
if (status) {
getArmorStandManager(player).spawn(player);
} else {
getArmorStandManager(player).destroy(player);
}
}
@Override
public void resumeArmorStands(TabPlayer player) {
if (isPlayerDisabled(player)) return;
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(viewer, player);
}
}
@Override
public void pauseArmorStands(TabPlayer player) {
if (isPlayerDisabled(player)) return;
getArmorStandManager(player).destroy();
}
@Override
public void updateNameTagVisibilityView(TabPlayer player) {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
getArmorStandManager(all).updateVisibility(true);
}
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
super.onQuit(disconnectedPlayer);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
getArmorStandManager(all).unregisterPlayer(disconnectedPlayer);
}
getArmorStandManager(disconnectedPlayer).destroy();
TAB.getInstance().getCPUManager().runTaskLater(500, this, TabConstants.CpuUsageCategory.PLAYER_QUIT, () -> getArmorStandManager(disconnectedPlayer).destroy());
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
super.onWorldChange(p, from, to);
if (isUnlimitedDisabled(p.getServer(), to)) {
getDisabledUnlimitedPlayers().add(p);
updateTeamData(p);
} else if (getDisabledUnlimitedPlayers().remove(p)) {
updateTeamData(p);
}
if (isPreviewingNametag(p)) {
getArmorStandManager(p).spawn(p);
}
}
/**
* Returns flat distance between two players ignoring Y value
* @param player1 - first player
* @param player2 - second player
* @return flat distance in blocks
*/
private double getDistance(TabPlayer player1, TabPlayer player2) {
public double getDistance(TabPlayer player1, TabPlayer player2) {
Location loc1 = ((Player) player1.getPlayer()).getLocation();
Location loc2 = ((Player) player2.getPlayer()).getLocation();
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
}
@Override
public BukkitArmorStandManager getArmorStandManager(TabPlayer player) {
return (BukkitArmorStandManager) armorStandManagerMap.get(player);
public boolean areInSameWorld(TabPlayer player1, TabPlayer player2) {
return ((Player) player1.getPlayer()).getWorld() == ((Player) player1.getPlayer()).getWorld();
}
public VehicleRefresher getVehicleManager() {
return vehicleManager;
@Override
public boolean canSee(TabPlayer viewer, TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player)target.getPlayer());
}
@Override
public void unregisterListener() {
HandlerList.unregisterAll(eventListener);
}
@SuppressWarnings("deprecation")
@Override
public List<Integer> getPassengers(Object entity) {
Entity vehicle = (Entity) entity;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger().getEntityId());
} else {
return Collections.emptyList();
}
}
}
@Override
public void registerVehiclePlaceholder() {
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder(TabConstants.Placeholder.VEHICLE, 100, p -> {
Entity v = ((Player)p.getPlayer()).getVehicle();
//There's a bug in Bukkit 1.19.3 throwing NPE on .toString(), use default toString implementation
return v == null ? "" : v.getClass().getName() + "@" + Integer.toHexString(v.hashCode());
});
}
@Override
public Object getVehicle(TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@Override
public int getEntityId(Object entity) {
return ((Entity)entity).getEntityId();
}
@Override
public String getEntityType(Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@Override
public BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset) {
return new BukkitArmorStand(this, feature, owner, lineName, yOffset, staticOffset);
}
}
@@ -1,50 +1,48 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
/**
* The event listener part for securing proper functionality of armor stands
* Class listening to Bukkit events which the feature requires and are
* sufficient, without packets being requires.
*/
@RequiredArgsConstructor
public class EventListener implements Listener {
//the NameTag feature handler
/** Reference to the main feature */
private final BukkitNameTagX feature;
/**
* Constructs new instance with given parameters
* @param feature - NameTag feature handler
*/
public EventListener(BukkitNameTagX feature) {
this.feature = feature;
}
/**
* Sneak event listener to de-spawn and spawn armor stands to skip animation
* @param e - sneak event
*
* @param e
* sneak event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
TabPlayer p = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || feature.isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
}
/**
* Respawning armor stands as respawn screen destroys all entities in client
* @param e - respawn event
*
* @param e
* respawn event
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isPlayerDisabled(respawned)) return;
feature.getArmorStandManager(respawned).teleport();
});
@@ -1,152 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
/**
* The packet listening part for securing proper functionality of armor stands
* Bukkit events are too unreliable and delayed/ahead which causes de-sync
*/
public class PacketListener extends TabFeature {
//main feature
private final BukkitNameTagX nameTagX;
//player data by entityId, used for better performance
private final Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
/**
* Constructs new instance with given parameters and loads config options
* @param nameTagX - main feature
*/
public PacketListener(BukkitNameTagX nameTagX) {
super(nameTagX.getFeatureName(), null);
this.nameTagX = nameTagX;
}
@Override
public void load() {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
entityIdMap.put(((Player) all.getPlayer()).getEntityId(), all);
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
entityIdMap.put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
entityIdMap.remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
}
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && nameTagX.getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.setField(packet, nms.PacketPlayInUseEntity_ENTITY, ((Player) attacked.getPlayer()).getEntityId());
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || nameTagX.isDisabledPlayer(receiver) || nameTagX.getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutEntityTeleport.isInstance(packet)) {
onEntityMove(receiver, nms.PacketPlayOutEntityTeleport_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (nms.PacketPlayOutEntityDestroy.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet);
if (entities instanceof List) {
onEntityDestroy(receiver, (List<Integer>) entities);
} else {
//1.17.0
onEntityDestroy(receiver, (int) entities);
}
} else {
onEntityDestroy(receiver, (int[]) nms.PacketPlayOutEntityDestroy_ENTITIES.get(packet));
}
}
}
/**
* Processes entity move packet
* @param receiver - packet receiver
* @param entityId - entity that moved
*/
private void onEntityMove(TabPlayer receiver, int entityId) {
TabPlayer pl = entityIdMap.get(entityId);
List<Entity> vehicleList;
if (pl != null) {
//player moved
if (nameTagX.isPlayerDisabled(pl)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE,
() -> nameTagX.getArmorStandManager(pl).teleport(receiver));
} else if ((vehicleList = nameTagX.getVehicleManager().getVehicles().get(entityId)) != null){
//a vehicle carrying something moved
for (Entity entity : vehicleList) {
TabPlayer passenger = entityIdMap.get(entity.getEntityId());
if (passenger != null && nameTagX.getArmorStandManager(passenger) != null) {
TAB.getInstance().getCPUManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE_PASSENGER,
() -> nameTagX.getArmorStandManager(passenger).teleport(receiver));
}
}
}
}
private void onEntitySpawn(TabPlayer receiver, int entityId) {
TabPlayer spawnedPlayer = entityIdMap.get(entityId);
if (spawnedPlayer != null && spawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(spawnedPlayer)) {
TAB.getInstance().getCPUManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_SPAWN,
() -> nameTagX.getArmorStandManager(spawnedPlayer).spawn(receiver));
}
}
private void onEntityDestroy(TabPlayer receiver, List<Integer> entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
}
private void onEntityDestroy(TabPlayer receiver, int... entities) {
for (int entity : entities) {
onEntityDestroy(receiver, entity);
}
}
private void onEntityDestroy(TabPlayer receiver, int entity) {
TabPlayer deSpawnedPlayer = entityIdMap.get(entity);
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer))
TAB.getInstance().getCPUManager().runMeasuredTask(nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_DESTROY,
() -> nameTagX.getArmorStandManager(deSpawnedPlayer).destroy(receiver));
}
}
@@ -1,136 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import me.neznamy.tab.shared.TabConstants;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
public class VehicleRefresher extends TabFeature {
//map of players currently in a vehicle
private final WeakHashMap<TabPlayer, Entity> playersInVehicle = new WeakHashMap<>();
//map of vehicles carrying players
private final Map<Integer, List<Entity>> vehicles = new ConcurrentHashMap<>();
//set of players currently on boats
private final Set<TabPlayer> playersOnBoats = Collections.newSetFromMap(new WeakHashMap<>());
private final BukkitNameTagX feature;
public VehicleRefresher(BukkitNameTagX feature) {
super(feature.getFeatureName(), "Refreshing vehicles");
this.feature = feature;
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(50,
this, TabConstants.CpuUsageCategory.PROCESSING_PLAYER_MOVEMENT, () -> {
for (TabPlayer inVehicle : playersInVehicle.keySet()) {
feature.getArmorStandManager(inVehicle).teleport();
// feature.getVehicleManager().processPassengers((Entity) inVehicle.getPlayer());
}
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (feature.isPreviewingNametag(p)) {
feature.getArmorStandManager(p).teleport(p);
}
}
});
addUsedPlaceholders(Collections.singletonList("%vehicle%"));
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder("%vehicle%", 100, p -> ((Player)p.getPlayer()).getVehicle() == null ? "" : ((Player)p.getPlayer()).getVehicle());
}
@Override
public void load() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Entity vehicle = ((Player)p.getPlayer()).getVehicle();
if (vehicle != null) {
vehicles.put(vehicle.getEntityId(), getPassengers(vehicle));
playersInVehicle.put(p, vehicle);
if (feature.isDisableOnBoats() && vehicle.getType() == EntityType.BOAT) {
playersOnBoats.add(p);
}
}
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
Entity vehicle = ((Entity) connectedPlayer.getPlayer()).getVehicle();
if (vehicle != null) vehicles.put(vehicle.getEntityId(), getPassengers(vehicle));
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
if (playersInVehicle.containsKey(disconnectedPlayer)) vehicles.remove(playersInVehicle.get(disconnectedPlayer).getEntityId());
}
@Override
public void refresh(TabPlayer p, boolean force) {
if (feature.isPlayerDisabled(p)) return;
Entity vehicle = ((Player)p.getPlayer()).getVehicle();
if (playersInVehicle.containsKey(p) && vehicle == null) {
//vehicle exit
vehicles.remove(playersInVehicle.get(p).getEntityId());
feature.getArmorStandManager(p).teleport();
playersInVehicle.remove(p);
if (feature.isDisableOnBoats() && playersOnBoats.contains(p)) {
playersOnBoats.remove(p);
feature.updateTeamData(p);
}
}
if (!playersInVehicle.containsKey(p) && vehicle != null) {
//vehicle enter
vehicles.put(vehicle.getEntityId(), getPassengers(vehicle));
feature.getArmorStandManager(p).respawn(); //making teleport instant instead of showing teleport animation
playersInVehicle.put(p, vehicle);
if (feature.isDisableOnBoats() && vehicle.getType() == EntityType.BOAT) {
playersOnBoats.add(p);
feature.updateTeamData(p);
}
}
}
public boolean isOnBoat(TabPlayer p) {
return playersOnBoats.contains(p);
}
public Map<Integer, List<Entity>> getVehicles() {
return vehicles;
}
/**
* Returns list of all passengers on specified vehicle
* @param vehicle - vehicle to check passengers of
* @return list of passengers
*/
@SuppressWarnings("deprecation")
public List<Entity> getPassengers(Entity vehicle){
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers();
} else {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger());
} else {
return new ArrayList<>();
}
}
}
/**
* Teleports armor stands of all passengers on specified vehicle
* @param vehicle - entity to check passengers of
*/
public void processPassengers(Entity vehicle) {
for (Entity passenger : getPassengers(vehicle)) {
if (passenger instanceof Player) {
feature.getArmorStandManager(TAB.getInstance().getPlayer(passenger.getUniqueId())).teleport();
}
processPassengers(passenger);
}
}
}
@@ -1,677 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.platforms.bukkit.Main;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherRegistry;
import me.neznamy.tab.shared.TAB;
@SuppressWarnings("rawtypes")
public final class NMSStorage {
//instance of this class
private static NMSStorage instance;
//server package, such as "v1_16_R3"
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
//server minor version such as "16"
private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
//another field due to fabric which uses 1.16 package on 1.17 server
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
//base
private final Class<?> Packet = getNMSClass("net.minecraft.network.protocol.Packet", "Packet");
public final Class<?> EnumChatFormat = getNMSClass("net.minecraft.EnumChatFormat", "EnumChatFormat");
public final Class<?> EntityPlayer = getNMSClass("net.minecraft.server.level.EntityPlayer", "EntityPlayer");
private final Class<?> Entity = getNMSClass("net.minecraft.world.entity.Entity", "Entity");
private final Class<?> EntityLiving = getNMSClass("net.minecraft.world.entity.EntityLiving", "EntityLiving");
private final Class<?> PlayerConnection = getNMSClass("net.minecraft.server.network.PlayerConnection", "PlayerConnection");
public Constructor<?> newEntityArmorStand;
public final Field PING = getField(EntityPlayer, "ping", "latency", "field_71138_i", "field_13967", "e");
public final Field PLAYER_CONNECTION = getFields(EntityPlayer, PlayerConnection).get(0);
public Field NETWORK_MANAGER;
public Field CHANNEL;
public final Method getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
public final Method sendPacket = getMethods(PlayerConnection, void.class, Packet).get(0);
public Method getProfile;
public final Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
public final Enum[] EnumChatFormat_values = getEnumValues(EnumChatFormat);
//chat
public Class<?> ChatComponentText;
public Class<?> ChatHoverable;
public Class<?> EnumClickAction;
private Class<?> IChatBaseComponent;
public Constructor<?> newChatComponentText;
public Constructor<?> newChatClickable;
public Constructor<?> newChatModifier;
public Constructor<?> newChatHoverable;
public Field ChatBaseComponent_extra;
public Field ChatBaseComponent_modifier;
public Field ChatComponentText_text;
public Field ChatClickable_action;
public Field ChatClickable_value;
public Field ChatModifier_bold;
public Field ChatModifier_italic;
public Field ChatModifier_underlined;
public Field ChatModifier_strikethrough;
public Field ChatModifier_obfuscated;
public Field ChatModifier_clickEvent;
public Field ChatModifier_hoverEvent;
public Field ChatHexColor_name;
public Field ChatHexColor_rgb;
public Field ChatModifier_color;
public Method ChatComponentText_addSibling;
public Method EnumHoverAction_a;
public Method ChatHoverable_getAction;
public Method ChatHexColor_ofInt;
public Method ChatHexColor_ofString;
public Method ChatHoverable_serialize;
public Method ChatHoverable_getValue;
public Method EnumHoverAction_fromJson;
public Method EnumHoverAction_fromLegacyComponent;
//PacketPlayOutChat
public Class<?> ChatMessageType;
public Constructor<?> newPacketPlayOutChat;
public Enum[] ChatMessageType_values;
//DataWatcher
private final Class<?> DataWatcher = getNMSClass("net.minecraft.network.syncher.DataWatcher", "DataWatcher");
private final Class<?> DataWatcherItem = getNMSClass("net.minecraft.network.syncher.DataWatcher$Item", "DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
public Class<?> DataWatcherRegistry;
public final Constructor<?> newDataWatcher = DataWatcher.getConstructors()[0];
public Constructor<?> newDataWatcherObject;
public Field DataWatcherItem_TYPE;
public final Field DataWatcherItem_VALUE = getFields(DataWatcherItem, Object.class).get(0);
public Field DataWatcherObject_SLOT;
public Field DataWatcherObject_SERIALIZER;
public Method DataWatcher_REGISTER;
private DataWatcherRegistry registry;
//PacketPlayOutSpawnEntityLiving
public final Class<?> PacketPlayOutSpawnEntityLiving = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
public Constructor<?> newPacketPlayOutSpawnEntityLiving;
public final Field PacketPlayOutSpawnEntityLiving_ENTITYID = getFields(PacketPlayOutSpawnEntityLiving, int.class).get(0);
public final Field PacketPlayOutSpawnEntityLiving_ENTITYTYPE = getFields(PacketPlayOutSpawnEntityLiving, int.class).get(1);
public final Field PacketPlayOutSpawnEntityLiving_YAW = getFields(PacketPlayOutSpawnEntityLiving, byte.class).get(0);
public final Field PacketPlayOutSpawnEntityLiving_PITCH = getFields(PacketPlayOutSpawnEntityLiving, byte.class).get(0);
public Field PacketPlayOutSpawnEntityLiving_UUID;
public Field PacketPlayOutSpawnEntityLiving_X;
public Field PacketPlayOutSpawnEntityLiving_Y;
public Field PacketPlayOutSpawnEntityLiving_Z;
public Field PacketPlayOutSpawnEntityLiving_DATAWATCHER;
//PacketPlayOutEntityTeleport
public final Class<?> PacketPlayOutEntityTeleport = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
public Constructor<?> newPacketPlayOutEntityTeleport;
public final Field PacketPlayOutEntityTeleport_ENTITYID = getFields(PacketPlayOutEntityTeleport, int.class).get(0);
public Field PacketPlayOutEntityTeleport_X;
public Field PacketPlayOutEntityTeleport_Y;
public Field PacketPlayOutEntityTeleport_Z;
public final Field PacketPlayOutEntityTeleport_YAW = getFields(PacketPlayOutEntityTeleport, byte.class).get(0);
public final Field PacketPlayOutEntityTeleport_PITCH = getFields(PacketPlayOutEntityTeleport, byte.class).get(1);
//PacketPlayOutPlayerListHeaderFooter
public Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
public Field PacketPlayOutPlayerListHeaderFooter_HEADER;
public Field PacketPlayOutPlayerListHeaderFooter_FOOTER;
//other entity packets
public final Class<?> PacketPlayInUseEntity = getNMSClass("net.minecraft.network.protocol.game.PacketPlayInUseEntity", "PacketPlayInUseEntity", "Packet7UseEntity");
public Class<?> PacketPlayInUseEntity$d;
public Field PacketPlayInUseEntity_ENTITY;
public Field PacketPlayInUseEntity_ACTION;
public final Class<?> PacketPlayOutEntity = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
public final Field PacketPlayOutEntity_ENTITYID = getFields(PacketPlayOutEntity, int.class).get(0);
public final Class<?> PacketPlayOutEntityDestroy = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
public Constructor<?> newPacketPlayOutEntityDestroy;
public final Field PacketPlayOutEntityDestroy_ENTITIES = setAccessible(PacketPlayOutEntityDestroy.getDeclaredFields()[0]);
public final Class<?> PacketPlayOutEntityLook = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
public final Class<?> PacketPlayOutEntityMetadata = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
public final Constructor<?> newPacketPlayOutEntityMetadata = PacketPlayOutEntityMetadata.getConstructor(int.class, DataWatcher, boolean.class);
public final Field PacketPlayOutEntityMetadata_LIST = getFields(PacketPlayOutEntityMetadata, List.class).get(0);
public final Class<?> PacketPlayOutNamedEntitySpawn = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
public final Field PacketPlayOutNamedEntitySpawn_ENTITYID = getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
//PacketPlayOutPlayerInfo
public Class<?> PacketPlayOutPlayerInfo;
public Constructor<?> newPacketPlayOutPlayerInfo;
public Constructor<?> newPlayerInfoData;
public Field PacketPlayOutPlayerInfo_ACTION;
public Field PacketPlayOutPlayerInfo_PLAYERS;
public Method PlayerInfoData_getProfile;
public Method PlayerInfoData_getLatency;
public Method PlayerInfoData_getGamemode;
public Method PlayerInfoData_getDisplayName;
public Enum[] EnumPlayerInfoAction_values;
public Enum[] EnumGamemode_values;
//scoreboard objectives
public final Class<?> PacketPlayOutScoreboardDisplayObjective = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective", "PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
public final Class<?> PacketPlayOutScoreboardObjective = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective", "PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
private final Class<?> Scoreboard = getNMSClass("net.minecraft.world.scores.Scoreboard", "Scoreboard");
private final Class<?> PacketPlayOutScoreboardScore = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore", "PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
private final Class<?> ScoreboardObjective = getNMSClass("net.minecraft.world.scores.ScoreboardObjective", "ScoreboardObjective");
private final Class<?> ScoreboardScore = getNMSClass("net.minecraft.world.scores.ScoreboardScore", "ScoreboardScore");
private final Class<?> IScoreboardCriteria = getNMSClass("net.minecraft.world.scores.criteria.IScoreboardCriteria", "IScoreboardCriteria");
public Class<?> EnumScoreboardHealthDisplay;
public final Constructor<?> newScoreboardObjective = ScoreboardObjective.getConstructors()[0];
public final Constructor<?> newScoreboard = Scoreboard.getConstructor();
public final Constructor<?> newScoreboardScore = ScoreboardScore.getConstructor(Scoreboard, ScoreboardObjective, String.class);
public final Constructor<?> newPacketPlayOutScoreboardDisplayObjective = PacketPlayOutScoreboardDisplayObjective.getConstructor(int.class, ScoreboardObjective);
public Constructor<?> newPacketPlayOutScoreboardObjective;
public Constructor<?> newPacketPlayOutScoreboardScore_1_13;
public Constructor<?> newPacketPlayOutScoreboardScore_String;
public Constructor<?> newPacketPlayOutScoreboardScore;
public final Field PacketPlayOutScoreboardDisplayObjective_POSITION = getFields(PacketPlayOutScoreboardDisplayObjective, int.class).get(0);
public final Field PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME = getFields(PacketPlayOutScoreboardDisplayObjective, String.class).get(0);
public final Field PacketPlayOutScoreboardObjective_OBJECTIVENAME = getFields(PacketPlayOutScoreboardObjective, String.class).get(0);
public Field PacketPlayOutScoreboardObjective_METHOD;
public final Field IScoreboardCriteria_self = getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
public Field PacketPlayOutScoreboardObjective_RENDERTYPE;
public Field PacketPlayOutScoreboardObjective_DISPLAYNAME;
public final Method ScoreboardScore_setScore = getMethod(ScoreboardScore, new String[]{"setScore", "func_96647_c", "method_1128", "b"}, int.class);
public Enum[] EnumScoreboardHealthDisplay_values;
public Enum[] EnumScoreboardAction_values;
//PacketPlayOutScoreboardTeam
public Class<?> PacketPlayOutScoreboardTeam;
public Constructor<?> newScoreboardTeam;
public Constructor<?> newPacketPlayOutScoreboardTeam;
public Field PacketPlayOutScoreboardTeam_NAME;
public Field PacketPlayOutScoreboardTeam_PLAYERS;
public Method ScoreboardTeam_getPlayerNameSet;
public Method ScoreboardTeam_setNameTagVisibility;
public Method ScoreboardTeam_setCollisionRule;
public Method ScoreboardTeam_setPrefix;
public Method ScoreboardTeam_setSuffix;
public Method ScoreboardTeam_setColor;
public Method ScoreboardTeam_setAllowFriendlyFire;
public Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
public Method PacketPlayOutScoreboardTeam_of;
public Method PacketPlayOutScoreboardTeam_ofBoolean;
public Method PacketPlayOutScoreboardTeam_ofString;
public Enum[] EnumNameTagVisibility_values;
public Enum[] EnumTeamPush_values;
public Enum[] PacketPlayOutScoreboardTeam_PlayerAction_values;
/**
* Creates new instance, initializes required NMS classes and fields
* @throws ReflectiveOperationException
* If any class, field or method fails to load
*/
public NMSStorage() throws ReflectiveOperationException {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
Class<?> NetworkManager = getNMSClass("net.minecraft.network.NetworkManager", "NetworkManager");
if (minorVersion >= 7) {
NETWORK_MANAGER = getFields(PlayerConnection, NetworkManager).get(0);
}
if (minorVersion >= 8) {
CHANNEL = getFields(NetworkManager, Channel.class).get(0);
getProfile = getMethods(getNMSClass("net.minecraft.world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
newEntityArmorStand = getNMSClass("net.minecraft.world.entity.decoration.EntityArmorStand", "EntityArmorStand")
.getConstructor(getNMSClass("net.minecraft.world.level.World", "World"), double.class, double.class, double.class);
}
initializeChatComponents();
initializeChatPacket();
initializeDataWatcher();
initializeEntityPackets();
initializeHeaderFooterPacket();
initializePlayerInfoPacket();
initializeScoreboardPackets();
try {
initializeTeamPackets();
} catch (ClassNotFoundException e) {
Bukkit.getConsoleSender().sendMessage(me.neznamy.tab.api.chat.EnumChatFormat.color("&c[TAB] Failed to load PacketPlayOutScoreboardTeam"));
//fabric with missing team packet
}
}
/**
* Sets new instance
* @param instance - new instance
*/
public static void setInstance(NMSStorage instance) {
NMSStorage.instance = instance;
}
/**
* Returns instance
* @return instance
*/
public static NMSStorage getInstance() {
return instance;
}
private void initializeChatComponents() throws ReflectiveOperationException {
if (minorVersion < 7) return;
Class<?> ChatBaseComponent = getNMSClass("net.minecraft.network.chat.ChatBaseComponent", "ChatBaseComponent");
Class<?> ChatClickable = getNMSClass("net.minecraft.network.chat.ChatClickable", "ChatClickable");
ChatComponentText = getNMSClass("net.minecraft.network.chat.ChatComponentText", "ChatComponentText");
ChatHoverable = getNMSClass("net.minecraft.network.chat.ChatHoverable", "ChatHoverable");
Class<?> ChatModifier = getNMSClass("net.minecraft.network.chat.ChatModifier", "ChatModifier");
EnumClickAction = getNMSClass("net.minecraft.network.chat.ChatClickable$EnumClickAction", "ChatClickable$EnumClickAction", "EnumClickAction");
Class<?> EnumHoverAction = getNMSClass("net.minecraft.network.chat.ChatHoverable$EnumHoverAction", "ChatHoverable$EnumHoverAction", "EnumHoverAction");
IChatBaseComponent = getNMSClass("net.minecraft.network.chat.IChatBaseComponent", "IChatBaseComponent");
newChatComponentText = ChatComponentText.getConstructor(String.class);
newChatClickable = ChatClickable.getConstructor(EnumClickAction, String.class);
ChatBaseComponent_extra = getFields(ChatBaseComponent, List.class).get(0);
ChatBaseComponent_modifier = getFields(ChatBaseComponent, ChatModifier).get(0);
ChatComponentText_text = getFields(ChatComponentText, String.class).get(0);
ChatClickable_action = getFields(ChatClickable, EnumClickAction).get(0);
ChatClickable_value = getFields(ChatClickable, String.class).get(0);
List<Field> booleans = getFields(ChatModifier, Boolean.class);
ChatModifier_bold = booleans.get(0);
ChatModifier_italic = booleans.get(1);
ChatModifier_underlined = booleans.get(2);
ChatModifier_strikethrough = booleans.get(3);
ChatModifier_obfuscated = booleans.get(4);
ChatModifier_clickEvent = getFields(ChatModifier, ChatClickable).get(0);
ChatModifier_hoverEvent = getFields(ChatModifier, ChatHoverable).get(0);
ChatComponentText_addSibling = getMethod(ChatComponentText, new String[]{"addSibling", "a", "func_150257_a", "method_10852"}, IChatBaseComponent);
EnumHoverAction_a = getMethod(EnumHoverAction, new String[]{"a", "func_150684_a", "method_27670"}, String.class);
ChatHoverable_getAction = getMethods(ChatHoverable, EnumHoverAction).get(0);
if (minorVersion >= 16) {
Class<?> ChatHexColor = getNMSClass("net.minecraft.network.chat.ChatHexColor", "ChatHexColor");
Class<?> MinecraftKey = getNMSClass("net.minecraft.resources.MinecraftKey", "MinecraftKey");
newChatModifier = setAccessible(ChatModifier.getDeclaredConstructor(ChatHexColor, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, ChatClickable, ChatHoverable, String.class, MinecraftKey));
newChatHoverable = ChatHoverable.getConstructor(EnumHoverAction, Object.class);
List<Field> list = getFields(ChatHexColor, String.class);
ChatHexColor_name = list.get(list.size()-1);
ChatHexColor_rgb = getFields(ChatHexColor, int.class).get(0);
ChatModifier_color = getFields(ChatModifier, ChatHexColor).get(0);
ChatHexColor_ofInt = getMethods(ChatHexColor, ChatHexColor, int.class).get(0);
ChatHexColor_ofString = getMethods(ChatHexColor, ChatHexColor, String.class).get(0);
ChatHoverable_serialize = getMethods(ChatHoverable, JsonObject.class).get(0);
ChatHoverable_getValue = getMethods(ChatHoverable, Object.class, EnumHoverAction).get(0);
EnumHoverAction_fromJson = getMethods(EnumHoverAction, ChatHoverable, JsonElement.class).get(0);
EnumHoverAction_fromLegacyComponent = getMethods(EnumHoverAction, ChatHoverable, IChatBaseComponent).get(0);
} else {
newChatModifier = ChatModifier.getConstructor();
newChatHoverable = ChatHoverable.getConstructor(EnumHoverAction, IChatBaseComponent);
ChatModifier_color = getFields(ChatModifier, EnumChatFormat).get(0);
ChatHoverable_getValue = getMethods(ChatHoverable, IChatBaseComponent).get(0);
}
}
private void initializeChatPacket() throws ReflectiveOperationException {
Class<?> PacketPlayOutChat = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutChat", "PacketPlayOutChat", "Packet3Chat");
if (minorVersion >= 12) {
ChatMessageType = getNMSClass("net.minecraft.network.chat.ChatMessageType", "ChatMessageType");
ChatMessageType_values = getEnumValues(ChatMessageType);
}
if (minorVersion >= 16) {
newPacketPlayOutChat = PacketPlayOutChat.getConstructor(IChatBaseComponent, ChatMessageType, UUID.class);
} else if (minorVersion >= 12) {
newPacketPlayOutChat = PacketPlayOutChat.getConstructor(IChatBaseComponent, ChatMessageType);
} else if (minorVersion >= 8) {
newPacketPlayOutChat = PacketPlayOutChat.getConstructor(IChatBaseComponent, byte.class);
} else if (minorVersion >= 7){
newPacketPlayOutChat = PacketPlayOutChat.getConstructor(IChatBaseComponent);
}
}
private void initializeDataWatcher() throws ReflectiveOperationException {
if (minorVersion >= 9) {
Class<?> DataWatcherObject = getNMSClass("net.minecraft.network.syncher.DataWatcherObject", "DataWatcherObject");
DataWatcherRegistry = getNMSClass("net.minecraft.network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
Class<?> DataWatcherSerializer = getNMSClass("net.minecraft.network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, DataWatcherSerializer);
DataWatcherItem_TYPE = getFields(DataWatcherItem, DataWatcherObject).get(0);
DataWatcherObject_SLOT = getFields(DataWatcherObject, int.class).get(0);
DataWatcherObject_SERIALIZER = getFields(DataWatcherObject, DataWatcherSerializer).get(0);
DataWatcher_REGISTER = getMethod(DataWatcher, new String[]{"register", "method_12784", "a"}, DataWatcherObject, Object.class);
} else {
DataWatcherItem_TYPE = getFields(DataWatcherItem, int.class).get(1);
DataWatcher_REGISTER = getMethod(DataWatcher, new String[]{"a", "func_75682_a"}, int.class, Object.class);
}
registry = new DataWatcherRegistry(this);
}
private void initializeEntityPackets() throws ReflectiveOperationException {
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor(EntityLiving);
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor(Entity);
} else {
newPacketPlayOutSpawnEntityLiving = PacketPlayOutSpawnEntityLiving.getConstructor();
newPacketPlayOutEntityTeleport = PacketPlayOutEntityTeleport.getConstructor();
}
if (minorVersion >= 17) {
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$d");
}
try {
newPacketPlayOutEntityDestroy = PacketPlayOutEntityDestroy.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
newPacketPlayOutEntityDestroy = PacketPlayOutEntityDestroy.getConstructor(int.class);
}
if (minorVersion >= 7) {
Class<?> EnumEntityUseAction = getNMSClass("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction", "PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction", "net.minecraft.class_2824$class_5906");
PacketPlayInUseEntity_ENTITY = getFields(PacketPlayInUseEntity, int.class).get(0);
PacketPlayInUseEntity_ACTION = getFields(PacketPlayInUseEntity, EnumEntityUseAction).get(0);
}
if (minorVersion >= 9) {
PacketPlayOutSpawnEntityLiving_UUID = getFields(PacketPlayOutSpawnEntityLiving, UUID.class).get(0);
PacketPlayOutSpawnEntityLiving_X = getFields(PacketPlayOutSpawnEntityLiving, double.class).get(0);
PacketPlayOutSpawnEntityLiving_Y = getFields(PacketPlayOutSpawnEntityLiving, double.class).get(1);
PacketPlayOutSpawnEntityLiving_Z = getFields(PacketPlayOutSpawnEntityLiving, double.class).get(2);
PacketPlayOutEntityTeleport_X = getFields(PacketPlayOutEntityTeleport, double.class).get(0);
PacketPlayOutEntityTeleport_Y = getFields(PacketPlayOutEntityTeleport, double.class).get(1);
PacketPlayOutEntityTeleport_Z = getFields(PacketPlayOutEntityTeleport, double.class).get(2);
} else {
PacketPlayOutSpawnEntityLiving_X = getFields(PacketPlayOutSpawnEntityLiving, int.class).get(2);
PacketPlayOutSpawnEntityLiving_Y = getFields(PacketPlayOutSpawnEntityLiving, int.class).get(3);
PacketPlayOutSpawnEntityLiving_Z = getFields(PacketPlayOutSpawnEntityLiving, int.class).get(4);
PacketPlayOutEntityTeleport_X = getFields(PacketPlayOutEntityTeleport, int.class).get(1);
PacketPlayOutEntityTeleport_Y = getFields(PacketPlayOutEntityTeleport, int.class).get(2);
PacketPlayOutEntityTeleport_Z = getFields(PacketPlayOutEntityTeleport, int.class).get(3);
}
if (minorVersion <= 14) {
PacketPlayOutSpawnEntityLiving_DATAWATCHER = getFields(PacketPlayOutSpawnEntityLiving, DataWatcher).get(0);
}
}
private void initializeHeaderFooterPacket() throws ReflectiveOperationException {
if (minorVersion < 8) return;
Class<?> PacketPlayOutPlayerListHeaderFooter = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
PacketPlayOutPlayerListHeaderFooter_HEADER = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent).get(0);
PacketPlayOutPlayerListHeaderFooter_FOOTER = getFields(PacketPlayOutPlayerListHeaderFooter, IChatBaseComponent).get(1);
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooter.getConstructor();
}
}
private void initializePlayerInfoPacket() throws ReflectiveOperationException {
if (minorVersion < 8) return;
PacketPlayOutPlayerInfo = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
Class<?> EnumPlayerInfoAction = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction", "net.minecraft.class_2703$class_5893");
Class<?> PlayerInfoData = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
Class<?> EnumGamemode = getNMSClass("net.minecraft.world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfo.getConstructor(EnumPlayerInfoAction, Array.newInstance(EntityPlayer, 0).getClass());
newPlayerInfoData = PlayerInfoData.getConstructors()[0];
PacketPlayOutPlayerInfo_ACTION = getFields(PacketPlayOutPlayerInfo, EnumPlayerInfoAction).get(0);
PacketPlayOutPlayerInfo_PLAYERS = getFields(PacketPlayOutPlayerInfo, List.class).get(0);
PlayerInfoData_getProfile = getMethods(PlayerInfoData, GameProfile.class).get(0);
PlayerInfoData_getLatency = getMethods(PlayerInfoData, int.class).get(0);
PlayerInfoData_getGamemode = getMethods(PlayerInfoData, EnumGamemode).get(0);
PlayerInfoData_getDisplayName = getMethods(PlayerInfoData, IChatBaseComponent).get(0);
EnumPlayerInfoAction_values = getEnumValues(EnumPlayerInfoAction);
EnumGamemode_values = getEnumValues(EnumGamemode);
}
private void initializeScoreboardPackets() throws ReflectiveOperationException {
List<Field> list = getFields(PacketPlayOutScoreboardObjective, int.class);
PacketPlayOutScoreboardObjective_METHOD = list.get(list.size()-1);
Class<?> EnumScoreboardAction = null;
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay = getNMSClass("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay", "IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardHealthDisplay_values = getEnumValues(EnumScoreboardHealthDisplay);
EnumScoreboardAction = getNMSClass("net.minecraft.server.ScoreboardServer$Action", "ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumScoreboardAction_values = getEnumValues(EnumScoreboardAction);
PacketPlayOutScoreboardObjective_RENDERTYPE = getFields(PacketPlayOutScoreboardObjective, EnumScoreboardHealthDisplay).get(0);
}
if (minorVersion >= 13) {
newPacketPlayOutScoreboardObjective = PacketPlayOutScoreboardObjective.getConstructor(ScoreboardObjective, int.class);
newPacketPlayOutScoreboardScore_1_13 = PacketPlayOutScoreboardScore.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
PacketPlayOutScoreboardObjective_DISPLAYNAME = getFields(PacketPlayOutScoreboardObjective, IChatBaseComponent).get(0);
} else {
newPacketPlayOutScoreboardObjective = PacketPlayOutScoreboardObjective.getConstructor();
newPacketPlayOutScoreboardScore_String = PacketPlayOutScoreboardScore.getConstructor(String.class);
PacketPlayOutScoreboardObjective_DISPLAYNAME = getFields(PacketPlayOutScoreboardObjective, String.class).get(1);
if (minorVersion >= 8) {
newPacketPlayOutScoreboardScore = PacketPlayOutScoreboardScore.getConstructor(ScoreboardScore);
} else {
newPacketPlayOutScoreboardScore = PacketPlayOutScoreboardScore.getConstructor(ScoreboardScore, int.class);
}
}
}
private void initializeTeamPackets() throws ReflectiveOperationException {
PacketPlayOutScoreboardTeam = getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam", "PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
Class<?> ScoreboardTeam = getNMSClass("net.minecraft.world.scores.ScoreboardTeam", "ScoreboardTeam");
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
PacketPlayOutScoreboardTeam_NAME = getFields(PacketPlayOutScoreboardTeam, String.class).get(0);
PacketPlayOutScoreboardTeam_PLAYERS = getFields(PacketPlayOutScoreboardTeam, Collection.class).get(0);
ScoreboardTeam_getPlayerNameSet = getMethods(ScoreboardTeam, Collection.class).get(0);
ScoreboardTeam_setAllowFriendlyFire = getMethod(ScoreboardTeam, new String[]{"setAllowFriendlyFire", "a", "func_96660_a"}, boolean.class);
ScoreboardTeam_setCanSeeFriendlyInvisibles = getMethod(ScoreboardTeam, new String[]{"setCanSeeFriendlyInvisibles", "b", "func_98300_b"}, boolean.class);
if (minorVersion >= 8) {
Class<?> EnumNameTagVisibility = getNMSClass("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility", "ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
EnumNameTagVisibility_values = getEnumValues(EnumNameTagVisibility);
ScoreboardTeam_setNameTagVisibility = getMethod(ScoreboardTeam, new String[]{"setNameTagVisibility", "a", "method_1149"}, EnumNameTagVisibility);
}
if (minorVersion >= 9) {
Class<?> EnumTeamPush = getNMSClass("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
EnumTeamPush_values = getEnumValues(EnumTeamPush);
ScoreboardTeam_setCollisionRule = getMethods(ScoreboardTeam, void.class, EnumTeamPush).get(0);
}
if (minorVersion >= 13) {
ScoreboardTeam_setPrefix = getMethod(ScoreboardTeam, new String[]{"setPrefix", "method_1138", "b"}, IChatBaseComponent);
ScoreboardTeam_setSuffix = getMethod(ScoreboardTeam, new String[]{"setSuffix", "method_1139", "c"}, IChatBaseComponent);
ScoreboardTeam_setColor = getMethods(ScoreboardTeam, void.class, EnumChatFormat).get(0);
} else {
ScoreboardTeam_setPrefix = getMethod(ScoreboardTeam, new String[]{"setPrefix", "func_96666_b"}, String.class);
ScoreboardTeam_setSuffix = getMethod(ScoreboardTeam, new String[]{"setSuffix", "func_96662_c"}, String.class);
}
if (minorVersion >= 17 || serverVersion.getMinorVersion() >= 17) {
Class<?> PacketPlayOutScoreboardTeam_PlayerAction = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$a");
PacketPlayOutScoreboardTeam_of = getMethods(PacketPlayOutScoreboardTeam, PacketPlayOutScoreboardTeam, ScoreboardTeam).get(0);
PacketPlayOutScoreboardTeam_ofBoolean = getMethods(PacketPlayOutScoreboardTeam, PacketPlayOutScoreboardTeam, ScoreboardTeam, boolean.class).get(0);
PacketPlayOutScoreboardTeam_ofString = getMethods(PacketPlayOutScoreboardTeam, PacketPlayOutScoreboardTeam, ScoreboardTeam, String.class, PacketPlayOutScoreboardTeam_PlayerAction).get(0);
PacketPlayOutScoreboardTeam_PlayerAction_values = getEnumValues(PacketPlayOutScoreboardTeam_PlayerAction);
} else {
newPacketPlayOutScoreboardTeam = PacketPlayOutScoreboardTeam.getConstructor(ScoreboardTeam, int.class);
}
}
/**
* Returns class with given potential names in same order
* @param names - possible class names
* @return class for specified name(s)
* @throws ClassNotFoundException if class does not exist
*/
private Class<?> getNMSClass(String fullPath_1_17, String... names) throws ClassNotFoundException {
if (minorVersion >= 17) {
return Class.forName(fullPath_1_17);
}
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names {modern = " + fullPath_1_17 + ", legacy = " + Arrays.toString(names) + "}");
}
/**
* Returns class from given name
* @param name - class name
* @return class from given name
* @throws ClassNotFoundException if class was not found
*/
private Class<?> getLegacyClass(String name) throws ClassNotFoundException {
try {
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
} catch (ClassNotFoundException | NullPointerException e) {
try {
//modded server?
Class<?> clazz = Main.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
if (clazz != null) return clazz;
throw new ClassNotFoundException(name);
} catch (ClassNotFoundException | NullPointerException e1) {
//maybe fabric?
return Class.forName(name);
}
}
}
/**
* Returns method with specified possible names and parameters. Throws exception if no such method was found
* @param clazz - class to get method from
* @param names - possible method names
* @param parameterTypes - parameter types of the method
* @return method with specified name and parameters
* @throws NoSuchMethodException if no such method exists
*/
private Method getMethod(Class<?> clazz, String[] names, Class<?>... parameterTypes) throws NoSuchMethodException {
for (String name : names) {
try {
return getMethod(clazz, name, parameterTypes);
} catch (NoSuchMethodException e) {
//not the first method in array
}
}
List<String> list = new ArrayList<>();
for (Method m : clazz.getMethods()) {
if (m.getParameterCount() != parameterTypes.length) continue;
Class<?>[] types = m.getParameterTypes();
boolean valid = true;
for (int i=0; i<types.length; i++) {
if (types[i] != parameterTypes[i]) {
valid = false;
break;
}
}
if (valid) list.add(m.getName());
}
throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(names) + " with parameters " +
Arrays.toString(parameterTypes) + " in class " + clazz.getName() + ". Methods with matching parameters: " + list);
}
private Method getMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
List<Method> list = new ArrayList<>();
for (Method m : clazz.getMethods()) {
if (!m.getName().equals(name) || m.getParameterCount() != parameterTypes.length) continue;
Class<?>[] types = m.getParameterTypes();
boolean valid = true;
for (int i=0; i<types.length; i++) {
if (types[i] != parameterTypes[i]) {
valid = false;
break;
}
}
if (valid) list.add(m);
}
if (!list.isEmpty()) return list.get(0);
throw new NoSuchMethodException("No method found with name " + name + " in class " + clazz.getName() + " with parameters " + Arrays.toString(parameterTypes));
}
private List<Method> getMethods(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes){
List<Method> list = new ArrayList<>();
for (Method m : clazz.getDeclaredMethods()) {
if (m.getReturnType() != returnType || m.getParameterCount() != parameterTypes.length || !Modifier.isPublic(m.getModifiers())) continue;
Class<?>[] types = m.getParameterTypes();
boolean valid = true;
for (int i=0; i<types.length; i++) {
if (types[i] != parameterTypes[i]) {
valid = false;
break;
}
}
if (valid) list.add(m);
}
return list;
}
/**
* Returns all fields of class with defined class type
* @param clazz - class to check fields of
* @param type - field type to check for
* @return list of all fields with specified class type
*/
private List<Field> getFields(Class<?> clazz, Class<?> type){
if (clazz == null) throw new IllegalArgumentException("Source class cannot be null");
List<Field> list = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == type) {
list.add(setAccessible(field));
}
}
return list;
}
/**
* Returns field with specified name and makes it accessible
* @param clazz - class to get field from
* @param name - field name
* @return accessible field with defined name
* @throws NoSuchFieldException if field was not found
*/
private Field getField(Class<?> clazz, String name) throws NoSuchFieldException {
for (Field f : clazz.getDeclaredFields()) {
if (f.getName().equals(name) || (f.getName().split("_").length == 3 && f.getName().split("_")[2].equals(name))) {
return setAccessible(f);
}
}
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
}
private Field getField(Class<?> clazz, String... potentialNames) throws NoSuchFieldException {
for (String name : potentialNames) {
try {
return getField(clazz, name);
} catch (NoSuchFieldException e) {
//not the first field name from array
}
}
throw new NoSuchFieldException("No field found in class " + clazz.getName() + " with potential names " + Arrays.toString(potentialNames));
}
private Enum[] getEnumValues(Class<?> enumClass) {
if (enumClass == null) throw new IllegalArgumentException("Class cannot be null");
if (!enumClass.isEnum()) throw new IllegalArgumentException(enumClass.getName() + " is not an enum class");
try {
return (Enum[]) enumClass.getMethod("values").invoke(null);
} catch (ReflectiveOperationException e) {
//this should never happen
TAB.getInstance().getErrorManager().criticalError("Failed to load enum constants of " + enumClass.getName(), e);
return new Enum[0];
}
}
public int getMinorVersion() {
return minorVersion;
}
public void setField(Object obj, Field field, Object value) throws IllegalAccessException {
field.set(obj, value);
}
public DataWatcherRegistry getDataWatcherRegistry() {
return registry;
}
public <T extends AccessibleObject> T setAccessible(T o) {
o.setAccessible(true);
return o;
}
}
@@ -1,23 +1,67 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.util.Arrays;
import lombok.Data;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
@Data
public class PacketPlayOutEntityDestroy implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/** Packet's instance fields */
private final int[] entities;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
(ENTITIES = CLASS.getDeclaredFields()[0]).setAccessible(true);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
/**
* Constructs new instance with given parameter
*
* @param entities
* Destroyed entities
*/
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
@Override
public String toString() {
return String.format("PacketPlayOutEntityDestroy{entities=%s}", Arrays.toString(entities));
}
public int[] getEntities() {
return entities;
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterTypes()[0] != int.class) {
return CONSTRUCTOR.newInstance(new Object[]{entities});
} else {
//1.17.0
return CONSTRUCTOR.newInstance(entities[0]);
}
}
}
@@ -1,31 +1,61 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.api.util.Preconditions;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityMetadata implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field LIST;
/** Packet's instance fields */
private final int entityId;
@NonNull private final DataWatcher dataWatcher;
private final DataWatcher dataWatcher;
public PacketPlayOutEntityMetadata(int entityId, DataWatcher dataWatcher) {
Preconditions.checkNotNull(dataWatcher, "data watcher");
this.entityId = entityId;
this.dataWatcher = dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
} else {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
}
LIST = nms.getFields(CLASS, List.class).get(0);
}
@Override
public String toString() {
return String.format("PacketPlayOutEntityMetadata{entityId=%s,dataWatcher=%s}", entityId, dataWatcher);
}
public int getEntityId() {
return entityId;
}
public DataWatcher getDataWatcher() {
return dataWatcher;
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
return CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(dataWatcher.build()));
} else {
return CONSTRUCTOR.newInstance(entityId, dataWatcher.build(), true);
}
}
}
@@ -1,32 +1,95 @@
package me.neznamy.tab.platforms.bukkit.nms;
import me.neznamy.tab.api.util.Preconditions;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import org.bukkit.Location;
import me.neznamy.tab.api.protocol.TabPacket;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityTeleport implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field YAW;
public static Field PITCH;
/** Packet's instance fields */
private final int entityId;
@NonNull private final Location location;
private final Location location;
public PacketPlayOutEntityTeleport(int entityId, Location location) {
Preconditions.checkNotNull(location, "location");
this.entityId = entityId;
this.location = location;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
ENTITY_ID = nms.getFields(CLASS, int.class).get(0);
YAW = nms.getFields(CLASS, byte.class).get(0);
PITCH = nms.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
if (nms.getMinorVersion() >= 9) {
X = nms.getFields(CLASS, double.class).get(0);
Y = nms.getFields(CLASS, double.class).get(1);
Z = nms.getFields(CLASS, double.class).get(2);
} else {
X = nms.getFields(CLASS, int.class).get(1);
Y = nms.getFields(CLASS, int.class).get(2);
Z = nms.getFields(CLASS, int.class).get(3);
}
}
@Override
public String toString() {
return String.format("PacketPlayOutEntityTeleport{entityId=%s,location=%s}", entityId, location);
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
if (nms.getMinorVersion() >= 9) {
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
return nmsPacket;
}
public int getEntityId() {
return entityId;
}
public Location getLocation() {
return location;
private int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,60 +1,148 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.util.UUID;
import me.neznamy.tab.api.util.Preconditions;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Class<?> EntityTypes;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field ENTITY_TYPE;
public static Field YAW;
public static Field PITCH;
public static Field UUID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field DATA_WATCHER;
public static Object EntityTypes_ARMOR_STAND;
/** Entity types */
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Packet's instance fields */
private final int entityId;
private final UUID uuid;
private final EntityType entityType;
private final Location location;
@NonNull private final UUID uniqueId;
@NonNull private final EntityType entityType;
@NonNull private final Location location;
private final DataWatcher dataWatcher;
public PacketPlayOutSpawnEntityLiving(int entityId, UUID uuid, EntityType entityType, Location location, DataWatcher dataWatcher) {
Preconditions.checkNotNull(uuid, "uuid");
Preconditions.checkNotNull(entityType, "entityType");
Preconditions.checkNotNull(location, "location");
this.entityId = entityId;
this.uuid = uuid;
this.entityType = entityType;
this.location = location;
this.dataWatcher = dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
entityIds.put(EntityType.ARMOR_STAND, 2);
} else if (nms.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (nms.getMinorVersion() >= 17) {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor(nms.EntityLiving);
}
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
ENTITY_ID = nms.getFields(CLASS, int.class).get(0);
YAW = nms.getFields(CLASS, byte.class).get(0);
PITCH = nms.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 9) {
UUID = nms.getFields(CLASS, UUID.class).get(0);
if (nms.getMinorVersion() >= 19) {
X = nms.getFields(CLASS, double.class).get(2);
Y = nms.getFields(CLASS, double.class).get(3);
Z = nms.getFields(CLASS, double.class).get(4);
} else {
X = nms.getFields(CLASS, double.class).get(0);
Y = nms.getFields(CLASS, double.class).get(1);
Z = nms.getFields(CLASS, double.class).get(2);
}
} else {
X = nms.getFields(CLASS, int.class).get(2);
Y = nms.getFields(CLASS, int.class).get(3);
Z = nms.getFields(CLASS, int.class).get(4);
}
if (nms.getMinorVersion() < 19) {
ENTITY_TYPE = nms.getFields(CLASS, int.class).get(1);
}
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER = nms.getFields(CLASS, DataWatcher.CLASS).get(0);
}
}
@Override
public String toString() {
return String.format("PacketPlayOutSpawnEntityLiving{entityId=%s,uuid=%s,entityType=%s,location=%s,dataWatcher=%s}",
entityId, uuid, entityType, location, dataWatcher);
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, dataWatcher.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, uniqueId);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
int id = entityIds.get(entityType);
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, id);
}
return nmsPacket;
}
public int getEntityId() {
return entityId;
}
public UUID getUniqueId() {
return uuid;
}
public EntityType getEntityType() {
return entityType;
}
public Location getLocation() {
return location;
}
public DataWatcher getDataWatcher() {
return dataWatcher;
private int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
Loaded 100 of 316 files, more files were not shown because too many files have changed in this diff. Show more