Compare commits

...
1147 Commits
Author SHA1 Message Date
NEZNAMY 9a7a105e7b fix %serveronline% and %worldonline% inaccuracies 2022-01-28 11:25:13 +01:00
NEZNAMY f54d024d97 3.0.2 2022-01-27 20:43:29 +01:00
NEZNAMY 94338bd53f update nested placeholders immediately on plugin message 2022-01-27 13:17:24 +01:00
NEZNAMY 1a9ecc0313 fix belowname update packets being sent in disabled worlds / servers 2022-01-26 22:04:13 +01:00
NEZNAMY 4baeb7b945 properly cancel delayed tasks on shutdown / reload 2022-01-26 18:24:24 +01:00
NEZNAMY baee479fb5 3.0.2-pre6 2022-01-26 01:03:29 +01:00
NEZNAMY 5ad4b2e9c4 so many issues from one wrong method call 2022-01-25 22:57:31 +01:00
NEZNAMY 26657cddfc code style suggestions by intellij 2022-01-25 22:56:52 +01:00
NEZNAMY 947a922f8c reduce amount of logged warns in console 2022-01-25 16:08:46 +01:00
NEZNAMY a2be76184e use tab's thread manager for scoreboard and bossbar announcing 2022-01-25 00:43:57 +01:00
NEZNAMY 8ddb733dc3 do not freeze tab's thread with delayed tasks 2022-01-25 00:33:44 +01:00
NEZNAMY 94e016129b run repeating task for the first time with initial delay 2022-01-25 00:33:21 +01:00
NEZNAMY 5e653ded5d process events in a single thread on velocity as well 2022-01-23 22:04:47 +01:00
NEZNAMY 1bf8097c3f 3.0.2-pre5 2022-01-23 21:39:16 +01:00
NEZNAMY 3d2f11ffd2 move enable call just in case 2022-01-23 18:28:08 +01:00
NEZNAMY 5e77a22d7a further javadoc progress 2022-01-23 18:27:46 +01:00
NEZNAMY 1dffcb1dc3 remove all thread safety checks since they are no longer needed 2022-01-23 17:24:26 +01:00
NEZNAMY edc18cbafb run all tasks in the same thread 2022-01-23 16:49:50 +01:00
NEZNAMY 2f23194432 3.0.2-pre4 2022-01-23 11:06:42 +01:00
NEZNAMY 20709472be fix vehicle / passenger handling 2022-01-23 11:06:16 +01:00
NEZNAMY 7a8c6a0cec some more velocity fixes 2022-01-22 23:11:51 +01:00
NEZNAMY 88558e40a7 round health even when copying max health 2022-01-21 14:49:27 +01:00
NEZNAMY 79386b2b6b allow 64 character scoreboard on <1.13 using "Long|" line prefix 2022-01-21 13:12:03 +01:00
NEZNAMY 3aad8d3cb5 allow per-server / per-world properties through commands 2022-01-21 10:49:06 +01:00
NEZNAMY 272a6f37ad fix NPE 2022-01-20 18:49:33 +01:00
NEZNAMY e10081025d fix load() method not executing methods in correct order resulting in enormous load times with large player counts 2022-01-20 18:31:04 +01:00
NEZNAMY e03a6d8f11 attempt to fix rare error probably caused by concurrent access 2022-01-20 18:29:37 +01:00
NEZNAMY 98e7dc0321 match velocity fixes in AlignedPlayerList as well 2022-01-20 18:24:52 +01:00
NEZNAMY 74438c5d74 so this is why someone was getting a NPE 2022-01-20 18:02:14 +01:00
NEZNAMY 0c4e548565 further increase event processing delay on velocity 2022-01-20 17:06:03 +01:00
NEZNAMY cc70e01e96 merge classes together 2022-01-20 14:34:19 +01:00
NEZNAMY 7c635748af simplify 2022-01-20 13:58:05 +01:00
NEZNAMY 0c874ad56d only keep lowercase entries in sorting map 2022-01-20 13:51:24 +01:00
NEZNAMY 2fb7f80b71 always display bossbars in defined order 2022-01-20 13:14:40 +01:00
NEZNAMY 3f7c3414ce increase server switch processing delay on velocity 2022-01-18 11:23:04 +01:00
NEZNAMY 97165db44d fix bridge placeholders rarely not working in scoreboard 2022-01-18 11:11:10 +01:00
NEZNAMY 82cdf04cf0 add feature override check for onServerChange 2022-01-18 11:10:34 +01:00
NEZNAMY 171d899609 3.0.2-pre3 2022-01-14 20:51:37 +01:00
NEZNAMY 6432278d5d fix scoreboard detection from other plugins not resetting on server switch 2022-01-14 20:41:15 +01:00
NEZNAMY ccaff12451 fix NPE caused by concurrent access 2022-01-14 20:40:54 +01:00
NEZNAMY 11347c080b more debug messages 2022-01-14 15:50:13 +01:00
NEZNAMY 553676c3f9 catch LinkageError instead of more specific ones 2022-01-14 15:49:49 +01:00
NEZNAMY 40b70e7418 fix compatibility with forks that set version field to "Unknown" 2022-01-14 14:22:52 +01:00
NEZNAMY 0ea75019ae catch NoSuchMethodError as well 2022-01-14 14:21:46 +01:00
NEZNAMY dbc092df77 add some missing bossbar command feedback (#462) 2022-01-14 11:09:15 +01:00
NEZNAMY 68049f0dca extract duplicated code into method (and also cancel tasks if plugin fails to load) 2022-01-13 20:54:58 +01:00
NEZNAMY 9db5dfc175 fix NPE when getting last value but placeholder threw error 2022-01-13 20:48:49 +01:00
NEZNAMY 039a316b3b clear players if plugin fails to load 2022-01-13 20:48:16 +01:00
NEZNAMY f4b529f1ce update README.md 2022-01-11 18:50:44 +01:00
NEZNAMY 9c6d606103 code cleanup 2022-01-11 17:57:15 +01:00
NEZNAMY 6cdc06aae7 fix config option path 2022-01-11 17:45:54 +01:00
NEZNAMY 2684654e7b drop redundant remove-strings option 2022-01-11 17:45:07 +01:00
NEZNAMY 25bcc33204 remove harming check 2022-01-10 20:44:41 +01:00
Callum Seabrook e5b3f6215b Added relocation for event 2022-01-10 15:03:16 +00:00
NEZNAMY 9306eadd90 a couple of config conversion fixes 2022-01-09 18:42:13 +01:00
NEZNAMY a27fbb5b8b MessageFile now extends YamlConfigurationFile 2022-01-08 17:27:28 +01:00
NEZNAMY cd4c82b0b8 Merge pull request #477 from JannesStroehlein/master
Fixed localization issue when dealing with floats numbers
2022-01-08 14:45:57 +01:00
Jannes Ströhlein aa6179ee9e made all decimal seperators points instead of some being commas. The commas made the placeholder replacement logic malfunction (2,43 -> 243 instead of 2.43) 2022-01-08 14:39:11 +01:00
NEZNAMY 6594b8c2dc fix replacements being applied twice for bridge placeholders (#476) 2022-01-07 10:47:19 +01:00
NEZNAMY d4c2cdf003 fix startup error when using placeholders with colors inside 2022-01-06 18:41:05 +01:00
NEZNAMY 2b529c37f5 fix rare concurrent modification 2022-01-06 17:55:16 +01:00
NEZNAMY 8599cd925b fix %vanished% refreshing with a much higher delay than it should on bungeecord 2022-01-05 14:32:22 +01:00
NEZNAMY 89f37b4b9e add synchronized modifier to stay safe 2022-01-04 16:46:40 +01:00
NEZNAMY f3136e85ee use equals instead of equalsIgnoreCase for better performance 2022-01-04 16:21:18 +01:00
NEZNAMY 845743073c fix wrong mysql checks 2022-01-04 15:33:36 +01:00
NEZNAMY b2c7bacaf9 fix errors when eventbus fails to load 2022-01-04 15:16:34 +01:00
NEZNAMY ed1673f032 fix #472 2022-01-04 15:15:53 +01:00
NEZNAMY bac9d443ea process all bungee events async 2022-01-03 20:32:28 +01:00
NEZNAMY 90e3868c08 use WeakHashMap for player mysql data & fix playeruuid not working 2022-01-03 15:53:07 +01:00
NEZNAMY c428572e01 add null check that doesn't really make sense, but someone needs it 2022-01-03 14:33:16 +01:00
NEZNAMY 884b5755ec 3.0.2-pre2 2022-01-02 22:47:51 +01:00
NEZNAMY 32d7366795 several fixes in redisbungee support when global playerlist is disabled 2022-01-02 22:47:11 +01:00
NEZNAMY 21bf4a5450 fix using % symbol without any placeholders resulting in %% being displayed 2022-01-02 22:38:59 +01:00
NEZNAMY 8042b02cf5 update README.md 2022-01-02 21:59:08 +01:00
NEZNAMY ae7cdd3982 no longer sending redundant packets on server/world switch 2022-01-01 22:03:19 +01:00
NEZNAMY 9ba945ad76 cache components on velocity as well 2022-01-01 21:51:18 +01:00
NEZNAMY 9cd04c3660 small optimization 2022-01-01 19:12:01 +01:00
NEZNAMY 444bc19781 fix pipeline injection detection on velocity 2022-01-01 18:23:46 +01:00
NEZNAMY eb08c4ba04 convert %afk% replacement when upgrading from free version as well 2022-01-01 14:34:57 +01:00
NEZNAMY 5846e24a29 improve placeholder API 2022-01-01 01:33:03 +01:00
NEZNAMY 9c2c09e433 remove debug messages 2021-12-31 14:20:08 +01:00
NEZNAMY 3c4329697b add PlayerLoadEvent#isJoin() method 2021-12-31 14:12:36 +01:00
NEZNAMY af9951d993 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-31 13:54:52 +01:00
NEZNAMY 630f6df2f5 Merge pull request #467 from NamiUni/patch-1
Correctly display the number of players online.
2021-12-31 13:54:38 +01:00
Namiu/うにたろう fcf07b8e7b Correctly display the number of players online. 2021-12-31 21:49:12 +09:00
NEZNAMY 4824c2f626 send remove packet always, just to stay safe 2021-12-30 14:01:05 +01:00
NEZNAMY 2b6b2e1b63 create a constant to reduce memory allocations 2021-12-29 18:02:22 +01:00
NEZNAMY 6926c1e57f redisbungee should not add/remove players when global playerlist is disabled 2021-12-29 17:12:16 +01:00
NEZNAMY 6a00a7f1ca fix wrong map key type 2021-12-29 00:32:51 +01:00
NEZNAMY 288c3d4c87 add onClickCopyToClipBoard method to ChatModifier class 2021-12-29 00:27:11 +01:00
NEZNAMY ac497bcc41 ignore vanished players in layout 2021-12-28 22:31:45 +01:00
NEZNAMY 7b8d0d6845 fix startup error when fixes slots list is null 2021-12-28 22:05:44 +01:00
NEZNAMY a9a4589fae disable logging of invalid numbers due to false positive on BungeeCord before bridge placeholders are initialized 2021-12-28 18:53:29 +01:00
NEZNAMY b8f972a774 disable scoreboard score packet check due to problems it causes 2021-12-28 18:44:14 +01:00
NEZNAMY 305e388478 translate to a more user-friendly exception when bossbar.yml is missing when trying to convert configuration 2021-12-26 15:18:42 +01:00
NEZNAMY 97e7848a78 add catch for IllegalStateException which may be thrown when using plugman 2021-12-26 15:15:16 +01:00
NEZNAMY d33058a924 fix rare error caused by concurrent access 2021-12-26 15:14:53 +01:00
NEZNAMY eda3cc1f17 fix yellow number & belowname not appearing after client reset packet (on server switch) 2021-12-26 14:47:02 +01:00
NEZNAMY 47043ad4ad fix check if injection is loaded by external plugin 2021-12-24 01:10:26 +01:00
NEZNAMY acf20088cc do not send packet if not needed 2021-12-24 01:09:43 +01:00
NEZNAMY 374cb2766c initialize array just in case 2021-12-24 00:54:31 +01:00
NEZNAMY 31685b9ba5 unregister-before-register should always be disabled on proxy 2021-12-24 00:35:38 +01:00
NEZNAMY 722ff4ce52 3.0.2-pre1 2021-12-23 21:05:45 +01:00
NEZNAMY 161b375043 add floodgate as softdepend to avoid md5 message 2021-12-23 21:04:17 +01:00
NEZNAMY 2c14191cde do not send yellow number to players in disabled worlds/servers 2021-12-23 20:48:45 +01:00
NEZNAMY 486546918f (probably) fix #455 2021-12-23 19:18:09 +01:00
NEZNAMY 23d08e58a4 make help menu text configurable (#448) 2021-12-23 19:15:55 +01:00
NEZNAMY 4c4c34ecaa fix #453 2021-12-23 18:55:29 +01:00
NEZNAMY bd3f2fd70a initialize EventBus in declaration 2021-12-23 18:52:59 +01:00
NEZNAMY b80e42a635 fix NPE in custom lines 2021-12-23 15:32:19 +01:00
NEZNAMY 9446a034d0 fix layout resetting on server switch on velocity 2021-12-23 13:41:13 +01:00
NEZNAMY da0c6ab3fd remove unused import 2021-12-23 11:58:57 +01:00
NEZNAMY c1adb27ee5 remove PacketAPI class as it's no longer needed 2021-12-22 20:54:03 +01:00
NEZNAMY 980d3027e3 use defined constant 2021-12-22 19:23:21 +01:00
NEZNAMY 53ba533f9b small optimizations in reducing amount of sent packets 2021-12-22 19:20:27 +01:00
NEZNAMY e40827da65 don't send empty packets 2021-12-22 18:45:15 +01:00
NEZNAMY b9848b6a96 add debug message for player quit processing time 2021-12-22 18:09:03 +01:00
NEZNAMY 147593b11a use WeakHashMap to store player data in features to not need to remove it on quit 2021-12-22 18:08:47 +01:00
NEZNAMY 21652f928b remove online placeholder update on join/leave 2021-12-22 16:05:07 +01:00
NEZNAMY 5b0352aff0 only unregister teams, no need for scores as well 2021-12-22 15:18:21 +01:00
NEZNAMY b2da2fa096 update README.md 2021-12-22 15:04:12 +01:00
NEZNAMY 0cbb01a2a8 fix #447 2021-12-22 15:02:55 +01:00
NEZNAMY 179840968d Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-22 15:00:53 +01:00
NEZNAMY 3b9c1649a1 Merge pull request #452 from kacperleague9/v3
Setup workflow artifact
2021-12-22 14:47:41 +01:00
NEZNAMY 5750c7dbe1 Merge pull request #440 from NEZNAMY/common-events
Added platform agnostic event system
2021-12-22 14:01:16 +01:00
Callum Seabrook 7ceca359be Let's not recreate the event bus on reload 2021-12-22 12:42:48 +00:00
Callum Seabrook 0f8152a6ed Remove priorities, make events interfaces, and expose bus to API 2021-12-22 11:44:56 +00:00
Callum Seabrook b3258bb020 Added platform agnostic event system 2021-12-22 11:43:30 +00:00
Kacper Smoliński 4b2e705cbc Update build.yml 2021-12-22 08:43:48 +01:00
Kacper Smoliński 31e41382f8 Update build.yml 2021-12-22 08:39:30 +01:00
Kacper Smoliński ecda7192dc Update build.yml 2021-12-22 08:22:32 +01:00
Kacper Smoliński 9a48562b9f test gradle workflow 2021-12-22 08:20:18 +01:00
NEZNAMY c181b58bc1 fix NPE that makes no sense 2021-12-21 17:18:32 +01:00
NEZNAMY 2624afa2ed update README.md 2021-12-21 12:04:18 +01:00
Callum Seabrook 3690af1e35 Removed vanished placeholder from Krypton registry 2021-12-20 18:09:13 +00:00
NEZNAMY 98966d3b38 update README.md 2021-12-20 19:06:09 +01:00
NEZNAMY 41fdd16ce1 fix error when running /tab scoreboard show 2021-12-19 18:52:25 +01:00
NEZNAMY 1bc94859c0 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-19 18:42:23 +01:00
Callum Seabrook 60f6f36437 Error manager good print stack trace bad 2021-12-19 13:16:43 +00:00
Callum Seabrook 82267ba16a Updated dependencies 2021-12-19 13:10:02 +00:00
NEZNAMY 14260deef8 make method synchronized to avoid rare bug caused by misconfiguration 2021-12-18 23:41:11 +01:00
NEZNAMY 8ba7342ccf register vanish placeholder in universal since implementation is same 2021-12-18 12:19:32 +01:00
NEZNAMY f6b935d78e fix double vanish status request on placeholder refresh 2021-12-18 12:17:39 +01:00
NEZNAMY 8cf3ae2091 ping spoof should only affect real players (#444) 2021-12-17 14:55:40 +01:00
NEZNAMY e02b997521 do not use %tps% in default config on bungeecord 2021-12-17 14:50:20 +01:00
NEZNAMY 229cef13c9 add cooldowns for attribute refreshing to prevent massive packet spam with many online players 2021-12-17 14:37:14 +01:00
NEZNAMY f503d6f896 move initialization to declaration to try to fix #442 2021-12-17 14:36:33 +01:00
NEZNAMY d851166bb7 add check for duplicated objectives and score updates without objectives 2021-12-17 13:56:44 +01:00
NEZNAMY 638a861350 display plugin message content in sent packets logging 2021-12-16 20:41:09 +01:00
NEZNAMY 20d7e4cbb0 fix entries with same value skipped in /tab cpu 2021-12-16 20:40:27 +01:00
NEZNAMY 36ccedd7c6 remove unused import 2021-12-16 18:08:38 +01:00
NEZNAMY 31854ba7a6 fix world being requested from bridge twice on reload 2021-12-16 18:07:22 +01:00
NEZNAMY 2639f07f5c recode and fixed 11 issues in config conversion 2021-12-16 17:56:29 +01:00
NEZNAMY b6e13f557f small fix in default config 2021-12-16 17:02:41 +01:00
NEZNAMY 6333d90059 remove redundant check 2021-12-16 17:02:27 +01:00
NEZNAMY fea5d0ec56 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-16 11:07:50 +01:00
NEZNAMY c77f02d1fd disable LD hook until they fix the repository 2021-12-16 11:04:43 +01:00
NEZNAMY c498965cae add API to return player's active scoreboard 2021-12-16 11:03:54 +01:00
Callum Seabrook cdaa77071d Updated Krypton to 0.59.2 2021-12-14 11:48:25 +00:00
NEZNAMY 6a72678c8d simplify 2021-12-14 10:19:47 +01:00
NEZNAMY bb67fc622f fix errors when redisbungee is not enabled correctly 2021-12-13 18:22:16 +01:00
NEZNAMY 5b50c02d93 fix replacements not working in some placeholders 2021-12-13 16:36:54 +01:00
NEZNAMY ffec7e3266 fix world/server groups loading in header/footer 2021-12-13 14:09:56 +01:00
NEZNAMY 039c1e7027 fix velocity compilation 2021-12-11 21:27:29 +01:00
NEZNAMY b27a21b3b8 remove unused variable and imports 2021-12-11 20:03:44 +01:00
NEZNAMY 2bcb5f617e create constants for feature names 2021-12-11 19:51:11 +01:00
NEZNAMY 7bf5294f9f remove redundant check 2021-12-11 17:43:49 +01:00
NEZNAMY c2fd1940f0 separate character width file loading into another method 2021-12-11 17:41:35 +01:00
NEZNAMY 2d095ce6db fix compilation 2021-12-11 17:24:57 +01:00
NEZNAMY e88bc36fca remove unused field 2021-12-11 17:23:20 +01:00
NEZNAMY 5613ea4f77 fix placeholders not updating in replacements 2021-12-11 17:12:58 +01:00
NEZNAMY 9c41599d60 remove redundant escape (again) 2021-12-11 16:04:40 +01:00
NEZNAMY 5f8dd869e0 remove redundant escape in RGB patterns 2021-12-11 15:58:45 +01:00
NEZNAMY fb17df0b7e add registered placeholders to command complete in parse command 2021-12-11 14:53:38 +01:00
NEZNAMY 900ae83028 3.0.1 2021-12-11 12:11:58 +01:00
NEZNAMY 2c09db25b7 Merge pull request #432 from LeonMangler/patch-1
Load TAB after PremiumVanish
2021-12-11 12:10:26 +01:00
NEZNAMY 004e53fa29 Merge pull request #437 from Tanguygab/master
fix NPE when executing scoreboard cmd from console
2021-12-11 12:10:01 +01:00
NEZNAMY e285b143d5 3.0.0-pre3 2021-12-11 00:15:44 +01:00
NEZNAMY f78eb0ffed add API to change group temporarily 2021-12-11 00:14:13 +01:00
NEZNAMY 4d761e1d6e allow decimals in sorting by placeholder 2021-12-11 00:04:20 +01:00
NEZNAMY 4a8c975319 fix startup on bungeecord when assigning groups by permissions AND using bukkit permission manager 2021-12-10 23:45:55 +01:00
NEZNAMY fe56b7ed2d add LP group event listener as well & fix refreshing when assigning by permissions 2021-12-10 23:21:40 +01:00
NEZNAMY 5a74654807 fix scoreboard being enabled by default when converting config 2021-12-10 22:16:08 +01:00
NEZNAMY 533ce4a529 remove unused import 2021-12-10 20:55:19 +01:00
Tanguygab 4ea3780114 fix NPE when executing cmd from console 2021-12-10 19:36:45 +01:00
NEZNAMY 0d229ee26c do not send objective changes before they are registered 2021-12-10 18:25:56 +01:00
NEZNAMY 29079ecad5 add 1.18.1 to recognized versions 2021-12-10 16:01:21 +01:00
NEZNAMY ea88401a58 fix NPE when placeholder returns null 2021-12-10 15:25:17 +01:00
NEZNAMY 298cef4d1f fix startup error if replacement value is null 2021-12-10 15:24:45 +01:00
NEZNAMY a51e9495de add player load status checks to other placeholder types as well 2021-12-10 15:12:43 +01:00
NEZNAMY eead8ca1e2 attempt to avoid rare concurrent modification 2021-12-10 15:11:39 +01:00
NEZNAMY a30712bc38 unregister on bungeecord as well to avoid issues with new bukkit-focused check 2021-12-10 14:56:06 +01:00
NEZNAMY 07b443fb21 actually fixed the error 2021-12-10 14:54:23 +01:00
NEZNAMY 61d927305c add very likely useless check to try to prevent errors 2021-12-10 13:24:29 +01:00
NEZNAMY 8f21c2f27d fix mohist (again) 2021-12-09 22:34:20 +01:00
NEZNAMY 9fcf76e0c4 3.0.1-pre2 2021-12-09 14:53:16 +01:00
NEZNAMY 773afe2fb5 fix _DEFAULT_ not being used when using MySQL (#433) 2021-12-09 14:05:28 +01:00
NEZNAMY 83ce5bd476 Drop <#RRGGBB> format due to conflict with the gradient pattern starting the same way (#430) 2021-12-09 13:50:04 +01:00
NEZNAMY 75a341c240 fix placeholder refreshing being broken for time travelers 2021-12-09 13:44:51 +01:00
NEZNAMY 921a793b5c return identifier instead of null for sync placeholders before initialized 2021-12-09 13:05:31 +01:00
NEZNAMY c1b92868cd fix world forwarding on bungeecord 2021-12-08 20:39:51 +01:00
NEZNAMY 94d19d728d fix condition refresh when only trigger placeholders are used inside 2021-12-08 20:38:39 +01:00
NEZNAMY 8ef518f7e2 add null check for tablist properties when refreshing 2021-12-08 19:11:43 +01:00
NEZNAMY 79fd1148a9 add back animation format converting from 2.8.10 -> 2.9.0 2021-12-08 18:51:00 +01:00
NEZNAMY 313ba8d765 attempt to fix error caused by concurrent access 2021-12-08 18:43:22 +01:00
NEZNAMY c12c422357 fix NPE when editing unknown bossbar 2021-12-08 18:37:56 +01:00
NEZNAMY b14c97d18a add try/catch for ConcurrentModificationException 2021-12-08 18:34:39 +01:00
NEZNAMY 2fe345cb65 add back "barcolors" animation since it's used in default config 2021-12-08 18:11:03 +01:00
NEZNAMY 96ff81370c fix animations lagging when using nested animations 2021-12-08 18:09:48 +01:00
Leon Mangler c4a1f41673 Load TAB after PremiumVanish
When BungeeTabPlayer#isVanished uses PV's API, PV needs to actually be enabled, not just loaded. A soft-dependency should ensure that PV is fully enabled before TAB's onEnable() uses the API.
2021-12-08 17:37:13 +01:00
NEZNAMY ca4c8ccb20 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-08 15:57:51 +01:00
Callum Seabrook 7471006f8a Updated to Krypton 0.58 2021-12-07 18:14:23 +00:00
NEZNAMY 896c709824 fix small memory leak 2021-12-04 22:41:55 +01:00
NEZNAMY 473a37cb6a fix NPE 2021-12-04 22:29:10 +01:00
NEZNAMY 9e84d96051 Add back %essentialsnick% for all the crybabies we get in #community-support who didn't even bother reading changelog of a major plugin update which is supposed to hint big changes were made and the changelog should be read. 2021-12-04 20:56:30 +01:00
NEZNAMY 019466d84d remove unused code 2021-12-04 19:27:02 +01:00
NEZNAMY c222061529 slightly optimize width calculation & partially avoid #347 2021-12-04 19:15:36 +01:00
NEZNAMY acdd99404c use different functions for file operations 2021-12-04 19:08:58 +01:00
NEZNAMY 799381bff3 properly convert objects to string 2021-12-04 19:02:11 +01:00
NEZNAMY b3654977ca cast to long 2021-12-04 18:59:03 +01:00
NEZNAMY b67d1928b5 add "final" modifier 2021-12-04 18:58:00 +01:00
NEZNAMY 052748d2b9 simplify 2021-12-04 18:47:18 +01:00
NEZNAMY e265187ff8 call function instead of duplicating code 2021-12-04 18:10:44 +01:00
NEZNAMY c622a95550 use fixed interval to avoid startup error 2021-12-04 17:09:40 +01:00
NEZNAMY 94d71a245b Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-04 13:28:23 +01:00
NEZNAMY a9403269be add LP softdepend on bungeecord 2021-12-04 13:26:16 +01:00
Callum Seabrook dfe6768eb5 Moved YamlAssist to Krypton repository version 2021-12-04 12:19:55 +00:00
Callum Seabrook d88bf65806 Setup publishing for all but the JAR module 2021-12-04 12:05:35 +00:00
NEZNAMY 30929062d6 what happened here 2021-12-04 12:20:56 +01:00
NEZNAMY 747e3d692d clear registered teams on login packet 2021-12-04 12:16:07 +01:00
Callum Seabrook 1d3ecbb7ed Somehow forgot that display name is now nullable 2021-12-04 10:28:25 +00:00
Callum Seabrook 39b88b0dd5 Updated Krypton to 0.57 2021-12-04 10:24:18 +00:00
NEZNAMY d13d3eb055 bridge placeholders will return identifier until they are initialized instead of null 2021-12-03 16:21:04 +01:00
NEZNAMY f8f72b9074 rgb support for remaining players text as well 2021-12-03 15:47:51 +01:00
NEZNAMY a9d0b0a0ae 3.0.1-pre1 2021-12-03 15:38:46 +01:00
NEZNAMY 37bd624537 double-fix for bungeecord bug kick 2021-12-03 15:37:23 +01:00
NEZNAMY e19f4ed771 reorder bungee.yml to match PluginDescription.java order 2021-12-03 12:11:27 +01:00
NEZNAMY c828fa8d52 Merge remote-tracking branch 'origin/master' 2021-12-03 12:00:36 +01:00
NEZNAMY 9e080a5f5c add argument check 2021-12-03 12:00:20 +01:00
NEZNAMY 7d7d4c05ab remove debug message 2021-12-03 11:59:34 +01:00
NEZNAMY d4c7044708 add proper color support to layout more players text 2021-12-02 20:36:38 +01:00
Callum Seabrook 52dd8c7c8b Added run folder to gitignore for test servers 2021-12-02 18:49:11 +00:00
NEZNAMY 398ac8b6cf register invalid relational placeholders on proxy correctly, so it doesn't throw errors 2021-12-02 18:12:53 +01:00
NEZNAMY 911792b214 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-12-02 13:50:21 +01:00
Callum Seabrook 8e54989502 Setup new publish repository for API 2021-12-02 12:49:11 +00:00
NEZNAMY 4f16579063 support mineskin by uuid 2021-12-02 11:01:19 +01:00
NEZNAMY e1f1698413 fix startup on mohist 1.12.2 2021-12-02 10:09:25 +01:00
NEZNAMY 5eb999b98a fixed typos, capital letters, more docs 2021-12-01 15:52:18 +01:00
NEZNAMY 5e7af835d8 %online% cannot be trigger due to vanish checks 2021-12-01 10:12:05 +01:00
NEZNAMY f19f2c5a20 add conversion for multiple groups with same sorting priority 2021-12-01 08:57:24 +01:00
NEZNAMY ff2aceb781 fix placeholder in default layout.yml 2021-12-01 08:50:37 +01:00
NEZNAMY dc1410adbb update README 2021-11-30 22:05:57 +01:00
NEZNAMY 49af38f3c5 3.0.0 2021-11-30 19:42:48 +01:00
NEZNAMY 09cf4dcd07 actually fix krypton compilation 2021-11-30 19:42:37 +01:00
Callum Seabrook 95c92285b9 Fixed Krypton 2021-11-30 18:05:07 +00:00
NEZNAMY 81b7be9205 remove tpsbar from default config (it's on wiki now) 2021-11-30 18:19:06 +01:00
NEZNAMY c308cde139 fix condition default value of "false" 2021-11-29 18:47:27 +01:00
NEZNAMY 49889cd2cc fix %afk% conversion 2021-11-29 12:33:28 +01:00
NEZNAMY d0ea71f0c9 fix method name 2021-11-28 21:42:25 +01:00
NEZNAMY a6f93dae1e Merge pull request #418 from KryptonMC/v3
Added blossom for plugin version source replacement
2021-11-28 13:34:09 +01:00
Callum Seabrook f968999393 Added blossom for plugin version source replacement 2021-11-28 12:21:15 +00:00
NEZNAMY 37348ed1f4 3.0.0-pre4 2021-11-28 12:19:41 +01:00
NEZNAMY 8602d39d7c fix mysql & load playerdata on every join 2021-11-28 12:14:34 +01:00
NEZNAMY fd41390e20 Merge pull request #417 from NEZNAMY/revert-416-v3
Revert "Switched to adapters over whacky NMSStorage"
2021-11-28 00:44:36 +01:00
NEZNAMY 712064bced Revert "Switched to adapters over whacky NMSStorage" 2021-11-28 00:44:25 +01:00
NEZNAMY 771a696de0 Merge pull request #416 from KryptonMC/v3
Switched to adapters over whacky NMSStorage
2021-11-28 00:02:01 +01:00
Callum Seabrook a56cbcc2f4 Added 1_17_R1 adapter 2021-11-27 23:01:11 +00:00
Callum Seabrook 8e659a2c97 Switched to adapters over whacky NMSStorage 2021-11-27 22:41:04 +00:00
NEZNAMY 423bd0adb8 1.18 2021-11-27 21:28:41 +01:00
NEZNAMY 1987848f8b fix weird NPE 2021-11-27 21:28:36 +01:00
NEZNAMY 13ac8541f2 fix utf symbol in default config 2021-11-27 21:04:31 +01:00
NEZNAMY 0e42d61ffa add %tab_scoreboard_name% placeholder 2021-11-27 18:09:35 +01:00
NEZNAMY 64b4cb1bb9 fix component cloning missing color 2021-11-27 14:08:13 +01:00
NEZNAMY efd723deec add else statement 2021-11-26 21:19:46 +01:00
NEZNAMY 4c2d2068a4 Merge pull request #415 from AlessioGr/v3
Improve TAB Completion for /tab width command
2021-11-25 11:14:37 +01:00
AlessioGr 32529dd7e9 Improve TAB Completion for /tab width command
Signed-off-by: AlessioGr <alessio@gravili.de>
2021-11-25 11:12:04 +01:00
NEZNAMY 3ad54d5f21 Merge pull request #414 from AlessioGr/v3
Further improve Width command
2021-11-25 10:56:38 +01:00
AlessioGr 6d81c37645 Further improve Width command
Signed-off-by: AlessioGr <alessio@gravili.de>
2021-11-25 10:50:53 +01:00
NEZNAMY da1c51eb5b Merge pull request #413 from AlessioGr/v3
Improve /tab width command
2021-11-25 10:18:26 +01:00
AlessioGr 70c37fb9f2 fix Codacy Static Code Analysis warning
Signed-off-by: AlessioGr <alessio@gravili.de>
2021-11-25 10:17:01 +01:00
AlessioGr 5e565be35b Improve /tab width command
Signed-off-by: AlessioGr <alessio@gravili.de>
2021-11-25 10:07:32 +01:00
NEZNAMY eeb39ca228 code style 2021-11-25 09:09:56 +01:00
NEZNAMY 2d5bc2bc52 on load as well 2021-11-24 15:08:47 +01:00
NEZNAMY 4fb517c8cb fix #412 2021-11-24 15:08:07 +01:00
NEZNAMY de376cd9f4 fix onServerChange not being called when it should be 2021-11-24 15:06:52 +01:00
NEZNAMY d939984f08 refresh animations at their original rate while keeping sync 2021-11-24 14:47:19 +01:00
NEZNAMY 89edcb7a38 fix %online_<server>% placeholders 2021-11-24 14:17:53 +01:00
NEZNAMY ac16a0fb26 set gradients first to fix issue from latest new format 2021-11-24 07:39:39 +01:00
NEZNAMY 9189fb2082 3.0.0-pre3 2021-11-23 00:03:38 +01:00
NEZNAMY a2e7670560 Merge branch 'v3' of https://github.com/NEZNAMY/TAB into v3 2021-11-22 23:13:55 +01:00
NEZNAMY 7c9f14b4d9 Merge pull request #411 from KryptonMC/v3
Added Java components for publishing and sources and javadoc jars
2021-11-22 23:13:29 +01:00
NEZNAMY f05849aada fix error on reload 2021-11-22 21:14:00 +01:00
NEZNAMY 66e91ee4e8 don't set numbers if disabled 2021-11-22 21:10:01 +01:00
NEZNAMY 015d8de101 fix error with static lines 2021-11-22 21:08:54 +01:00
Callum Seabrook 69e575e0ef Added Java components for publishing and sources and javadoc jars 2021-11-22 18:46:14 +00:00
NEZNAMY 6c84e49a7f improve default files 2021-11-22 10:01:37 +01:00
NEZNAMY f4e5e8fd72 fix startup error 2021-11-22 09:51:13 +01:00
NEZNAMY fe8c738196 only catch ClassNotFoundException in fabric check 2021-11-22 09:37:20 +01:00
NEZNAMY 665dfac5ea remove unnecessary String.valueOf 2021-11-21 21:50:33 +01:00
NEZNAMY 57f9806819 fix /tab reload breaking external placeholders 2021-11-21 21:32:44 +01:00
NEZNAMY 7874135014 update scoreboard scores when line hides/shows 2021-11-21 21:32:28 +01:00
NEZNAMY f86e7b17ef add another rgb format 2021-11-21 21:04:51 +01:00
NEZNAMY 3803173745 Merge branch 'v3' of https://github.com/NEZNAMY/TAB into v3 2021-11-21 13:31:56 +01:00
NEZNAMY 6af30bf6df improve placeholder registration at runtime 2021-11-21 13:31:43 +01:00
NEZNAMY 7bea6a214a Merge pull request #410 from KryptonMC/v3
Updated Krypton, added vehicle support, and fixed many features being non-functional
2021-11-21 13:30:51 +01:00
Callum Seabrook 9becc35fa1 Fixed many features in Krypton 2021-11-21 12:24:52 +00:00
NEZNAMY 7212a4cd7f round health up 2021-11-21 12:09:24 +01:00
NEZNAMY c604640ab9 fix placeholder registration at runtime 2021-11-21 11:23:13 +01:00
NEZNAMY 23fee2b7b7 fix LP suffix placeholder 2021-11-20 23:44:32 +01:00
NEZNAMY c3d521960e only call onDisable if placeholder was used 2021-11-20 23:09:44 +01:00
NEZNAMY 484396cce1 move %online% update to a better spot and fix NPE 2021-11-20 22:50:49 +01:00
NEZNAMY ce4dec6136 disable krypton compilation 2021-11-20 21:13:59 +01:00
NEZNAMY 67f42f518e implement trigger placeholders 2021-11-20 21:13:50 +01:00
Callum Seabrook 0a0e947282 Added option to specify the config name to use 2021-11-20 19:46:26 +00:00
Callum Seabrook 8a4127574a Fixed NPEs from packets and formatting on Spark placeholders 2021-11-20 18:33:20 +00:00
Callum Seabrook b29d3e5d6a Updated to Krypton 0.50 and made vehicle refresher actually work 2021-11-20 17:41:46 +00:00
NEZNAMY daaece8d36 Merge branch 'v3' of https://github.com/NEZNAMY/TAB into v3 2021-11-20 11:48:49 +01:00
NEZNAMY 0e5b1aaf0e obtain channel instance 2021-11-20 11:48:28 +01:00
NEZNAMY e672e49435 Merge pull request #409 from KryptonMC/v3
Updated Krypton to latest and added support for newest changes
2021-11-20 11:13:50 +01:00
NEZNAMY b9a38a486d make packetBuilder field editable 2021-11-20 10:59:21 +01:00
Callum Seabrook d34c46658d Added refresh display names to refresher constructors 2021-11-19 23:54:48 +00:00
Callum Seabrook e120b647ea Cleaned up Krypton platform and added support for new features 2021-11-19 23:52:46 +00:00
Callum Seabrook 936b8ae903 Updated to Krypton 0.48.1 2021-11-19 23:52:19 +00:00
NEZNAMY 147fe2c277 fix krypton 2021-11-20 00:14:06 +01:00
NEZNAMY 84413aa490 define refresh display name in super() call 2021-11-19 23:13:19 +01:00
NEZNAMY 3fea95f8c5 instance is never null 2021-11-19 22:51:53 +01:00
NEZNAMY b839bce010 change display name and split elsewhere in cpu output 2021-11-19 22:51:05 +01:00
NEZNAMY fc70c098a9 add "final" modifier to all fields possible 2021-11-18 23:00:09 +01:00
NEZNAMY 7b74d73a9a add getter for defined lines into API 2021-11-18 20:38:54 +01:00
NEZNAMY 44ee3bbdfe using shortcuts 2021-11-18 18:30:05 +01:00
NEZNAMY 673285baf8 copy files automatically in UTF-8 encoding 2021-11-18 15:47:10 +01:00
NEZNAMY 2b22e327e3 cache player name widths in aligned suffix
and maybe introduce new bugs, who knows
2021-11-18 15:20:25 +01:00
NEZNAMY 95d65236f6 in NPE 2021-11-18 14:11:02 +01:00
NEZNAMY 8971172444 fix typo in messages 2021-11-18 14:08:05 +01:00
NEZNAMY 6ff5836ca9 fix NPE on fabric 2021-11-18 12:01:47 +01:00
NEZNAMY 8e9a4e9601 use the other way of checking server minor version 2021-11-18 11:54:17 +01:00
NEZNAMY 1ce5c05622 use more universal code 2021-11-18 11:48:52 +01:00
NEZNAMY 7f83e83aed refresh boats in vehicle refresher as well 2021-11-18 11:33:37 +01:00
NEZNAMY e677e7345b remove vehicle on quit 2021-11-18 11:18:11 +01:00
NEZNAMY ae52aa2c55 instant vehicle enter teleport instead of an animation 2021-11-18 11:13:40 +01:00
NEZNAMY b83f6e93bf refreshing vehicles and preview using placeholder engine instead of refreshing task 2021-11-18 11:01:37 +01:00
NEZNAMY 3606ee6275 fix startup on fabric 2021-11-17 21:28:48 +01:00
NEZNAMY 70571cccfe replace stacked reflection exceptions with ReflectiveOperationException 2021-11-17 21:20:12 +01:00
NEZNAMY 87fe02b0b4 attempt to clean up NMSStorage class 2021-11-17 21:05:23 +01:00
NEZNAMY 8e48087f65 load config options in field declarations instead of constructors 2021-11-17 18:45:58 +01:00
NEZNAMY 4e7c3ee2f1 remove duplicated file 2021-11-17 13:33:17 +01:00
NEZNAMY 60a7549662 move constants 2021-11-17 13:32:51 +01:00
NEZNAMY fe7c9488d8 rework property API per-feature 2021-11-16 16:51:31 +01:00
NEZNAMY b7d39e55b6 send armor stand destroy without delay as well 2021-11-16 16:10:51 +01:00
NEZNAMY 805d8030b4 add option to disable unlimited nametags for specific player using API 2021-11-16 16:08:06 +01:00
NEZNAMY 4c6fc5d2d2 fix #408
caused by unregister-register operation on server switch being removed in v3 due to per-world/per-server scoreboards being removed
2021-11-16 12:18:30 +01:00
NEZNAMY 913b8ee238 avoid duplicating code 2021-11-16 11:49:54 +01:00
NEZNAMY 3688e4d938 Merge pull request #407 from KryptonMC/gradle
Gradle!
2021-11-15 21:58:41 +01:00
Callum Seabrook 03842db57f Removed SonarQube and updated workflow to match 2021-11-15 20:56:19 +00:00
Callum Seabrook 6f34157a84 Added inclusion for gradle wrapper 2021-11-15 20:56:19 +00:00
Callum Seabrook 86a82faa15 Fixed Krypton and Velocity annotation processors not working 2021-11-15 20:56:19 +00:00
Callum Seabrook 2e506b72a6 Fixed incorrect shading 2021-11-15 20:56:19 +00:00
Callum Seabrook d2ba0a22d5 These should be this 2021-11-15 20:56:19 +00:00
Callum Seabrook cfc3488a8c Switched build to Gradle 2021-11-15 20:56:17 +00:00
NEZNAMY 0463e82470 Merge pull request #406 from NEZNAMY/revert-405-v3
Revert "Made Krypton a separate JAR"
2021-11-15 18:12:08 +01:00
NEZNAMY 93b151b377 Revert "Made Krypton a separate JAR" 2021-11-15 18:11:47 +01:00
NEZNAMY 1c6fa28d87 Merge pull request #405 from KryptonMC/v3
Made Krypton a separate JAR
2021-11-15 18:06:23 +01:00
Callum Seabrook 71d01263b5 Made Krypton a separate JAR 2021-11-15 17:04:50 +00:00
NEZNAMY 23268907bd compile krypton with java 8 2021-11-15 17:22:43 +01:00
NEZNAMY 076e3061b3 fix pauseTeamHandling not disabling unlimited nametags 2021-11-15 17:15:33 +01:00
NEZNAMY d9f8fa2ed0 split feature elements in cpu output 2021-11-15 16:03:13 +01:00
NEZNAMY c33f9a6d61 drop latency refresher since it doesn't work with new change anymore 2021-11-15 15:49:57 +01:00
NEZNAMY 07f71f3d4d add support for CMI's {#RRGGBB<>} shortcut 2021-11-15 15:40:46 +01:00
NEZNAMY 48f8a4c718 fix spectators exposed for players who joined
caused by pipeline injection being late (async)
2021-11-15 15:03:33 +01:00
NEZNAMY ff29b1badf remove deleted options from conversion 2021-11-15 14:54:49 +01:00
NEZNAMY 05d3233c03 fix typo 2021-11-15 14:50:19 +01:00
NEZNAMY 203b5d925b fix wrong argument 2021-11-15 14:30:08 +01:00
NEZNAMY b2f6338b0b fix krypton 2021-11-15 14:12:32 +01:00
NEZNAMY 26e526d5fe create constants for permission checks 2021-11-15 13:55:03 +01:00
NEZNAMY 054d6c58cb rename class 2021-11-15 13:22:42 +01:00
NEZNAMY cc6b5ea0f5 add missing repository 2021-11-15 13:16:32 +01:00
NEZNAMY 495985ace8 Merge branch 'v3' of https://github.com/NEZNAMY/TAB into v3 2021-11-15 12:36:55 +01:00
NEZNAMY a304853a7b rework command output messages and rename file translation.yml -> messages.yml 2021-11-15 12:36:52 +01:00
NEZNAMY eb2c58f0e9 Merge pull request #404 from KryptonMC/feature/krypton
Added support for Krypton
2021-11-15 12:09:31 +01:00
NEZNAMY 125429bccd ignore case when getting player by name 2021-11-15 11:23:33 +01:00
Callum Seabrook c5bf1b65ba Added online placeholders and made spark placeholders server placeholders 2021-11-14 15:09:35 +00:00
Callum Seabrook 5286a018df Added support for Krypton 2021-11-14 15:09:34 +00:00
NEZNAMY c408744385 add back %% transforming into a single % 2021-11-14 14:19:05 +01:00
NEZNAMY 5efd5f310f change names of slots 2021-11-13 23:52:15 +01:00
NEZNAMY e35c14d8e3 measure NMS hook load time 2021-11-13 22:07:55 +01:00
NEZNAMY d49fcc2307 properly send action bar messages on velocity 2021-11-13 18:48:49 +01:00
NEZNAMY afc1dfcf10 remove redundant initialization 2021-11-13 14:49:17 +01:00
NEZNAMY 2dd06ed0c3 cleanup in placeholder registration 2021-11-13 13:35:28 +01:00
NEZNAMY 2ca223cd2c drop AntiAFKPlus hook 2021-11-13 13:24:29 +01:00
NEZNAMY f6cf018625 update supported versions 2021-11-13 13:18:45 +01:00
NEZNAMY 1d7806c96e fill exception message 2021-11-12 17:18:05 +01:00
NEZNAMY 650fd892f8 fix error when output is not a number 2021-11-12 16:52:52 +01:00
NEZNAMY 83710b14b1 add "me" player name completion to /tab parse 2021-11-12 16:47:49 +01:00
NEZNAMY 1eeb22f579 do not send score changes when player is not loaded yet 2021-11-12 16:47:31 +01:00
NEZNAMY 1d70e73106 add null check for redisbungee api instance 2021-11-12 13:57:37 +01:00
NEZNAMY 9d93b52be1 only process nicking once instead of for every online player 2021-11-12 13:21:52 +01:00
NEZNAMY 4431772007 create instance of CollisionManager on 1.8 to prevent NPEs 2021-11-12 13:15:14 +01:00
NEZNAMY f17fafbb48 shorter alternative 2021-11-11 22:41:31 +01:00
NEZNAMY 8d30399f33 initial support for nick plugins 2021-11-11 22:20:59 +01:00
NEZNAMY 5feb934a4a add space 2021-11-11 18:06:30 +01:00
NEZNAMY 9466b96f69 properly forward team flags in packets 2021-11-11 13:08:40 +01:00
NEZNAMY ad00d1b145 only start layout feature on 1.8+ 2021-11-11 13:08:06 +01:00
NEZNAMY 89ea9ba8a0 return empty list instead of null 2021-11-11 11:57:24 +01:00
NEZNAMY e08fb3eeb6 fix skin saving 2021-11-11 11:52:58 +01:00
NEZNAMY 5800c8910e split skinmanager class into multiple 2021-11-11 11:48:55 +01:00
NEZNAMY 1ec77cb0b5 don't refresh collision if no disguise plugin is available 2021-11-11 11:09:37 +01:00
NEZNAMY 3329514066 decrease default time to 10000 since it can't get higher than that anyway 2021-11-11 10:41:11 +01:00
NEZNAMY addc3f322f load feature after registering placeholders correctly to avoid wrong registration 2021-11-11 10:40:56 +01:00
NEZNAMY 79dcd16d37 only refresh visibility if 1.8.x clients are accepted 2021-11-11 10:09:58 +01:00
NEZNAMY 802c72a9aa don't refresh collision if it's disabled already 2021-11-10 22:53:01 +01:00
NEZNAMY bf96676df5 properly update collision on api call 2021-11-10 22:48:21 +01:00
NEZNAMY d245b93196 remove unnecessary this. 2021-11-10 21:51:59 +01:00
NEZNAMY 6f96b555e9 split collision management into separate class + placeholder based refreshing instead of repeating task 2021-11-10 21:47:48 +01:00
NEZNAMY 872700d1be create placeholder for visibility check instead of refreshing task 2021-11-10 21:01:07 +01:00
NEZNAMY 1ac08ee1f2 try/catch for more exception types 2021-11-10 20:34:06 +01:00
NEZNAMY ada433150c fix forceTeamName method not working 2021-11-10 13:58:02 +01:00
NEZNAMY ee40a69e57 use cleaner alternative 2021-11-10 13:57:37 +01:00
NEZNAMY d6fcaffa99 add team name changing support to redisbungee 2021-11-10 13:45:42 +01:00
NEZNAMY fe4bdda549 fix property name in debug output 2021-11-10 13:01:08 +01:00
NEZNAMY 6a1016a383 move &r 2021-11-10 12:59:14 +01:00
NEZNAMY c53f08d0a7 surround deserialize method with try/catch instead of throwing 2021-11-10 11:59:51 +01:00
NEZNAMY 2807ff30e6 fix premiumconfig not being deleted 2021-11-10 11:23:41 +01:00
NEZNAMY cf3b9c21dd apply rgb formats later so raw value returns actual raw value 2021-11-09 11:07:28 +01:00
NEZNAMY ee616412d6 simplify 2021-11-08 19:13:45 +01:00
NEZNAMY 341c6f8722 3.0.0-pre2 2021-11-07 15:27:25 +01:00
NEZNAMY 978df99e5c create dummy placeholder instead of thread for cpu stats reset 2021-11-06 19:19:03 +01:00
NEZNAMY a91e2a90b4 fix substring size 2021-11-06 15:25:44 +01:00
NEZNAMY 49c54c6a58 Merge pull request #402 from Tanguygab/v3
added texture skin type for Layout SkinManager
2021-11-04 12:17:38 +01:00
Tanguygab 6b72163dbe added texture: skin type 2021-11-04 12:09:14 +01:00
NEZNAMY 93a04c64d2 fix startup error with relational placeholders 2021-11-03 22:41:15 +01:00
NEZNAMY d01d814778 show configured fake ping instead of 0 2021-11-02 21:33:39 +01:00
NEZNAMY af15c256b9 use more accurate task name 2021-11-02 21:32:16 +01:00
NEZNAMY dde0056ba2 fix nested placeholders not working in replacements 2021-11-02 20:34:25 +01:00
NEZNAMY dede5b548c reduce memory allocations of property loading 2021-11-02 14:55:02 +01:00
NEZNAMY 7be0831e7c do not use split function if not needed 2021-11-02 14:33:54 +01:00
NEZNAMY 660275dc74 change List to array 2021-11-02 13:37:45 +01:00
NEZNAMY 957e01fbf2 dynamic placeholder refresh task interval based on fastest placeholder 2021-11-02 00:41:14 +01:00
NEZNAMY b01ffaf763 simplify 2021-11-02 00:00:54 +01:00
NEZNAMY 8d6d53a448 use stringbuilder 2021-11-01 22:34:45 +01:00
NEZNAMY 8f13bfcb4a merge ifs 2021-11-01 22:32:29 +01:00
NEZNAMY ddf45bf7a7 remove unnecessary {} 2021-11-01 22:31:36 +01:00
NEZNAMY 9fabeac7e1 remove redundant cast 2021-11-01 22:30:15 +01:00
NEZNAMY 758b1f9365 simplify return 2021-11-01 22:29:34 +01:00
NEZNAMY 0050d34d0e remove redundant check 2021-11-01 22:27:09 +01:00
NEZNAMY fd547383c4 remove unused methods 2021-11-01 22:02:35 +01:00
NEZNAMY edb640f496 add expected team name into logging 2021-11-01 21:38:35 +01:00
NEZNAMY 4569fa3e86 optimize method 2021-11-01 21:22:58 +01:00
NEZNAMY 33af012352 remove redundant call 2021-11-01 20:58:42 +01:00
NEZNAMY 5cc6688d9c optimize disabled world checking for unlimited nametags 2021-11-01 20:45:40 +01:00
NEZNAMY f8a5f610bb remove redundant null check 2021-11-01 18:57:12 +01:00
NEZNAMY 877ac8adc7 GroupManager refreshing engine change from repeating task to placeholder core 2021-11-01 18:22:36 +01:00
NEZNAMY 39040c263e refresh world on switch instead of periodically on proxy 2021-11-01 17:03:05 +01:00
NEZNAMY 08759762a1 fix global playerlist on velocity 2021-11-01 15:50:12 +01:00
NEZNAMY 7897fbcf36 fix tablist format on server switch on velocity 2021-11-01 15:45:09 +01:00
NEZNAMY ce93bb1afb fix NPE with component sent to console 2021-11-01 15:12:44 +01:00
NEZNAMY 3e8085f460 fix sorting startup with layout on proxy 2021-11-01 15:06:09 +01:00
NEZNAMY 2ab201d278 print LibsDisguises hook error once instead of spamming it 2021-11-01 14:42:09 +01:00
NEZNAMY ccdac844b9 move plugin info component to command class 2021-11-01 14:39:34 +01:00
NEZNAMY 4474939c51 small code structure change 2021-11-01 14:19:31 +01:00
NEZNAMY f32975ccb0 decrease wither distance to 60 blocks 2021-11-01 14:17:05 +01:00
NEZNAMY 4f92b22955 use placeholder refreshing engine for bossbar updating instead of repeating task
this will also reduce teleports if players don't move
2021-11-01 14:07:24 +01:00
NEZNAMY 1d8ac0ab45 warn in console about Tablisknu compatibility issue 2021-11-01 12:52:33 +01:00
NEZNAMY 4cfc5258b2 add separate cpu measuring of hasPermission method 2021-11-01 12:30:48 +01:00
NEZNAMY 14b7415005 cache floodgate presence 2021-11-01 00:29:48 +01:00
NEZNAMY f482b712ca save registered scoreboards in array to reduce allocations from iteration 2021-11-01 00:25:40 +01:00
NEZNAMY c40a5cbaaa changed parse command syntax to include player name 2021-11-01 00:13:14 +01:00
NEZNAMY 9d0efca1e5 avoid any possible double bossbar create 2021-10-29 13:03:37 +02:00
NEZNAMY cf4d76e9a4 make method synchronized to avoid rare error 2021-10-28 15:58:12 +02:00
NEZNAMY 56569509f9 use ConcurrentHashMap to avoid ConcurrentModificationException 2021-10-27 21:07:05 +02:00
NEZNAMY a0904be305 use more appropriate names for anonymous conditions 2021-10-26 21:15:14 +02:00
NEZNAMY 7d156fb47b fix rare NPE 2021-10-26 21:04:34 +02:00
NEZNAMY e1796d24ad fix mysql 2021-10-24 10:48:19 +02:00
NEZNAMY 2899b1f8eb add announcement-bar property instead of listing default-bars 2021-10-21 13:14:14 +02:00
NEZNAMY 335a033091 fix NPE 2021-10-21 09:24:22 +02:00
NEZNAMY 80aa2367b0 add back width command
as well as showing character sequence if a multi-character symbol is entered
2021-10-20 21:06:17 +02:00
NEZNAMY e6ca5884a3 only decrease interval instead of possibly increasing 2021-10-20 20:14:59 +02:00
NEZNAMY ef52c84e25 calculate condition refresh intervals based on fastest placeholder 2021-10-20 19:03:25 +02:00
NEZNAMY 478f2fc9ae add back support for ; and * for groups in per-world / per-server 2021-10-20 18:25:21 +02:00
NEZNAMY 98afededa6 create method to clean up code 2021-10-20 18:04:03 +02:00
NEZNAMY 7ad2ca9f16 add viaversion compatibility check 2021-10-20 18:02:13 +02:00
NEZNAMY 03ab379c63 remove default-scoreboard option when converting config 2021-10-20 17:51:38 +02:00
NEZNAMY bd71efd22b update readme to reflect this branch 2021-10-19 22:52:27 +02:00
NEZNAMY 87b7a1c759 initial RedisBungee support
this update was only slightly tested and may contain bugs, which will eventually be fixed
2021-10-19 22:49:40 +02:00
NEZNAMY 5c59c3b1b8 fix server switch listener on bungeecord 2021-10-19 21:18:08 +02:00
NEZNAMY bb98db4568 remove and rename placeholders 2021-10-19 20:42:23 +02:00
NEZNAMY 80f0afd600 disable layout by default 2021-10-19 14:17:56 +02:00
NEZNAMY 5bc37b51bb fix player initialization 2021-10-19 14:10:04 +02:00
NEZNAMY 0f68892d11 add sorting by player name to conversion 2021-10-18 18:23:50 +02:00
NEZNAMY 37e731c2fd fix typo 2021-10-18 14:10:06 +02:00
NEZNAMY 8312ae30f1 add getter for spyServers list 2021-10-18 13:58:50 +02:00
NEZNAMY 28ec42dddf improve parity between platforms 2021-10-17 20:45:25 +02:00
NEZNAMY d9f572188e bump YamlAssist version 2021-10-17 16:54:47 +02:00
NEZNAMY 720a95d472 3.0.0-pre1 2021-10-17 12:12:35 +02:00
NEZNAMY 74ba60f4f1 fix resetScoreboard not working 2021-10-16 21:35:52 +02:00
NEZNAMY e717473a21 code style 2021-10-16 00:39:26 +02:00
NEZNAMY aca1e26c3b attempt to fix concurrent modification 2021-10-15 21:27:18 +02:00
NEZNAMY 0b1bcebc72 move initialization 2021-10-15 21:07:16 +02:00
NEZNAMY 058a5dca17 fix NPE 2021-10-15 20:43:17 +02:00
NEZNAMY da21131f9c code style 2021-10-15 20:35:25 +02:00
NEZNAMY 30fd9f84e0 code style 2021-10-15 20:29:35 +02:00
NEZNAMY 291f881e61 remove unused import 2021-10-15 19:52:13 +02:00
NEZNAMY bdb465b54e change return type from Future<?> to Future<Void> 2021-10-15 19:46:26 +02:00
NEZNAMY e39a10da22 code style 2021-10-15 17:37:48 +02:00
NEZNAMY 8f4ab6a7f1 fix premiumconfig absence when using free version 2021-10-15 17:20:22 +02:00
NEZNAMY 3259fc1a52 improve console message 2021-10-15 17:16:32 +02:00
NEZNAMY 05d85a0dc1 rethrow exceptions 2021-10-15 17:05:24 +02:00
NEZNAMY e7c4658805 add missing collision option conversion 2021-10-15 17:02:14 +02:00
NEZNAMY 51220bcae6 fix startup when sorting placeholder is not valid 2021-10-15 17:01:47 +02:00
NEZNAMY e8ff42dd75 fix typos 2021-10-15 17:01:15 +02:00
NEZNAMY 6bd54ccf2b Merge pull request #393 from Tanguygab/v3
Added 2.9.2 -> 3.0 config converter
2021-10-15 16:42:54 +02:00
NEZNAMY c3bcaba61e reduce retry count and only print error if debug is enabled 2021-10-15 16:40:37 +02:00
Tanguygab 14693b4a6e converting _OTHER_ to _DEFAULT_ 2021-10-13 15:12:12 +02:00
Tanguygab b1a75df507 Merge remote-tracking branch 'upstream/v3' into v3 2021-10-13 14:58:57 +02:00
NEZNAMY 42d65cf0dd create a copy of map to avoid error on modification 2021-10-10 17:08:30 +02:00
NEZNAMY b18803e343 add null check 2021-10-10 17:00:15 +02:00
NEZNAMY a36a071dbe update sorting error message to match new config 2021-10-10 16:53:39 +02:00
NEZNAMY edc95e1fa0 create dummy entity to use in packet constructor instead of using first player
which randomly caused NoSuchElementException
2021-10-10 16:51:14 +02:00
NEZNAMY 11b3d3ef41 remove unnecessary null check from previous testing 2021-10-10 16:45:10 +02:00
NEZNAMY 64f339b1fa synchronize list to avoid rare toArray null element issue 2021-10-10 16:09:09 +02:00
Tanguygab a6eefa7afd using File.separator instead of \\ 2021-10-09 00:53:26 +02:00
Tanguygab 837d1fb43d check if legacy config 2021-10-09 00:44:56 +02:00
Tanguygab 0d190b4ade checking if file already exists 2021-10-09 00:30:25 +02:00
Tanguygab fbc7acea60 wrong config 2021-10-09 00:25:29 +02:00
Tanguygab e6676e1a05 added 2.9.2 -> 3.0 config conversion 2021-10-09 00:22:28 +02:00
Tanguygab b9a56c6c65 Intellij didn't recognize API's classes without that for some reasons 2021-10-09 00:21:42 +02:00
NEZNAMY 520c152cc6 add debug message when missing config option was added 2021-10-08 23:35:20 +02:00
NEZNAMY f7ddf6f872 add null check 2021-10-07 09:57:05 +02:00
NEZNAMY e6997d81c1 do not show layout to bedrock and 1.7 players 2021-10-06 09:57:16 +02:00
NEZNAMY 31923fc73c attempt to fix rare error 2021-10-03 23:30:33 +02:00
NEZNAMY 5dbfc97780 fix placeholders only used in tab expansion not refreshing 2021-10-03 23:30:11 +02:00
NEZNAMY da525fa1c8 no longer require nametag sorting feature for layout to work 2021-09-23 18:39:01 +02:00
NEZNAMY 372866ee95 fix typo 2021-09-23 14:41:10 +02:00
NEZNAMY d1e1df01c0 only recalculate used placeholders if changed 2021-09-23 11:53:20 +02:00
NEZNAMY b12d9f352b port changes from #389 2021-09-20 22:38:48 +02:00
NEZNAMY b3dc4b617b change lists back to sets to avoid duplicates and cpu leak 2021-09-20 20:18:54 +02:00
NEZNAMY 67e3ff368d create class for placeholder replacements and reduce memory allocations 2021-09-20 18:25:48 +02:00
NEZNAMY 569ad41894 separate groupmanager class and fix loading order 2021-09-19 00:42:55 +02:00
NEZNAMY bd5225967d replace all sets with lists 2021-09-15 18:14:45 +02:00
NEZNAMY 9c9df4ee3b pre-parse static numeric values in conditions for better performance and lower memory allocations 2021-09-13 21:26:10 +02:00
NEZNAMY a859a20062 fix API scoreboard overridden on world/server switch 2021-09-13 21:03:43 +02:00
NEZNAMY 4fd8e736f9 add option to disable remaining players text in layout 2021-09-13 20:56:46 +02:00
NEZNAMY 41fc9b329b improve placeholder exception catching 2021-09-12 13:37:29 +02:00
NEZNAMY 3ea9408103 optimize conditions using functions 2021-09-11 23:33:44 +02:00
NEZNAMY 589170ac8a catch specific exceptions and remove redundant catch 2021-09-11 23:29:26 +02:00
NEZNAMY 54bd6cd402 use more optimized check and avoid exception throwing 2021-09-11 22:41:35 +02:00
NEZNAMY 9374b56ec7 fix method not working correctly after recode 2021-09-11 22:39:36 +02:00
NEZNAMY f6a3ea79ef use Bukkit.getTPS() if possible 2021-09-11 19:25:23 +02:00
NEZNAMY 840e2610c5 remove unused field 2021-09-08 13:44:04 +02:00
NEZNAMY d673dbf1cc fix memory leak in layouts 2021-09-07 23:27:18 +02:00
NEZNAMY 26c5de3122 include layout name in fixed slot property name to fix clash 2021-09-07 22:59:55 +02:00
NEZNAMY 7c5d2647c8 fix NPE 2021-09-07 21:49:17 +02:00
NEZNAMY 3a6256179c code cleanup with shorter load alternative 2021-09-07 19:59:39 +02:00
NEZNAMY 1d4e5fbd70 share Yaml instance across configuration files 2021-09-07 19:24:32 +02:00
NEZNAMY a456fffe45 placeholder classes no longer need to be abstract 2021-09-07 18:55:12 +02:00
NEZNAMY 3240924b2c aligned playerlist class now extends playerlist 2021-09-07 18:20:09 +02:00
NEZNAMY 670406991c match _OTHER_ being renamed to _DEFAULT_ 2021-09-07 14:56:55 +02:00
NEZNAMY 839543bbdd fix memory leak 2021-09-07 01:09:45 +02:00
NEZNAMY 47511b33a3 remove placeholder refresh interval check 2021-09-07 01:09:38 +02:00
NEZNAMY 264fd0b3a4 merge methods 2021-09-06 20:41:25 +02:00
NEZNAMY f10af90c14 remove redundant check 2021-09-06 20:34:08 +02:00
NEZNAMY 782a82ea30 use essentials directly instead of reflection 2021-09-06 20:05:25 +02:00
NEZNAMY bc0d6bb09e drop AFK+ support since it has papi support 2021-09-06 19:39:44 +02:00
NEZNAMY 54fd9d9492 rethrow InterruptedException 2021-09-06 16:15:21 +02:00
NEZNAMY 59f804c96a replace lambdas with method references 2021-09-06 16:12:51 +02:00
NEZNAMY fba1325abe remove forgotten debug message 2021-09-06 16:06:53 +02:00
NEZNAMY 4b634ad67a add support for multiple layouts with conditions 2021-09-06 16:02:17 +02:00
NEZNAMY 3990ba055a use first color in case of 1 character gradient instead of middle color 2021-09-06 15:33:40 +02:00
NEZNAMY 48784a3d7a fix random NPE which should never be thrown 2021-09-06 15:09:24 +02:00
NEZNAMY 61e87718da avoid NPE caused by invalid data sent 2021-09-06 15:02:49 +02:00
NEZNAMY a55dd34eda initialize world on proxy to prevent NPE 2021-09-06 13:20:35 +02:00
NEZNAMY fc304e5dc7 fix string comparison 2021-09-06 11:26:13 +02:00
NEZNAMY 3870950e29 avoid duplicating code 2021-09-06 01:21:54 +02:00
NEZNAMY fc97830d22 change abstract class to interface and save as array to save memory when iterating 2021-09-06 00:45:35 +02:00
NEZNAMY c7579c259b cache property names 2021-09-06 00:19:52 +02:00
NEZNAMY 1272e09a73 fix sync placeholders registering non-sync version if they didn't parse 2021-09-05 20:19:10 +02:00
NEZNAMY 79b4cbe7f1 fix gradient if text is only 1 character long 2021-09-05 17:26:44 +02:00
NEZNAMY 9a000f7881 fix startup if injection is not enabled 2021-09-05 17:16:28 +02:00
NEZNAMY 30367ad328 fix tps placeholder in default configs 2021-09-05 17:12:15 +02:00
NEZNAMY d67b447895 include plugin message packets in packet count 2021-09-05 16:28:42 +02:00
NEZNAMY 0d9c727dde remove separate measurement of component conversion methods 2021-09-05 16:26:37 +02:00
NEZNAMY 321a56d5cf add override check to onQuit as well 2021-09-05 13:36:20 +02:00
NEZNAMY fbbc4ee864 change display name of cpu value 2021-09-05 00:21:03 +02:00
NEZNAMY 36f6e792db only refresh belowname in same world (and server) 2021-09-05 00:11:30 +02:00
NEZNAMY f71c7c5076 show N/A for %world% on proxy if bridge is missing 2021-09-04 23:49:16 +02:00
NEZNAMY 33ec5a5cce remove internal: prefix from vanish placeholder since literal output is not used directly and add it on bukkit as well 2021-09-04 23:43:16 +02:00
NEZNAMY bbcf0add3f don't deserialize bytebufs if no features use it 2021-09-04 23:40:02 +02:00
NEZNAMY 80aa041d1c fix desync between my workspace and github (2/2) 2021-09-04 20:27:33 +02:00
NEZNAMY 35617e97a5 fix desync between my workspace and github (1/2) 2021-09-04 20:26:07 +02:00
NEZNAMY 9a802c0db9 fix vanished player removal 2021-09-04 20:24:32 +02:00
NEZNAMY d588f2fdee new config example equal to purpur's tpsbar 2021-09-04 13:10:26 +02:00
NEZNAMY ad0424175d partially redocument api package 2021-09-02 21:35:35 +02:00
NEZNAMY 3bf9856c26 fix nearby players registration 2021-09-01 23:35:24 +02:00
NEZNAMY 2395ee05f9 save cached placeholder array in Placeholder type 2021-08-31 20:33:10 +02:00
NEZNAMY 2a5168e800 add API methods used by addon 2021-08-31 15:32:20 +02:00
NEZNAMY da861cc73c sorting by name is no longer forced and is configurable 2021-08-29 11:35:32 +02:00
NEZNAMY 4cd974cb0e ignore empty collections 2021-08-29 11:23:27 +02:00
NEZNAMY 14cb32fc58 don't start task if no permission plugin is found 2021-08-29 11:08:15 +02:00
NEZNAMY 3197671950 use placeholder refresh core to refresh team names instead of repeating task 2021-08-29 11:07:22 +02:00
NEZNAMY cd29baf9cc fix sync placeholders from v3 placeholder loading recode 2021-08-28 22:41:29 +02:00
NEZNAMY 03ca76e679 add config option to disable latency updating 2021-08-28 22:21:05 +02:00
NEZNAMY 933855baac add override check to all methods to not have cpu output spammed with 0s 2021-08-28 22:15:54 +02:00
NEZNAMY ee61259114 rework placeholder registration methods 2021-08-28 18:06:42 +02:00
NEZNAMY d26f88e0f1 add check which should not be necessary 2021-08-28 17:24:23 +02:00
NEZNAMY c8fc144b8a replace field with local variable 2021-08-28 17:07:49 +02:00
NEZNAMY d8ae647140 again 2021-08-28 16:59:01 +02:00
NEZNAMY ab5767bee8 move some methods around 2021-08-28 16:49:07 +02:00
NEZNAMY 7c63d3c176 nah 2021-08-28 16:27:50 +02:00
NEZNAMY d837dfdd77 remove duplication 2021-08-28 16:10:50 +02:00
NEZNAMY 31612a7cb1 refer to EnumChatFormat class for colorizing and color char 2021-08-28 16:04:26 +02:00
NEZNAMY 3d25ec32c0 create and use platform-specific config file 2021-08-28 15:06:03 +02:00
NEZNAMY e3cda07f0f fix NPE during startup 2021-08-28 14:54:11 +02:00
NEZNAMY 18762961e1 count cpu usage of LP event listener 2021-08-28 14:22:18 +02:00
NEZNAMY 9cc1112a3e use event listener for LP group refreshing instead of repeating task 2021-08-28 11:22:08 +02:00
NEZNAMY 2204a8437f remove method that is no longer used 2021-08-28 11:21:26 +02:00
NEZNAMY c37e0d2fab avoid NPE 2021-08-28 11:03:28 +02:00
NEZNAMY 4ce85692a7 fix method from recode 2021-08-28 11:00:43 +02:00
NEZNAMY 860658f367 move PermissionPlugin instance to GroupRefresher class 2021-08-27 21:29:49 +02:00
NEZNAMY 4310ce95ea increase refresh interval 2021-08-27 18:28:37 +02:00
NEZNAMY 66ae6b2a33 hook into LD directly instead of using reflection 2021-08-27 18:17:00 +02:00
NEZNAMY 5f45a38f08 save nearby players into array 2021-08-27 18:03:44 +02:00
NEZNAMY c26cd30dfd fix component cache between legacy and modern versions and cache raw strings for bungeecord as well 2021-08-27 16:32:24 +02:00
NEZNAMY 4b0fe94f34 handle 1.9 bossbar packet using VV API instead of directt packets 2021-08-27 15:52:15 +02:00
NEZNAMY 7fc80f8e10 fix bossbar load/unload on <1.9 2021-08-27 15:51:23 +02:00
NEZNAMY 2875676308 update URL 2021-08-27 14:50:17 +02:00
NEZNAMY 814726d545 fix startup on fabric 2021-08-27 13:18:28 +02:00
NEZNAMY 0ae60a0463 Update PipelineInjector.java 2021-08-27 12:33:16 +02:00
NEZNAMY c96bef9ac5 remove remove-strings array instead of list 2021-08-25 22:11:26 +02:00
NEZNAMY db9fd9e4d5 cache variables for %afk% placeholder 2021-08-25 21:55:12 +02:00
NEZNAMY 90ab2f0dbd remove redundant catch 2021-08-25 17:05:57 +02:00
NEZNAMY f8f6cb3cc2 simplify 2021-08-25 17:04:37 +02:00
NEZNAMY 72d741f795 move constructors 2021-08-25 16:56:29 +02:00
NEZNAMY e784f4bcf0 create constants instead of copying strings 2021-08-25 13:58:54 +02:00
NEZNAMY a50f34d2b4 merge calls 2021-08-25 01:22:11 +02:00
NEZNAMY dce58a7793 increase quit delay 2021-08-24 23:11:15 +02:00
NEZNAMY 5c9de8de7c fix placeholders only called through expansion not refreshing 2021-08-24 23:11:06 +02:00
NEZNAMY c1c2ceb33e no longer needed 2021-08-24 23:10:13 +02:00
NEZNAMY b39eca0e0e call placeholder directly since it's possible now 2021-08-24 23:07:53 +02:00
NEZNAMY cadcc69eef rename field 2021-08-24 18:55:18 +02:00
NEZNAMY 120f2b2d44 save bukkit bossbar style formats instead of replacing 2021-08-24 16:07:35 +02:00
NEZNAMY 495d7ecf19 save Formatter instance instead of creating new one every time 2021-08-24 15:47:38 +02:00
NEZNAMY 140a45a7cb cache enabled plugin booleans 2021-08-24 12:59:38 +02:00
NEZNAMY bd12ff73f8 cache created components to reduce memory allocations and speed up code 2021-08-23 18:33:24 +02:00
NEZNAMY 7b4182dce3 add floodgate hook to use numbered scoreboard for bedrock users 2021-08-23 17:36:45 +02:00
NEZNAMY f8098423a3 add header/footer API 2021-08-23 17:11:01 +02:00
NEZNAMY 9e1a8c29ac remove redundant .toString() call 2021-08-22 21:59:58 +02:00
NEZNAMY 157833d806 prevent NPE 2021-08-22 21:58:34 +02:00
NEZNAMY 91a61eb014 fix sorting not updating in layout 2021-08-22 18:29:39 +02:00
NEZNAMY 1f037dbda7 cache used placeholder keyset 2021-08-22 17:43:52 +02:00
NEZNAMY d9c68e7360 remove redundant check 2021-08-22 16:51:12 +02:00
NEZNAMY 51507d3905 use correct description 2021-08-22 16:50:34 +02:00
NEZNAMY 31e83ebbb5 use feature subcategories in sent packets in /tab cpu 2021-08-22 16:21:40 +02:00
NEZNAMY 3e2f1fb5a8 make layout display condition checking placeholder based instead of refreshing 2021-08-22 14:00:59 +02:00
NEZNAMY 5ad360eaf2 make bossbar display condition checking placeholder based instead of refreshing 2021-08-22 13:30:15 +02:00
NEZNAMY b7b8cd3fea make scoreboard display condition checking placeholder based instead of refreshing 2021-08-22 13:27:28 +02:00
NEZNAMY 243fccac9e make vanish status refreshing for global playerlist placeholder based 2021-08-22 13:07:01 +02:00
NEZNAMY 51b3035434 fix check 2021-08-21 22:10:54 +02:00
NEZNAMY be9fba23ac fix PLACEHOLDER sorting type requiring lowercase values 2021-08-21 19:00:36 +02:00
NEZNAMY 0ac9b703d5 remove redundant call 2021-08-21 17:40:02 +02:00
NEZNAMY 21c51ff074 remove debug message 2021-08-21 17:39:43 +02:00
NEZNAMY a735b2f0b0 split subfeatures for better cpu tracking 2021-08-21 17:31:56 +02:00
NEZNAMY 0baea2c5ea add remaining players text to layout 2021-08-21 17:14:53 +02:00
NEZNAMY ca2463567e bring fix from v2 2021-08-21 14:12:50 +02:00
NEZNAMY c950dc5f53 improve debug and fix sorting type name 2021-08-20 22:08:08 +02:00
NEZNAMY 5c6a29f2d7 colorize and use | instead of space for same priority 2021-08-20 22:07:33 +02:00
NEZNAMY 6e12a9d22e bring fix from v2 2021-08-20 16:51:13 +02:00
NEZNAMY df819853ef cache sorted players in layout 2021-08-20 10:56:12 +02:00
NEZNAMY d8c3fb4190 how did this get here 2021-08-20 09:55:28 +02:00
NEZNAMY 25e5ecfe61 disable belowname by default 2021-08-20 01:33:41 +02:00
NEZNAMY 809b5bbe18 cleaner check and return server version if hook failed 2021-08-19 14:54:48 +02:00
NEZNAMY 1ec9139307 remove one of 2 old methods 2021-08-18 23:13:21 +02:00
NEZNAMY 293ea1f6cf using strings instead of UsageType enum 2021-08-18 22:55:05 +02:00
NEZNAMY ed43a47ad7 added comment 2021-08-18 21:44:49 +02:00
NEZNAMY 82980639c8 drastically optimize unlimited nametags 2021-08-18 19:33:20 +02:00
NEZNAMY 01dd493475 fix inaccessible constructor 2021-08-17 20:39:33 +02:00
NEZNAMY 0b29c8c51b create and call method instead of copypasting 2021-08-17 19:35:34 +02:00
NEZNAMY 7c2f4277c3 small nms loading method changes 2021-08-17 19:31:03 +02:00
NEZNAMY 8c515f542c define all variables manually instead of using maps 2021-08-17 19:11:45 +02:00
NEZNAMY 73d80b136f fix NPE when disabled worlds/servers are not defined 2021-08-16 23:37:13 +02:00
NEZNAMY 93c5cf70a0 fix method name 2021-08-16 15:33:18 +02:00
NEZNAMY e2a098c3ff remove debug code 2021-08-16 15:32:13 +02:00
NEZNAMY c766048224 add fabric support via CardBoard 2021-08-16 13:45:06 +02:00
NEZNAMY 9eb87fae62 return methods by other parameters than name itself
let's see how many hotfixes will this require
2021-08-15 23:32:37 +02:00
NEZNAMY 7f2f4d8e3b easier way 2021-08-15 21:26:52 +02:00
NEZNAMY ce5f9c0f08 use existing field 2021-08-15 20:27:11 +02:00
NEZNAMY cf443ed217 create method instead of copypasting content 2021-08-15 20:25:17 +02:00
NEZNAMY bde582d8ff rename fields 2021-08-15 20:17:33 +02:00
NEZNAMY 0a00332580 implement /tab scoreboard show <name> [player] 2021-08-15 16:17:08 +02:00
NEZNAMY 137d7bf240 use scoreboard name in property names to avoid issues 2021-08-15 15:56:21 +02:00
NEZNAMY 302c7ef455 properly register and unregister lines when using via API 2021-08-15 15:50:34 +02:00
NEZNAMY 87b54a44c0 remove unused field 2021-08-15 15:29:52 +02:00
NEZNAMY f9eee0de93 improve tabcomplete 2021-08-15 15:18:22 +02:00
NEZNAMY 1114d2d30d register sorting placeholders for refreshing 2021-08-15 13:51:09 +02:00
NEZNAMY 65d50d046c mighty replace function 2021-08-14 12:03:57 +02:00
NEZNAMY b2ef957682 implement all data watcher registry values 2021-08-14 12:03:47 +02:00
NEZNAMY 56be23e304 specify placeholder in debug message 2021-08-14 00:03:27 +02:00
NEZNAMY 215869037b rename artifact 2021-08-13 23:54:28 +02:00
NEZNAMY f5d4581911 add and use custom packet classes for entity packets 2021-08-13 23:30:29 +02:00
NEZNAMY 5848efdf67 rename interface 2021-08-13 22:31:04 +02:00
NEZNAMY c79277afaf add &r after every sorting element 2021-08-13 21:59:42 +02:00
NEZNAMY 59c8e1ae1c increase access level 2021-08-12 20:10:21 +02:00
NEZNAMY d50678649a fix async event call 2021-08-12 15:28:32 +02:00
NEZNAMY c19f440094 add getter 2021-08-12 15:28:23 +02:00
NEZNAMY b60160f2b9 rename field 2021-08-12 13:33:37 +02:00
NEZNAMY e54c4ed435 use method reference instead 2021-08-12 13:32:47 +02:00
NEZNAMY 749774cd03 remove redundant throws 2021-08-12 13:31:13 +02:00
NEZNAMY 7cc609fdc3 remove wrong IF 2021-08-12 13:29:51 +02:00
NEZNAMY 2cc8bed203 apparently fix locale issue 2021-08-12 13:24:04 +02:00
NEZNAMY 175f02a1c3 fix forgotten change 2021-08-12 01:53:13 +02:00
NEZNAMY 8a8ac0ccde use default group name instead of null 2021-08-12 01:47:27 +02:00
NEZNAMY 168427f24e avoid code duplicates in pipeline injection 2021-08-12 01:13:36 +02:00
NEZNAMY 8338518405 armor stand API test 2021-08-12 00:49:24 +02:00
NEZNAMY 95783b7ff3 add, rename and repackage events 2021-08-12 00:14:34 +02:00
NEZNAMY f7a4f956d7 correctly update latency in global playerlist 2021-08-11 14:24:06 +02:00
NEZNAMY feea4d4785 not anymore 2021-08-11 13:00:42 +02:00
NEZNAMY e267623025 remove duplicated check 2021-08-11 12:59:57 +02:00
NEZNAMY 5c22583a52 correctly show arrays in output 2021-08-11 12:57:02 +02:00
NEZNAMY 7ba93c9742 rework scoreboard assign logic (simplify for users) 2021-08-11 12:53:05 +02:00
NEZNAMY b4b40bf82f remove unused parameter 2021-08-11 12:19:58 +02:00
NEZNAMY b96218f305 catch NoClassDefFoundError 2021-08-11 00:12:23 +02:00
NEZNAMY e4013a616f finish scoreboard line change API 2021-08-11 00:12:12 +02:00
NEZNAMY 6f3ab8f974 oops 2021-08-10 23:31:44 +02:00
NEZNAMY c47fc61b73 catch IllegalArgument 2021-08-10 23:27:09 +02:00
NEZNAMY 01042bbc54 api changes and stuff [buffering] 2021-08-10 20:53:55 +02:00
NEZNAMY e75f8b75c5 add catserver support 2021-08-09 00:10:15 +02:00
NEZNAMY ae5e77925a shorter code alternative 2021-08-08 23:33:26 +02:00
NEZNAMY b1f2a0e057 do not send bossbars if condition is not met 2021-08-08 22:13:38 +02:00
NEZNAMY 6f61d5aa11 drop direct per-world bossbar support (still doable with conditions) 2021-08-08 22:10:11 +02:00
NEZNAMY 9562f06fea remove warn 2021-08-08 21:49:17 +02:00
NEZNAMY c6eaf4ae03 do not show warning with default group 2021-08-08 20:47:19 +02:00
NEZNAMY 24c994dfb5 properly clone extra components 2021-08-07 19:16:55 +02:00
NEZNAMY f623e5d004 hexcode variable no longer uses # as prefix 2021-08-07 17:20:49 +02:00
NEZNAMY 9ac03a00d8 apply gradients in animations once instead of every refresh 2021-08-07 14:37:29 +02:00
NEZNAMY 52b819a1b7 use same feature name everywhere 2021-08-06 23:37:29 +02:00
NEZNAMY 3ad32e0fc7 iterating over arrays instead of lists where possible 2021-08-06 13:01:21 +02:00
NEZNAMY 18c4a55518 only initialize if used 2021-08-06 12:11:28 +02:00
NEZNAMY 0d8a889556 return if no rgb codes are used 2021-08-06 12:11:10 +02:00
NEZNAMY 25f180a297 change return type of nested placeholders to String[] 2021-08-06 12:01:29 +02:00
NEZNAMY ae2f2d8294 create string once instead of every refresh 2021-08-06 11:33:22 +02:00
NEZNAMY d3ac95d1f6 change return type of getOnlinePlayers to TabPlayer[] 2021-08-06 11:08:02 +02:00
NEZNAMY a138c4a1c7 significantly optimize PropertyImpl#update() method in specific cases 2021-08-06 01:31:57 +02:00
NEZNAMY 0f359e84d6 do not call parseFloat if not needed 2021-08-06 01:27:16 +02:00
NEZNAMY 49d73b7d4f simplify 2021-08-06 00:24:11 +02:00
NEZNAMY 33dc7b82ea do not warn about null group 2021-08-03 18:51:08 +02:00
NEZNAMY 4714ea6037 add back required checks until thread management code 2021-08-03 18:45:24 +02:00
NEZNAMY c2e5f241b0 give thread pool a custom name 2021-08-03 17:49:18 +02:00
NEZNAMY 7f0e299b74 getting my try/catch back 2021-08-03 17:06:43 +02:00
NEZNAMY e418ccf8e0 using StringBuilder 2021-08-03 16:54:32 +02:00
NEZNAMY 53e119c0ce only create a single instance of nms Scoreboard 2021-08-03 15:54:02 +02:00
NEZNAMY f934d3b7b1 display placeholderapi errors when debug is enabled 2021-08-03 15:53:21 +02:00
NEZNAMY 12d5b9f6f6 fix papi placeholder error causing last value to not apply 2021-08-03 14:05:48 +02:00
NEZNAMY 727a5cfbf5 remove unnecessary casts and calls 2021-08-03 13:40:26 +02:00
NEZNAMY 6345080769 initialize server placeholders on load and move task 2021-08-03 13:34:56 +02:00
NEZNAMY 928f3d32cd fix sorting by predefined placeholder 2021-08-03 13:08:35 +02:00
NEZNAMY 81fd2dc82f do not mass allocate memory (might break addon loading) 2021-08-03 01:50:37 +02:00
NEZNAMY 225918ac52 how did this even happen 2021-08-03 01:39:40 +02:00
NEZNAMY 796343bc5e cleanup and remove duplicated code 2021-08-03 01:39:00 +02:00
NEZNAMY cdb86f6c11 fix placeholder registration in anonymous conditions 2021-08-03 01:38:34 +02:00
NEZNAMY 625e26f01f change placeholder type to fix nested player placeholders 2021-08-03 01:05:54 +02:00
NEZNAMY 21f0429fd6 return type changes 2021-08-03 01:01:57 +02:00
NEZNAMY e7a30048cf replace Map with byte[] for better performance and memory 2021-08-02 23:04:44 +02:00
NEZNAMY ed952e19eb hopefully small optimizations with stringbuilder 2021-08-02 22:19:41 +02:00
NEZNAMY e4a7bdd0eb disable by default 2021-08-02 22:16:40 +02:00
NEZNAMY 13e394bbfe use arrays instead of lists 2021-08-02 21:40:05 +02:00
NEZNAMY a6b95064d9 optimize method's memory allocation 2021-08-02 21:33:17 +02:00
NEZNAMY c30a556bdc obviously update method names 2021-08-02 20:44:17 +02:00
NEZNAMY 2bebba740e fix NPE when player is not loaded 2021-08-02 20:26:33 +02:00
NEZNAMY 57dfb02689 fix false negative when nested placeholder changed value
also nested placeholders in conditions not being registered
2021-08-02 00:01:37 +02:00
NEZNAMY 7931d8b26e reformat 2021-08-01 14:50:43 +02:00
NEZNAMY 434d50b2d1 add skin support to layout 2021-08-01 14:44:05 +02:00
NEZNAMY fad905a426 simplify method 2021-08-01 01:01:55 +02:00
NEZNAMY 475c3b690b fix NPE when skin is null 2021-08-01 00:53:42 +02:00
NEZNAMY 88eae3cb9a fix NPE when placeholder returns null 2021-08-01 00:49:04 +02:00
NEZNAMY 0021c7233c fix placeholders used only in conditions not refreshing 2021-07-31 20:11:46 +02:00
NEZNAMY 725224b6c7 move method 2021-07-31 16:44:22 +02:00
NEZNAMY 28f399079d remove redundant check 2021-07-31 16:33:50 +02:00
NEZNAMY 3e0d594e29 remove unused code 2021-07-31 16:33:08 +02:00
NEZNAMY 9092b1f9b0 remove unused method 2021-07-31 16:21:10 +02:00
NEZNAMY 4ac30ff7b2 remove animation file conversion 2021-07-31 16:20:27 +02:00
NEZNAMY 803d3395d6 split method 2021-07-31 16:11:10 +02:00
NEZNAMY 51288231cd add displayname refreshing & aligned suffix support in layout 2021-07-31 15:25:09 +02:00
NEZNAMY a44cd528ef better YellowNumber integration with layout 2021-07-31 15:13:09 +02:00
NEZNAMY cfbe0b031e add way to not duplicate properties in layout 2021-07-31 14:57:34 +02:00
NEZNAMY bf7fb1620e use function that doesn't throw patern exception 2021-07-31 14:41:22 +02:00
NEZNAMY 50056fd301 implement backwards hoverable conversion 2021-07-30 22:23:15 +02:00
NEZNAMY 927b134996 move class to correct package 2021-07-29 23:35:40 +02:00
NEZNAMY 9d66619cda removed unnecessary cast 2021-07-29 23:25:39 +02:00
NEZNAMY c9670536cc fix conditions loading 2021-07-29 23:21:30 +02:00
NEZNAMY 731518a4fc implement chat hoverable conversion 2021-07-29 22:54:18 +02:00
NEZNAMY 764df316a7 fix startup 2021-07-29 16:29:13 +02:00
NEZNAMY 1ecfdafbe8 small fixes 2021-07-29 16:20:05 +02:00
NEZNAMY 4b09aeefd6 . 2021-07-29 16:11:09 +02:00
NEZNAMY 71e407c3a5 split IChatBaseComponent class 2021-07-29 15:14:30 +02:00
NEZNAMY 48c4e7432d . 2021-07-29 12:33:37 +02:00
NEZNAMY 202194c16f fix github compilation 2021-07-28 23:54:08 +02:00
NEZNAMY 0af5713483 more stuff 2021-07-28 23:51:08 +02:00
NEZNAMY 3b6a5c353c massively reduced memory allocations 2021-07-27 23:42:04 +02:00
NEZNAMY e6d8f34db0 more random things for code analysis websites 2021-07-26 16:42:50 +02:00
NEZNAMY ef97a0d184 . 2021-07-26 16:04:17 +02:00
NEZNAMY 40866754d1 . 2021-07-26 15:01:29 +02:00
NEZNAMY d5fb7d094c more random changes 2021-07-26 14:35:47 +02:00
NEZNAMY 48b861054c temporarily disable code 2021-07-26 13:07:04 +02:00
NEZNAMY 568b7541a4 random stuff 2021-07-26 13:05:19 +02:00
NEZNAMY 914a6e6a57 Update build.yml 2021-07-26 11:58:26 +02:00
NEZNAMY d1e737dc96 Update plugin.yml 2021-07-26 11:51:16 +02:00
NEZNAMY ae68a05f87 cpu boost and rip child groups 2021-07-26 02:11:27 +02:00
NEZNAMY 79e6f3ef1c further progress 2021-07-25 17:31:56 +02:00
NEZNAMY 5f137f4055 more api stuff 2021-07-24 23:31:14 +02:00
NEZNAMY 4f5215a312 more recoding and per-world support on bungee 2021-07-23 23:51:38 +02:00
NEZNAMY 06ea8f6cb1 initial mysql support 2021-07-15 21:54:03 +02:00
NEZNAMY 2e2890d8d3 config recode and more 2021-07-13 01:11:25 +02:00
NEZNAMY a07ef6c273 API & feature recode part 1 2021-07-12 00:11:01 +02:00
NEZNAMY a02acc9c67 remove file 2021-07-11 15:55:28 +02:00
NEZNAMY bc5e61b04b make conditional placeholders refresh intervals configurable 2021-07-11 15:43:41 +02:00
NEZNAMY 665036189d always show sync placeholders red 2021-07-11 15:34:36 +02:00
NEZNAMY 8fe72e7f2e remove cputest subcommand 2021-07-11 15:21:36 +02:00
NEZNAMY 4a373015de send different message when reload fails due to internal error 2021-07-11 15:17:56 +02:00
NEZNAMY 31ad540286 do not allow same bossbar announced multiple times at once 2021-07-11 15:07:01 +02:00
NEZNAMY 448faebfad ignore players who are not loaded
will fix double bossbar bug
2021-07-11 15:02:22 +02:00
NEZNAMY 3b7f25071e remove spigot link 2021-07-11 00:46:51 +02:00
NEZNAMY c661395bb0 Update README.md 2021-07-10 13:25:11 +02:00
NEZNAMY c231220b42 2.9.2 2021-07-10 13:01:29 +02:00
NEZNAMY 1c6e5bec73 fix IllegalArgument 2021-07-10 12:57:16 +02:00
NEZNAMY 8919aaad94 add missing config option 2021-07-09 16:42:10 +02:00
NEZNAMY ff6a2fe734 equals 2021-07-09 11:58:18 +02:00
NEZNAMY 5ca4f7e411 2.9.2-pre3 2021-07-07 17:13:01 +02:00
NEZNAMY 2c105718b0 1.17.1 support 2021-07-07 17:10:37 +02:00
NEZNAMY 9cc471f66f catch throwable in pipeline 2021-07-06 21:20:47 +02:00
NEZNAMY e18867c938 do not ignore Errors 2021-07-06 21:01:46 +02:00
NEZNAMY 27bd942c4c fix NoSuchFieldError on <1.11 2021-07-06 21:01:36 +02:00
NEZNAMY 234ca72e73 tab out stack traces 2021-07-06 21:01:17 +02:00
NEZNAMY f35a09a34d avoid rare NPE 2021-07-06 19:54:19 +02:00
NEZNAMY fa6d06a4ac 2.9.2-pre2 2021-07-06 09:50:48 +02:00
NEZNAMY ec6daeeadb colorize text immediately instead of in every refresh 2021-07-04 13:50:05 +02:00
NEZNAMY f24f25f071 apparently this method is required 2021-07-04 12:05:10 +02:00
NEZNAMY dbf0e3af9f small cpu and memory optimization in pet fix feature 2021-07-04 00:02:56 +02:00
NEZNAMY 6ecb607c0b add private constructor 2021-07-03 20:48:18 +02:00
NEZNAMY 1bf6b00be0 remove unnecessary code 2021-07-03 20:47:56 +02:00
NEZNAMY 4b4f52e39a use constants instead of literal strings 2021-07-03 20:35:50 +02:00
NEZNAMY 1170efbe02 cache disabled worlds 2021-07-03 19:52:42 +02:00
NEZNAMY 3e86e73fed refactor to avoid duplication 2021-07-03 16:51:49 +02:00
NEZNAMY dd43861572 cache methods for placeholders (mostly %afk%) 2021-07-03 16:32:37 +02:00
NEZNAMY 61a19ca9e0 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-07-03 16:19:20 +02:00
NEZNAMY d02af31e7c Merge pull request #342 from etil2jz/patch-1
Update README.md
2021-07-03 16:16:38 +02:00
Arthur B ab7b88b67b Update README.md
Correct isPremium line
2021-07-03 16:13:52 +02:00
NEZNAMY b80c816c5a relocate json-simple 2021-07-01 18:56:51 +02:00
NEZNAMY c0ad6f49e7 minor changes 2021-07-01 18:53:24 +02:00
NEZNAMY 91a56a8c2c remove IllegalArgument throws forwarding 2021-07-01 12:09:09 +02:00
NEZNAMY 135a473dc5 more abstraction to remove duplications 2021-07-01 11:14:41 +02:00
NEZNAMY a8b53b07e4 increase delay and repeat times 2021-07-01 10:04:19 +02:00
NEZNAMY 171cbd4497 NPE fix when plugin is disabled and user does not have reload permission 2021-06-30 21:24:27 +02:00
NEZNAMY a1383612bc making code analysis websites happy part 6
+ switching to bukkit metrics shading instead of copying classes
2021-06-30 18:03:21 +02:00
NEZNAMY fe798c19f3 making code analysis websites happy part 5 2021-06-30 13:32:15 +02:00
NEZNAMY ead3add4cc making code analysis websites happy part 4 2021-06-30 11:54:14 +02:00
NEZNAMY db13c2d1c5 making code analysis websites happy part 3 2021-06-30 10:58:39 +02:00
NEZNAMY f2daab4b80 making code analysis websites happy part 2 2021-06-30 00:55:26 +02:00
NEZNAMY 6fd02c83ab attempt 2 2021-06-30 00:02:02 +02:00
NEZNAMY c288a0503a test 2021-06-29 23:54:12 +02:00
NEZNAMY 2e2b65a4bb making code analysis websites happy part 1 2021-06-29 23:48:23 +02:00
NEZNAMY 982d0a53f9 correctly set bossbar progress on add 2021-06-29 19:01:46 +02:00
NEZNAMY f89ecfb595 remove unused options 2021-06-29 14:10:42 +02:00
NEZNAMY bddb96a5fa increase default refresh 2021-06-29 14:05:57 +02:00
NEZNAMY 39b29d12e4 problem solved ez game ez life
referring to expansion download errors while jar file was modified such as https://paste.gg/p/anonymous/449c3eb326f34015993b4025002be355
2021-06-29 13:59:47 +02:00
NEZNAMY 6cb1e2c06d debug option is now public 2021-06-29 13:49:46 +02:00
NEZNAMY d932a9a862 add VV as softdepend to avoid troll console message 2021-06-29 13:29:19 +02:00
NEZNAMY 341b552d0b 2.9.2-pre1 2021-06-25 08:25:25 +02:00
NEZNAMY 1586bd9768 implement correct interface 2021-06-24 21:55:06 +02:00
NEZNAMY e8dde3a756 reuse the same instance 2021-06-24 21:51:37 +02:00
NEZNAMY bb591bf56d spigot fixed the bug 2021-06-24 20:18:07 +02:00
NEZNAMY 8cf83b276a fix the feature on 1.17 2021-06-23 21:59:24 +02:00
NEZNAMY a1547a7288 cleaner implementation of #322 2021-06-23 21:56:10 +02:00
NEZNAMY 0722403a23 remove duplicated line 2021-06-22 21:27:59 +02:00
NEZNAMY cd4a91d975 return afk if at least 1 plugin says player is afk 2021-06-22 20:42:26 +02:00
NEZNAMY c7870b4e9d fix NPE in API when feature is not enabled 2021-06-21 22:52:59 +02:00
NEZNAMY 239dae5a65 change reference type to player name 2021-06-21 22:43:47 +02:00
NEZNAMY 13c89d18d7 remove unused field since 1.17 support 2021-06-21 21:47:18 +02:00
NEZNAMY c5dc42eafe cleanup 2021-06-21 14:47:56 +02:00
NEZNAMY 59b3faab1e fix team name note message in console 2021-06-21 13:41:15 +02:00
NEZNAMY b88beb5002 added null check to placeholder registration 2021-06-21 12:58:46 +02:00
NEZNAMY 9fea35c25d retry viaversion call if it returned -1 2021-06-21 12:03:33 +02:00
NEZNAMY 372013bcc6 add online status into debug message 2021-06-21 11:07:21 +02:00
NEZNAMY 20ca58b570 fix NPE 2021-06-20 21:49:48 +02:00
NEZNAMY 9c4cca20fb just an attempt 2021-06-19 11:43:40 +02:00
NEZNAMY 6b8536a86e fix possible memory leak? 2021-06-19 11:41:46 +02:00
NEZNAMY 3b41857637 fixed NPE caused by misconfiguration 2021-06-19 09:27:33 +02:00
NEZNAMY fa78ac2157 fix it again? 2021-06-18 13:39:05 +02:00
NEZNAMY 0a0bb9c83c swap order to avoid NPE when player is not loaded 2021-06-18 12:28:19 +02:00
NEZNAMY 36834a039e 2.9.1 2021-06-17 18:43:34 +02:00
NEZNAMY 4960c898bb memory leak? 2021-06-17 18:35:35 +02:00
NEZNAMY af61edbc5c added replacements support to relational placeholders 2021-06-17 15:53:24 +02:00
NEZNAMY 237bfc08a1 forward raw packets instead, no idea why was it not working 2021-06-17 15:52:39 +02:00
NEZNAMY 032e9a089d bump supported versions 2021-06-17 12:50:37 +02:00
NEZNAMY ba83779dde create and use new field getter method 2021-06-16 21:51:51 +02:00
NEZNAMY 267d72f844 spacing 2021-06-16 21:38:24 +02:00
NEZNAMY b533cdb89f send deserialized packets instead of malformed bytebufs 2021-06-16 17:26:05 +02:00
NEZNAMY 3f118ac76b add option to define custom character widths 2021-06-16 16:24:08 +02:00
NEZNAMY 08a8f0097c fix wrong constructor taken on random 1.8.8 forks 2021-06-16 16:23:26 +02:00
NEZNAMY c7a3599e46 update maven repository 2021-06-16 14:22:45 +02:00
NEZNAMY 62f80baa69 version bump 2021-06-16 14:01:31 +02:00
NEZNAMY 09dc8cd437 version bump 2021-06-16 13:52:27 +02:00
NEZNAMY c613ba401b remove unnecessary this. 2021-06-16 13:03:52 +02:00
NEZNAMY 866df0b5cf remove duplicate content 2021-06-16 11:12:43 +02:00
NEZNAMY 8f2b56ba05 2.9.0 2021-06-16 10:09:20 +02:00
NEZNAMY 2049885f11 more accurate error message 2021-06-16 09:56:15 +02:00
NEZNAMY 4caef337d5 ANOTHER BOSSBAR API BUG IN ANOTHER PLATFORM HAHA LOL 2021-06-15 23:37:02 +02:00
NEZNAMY a644792bf6 simplify 2021-06-15 22:50:16 +02:00
NEZNAMY 54c7f8fc6a drop the rest of title packet 2021-06-15 22:49:23 +02:00
NEZNAMY 2e59b87bbe use getters instead of fields 2021-06-15 22:46:42 +02:00
NEZNAMY f89242ad8f add and use magic code Boolean getters 2021-06-15 22:40:02 +02:00
NEZNAMY 845903788c remove unused code 2021-06-15 22:39:32 +02:00
NEZNAMY 53a9047c20 fix conflict between hidden-by-default and remember-toggle-choice 2021-06-15 22:36:55 +02:00
NEZNAMY a2b74bbc30 correctly display used color codes in parse error messages 2021-06-15 22:36:31 +02:00
NEZNAMY 50ef93cc45 fix stack overflow and stretch with relational placeholders 2021-06-15 22:36:00 +02:00
NEZNAMY e2a5da9c14 deserialize other scoreboard packets as well 2021-06-15 22:35:24 +02:00
NEZNAMY bf31116d0d split method 2021-06-15 22:34:42 +02:00
NEZNAMY d79fa8c4f3 drop title packet 2021-06-15 22:34:31 +02:00
NEZNAMY 87e0a7bea3 remove unused fields 2021-06-15 22:33:33 +02:00
NEZNAMY b34cabd55c optimize check 2021-06-15 22:32:50 +02:00
NEZNAMY 704bd36c77 do not try to use rgb on <1.16 servers 2021-06-14 09:35:23 +02:00
NEZNAMY 5c2c4d0f4b improved error message 2021-06-14 08:43:36 +02:00
NEZNAMY 80cca5cb34 fix #329 2021-06-14 08:22:37 +02:00
NEZNAMY dcd603f2f5 null check to prevent errors 2021-06-13 22:10:30 +02:00
NEZNAMY 1bee3b49bf sending bossbar using bukkit api 2021-06-13 20:46:16 +02:00
NEZNAMY 11bcccb8f9 fix reload from previous commit 2021-06-13 20:24:39 +02:00
NEZNAMY 7425493706 initialization cleanup 2021-06-13 19:09:34 +02:00
NEZNAMY dca0cfd424 removed old value from config 2021-06-13 17:09:03 +02:00
NEZNAMY 21314def89 fix #328 2021-06-13 15:57:06 +02:00
NEZNAMY 90e544d279 option to take groups from backend servers 2021-06-13 11:54:03 +02:00
NEZNAMY 89e21558a1 2.8.11-pre19 2021-06-12 23:23:15 +02:00
NEZNAMY b28cc51457 spigot one dot god damn seventeen 2021-06-12 23:19:09 +02:00
NEZNAMY 2d0a203b5f increased initial delay 2021-06-12 14:28:44 +02:00
NEZNAMY 2948666c1d 2.8.11-pre18 2021-06-11 19:14:47 +02:00
NEZNAMY b2a2659546 added little comment 2021-06-11 17:14:03 +02:00
NEZNAMY e2eb7102c9 removed unnecessary 'throws' from methods 2021-06-10 21:22:15 +02:00
NEZNAMY 56301e653b splitting method into multiple 2021-06-10 21:13:40 +02:00
NEZNAMY 1d825e08d2 using correct uuid all the time 2021-06-10 19:38:46 +02:00
NEZNAMY c81a27709c 1.17 bungeecord 2021-06-10 17:22:58 +02:00
NEZNAMY 2b143172f8 spacing 2021-06-10 17:19:55 +02:00
NEZNAMY 84330ec6eb Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-06-10 17:19:25 +02:00
NEZNAMY 0bd703de7f Merge pull request #325 from Camotoy/viaversion4.0.0
Update to ViaVersion 4.0.0
2021-06-10 17:17:55 +02:00
Camotoy e6a55b7d5c Change packet ID if using 1.17 2021-06-10 11:13:45 -04:00
Camotoy b6ec7631d2 Update to ViaVersion 4.0.0
Tested on 1.17 and the bossbar showed up with no error in console.
2021-06-10 10:53:36 -04:00
NEZNAMY 5abbd00d63 fixed unloading when entering disabled world 2021-06-10 11:22:07 +02:00
NEZNAMY 515c94976e removed internal methods from TabPlayer API interface 2021-06-09 21:06:01 +02:00
NEZNAMY 912adad8f6 player 2021-06-09 20:08:36 +02:00
NEZNAMY 70fdd193a3 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-06-09 19:43:08 +02:00
NEZNAMY 9d83e968fc Merge pull request #323 from Score2/bukkit-permission
Support to obtain bukkit permissions from bungee.
2021-06-09 19:42:35 +02:00
83669 04329da133 resolve. 2021-06-10 01:41:32 +08:00
83669 19ccfbb1a6 Merge branch 'master' of https://github.com/NEZNAMY/TAB into bukkit-permission
 Conflicts:
	velocity-1.1.0/src/main/resources/velocityconfig.yml
2021-06-10 01:29:02 +08:00
83669 c53ab09abc Support to obtain bukkit permissions from bungee. 2021-06-10 01:24:23 +08:00
NEZNAMY a4d8daac1d fixed wrong logic causing name formatting to not work 2021-06-09 16:12:33 +02:00
NEZNAMY 99d566d521 wrong action order 2021-06-09 16:05:08 +02:00
NEZNAMY aa37f73161 fixed wrong config option name 2021-06-09 15:54:12 +02:00
NEZNAMY 087518378d pom.xml cleanup 2021-06-09 15:39:57 +02:00
NEZNAMY 913d27929b shortcut and no longer requiring guava 2021-06-09 15:24:04 +02:00
NEZNAMY 530d39bec5 added property name into exception message 2021-06-09 10:47:42 +02:00
NEZNAMY cf70dc3845 fixed priorities (caused <= and >= to not work) 2021-06-08 23:17:31 +02:00
NEZNAMY d1421fee3f new colors 2021-06-08 21:58:18 +02:00
NEZNAMY 3ec490b36e only the the API now 2021-06-08 09:58:46 +02:00
NEZNAMY ee42685179 Added API to disable team handling of a player 2021-06-07 13:31:44 +02:00
NEZNAMY e02d687771 PingSpoof and SpectatorFix now properly load/unload 2021-06-06 14:55:47 +02:00
NEZNAMY dddb592253 update to default config comments 2021-06-05 20:22:20 +02:00
NEZNAMY d0ad88d317 fixed potential security issue when player is not loaded due to an error 2021-06-05 20:20:02 +02:00
NEZNAMY 3d3ce0151f added gamemode getter to TabPlayer 2021-06-05 19:56:37 +02:00
NEZNAMY f017b0e562 %canseeonline% now uses vanish detection from previous commits instead of PV API on bungeecord 2021-06-05 19:47:03 +02:00
NEZNAMY 4ba2bb669b added back 1.5 and 1.6 support 2021-06-05 16:26:32 +02:00
NEZNAMY 9fc2424736 Create FUNDING.yml 2021-06-03 16:55:26 +02:00
NEZNAMY 980c01ea70 this never happened 2021-06-01 13:25:01 +02:00
NEZNAMY 758edcaf5c fixed NPE in API usage 2021-05-30 20:48:49 +02:00
NEZNAMY 950f825e31 added unnecessary softdependency due to md5's troll console message 2021-05-30 20:47:48 +02:00
NEZNAMY 0a985f5c78 using method from component to save color codes due to limits 2021-05-28 11:43:17 +02:00
NEZNAMY 58353f4e6d wrong variable 2021-05-28 11:41:22 +02:00
NEZNAMY c3d2b5c4b0 fixed error in initialization 2021-05-28 11:19:45 +02:00
NEZNAMY c2764b4b8a removed redundant ; 2021-05-28 11:19:31 +02:00
NEZNAMY 9bc8ad0d23 typo 2021-05-28 09:47:02 +02:00
NEZNAMY 488c3540a8 using more reliable check 2021-05-27 19:58:17 +02:00
NEZNAMY 1d03950a64 removed technically useless check 2021-05-27 19:51:21 +02:00
NEZNAMY e24ea7df2e added null check, continuation of previous commit 2021-05-27 18:06:09 +02:00
NEZNAMY fe5e3d6405 better compatibility with forks that use "Unknown" server version
by using a minor version check which is more reliable
2021-05-27 16:20:08 +02:00
NEZNAMY db8584e40b correctly translating colors 2021-05-27 16:18:43 +02:00
NEZNAMY 2e4db38d1a properly compensating for the bug on all horse types 2021-05-25 14:31:44 +02:00
NEZNAMY f82470c1b1 2.8.11-pre17 2021-05-24 22:37:10 +02:00
NEZNAMY 650cc93fc0 fixed placeholders not refreshing when updating properties with API 2021-05-24 22:35:02 +02:00
NEZNAMY e61070307a fixed rare NPE 2021-05-23 23:27:56 +02:00
NEZNAMY 2fa46867e5 moved repeating task to separate method 2021-05-23 18:51:39 +02:00
NEZNAMY 636b2e760a fixed wrong check 2021-05-23 18:46:10 +02:00
NEZNAMY 8cecf46310 NPE fix 2021-05-23 00:38:16 +02:00
NEZNAMY a7bcda7eba fixed NPEs when running commands from console 2021-05-22 20:48:09 +02:00
NEZNAMY a610dd2c70 cleanup 2021-05-22 16:49:30 +02:00
NEZNAMY df98c77a48 java 8 shortcut 2021-05-22 16:48:01 +02:00
NEZNAMY 331dc33c86 removed redundant method 2021-05-22 16:46:56 +02:00
NEZNAMY 8493b37171 recoded feature to fix issues, worked 2021-05-22 16:41:56 +02:00
NEZNAMY 2449c46165 using default locale
https://support-system.xyz/redirect?link=https://cdn.discordapp.com/attachments/690574378454483034/845273706287988807/ticket-844852135032127489.html
2021-05-21 14:16:55 +02:00
NEZNAMY a642c2d344 moved code to new method to avoid duplication 2021-05-20 16:59:48 +02:00
NEZNAMY 88c7b988e7 not displaying the error message anymore 2021-05-19 20:40:07 +02:00
NEZNAMY 36651260c6 added debug messages 2021-05-19 18:10:54 +02:00
NEZNAMY 6c0072dc54 using new method (also supports essentials vanish now) 2021-05-18 21:12:54 +02:00
NEZNAMY e38f5a77c0 added support for bukkit vanish plugins into global playerlist via bridge 2021-05-18 21:11:41 +02:00
NEZNAMY 6443926a79 added a reminder since even I forgot 2021-05-18 19:55:36 +02:00
NEZNAMY 489deb9ee6 2.8.11-pre16 2021-05-16 22:06:22 +02:00
NEZNAMY a2039110e0 improved compatibility with vanish plugins 2021-05-16 22:01:25 +02:00
NEZNAMY f1666b64ee moved shared module to top to avoid compilation error on first compilation 2021-05-16 21:11:45 +02:00
NEZNAMY d7e419c88b added getters to bossbar interface 2021-05-16 21:10:01 +02:00
NEZNAMY de8f8053be recalculating more often, seems to have fixed the issue 2021-05-16 20:56:15 +02:00
NEZNAMY 7aadef5c21 resending packet with a delay when anti-override is disabled 2021-05-16 20:55:26 +02:00
NEZNAMY f8384dfbdf removed character ID from widths for lower file size & counting them internally while loading 2021-05-16 20:52:38 +02:00
NEZNAMY b91cbf827c removed redundant check 2021-05-16 20:51:46 +02:00
NEZNAMY 096619d9f7 moved teleport event back to packets to avoid various issues 2021-05-16 13:00:28 +02:00
NEZNAMY 7b8006406c should fix rare issue caused by multi-thread access 2021-05-16 12:58:48 +02:00
NEZNAMY 90483d549a removed width command & loading since the file is now complete 2021-05-15 21:26:16 +02:00
NEZNAMY 5473055c1c found a way to measure & added arabic characters 2021-05-15 21:23:41 +02:00
NEZNAMY f3b082b8e1 removed unnecessary cast 2021-05-15 19:57:04 +02:00
NEZNAMY 52fe99a080 fixed stack overflow on sync placeholders 2021-05-15 13:33:57 +02:00
NEZNAMY 68b1e941d1 correctly formatting tablist name in packets sent on world change 2021-05-13 18:51:15 +02:00
NEZNAMY 109fce9b1a updated line number reference 2021-05-13 18:50:45 +02:00
NEZNAMY 014d73dd3f ignoring info packet on <1.8 clients so nametag feature can display prefixes 2021-05-13 18:50:08 +02:00
NEZNAMY ad82744e91 added configuration simplification console message 2021-05-09 16:12:41 +02:00
NEZNAMY 638f66ade4 added catch for ArrayIndexOutOfBoundsException 2021-05-09 16:11:22 +02:00
NEZNAMY c190efa7c4 removed config conversion
users had enough time to update to 2.8.10 which converts everything, only keeping 2.8.10 ->2.9.0 changes
2021-05-09 12:35:17 +02:00
NEZNAMY b071eb3366 added purpur afk api support for %afk% 2021-05-08 18:03:57 +02:00
NEZNAMY a7c7a86a90 removed feature interface not used by the main plugin 2021-05-08 17:18:57 +02:00
NEZNAMY 0c21519cf6 listening to respawn event directly in features instead of globally and being unused for most servers 2021-05-08 00:45:33 +02:00
NEZNAMY d7f805b26e goodbye 2021-05-07 21:14:11 +02:00
NEZNAMY 4f702feab3 random feature request in my private messages 2021-05-07 20:24:52 +02:00
NEZNAMY 46175ceb1f fixed error on that platform made by a group of friendly and amazing people 2021-05-07 20:24:07 +02:00
NEZNAMY c206860736 removed redundant code 2021-05-02 22:20:09 +02:00
NEZNAMY b97c222490 remove modify-npc-names config option
all NPC plugins offer configuration of NPC names, modification can be performed by competent server owners there instead
2021-05-02 17:32:38 +02:00
NEZNAMY 53ad5e3cec added debug messages 2021-05-02 13:15:04 +02:00
NEZNAMY e67ce8dbf4 possible issue fix 2021-05-01 23:39:58 +02:00
NEZNAMY ac661be5c4 manually adding yes/no keys to replacements to compensate for snakeyaml conversion 2021-05-01 22:23:26 +02:00
NEZNAMY ab4e386491 small visual code update, functionality remains same 2021-05-01 22:11:32 +02:00
NEZNAMY aa125b535e Update README.md 2021-05-01 00:49:46 +02:00
NEZNAMY 29df3bb5e2 version bump 2021-04-30 15:36:14 +02:00
NEZNAMY 80ee30a532 map must be linked, found a way to do both synchronized and linked 2021-04-28 20:47:08 +02:00
NEZNAMY a20b1bcd89 added hidden config option to disable pipeline injection 2021-04-28 10:33:39 +02:00
NEZNAMY 4269abd2ca 2.8.11-pre15 2021-04-27 20:51:35 +02:00
NEZNAMY a73397065c added fix for rare NPE due to multi-thread access 2021-04-27 20:47:58 +02:00
NEZNAMY 5805a6c5a3 fixed rare concurrent modification 2021-04-27 17:43:30 +02:00
NEZNAMY e813077c92 check is no longer in constructor, must be added elsewhere 2021-04-27 17:38:27 +02:00
NEZNAMY b1ae68e8c2 not spamming class initializations and getting field values directly 2021-04-26 14:45:52 +02:00
NEZNAMY b6162b2d4a not refreshing header/footer on world switch if values did not change 2021-04-26 14:07:45 +02:00
NEZNAMY 0da7854f7f using concurrent hashmap to not need to copy value collection each time it's iterated 2021-04-26 14:00:41 +02:00
NEZNAMY a073747e66 added comment 2021-04-26 13:25:00 +02:00
NEZNAMY 0975e31371 better instance check 2021-04-26 13:23:25 +02:00
NEZNAMY a1231f1708 anti-override was removed already 2021-04-26 09:14:41 +02:00
NEZNAMY 3581da0fe7 only running toString once instead of every loop 2021-04-26 08:43:51 +02:00
NEZNAMY 531810e633 remake using switch 2021-04-25 13:53:16 +02:00
NEZNAMY 36a1db78e2 separate method to reduce complexity 2021-04-25 13:50:50 +02:00
NEZNAMY 0e58009ced starting tasks in separate methods 2021-04-25 13:48:18 +02:00
NEZNAMY ecdcc6894b fixed wrong cpu calculation 2021-04-25 00:16:19 +02:00
NEZNAMY 677a2f9396 reduced memory allocations 2021-04-25 00:14:53 +02:00
NEZNAMY b14f30d4d1 using isInstance instead of equals for better performance 2021-04-23 20:30:17 +02:00
NEZNAMY 4396df55ae updated dependency 2021-04-22 22:07:00 +02:00
NEZNAMY de6ce93a31 partially reverted pre6 recode + fixed disabled worlds not working
also deleted SneakEventListener class and replaced it with direct event lisener
2021-04-22 16:31:01 +02:00
NEZNAMY b449b7cdfc fixed errors on <1.16 2021-04-22 14:52:15 +02:00
NEZNAMY de4412fcff 2.8.11-pre14 2021-04-21 18:02:09 +02:00
NEZNAMY 8e9b34125d added placeholder value printing into team name note in debug 2021-04-21 17:45:57 +02:00
NEZNAMY 024b3175fa updated link 2021-04-20 19:48:33 +02:00
NEZNAMY 9fadc6df3b removed links 2021-04-20 19:47:48 +02:00
NEZNAMY f51f8f0f2d updated info 2021-04-20 15:51:48 +02:00
NEZNAMY d805df4887 no longer required 2021-04-20 15:50:41 +02:00
NEZNAMY d161c6c074 no need for reflection since jar is manual
also fixed title packet
2021-04-20 15:40:24 +02:00
NEZNAMY 6603c9e682 removed redundant file 2021-04-20 15:23:58 +02:00
NEZNAMY 157360c26c added dependency exclusions 2021-04-20 14:11:03 +02:00
NEZNAMY e16199b360 defined encoding to avoid warn in compilation 2021-04-20 11:58:13 +02:00
NEZNAMY df42424970 more cleanup 2021-04-20 11:42:30 +02:00
NEZNAMY 22ce6ce75e more cleanup 2021-04-20 11:36:55 +02:00
NEZNAMY e8dde142d2 another savestate before I break something 2021-04-20 11:25:05 +02:00
NEZNAMY 44fe272ce5 making another savestate of a working project 2021-04-20 11:23:41 +02:00
NEZNAMY 23a8f32910 pom.xml cleanup and resource fix 2021-04-20 11:12:04 +02:00
NEZNAMY d523c0408e Merge pull request #300 from A248/fix-modularization
Fix modularization
2021-04-20 01:51:37 +02:00
A248 0c2edba316 Add all resources and delete target folders 2021-04-19 19:49:47 -04:00
A248 7cfc643522 Add more to .gitignore 2021-04-19 19:42:32 -04:00
A248 98b446efd2 Shade all the modules into single jar + Fix filename of velocity 2
Changed velocity/2.0.0/libs/Velocity 2.0.0jar to Velocity-2.0.0.jar
2021-04-19 19:40:24 -04:00
NEZNAMY bbf9aaed64 initial modulatization 2021-04-20 01:11:57 +02:00
NEZNAMY 13d36457af repackage 2021-04-19 15:12:39 +02:00
NEZNAMY 4f850b0f2e it's shutdown 2021-04-18 21:45:42 +02:00
NEZNAMY 7118e0d9d3 fixed magic code error in gradients due to recent gradient apply changes 2021-04-18 20:59:35 +02:00
NEZNAMY 900edef407 fixed NPE when player is not loaded 2021-04-18 15:13:33 +02:00
NEZNAMY a7e4b99ce6 2.8.11-pre13 2021-04-18 12:27:12 +02:00
NEZNAMY 0463667270 Update README.md 2021-04-16 21:03:23 +02:00
NEZNAMY d179bc20ae now with link 2021-04-16 20:07:31 +02:00
NEZNAMY bc658a9223 Update README.md 2021-04-16 20:03:30 +02:00
NEZNAMY e32e3baf3a added anti-smartass message 2021-04-16 14:52:57 +02:00
NEZNAMY 4436f4cf42 new sorting type 2021-04-15 01:28:15 +02:00
NEZNAMY 1dc7b40ee9 found a workaround, works fine 2021-04-15 00:36:34 +02:00
NEZNAMY fb2221d281 removed unnecessary local variables 2021-04-15 00:36:09 +02:00
NEZNAMY b5a295970b correctly moving to the bottom even when more than 9 groups are defined 2021-04-15 00:02:38 +02:00
NEZNAMY 485a453b27 added misconfiguration check 2021-04-14 19:36:39 +02:00
NEZNAMY 03d9ba9579 removed debug message 2021-04-14 19:29:08 +02:00
NEZNAMY ead7adea31 increased delay because requested 2021-04-14 19:28:17 +02:00
NEZNAMY 9793fdfc06 added debug messages 2021-04-14 19:27:50 +02:00
NEZNAMY 60b73e5c0d fixed NPE when modifier is null 2021-04-13 17:37:01 +02:00
NEZNAMY ed374c83d1 moving to API methods where possible 2021-04-13 15:24:56 +02:00
NEZNAMY f53828e01a no longer needed after updating setBossbarVisible method 2021-04-13 14:04:55 +02:00
NEZNAMY 91ab50fa6a removed unused method 2021-04-13 13:22:24 +02:00
NEZNAMY bee0d9d086 fixed issue from previous commits 2021-04-13 01:36:15 +02:00
NEZNAMY b38f09f118 moved symbol into constructor 2021-04-13 01:17:30 +02:00
NEZNAMY 65f57e8bf1 wrote the method from scratch for better performance & memory 2021-04-13 01:13:02 +02:00
NEZNAMY e424e42e05 matching change made to TextColor constructor 2021-04-13 00:48:11 +02:00
NEZNAMY a22cd5338d initializing value only once instead of every time it's used 2021-04-13 00:35:54 +02:00
NEZNAMY f921737d1c converting color object instead of calling fromString(.toString()) 2021-04-13 00:28:37 +02:00
NEZNAMY 2f760afc70 the constructor expects # 2021-04-13 00:06:59 +02:00
NEZNAMY 4ce56433f9 fowarding StringBuilder 2021-04-12 16:57:22 +02:00
NEZNAMY f8ace5fbb5 merged ifs 2021-04-12 16:51:26 +02:00
NEZNAMY f89778e969 not initializing map when unused 2021-04-12 16:35:56 +02:00
NEZNAMY 99f74d3824 using String.valueOf for less allocations 2021-04-12 16:28:13 +02:00
NEZNAMY dec9c42edc shorter alternative 2021-04-12 16:05:06 +02:00
NEZNAMY 34365c13cb calling int method directly if possible for better performance & memory 2021-04-12 15:20:39 +02:00
NEZNAMY 8a4c780fb1 parsing gradients only once instead of every refresh 2021-04-12 14:39:53 +02:00
NEZNAMY bed49408da only checking plugin presence on startup 2021-04-12 13:12:24 +02:00
NEZNAMY 5dd400bdc5 only initializing once 2021-04-12 13:01:04 +02:00
NEZNAMY 07d5a03a95 using AtomicInteger to avoid internal Integer.valueOf(int) call 2021-04-12 12:44:19 +02:00
NEZNAMY d53e7665d7 only initializing hexcodes once instead of every time 2021-04-12 12:17:33 +02:00
NEZNAMY 4021d7cbac fix from previous commit 2021-04-12 12:16:44 +02:00
NEZNAMY 2634ca5f28 not initializing list if not needed 2021-04-11 23:24:02 +02:00
NEZNAMY aa887fe27b 2.8.11-pre12 2021-04-10 21:39:40 +02:00
NEZNAMY c9077e1254 removed header/footer anti-override entirely
it was rather useless and very resource intensive
2021-04-10 20:22:42 +02:00
NEZNAMY fcf5636d9a small optimization with StringBuilder and String.format 2021-04-10 20:16:33 +02:00
NEZNAMY 1c4713ef1a not initializing maps when not needed 2021-04-10 18:13:15 +02:00
NEZNAMY d48da82f55 removed unnecessary local variable 2021-04-10 18:12:54 +02:00
NEZNAMY 7128ab26d8 using AtomicLong to prevent Long.valueOf(long) spamming allocations 2021-04-10 15:31:05 +02:00
NEZNAMY 58155041d1 loading placeholder usage map only once (per hard refresh) instead of every loop 2021-04-10 15:05:49 +02:00
NEZNAMY e96453eac9 optimized check 2021-04-09 22:22:37 +02:00
NEZNAMY a1d98c54ad why is it /btab not /vtab? now it's both 2021-04-09 21:35:15 +02:00
NEZNAMY fd423481cb small memory optimization using StringBuilder 2021-04-09 20:14:00 +02:00
NEZNAMY 48da7d0c2e added cpu measuring of some methods 2021-04-09 17:49:03 +02:00
NEZNAMY 949577fb07 properly using color field to prevent white text in next line 2021-04-09 17:48:09 +02:00
NEZNAMY dc876efff8 changed placeholder display to top 5 2021-04-09 16:35:55 +02:00
NEZNAMY c39b650e9b found a way to keep code in the same thread without double cpu measuring 2021-04-09 15:56:22 +02:00
NEZNAMY 12c1cb871d various minor memory optimizations 2021-04-09 15:39:57 +02:00
NEZNAMY c75e86812d created Component convert methods for faster code & less memory allocations 2021-04-09 13:56:25 +02:00
NEZNAMY cc1fffbe8b missing changes from previous commit 2021-04-09 13:27:19 +02:00
NEZNAMY e6313a5e52 showing correct threshold 2021-04-09 01:25:08 +02:00
NEZNAMY 2a278dcff9 added custom line properties support to commands 2021-04-09 01:16:47 +02:00
NEZNAMY 8539529061 updated line numbers 2021-04-09 00:00:16 +02:00
NEZNAMY 5a4b7d93c0 reduced display threshold 2021-04-08 16:01:07 +02:00
NEZNAMY 5e459effc0 fixed &# RGB format issue from previous commits 2021-04-08 14:24:17 +02:00
NEZNAMY 5f0cf2ecb5 Update TAB.java 2021-04-08 01:04:40 +02:00
NEZNAMY 44c133a68c added requested method 2021-04-07 23:19:39 +02:00
NEZNAMY f842f15cd4 wrong color symbol 2021-04-07 11:17:47 +02:00
NEZNAMY 04c78a37d7 various random small memory and cpu improvements 2021-04-06 21:33:01 +02:00
NEZNAMY cba4dbdf84 2.8.11-pre11 2021-04-05 20:40:55 +02:00
NEZNAMY 2fbb162bbf fixed NPE when joining into disabled world with aligned tabsuffix enabled 2021-04-05 20:36:16 +02:00
NEZNAMY a1be962997 disabled by default 2021-04-05 20:34:04 +02:00
NEZNAMY a3c3f5d990 reverted broken commit 2021-04-05 19:20:55 +02:00
NEZNAMY c785d4fdca fixed #282 2021-04-05 16:21:33 +02:00
NEZNAMY 6b78707bd8 not overriding other plugins with delayed scoreboard 2021-04-05 00:23:57 +02:00
457 changed files with 93102 additions and 87312 deletions

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
name: Build
on:
push:
branches:
- master
- v3
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
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
with:
java-version: 11
- name: Cache SonarCloud packages
uses: actions/cache@v1
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: "${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}"
restore-keys: |
${{ runner.os }}-gradle-
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
run: ./gradlew clean build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.3.1
with:
name: Jar
path: jar/build/libs/TAB-**.jar
+10 -5
View File
@@ -1,8 +1,7 @@
# Compiled class file
*.class
/bin/
/target/
/src/test/
bin/
target/
# Log file
*.log
@@ -31,8 +30,14 @@ hs_err_pid*
.project
# intellij
/.idea/
.idea/
# other
.gitattributes
*.iml
*.iml
run/
# Gradle
.gradle/
build/
!gradle/wrapper/gradle-wrapper.jar
+39 -47
View File
@@ -1,49 +1,41 @@
## Introduction
Here it is. After countless requests to go opensource, I did it to make those people quiet. It worked.
It's already been a year and I still can't see any positives of it. This way I just made it easier for everyone to make a plugin as good, so people can stop using it and switch to something better.
## Compiling
Plugin can be compiled using maven.
# About
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
similar plugins in terms of features, performance, compatibility and support.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
## Premium code
The source contains content of premium version as well. However, this must be activated by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/src/main/java/me/neznamy/tab/shared/TAB.java#L79).
## Links
Free version
    Spigot (releases): https://www.spigotmc.org/resources/57806/
    Pre-releases https://github.com/NEZNAMY/TAB/releases
Premium version
    Polymart (releases): https://polymart.org/resource/484
    MC-Market (releases): https://www.mc-market.org/resources/14009/
    Pre-releases on discord for verified buyers
Bukkit bridge https://www.spigotmc.org/resources/83966/
Wiki https://github.com/NEZNAMY/TAB/wiki
Discord invite link: https://discord.gg/EaSvdk6
## Downloads
Official release: https://www.spigotmc.org/resources/57806/
Pre-releases: https://github.com/NEZNAMY/TAB/releases
## Maven Repository
```
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
# 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.
<dependencies>
<!-- TAB -->
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>TAB-API</artifactId>
<version>2.8.10</version>
</dependency>
</dependencies>
```
# Compiling
Compilation requires JDK 8 and up.
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
[github releases](https://github.com/NEZNAMY/TAB/releases)
and [MC-Market](https://www.mc-market.org/resources/20631/).
If you want to show your appreciation for my work, you can purchase the plugin on
[MC-Market](https://www.mc-market.org/resources/14009/),
[Polymart](https://polymart.org/resource/484) or just [send a donation](https://paypal.me/neznamy1).
Support is provided to buyers on [discord](https://discord.gg/EaSvdk6).
A detailed explanation of all the plugin's functions can be found on the
[wiki](https://github.com/NEZNAMY/TAB/wiki).
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for **bug reports**
or other issues which are not directly a bug (performance issues, errors, etc.).
Support is provided on discord and feature requests are currently not accepted due to high demand and
being busy with bug reports, which take priority over new features.
We are not looking to add any new major features anymore.
We would rather spend time improving existing features instead.
When opening a bug report, make sure you provide all necessary information to reproduce the bug,
otherwise we will not able to fix it. Saying "it only occurs randomly" or "I don't know how to
reproduce it" has no value and cannot be converted into actual code.
+9
View File
@@ -0,0 +1,9 @@
dependencies {
compileOnlyApi(libs.netty)
api(libs.jsonSimple) {
exclude("junit", "junit")
}
compileOnlyApi(libs.snakeyaml)
api(libs.yamlAssist)
compileOnlyApi(libs.gson)
}
@@ -1,96 +1,82 @@
package me.neznamy.tab.api;
import java.util.Set;
import me.neznamy.tab.shared.Property;
public interface ArmorStand {
/**
* Returns true if offset is static, false if not
* @return true if static, false if dynamic
*/
public boolean hasStaticOffset();
boolean hasStaticOffset();
/**
* Changes offset of the armor stand
* @param offset - new offset
*/
public void setOffset(double offset);
void setOffset(double offset);
/**
* Returns offset of the armor stand
* @return offset from base
*/
public double getOffset();
double getOffset();
/**
* Returns property for armor stand's name
* @return property for armor stand's name
*/
public Property getProperty();
Property getProperty();
/**
* Teleports armor stand to owner's current location for everyone in range
*/
public void teleport();
void teleport();
/**
* Teleports armor stand to owner's current location for specified player
* @param viewer - player to send packet to
*/
public void teleport(TabPlayer viewer);
void teleport(TabPlayer viewer);
/**
* Changes sneaking flag and sends packets to everyone in range
* @param sneaking
* @param sneaking
* new sneaking status
*/
public void sneak(boolean sneaking);
void sneak(boolean sneaking);
/**
* Despawns armor stand for everyone
* DeSpawns armor stand for everyone
*/
public void destroy();
void destroy();
/**
* Despawns armor stand for specified player
* DeSpawns armor stand for specified player
* @param viewer - player to send packet to
*/
public void destroy(TabPlayer viewer);
void destroy(TabPlayer viewer);
/**
* Updates armor stand's name if needed
*/
public void refresh();
void refresh();
/**
* Updates visibility if needed
* @param force - if refresh should be forced
*/
public void updateVisibility(boolean force);
/**
* Removes specified player from list of players in range
* @param viewer - player to remove
*/
public void removeFromRegistered(TabPlayer viewer);
void updateVisibility(boolean force);
/**
* Returns entity ID of this armor stand
* @return entity ID of this armor stand
*/
public int getEntityId();
int getEntityId();
/**
* Spawns armor stand for specified player
* @param viewer - player to spawn for
* @param addToRegistered - if player should be added to players in range
*/
public void spawn(TabPlayer viewer);
void spawn(TabPlayer viewer);
/**
* Returns list of players in entity tracking range (48 blocks)
* @return list of nearby players
*/
public Set<TabPlayer> getNearbyPlayers();
void respawn(TabPlayer viewer);
}
@@ -1,20 +1,26 @@
package me.neznamy.tab.api;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* A helper class for easy management of armor stands of a player
*/
public class ArmorStandManager {
//list of registered armor stands
private Map<String, ArmorStand> armorStands = new LinkedHashMap<String, ArmorStand>();
//map of registered armor stands
private final Map<String, ArmorStand> armorStands = new LinkedHashMap<>();
//players in entity tracking range
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
//array to iterate over to avoid concurrent modification and slightly boost performance & memory
private ArmorStand[] armorStandArray = new ArmorStand[0];
private TabPlayer[] nearbyPlayerArray = new TabPlayer[0];
/**
* Adds armor stand into list
* @param name - key name of the armor stand
@@ -22,23 +28,38 @@ public class ArmorStandManager {
*/
public void addArmorStand(String name, ArmorStand as) {
armorStands.put(name, as);
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
for (TabPlayer p : nearbyPlayerArray) as.spawn(p);
}
/**
* Returns armor stand with given name or null if not present
* @param name - name of armor stand registered with
* @return armor stand with given name
*/
public ArmorStand getArmorStand(String name) {
return armorStands.get(name);
}
/**
* Removes armor stand from list. It is not automatically destroyed for players
* @param name - name of armor stand to remove
*/
public void removeArmorStand(String name) {
armorStands.get(name).destroy();
armorStands.remove(name);
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
}
/**
* Spawns all armor stands for specified viewer and adds them into nearby players
* @param viewer - player to spawn armor stands for
*/
public void spawn(TabPlayer viewer) {
nearbyPlayers.add(viewer);
nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
if (viewer.getVersion().getMinorVersion() < 8) return;
getArmorStands().forEach(a -> a.spawn(viewer));
for (ArmorStand a : armorStandArray) a.spawn(viewer);
}
/**
@@ -46,14 +67,14 @@ public class ArmorStandManager {
* @param sneaking - new sneaking status
*/
public void sneak(boolean sneaking) {
getArmorStands().forEach(a -> a.sneak(sneaking));
for (ArmorStand a : armorStandArray) a.sneak(sneaking);
}
/**
* Teleports armor stands to player's current location for all nearby players
*/
public void teleport() {
getArmorStands().forEach(a -> a.teleport());
for (ArmorStand a : armorStandArray) a.teleport();
}
/**
@@ -61,14 +82,14 @@ public class ArmorStandManager {
* @param viewer - player to teleport armor stands for
*/
public void teleport(TabPlayer viewer) {
getArmorStands().forEach(a -> a.teleport(viewer));
for (ArmorStand a : armorStandArray) a.teleport(viewer);
}
/**
* Refreshes name of all armor stands for all nearby players
*/
public void refresh() {
getArmorStands().forEach(a -> a.refresh());
for (ArmorStand a : armorStandArray) a.refresh();
}
/**
@@ -76,7 +97,7 @@ public class ArmorStandManager {
* @param force - true if packets should be sent despite seemingly not needed
*/
public void updateVisibility(boolean force) {
getArmorStands().forEach(a -> a.updateVisibility(force));
for (ArmorStand a : armorStandArray) a.updateVisibility(force);
}
/**
@@ -84,14 +105,16 @@ public class ArmorStandManager {
* @param viewer - player to remove
*/
public void unregisterPlayer(TabPlayer viewer) {
getArmorStands().forEach(a -> a.removeFromRegistered(viewer));
if (nearbyPlayers.remove(viewer)) nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
}
/**
* Sends destroy packet of all armor stands to everyone and clears nearby players list
*/
public void destroy() {
getArmorStands().forEach(a -> a.destroy());
for (ArmorStand a : armorStandArray) a.destroy();
nearbyPlayers.clear();
nearbyPlayerArray = new TabPlayer[0];
}
/**
@@ -99,7 +122,8 @@ public class ArmorStandManager {
* @param viewer - player to destroy armor stands for
*/
public void destroy(TabPlayer viewer) {
getArmorStands().forEach(a -> a.destroy(viewer));
for (ArmorStand a : armorStandArray) a.destroy(viewer);
unregisterPlayer(viewer);
}
/**
@@ -108,28 +132,39 @@ public class ArmorStandManager {
* @return true if armor stand with specified entity id exists, false if not
*/
public boolean hasArmorStandWithID(int entityId) {
for (ArmorStand as : getArmorStands()) {
if (as.getEntityId() == entityId) {
for (ArmorStand a : armorStandArray) {
if (a.getEntityId() == entityId) {
return true;
}
}
return false;
}
/**
* Returns a copy of list of all registered armor stands
* @return copy of armor stands
* Returns array of all registered armor stands
* @return armor stand array
*/
public Collection<ArmorStand> getArmorStands(){
return new ArrayList<>(armorStands.values());
public ArmorStand[] getArmorStands(){
return armorStandArray;
}
/**
* Returns array of nearby players
* @return array of nearby players
*/
public TabPlayer[] getNearbyPlayers(){
return nearbyPlayerArray;
}
/**
* Returns list of nearby players
* @return list of nearby players
*/
public Set<TabPlayer> getNearbyPlayers(){
if (armorStands.values().size() == 0) return new HashSet<>(); //not initialized yet
return armorStands.values().iterator().next().getNearbyPlayers();
public boolean isNearby(TabPlayer viewer) {
return nearbyPlayers.contains(viewer);
}
public void respawn() {
for (ArmorStand a : armorStandArray) {
for (TabPlayer viewer : nearbyPlayerArray) {
a.respawn(viewer);
}
}
}
}
@@ -0,0 +1,35 @@
package me.neznamy.tab.api;
public interface FeatureManager {
/**
* Registers a feature, which will start receiving events
*
* @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
*/
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
*/
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
*/
TabFeature getFeature(String name);
}
@@ -0,0 +1,16 @@
package me.neznamy.tab.api;
public interface HeaderFooterManager {
void setHeader(TabPlayer player, String header);
void setFooter(TabPlayer player, String footer);
void setHeaderAndFooter(TabPlayer player, String header, String footer);
void resetHeader(TabPlayer player);
void resetFooter(TabPlayer player);
void resetHeaderAndFooter(TabPlayer player);
}
@@ -0,0 +1,60 @@
package me.neznamy.tab.api;
public interface Property {
/**
* Changes raw value to new one
* @param rawValue - new value to be used
*/
void changeRawValue(String rawValue);
/**
* Returns original raw value ignoring API calls
* @return original raw value
*/
String getOriginalRawValue();
/**
* Temporarily overrides current raw value with an API call
* @param temporaryValue - temporary value to be assigned
*/
void setTemporaryValue(String temporaryValue);
/**
* Returns current temporary value
* @return temporary value or null if not set
*/
String getTemporaryValue();
/**
* Returns temporary value (via API) if present, raw value otherwise
* @return current raw value
*/
String getCurrentRawValue();
/**
* Returns 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
*/
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
*/
String updateAndGet();
/**
* Returns value for defined viewer by applying relational placeholders to last known value
* @param viewer - the viewer
* @return format for the viewer
*/
String getFormat(TabPlayer viewer);
}
@@ -0,0 +1,10 @@
package me.neznamy.tab.api;
public interface PropertyConfiguration {
void setProperty(String name, String property, String server, String world, String value);
String[] getProperty(String name, String property, String server, String world);
void remove(String name);
}
@@ -0,0 +1,152 @@
package me.neznamy.tab.api;
/**
* Enum class representing all possibly used protocol versions
*/
public enum ProtocolVersion {
UNKNOWN ("Unknown"),
PROXY ("Proxy"),
V1_18_1 (757),
V1_18 (757),
V1_17_1 (756),
V1_17 (755),
V1_16_5 (754),
V1_16_4 (754),
V1_16_3 (753),
V1_16_2 (751),
V1_16_1 (736),
V1_16 (735),
V1_15_2 (578),
V1_15_1 (575),
V1_15 (573),
V1_14_4 (498),
V1_14_3 (490),
V1_14_2 (485),
V1_14_1 (480),
V1_14 (477),
V1_13_2 (404),
V1_13_1 (401),
V1_13 (393),
V1_12_2 (340),
V1_12_1 (338),
V1_12 (335),
V1_11_2 (316),
V1_11_1 (316),
V1_11 (315),
V1_10_2 (210),
V1_10_1 (210),
V1_10 (210),
V1_9_4 (110),
V1_9_3 (110),
V1_9_2 (109),
V1_9_1 (108),
V1_9 (107),
V1_8 (47),
V1_7_10 (5),
V1_7_9 (5),
V1_7_8 (5),
V1_7_7 (5),
V1_7_6 (5),
V1_7_5 (4),
V1_7_4 (4),
V1_7_2 (4),
V1_6_4 (78),
V1_6_2 (74),
V1_6_1 (73),
V1_5_2 (61),
V1_5_1 (60),
V1_5 (60),
V1_4_7 (51),
V1_4_6 (51);
//version's network id found at https://wiki.vg/Protocol_version_numbers
private final int networkId;
//minor version, such as 16
private int minorVersion;
//friendly name displayed in %player-version% placeholder
private final String friendlyName;
/**
* Constructs new instance with given network id
* @param networkId - network id of this version
*/
ProtocolVersion(int networkId){
this.networkId = networkId;
this.minorVersion = Integer.parseInt(toString().split("_")[1]);
this.friendlyName = toString().substring(1).replace("_", ".");
}
/**
* Constructs new instance with given friendly name
* @param friendlyName - friendly name to display
*/
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 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 if unknown version
* @param friendlyName - friendly name of the version
* @return version or UNKNOWN if version is unknown
*/
public static ProtocolVersion fromFriendlyName(String friendlyName) {
if (friendlyName.startsWith("1.8")) return V1_8;
try {
return valueOf("V" + friendlyName.replace(".", "_"));
} catch (IllegalArgumentException e) {
return UNKNOWN;
}
}
/**
* Returns 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()) {
if (networkId == v.getNetworkId()) return v;
}
return UNKNOWN;
}
}
@@ -0,0 +1,115 @@
package me.neznamy.tab.api;
import java.util.UUID;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.api.event.EventBus;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.task.ThreadManager;
import me.neznamy.tab.api.team.TeamManager;
/**
* The primary API class to get instances of other API classes
*/
public abstract class TabAPI {
private static TabAPI instance;
public static TabAPI getInstance() {
return instance;
}
public static void setInstance(TabAPI instance) {
TabAPI.instance = instance;
}
/**
* Returns player object from given UUID
* @return player object from given UUID
* @param id - Player UUID
*/
public abstract TabPlayer getPlayer(UUID id);
/**
* Returns player object from given name
* @return player object from given name
* @param name - Player 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
*/
public abstract TabPlayer[] getOnlinePlayers();
/**
* Return BossBar manager instance if the feature is enabled. Returns null otherwise.
* @return BossBar manager
*/
public abstract BossBarManager getBossBarManager();
/**
* Returns scoreboard manager instance if the feature is enabled. Returns null otherwise.
* @return scoreboard manager
*/
public abstract ScoreboardManager getScoreboardManager();
/**
* Returns team manager instance if the feature is enabled, false otherwise
* @return team manager
*/
public abstract TeamManager getTeamManager();
public abstract HeaderFooterManager getHeaderFooterManager();
/**
* Returns PlaceholderManager instance
* @return PlaceholderManager instance
*/
public abstract PlaceholderManager getPlaceholderManager();
/**
* Returns feature manager instance
* @return feature manager instance
*/
public abstract FeatureManager getFeatureManager();
public abstract TablistFormatManager getTablistFormatManager();
/**
* Gets the event bus for registering listeners for TAB events.
*
* @return the event bus
*/
public abstract EventBus getEventBus();
/**
* Returns server version. On proxy installation returns PROXY.
* @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
*/
public abstract void sendConsoleMessage(String message, boolean translateColors);
public abstract ThreadManager getThreadManager();
public abstract ConfigurationFile getPlayerCache();
public abstract ConfigurationFile getConfig();
public abstract PropertyConfiguration getGroups();
public abstract PropertyConfiguration getUsers();
public abstract void debug(String message);
public abstract void logError(String message, Throwable t);
}
@@ -0,0 +1,261 @@
package me.neznamy.tab.api;
import java.util.*;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
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;
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
private final List<String> methodOverrides = new ArrayList<>();
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");
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onJoin");
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onQuit");
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
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)
methodOverrides.add("onDisplayObjective");
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onLoginPacket");
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onObjective");
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)
methodOverrides.add("onPacketReceive");
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketSend");
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("refresh");
} catch (NoSuchMethodException e) {
//this will never happen
}
}
/**
* Loads all players and sends packets
*/
public void load() {
//empty by default
}
/**
* Unloads all players and sends clear packets
*/
public void unload() {
//empty by default
}
/**
* Processes command from player
* @param sender - command sender
* @param message - command line
* @return true if event should be cancelled, false if not
*/
public boolean onCommand(TabPlayer sender, String message) {
return false;
}
/**
* Processes join event
* @param connectedPlayer - player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
//empty by default
}
/**
* Processes quit event
* @param disconnectedPlayer - player who disconnected
*/
public void onQuit(TabPlayer disconnectedPlayer) {
}
/**
* Processes world switch
* @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
}
/**
* Processes server switch
* @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
}
/**
* Processes the packet send and returns true if packet should be cancelled
* @param receiver - player receiving packet
* @param packet - received packet
*/
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
//empty by default
}
/**
* Processes login packet, only available on BungeeCord
* @param packetReceiver - player receiving client reset packet
*/
public void onLoginPacket(TabPlayer packetReceiver) {
//empty by default
}
/**
* Processes the packet send
* @param receiver - player receiving packet
* @param packet - received packet
*/
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
//empty by default
}
/**
* Processes the packet send and possibly modifies it
* @param receiver - player receiving packet
* @param info - received packet
*/
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
//empty by default
}
/**
* 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
*/
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
return false;
}
/**
* Processes raw packet sent to client
* @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
}
/**
* Performs refresh of specified player
* @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
}
/**
* Registers this feature as one using specified placeholders
* @param placeholders - placeholders to add as used in this feature
*/
public void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* 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
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns name of the feature displayed in /tab cpu.
* @return name of the feature displayed in /tab cpu
*/
public String getFeatureName() {
return featureName;
}
public boolean overridesMethod(String method) {
return methodOverrides.contains(method);
}
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
public String getRefreshDisplayName() {
return refreshDisplayName;
}
}
@@ -0,0 +1,263 @@
package me.neznamy.tab.api;
import java.util.UUID;
import io.netty.channel.Channel;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.TabPacket;
/**
* An interface representing a player
*/
public interface TabPlayer {
/**
* Refreshes all visuals on the player
*/
void forceRefresh();
/**
* Returns player's name
* @return Player's name
*/
String getName();
/**
* Returns 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
*/
UUID getTablistUUID();
/**
* Returns 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
*/
Object getPlayer();
/**
* Returns player's current world name (on BungeeCord this requires bridge installed)
* @return name of world where player is currently in, null on BungeeCord if bridge is not installed
*/
String getWorld();
/**
* Returns player's current server name on BungeeCord (on bukkit this returns null)
* @return name of server where player is currently in, null 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
*/
boolean hasPermission(String permission);
/**
* Sends the player a custom universal packet
* @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
*/
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
*/
void sendCustomPacket(TabPacket packet, String feature);
/**
* Sends the player a platform-specific packet
* @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
*/
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
*/
void sendMessage(String message, boolean translateColors);
/**
* Sends specified component as a chat message
* @param message - message to send
*/
void sendMessage(IChatBaseComponent message);
/**
* Returns player's primary permission group
* @return player's group depending on configuration
*/
String getGroup();
/**
* Toggles armor stands preview mode for the player
*/
void toggleNametagPreview();
/**
* Returns whether player is previewing NameTag or not
* @return true if previewing, false if not
*/
boolean isPreviewingNametag();
/**
* Returns player's channel or null if server is 1.7 or older
* @return player's channel
*/
Channel getChannel();
/**
* Returns player's ping calculated by server
* @return player's ping
*/
int getPing();
/**
* Returns player's platform-specific skin data
* @return player's skin
*/
Object getSkin();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
* @return true if player is fully loaded, 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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
boolean isVanished();
/**
* Returns true if player is online according to server
* @return true if online, false if not
*/
boolean isOnline();
/**
* Returns player's armor stand manager if unlimited GameMode mode is enabled, null if disabled
* @return player's armor stand manager
*/
ArmorStandManager getArmorStandManager();
/**
* Sets player's armor stands manager to new instance
* @param armorStandManager - new instance
*/
void setArmorStandManager(ArmorStandManager armorStandManager);
/**
* Returns GameMode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
* @return GameMode of the player
*/
int getGamemode();
boolean isBedrockPlayer();
void setTemporaryGroup(String group);
boolean hasTemporaryGroup();
void resetTemporaryGroup();
}
@@ -0,0 +1,29 @@
package me.neznamy.tab.api;
public interface TablistFormatManager {
void setPrefix(TabPlayer player, String prefix);
void setName(TabPlayer player, String customName);
void setSuffix(TabPlayer player, String suffix);
void resetPrefix(TabPlayer player);
void resetName(TabPlayer player);
void resetSuffix(TabPlayer player);
String getCustomPrefix(TabPlayer player);
String getCustomName(TabPlayer player);
String getCustomSuffix(TabPlayer player);
String getOriginalPrefix(TabPlayer player);
String getOriginalName(TabPlayer player);
String getOriginalSuffix(TabPlayer player);
}
@@ -1,7 +1,8 @@
package me.neznamy.tab.api.bossbar;
/**
* An enum representing bossbar colors
* An enum representing all available BossBar colors.
* Calling ordinal() will return color's network ID.
*/
public enum BarColor {
@@ -11,5 +12,5 @@ public enum BarColor {
GREEN,
YELLOW,
PURPLE,
WHITE;
WHITE
}
@@ -0,0 +1,36 @@
package me.neznamy.tab.api.bossbar;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
public enum BarStyle {
PROGRESS("SOLID"),
NOTCHED_6("SEGMENTED_6"),
NOTCHED_10("SEGMENTED_10"),
NOTCHED_12("SEGMENTED_12"),
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
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;
}
}
@@ -0,0 +1,185 @@
package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.UUID;
import me.neznamy.tab.api.TabPlayer;
/**
* An interface representing a BossBar line.
* <p>
* For clients 1.8 and lower it uses wither, which only allows 1 bar
* to be displayed at a time. Entity packets are not available on BungeeCord,
* where nothing will be displayed for these players.
* <p>
* For 1.9+ it uses the new BossBar feature, allowing configurable styles
* and colors, as well as display several bars at once. Limit of maximum
* displayed BossBars depends on client resolution and GUI scale, up to
* 9 concurrent BossBars displayed at once.
*/
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
*/
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
*/
UUID getUniqueId();
/**
* Changes BossBar title to specified string, supporting placeholders.
* <p>
* If title contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified title is equal to the current one, nothing happens.
*
* @param title
* New title to display in this BossBar
*/
void setTitle(String title);
/**
* Changes BossBar progress to specified string, supporting placeholders.
* The string must be a string version of a float value, or a placeholder that
* outputs a float value with range 0-100.
* <p>
* If progress contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified progress is equal to the current one, nothing happens.
*
* @param progress
* New progress to use in this BossBar
*/
void setProgress(String progress);
/**
* 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
*/
void setProgress(float progress);
/**
* Changes BossBar color to specified string, supporting placeholders.
* The string must be a string version of one of the supported values,
* or a placeholder that outputs one of them.
* <p>
* If color contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
*/
void setColor(String color);
/**
* Changes BossBar color to specified enum constant.
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
*/
void setColor(BarColor color);
/**
* Changes BossBar style to specified string, supporting placeholders.
* The string must be a string version of one of the supported values,
* or a placeholder that outputs one of them.
* <p>
* If style contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
*/
void setStyle(String style);
/**
* Changes BossBar style to specified enum constant.
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @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
*/
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
*/
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
*/
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
*/
String getStyle();
/**
* Registers this BossBar to specified player.
* <p>
* If the player already sees this BossBar, nothing happens.
*
* @param player
* Player to register this BossBar to
*/
void addPlayer(TabPlayer player);
/**
* Unregisters this BossBar from specified player.
* <p>
* If the player does not see this BossBar, nothing happens.
*
* @param player
* Player to unregister this BossBar from
*/
void removePlayer(TabPlayer player);
/**
* Returns list of players who can see this BossBar.
* <p>
* 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
*/
List<TabPlayer> getPlayers();
}
@@ -0,0 +1,167 @@
package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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()}.
* This requires the BossBar feature to be enabled in config, otherwise the method will
* return null.
*/
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
*/
BossBar createBossBar(String title, float progress, BarColor color, BarStyle style);
/**
* 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
*/
BossBar createBossBar(String title, String progress, String color, String style);
/**
* Returns registered BossBar with given name. For BossBars from config it is the name
* they were created with, for API BossBars their randomly generated name, which can be
* 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
*/
BossBar getBossBar(String name);
/**
* Returns registered BossBar with specified UUID. This UUID is randomly generated when
* 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
*/
BossBar getBossBar(UUID id);
/**
* Returns a map of all registered BossBars. This includes both BossBars from config
* and those registered via API.
* <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
*/
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)
*/
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)
*/
boolean hasBossBarVisible(TabPlayer player);
/**
* 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
* @see #toggleBossBar(TabPlayer, boolean)
* @see #hasBossBarVisible(TabPlayer)
*/
void setBossBarVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* 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()
*/
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()
*/
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)
*/
List<BossBar> getAnnouncedBossBars();
}
@@ -0,0 +1,59 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
/**
* Class for click event action in chat component
*/
public class ChatClickable {
/** Click action */
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 cannot be null");
Preconditions.checkNotNull(value, "hover value cannot be null");
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;
}
/**
* Enum for all possible click actions
*/
public enum EnumClickAction {
OPEN_URL,
RUN_COMMAND,
CHANGE_PAGE, //since 1.8
SUGGEST_COMMAND,
COPY_TO_CLIPBOARD //since 1.15
}
}
@@ -0,0 +1,38 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import com.google.gson.JsonObject;
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) {
this.type = type;
this.id = id;
this.name = name;
}
@Override
public String toRawText() {
return toString();
}
@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;
}
}
@@ -0,0 +1,57 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
/**
* Class for hover event action in chat component
*/
public class ChatHoverable {
/** Hover action */
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 cannot be null");
Preconditions.checkNotNull(value, "hover value cannot be null");
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;
}
/**
* Enum for all possible hover actions
*/
public enum EnumHoverAction {
SHOW_TEXT,
SHOW_ITEM,
SHOW_ENTITY
}
}
@@ -0,0 +1,289 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import org.json.simple.JSONObject;
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;
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) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
this.bold = modifier.bold;
this.italic = modifier.italic;
this.underlined = modifier.underlined;
this.strikethrough = modifier.strikethrough;
this.obfuscated = modifier.obfuscated;
this.clickEvent = modifier.clickEvent == null ? null : new ChatClickable(modifier.clickEvent.getAction(), modifier.clickEvent.getValue());
this.hoverEvent = modifier.hoverEvent == null ? null : new ChatHoverable(modifier.hoverEvent.getAction(), modifier.hoverEvent.getValue());
this.font = modifier.font;
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
*/
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
*/
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
*/
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
*/
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
*/
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
*/
public void onClickOpenUrl(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
*/
public void onClickRunCommand(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
*/
public void onClickSuggestCommand(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
*/
public void onClickChangePage(int newPage) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() < 8) throw new UnsupportedOperationException("change_page click action is not supported on <1.8");
clickEvent = new ChatClickable(EnumClickAction.CHANGE_PAGE, String.valueOf(newPage));
}
/**
* Sets click action to COPY_TO_CLIPBOARD and text to provided value
* @param text - text to copy to clipboard on click
*/
public void onClickCopyToClipBoard(String text) {
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
}
public void onClick(EnumClickAction action, String value) {
clickEvent = new ChatClickable(action, value);
}
/**
* Sets hover action to SHOW_TEXT and text to given value
* @param text - text to show
*/
public void onHoverShowText(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
*/
public void onHoverShowItem(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
*/
public void onHoverShowEntity(String type, UUID id, String name) {
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 onHover(EnumHoverAction action, 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();
if (color != null) json.put("color", targetVersion.getMinorVersion() >= 16 ? color.toString() : color.getLegacyColor().toString().toLowerCase());
if (bold != null) json.put("bold", bold);
if (italic != null) json.put("italic", italic);
if (underlined != null) json.put("underlined", underlined);
if (strikethrough != null) json.put("strikethrough", strikethrough);
if (obfuscated != null) json.put("obfuscated", obfuscated);
if (clickEvent != null) {
JSONObject click = new JSONObject();
click.put("action", clickEvent.getAction().toString().toLowerCase());
click.put("value", clickEvent.getValue());
json.put("clickEvent", click);
}
if (hoverEvent != null) {
JSONObject hover = new JSONObject();
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hover.put("contents", hoverEvent.getValue());
} else {
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
}
json.put("hoverEvent", hover);
}
if (font != null) json.put("font", font);
return json;
}
public String getMagicCodes() {
StringBuilder builder = new StringBuilder();
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
return builder.toString();
}
public void setTargetVersion(ProtocolVersion targetVersion) {
this.targetVersion = targetVersion;
}
public ProtocolVersion getTargetVersion() {
return targetVersion;
}
}
@@ -0,0 +1,245 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
/**
* An enum containing all possible legacy color codes and magic codes. Also contains handy color-related methods.
*/
public enum EnumChatFormat {
BLACK('0', "000000"),
DARK_BLUE('1', "0000AA"),
DARK_GREEN('2', "00AA00"),
DARK_AQUA('3', "00AAAA"),
DARK_RED('4', "AA0000"),
DARK_PURPLE('5', "AA00AA"),
GOLD('6', "FFAA00"),
GRAY('7', "AAAAAA"),
DARK_GRAY('8', "555555"),
BLUE('9', "5555FF"),
GREEN('a', "55FF55"),
AQUA('b', "55FFFF"),
RED('c', "FF5555"),
LIGHT_PURPLE('d', "FF55FF"),
YELLOW('e', "FFFF55"),
WHITE('f', "FFFFFF"),
OBFUSCATED('k'),
BOLD('l'),
STRIKETHROUGH('m'),
UNDERLINE('n'),
ITALIC('o'),
RESET('r');
/** Creating a constant to avoid memory allocations on each request */
private static final EnumChatFormat[] VALUES = values();
/** The symbol minecraft uses to colorize text */
public static final char COLOR_CHAR = 0x00a7;
/** The color symbol in form of a string */
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
/** Character representing the color or magic code */
private final char character;
/** Red value of this constant, 0 for magic codes */
private final short red;
/** Green value of this constant, 0 for magic codes */
private final short green;
/** Blue value of this constant, 0 for magic codes */
private final short blue;
/** Color as 6-digit hex code, null for magic codes */
private final String hexCode;
/** Color symbol followed by constant's character */
private final String chatFormat;
/**
* 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
*/
EnumChatFormat(char character, String hexCode) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
this.hexCode = hexCode;
int hexColor = Integer.parseInt(hexCode, 16);
red = (short) ((hexColor >> 16) & 0xFF);
green = (short) ((hexColor >> 8) & 0xFF);
blue = (short) (hexColor & 0xFF);
}
/**
* Constructs new magic code instance with given character
* @param character
* character representing the magic code
*/
EnumChatFormat(char character) {
this.character = character;
this.chatFormat = 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
*/
public static EnumChatFormat getByChar(char c) {
for (EnumChatFormat format : VALUES) {
if (format.character == c) return format;
}
return null;
}
/**
* 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
*/
public static EnumChatFormat lastColorsOf(String string) {
if (string == null || string.length() == 0) return EnumChatFormat.WHITE;
String legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
String last = getLastColors(legacyText);
if (last.length() > 0) {
char c = last.toCharArray()[1];
for (EnumChatFormat e : VALUES) {
if (e.character == c) return e;
}
}
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
*/
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;
}
return null;
}
/**
* Color translation method taken from bukkit, which converts '&' 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
*/
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)){
b[i] = COLOR_CHAR;
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
}
}
return new String(b);
}
/**
* Turns back the color symbol into '&' symbol in provided text.
* @param text
* text to revert colors in
* @return reverted text
*/
public static String decolor(String text) {
if (text == null) return null;
if (!text.contains(COLOR_STRING)) return text;
return text.replace(COLOR_CHAR, '&');
}
/**
* 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
*/
public static String getLastColors(String input) {
StringBuilder result = new StringBuilder();
int length = input.length();
for (int index = length - 1; index > -1; index--){
char section = input.charAt(index);
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);
result.insert(1, c);
if ("0123456789AaBbCcDdEeFfRr".contains(String.valueOf(c))) {
break;
}
}
}
}
return result.toString();
}
}
@@ -0,0 +1,476 @@
package me.neznamy.tab.api.chat;
import java.util.*;
import me.neznamy.tab.api.util.Preconditions;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
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 me.neznamy.tab.api.chat.rgb.RGBUtils;
import org.json.simple.parser.ParseException;
/**
* A class representing the n.m.s.IChatBaseComponent class to make work with it much easier
*/
@SuppressWarnings("unchecked")
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<>();
/** Text of the component */
private String text;
/** Chat modifier containing color, magic codes, hover and click event */
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
*/
public IChatBaseComponent(IChatBaseComponent component) {
this.text = component.text;
this.modifier = new ChatModifier(component.modifier);
for (IChatBaseComponent child : component.getExtra()) {
addExtra(new IChatBaseComponent(child));
}
}
/**
* Constructs new instance with given text
* @param text
* text to display
*/
public IChatBaseComponent(String text) {
this.text = text;
}
/**
* Returns list of extra components. If no extra components are defined, returns empty list.
* @return list of extra components
*/
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
*/
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;
}
/**
* Appends provided component as extra component
* @param child
* component to append
*/
public void addExtra(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
*/
public static IChatBaseComponent deserialize(String json) {
if (json == null) return null;
if (json.startsWith("\"") && json.endsWith("\"") && json.length() > 1) {
//simple component with only text used, minecraft serializer outputs the text in quotes instead of full json
return new IChatBaseComponent(json.substring(1, json.length()-1));
}
JSONObject jsonObject;
try {
jsonObject = (JSONObject) new JSONParser().parse(json);
} catch (ParseException e) {
TabAPI.getInstance().logError("Failed to deserialize json component " + json, e);
return null;
}
IChatBaseComponent component;
if (jsonObject.containsKey("type")) {
return new ChatComponentEntity((String) jsonObject.get("type"), UUID.fromString((String) jsonObject.get("id")), IChatBaseComponent.deserialize(jsonObject.get("name").toString()).toFlatText());
}
component = new IChatBaseComponent();
component.setText((String) jsonObject.get("text"));
component.modifier.setBold(getBoolean(jsonObject, "bold"));
component.modifier.setItalic(getBoolean(jsonObject, "italic"));
component.modifier.setUnderlined(getBoolean(jsonObject, "underlined"));
component.modifier.setStrikethrough(getBoolean(jsonObject, "strikethrough"));
component.modifier.setObfuscated(getBoolean(jsonObject, "obfuscated"));
component.modifier.setColor(TextColor.fromString(((String) jsonObject.get("color"))));
if (jsonObject.containsKey("clickEvent")) {
JSONObject clickEvent = (JSONObject) jsonObject.get("clickEvent");
String action = (String) clickEvent.get("action");
String value = clickEvent.get("value").toString();
component.modifier.onClick(EnumClickAction.valueOf(action.toUpperCase()), value);
}
if (jsonObject.containsKey("hoverEvent")) {
JSONObject hoverEvent = (JSONObject) jsonObject.get("hoverEvent");
String action = (String) hoverEvent.get("action");
String value = (String) hoverEvent.get("value");
component.modifier.onHover(EnumHoverAction.valueOf(action.toUpperCase()), deserialize(value));
}
if (jsonObject.containsKey("extra")) {
List<Object> list = (List<Object>) jsonObject.get("extra");
for (Object extra : list) {
String string = extra.toString();
//reverting .toString() removing "" for simple text
if (!string.startsWith("{")) string = "\"" + string + "\"";
component.addExtra(deserialize(string));
}
}
return component;
}
/**
* 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
*/
private static Boolean getBoolean(JSONObject jsonObject, String key) {
String value = String.valueOf(jsonObject.getOrDefault(key, null));
return "null".equals(value) ? null : Boolean.parseBoolean(value);
}
/**
* 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)
*/
@Override
public String toString() {
JSONObject json = new JSONObject();
if (text != null) json.put("text", text);
if (modifier.getTargetVersion() == null) modifier.setTargetVersion(TabAPI.getInstance().getServerVersion()); //packet.toString() was called as a part of a debug message
json.putAll(modifier.serialize());
if (extra != null) json.put("extra", extra);
return json.toString();
}
/**
* Serializes this component with colors based on client version.
* If client version is <1.16, HEX colors will be converted to legacy colors.
* @param clientVersion
* client version to adapt component for
* @return serialized string
*/
public String toString(ProtocolVersion clientVersion) {
return toString(clientVersion, false);
}
/**
* Serializes this component with colors based on client version.
* If client version is <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
*/
public String toString(ProtocolVersion clientVersion, boolean sendTranslatableIfEmpty) {
if (extra == null) {
if (text == null) return null;
if (text.length() == 0) {
if (sendTranslatableIfEmpty) {
return "{\"translate\":\"\"}";
} else {
return "{\"text\":\"\"}";
}
}
}
modifier.setTargetVersion(clientVersion);
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;
}
/**
* Returns organized component from colored text
* @param originalText
* text to convert
* @return organized component from colored text
*/
public static IChatBaseComponent fromColoredText(String originalText){
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), false);
List<IChatBaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
IChatBaseComponent component = new IChatBaseComponent();
for (int i = 0; i < text.length(); i++){
char c = text.charAt(i);
if (c == EnumChatFormat.COLOR_CHAR){
i++;
if (i >= text.length()) {
break;
}
c = text.charAt(i);
if ((c >= 'A') && (c <= 'Z')) {
c = (char)(c + ' ');
}
EnumChatFormat format = EnumChatFormat.getByChar(c);
if (format != null){
if (builder.length() > 0) {
component.setText(builder.toString());
components.add(component);
component = new IChatBaseComponent(component);
component.text = null;
builder = new StringBuilder();
}
switch (format){
case BOLD:
component.modifier.setBold(true);
break;
case ITALIC:
component.modifier.setItalic(true);
break;
case UNDERLINE:
component.modifier.setUnderlined(true);
break;
case STRIKETHROUGH:
component.modifier.setStrikethrough(true);
break;
case OBFUSCATED:
component.modifier.setObfuscated(true);
break;
case RESET:
component = new IChatBaseComponent();
component.modifier.setColor(new TextColor(EnumChatFormat.WHITE));
break;
default:
component = new IChatBaseComponent();
component.modifier.setColor(new TextColor(format));
break;
}
}
} else if (c == '#' && text.length() > i+6){
String hex = text.substring(i+1, i+7);
if (RGBUtils.getInstance().isHexCode(hex)) {
TextColor color;
if (containsLegacyCode(text, i)) {
color = new TextColor(hex, EnumChatFormat.getByChar(text.charAt(i+8)));
i += 8;
} else {
color = new TextColor(hex);
i += 6;
}
if (builder.length() > 0){
component.setText(builder.toString());
components.add(component);
builder = new StringBuilder();
}
component = new IChatBaseComponent();
component.modifier.setColor(color);
} else {
builder.append(c);
}
} else {
builder.append(c);
}
}
component.setText(builder.toString());
components.add(component);
return new IChatBaseComponent("").setExtra(components);
}
/**
* 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
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
}
/**
* Converts this component into a simple text with legacy colors (the closest match if color is set to RGB)
* @return The simple text format
*/
public String toLegacyText() {
StringBuilder builder = new StringBuilder();
append(builder, "");
return builder.toString();
}
/**
* 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
*/
private String append(StringBuilder builder, String previousFormatting) {
String formatting = previousFormatting;
if (text != null) {
formatting = getFormatting();
if (!formatting.equals(previousFormatting)) {
builder.append(formatting);
}
builder.append(text);
}
for (IChatBaseComponent component : getExtra()) {
formatting = component.append(builder, formatting);
}
return formatting;
}
/**
* Returns colors and magic codes of this component
* @return used colors and magic codes
*/
private String getFormatting() {
StringBuilder builder = new StringBuilder();
if (modifier.getColor() != null) {
if (modifier.getColor().getLegacyColor() == EnumChatFormat.WHITE) {
//preventing unwanted &r -> &f conversion and stopping the <1.13 client bug fix from working
builder.append(EnumChatFormat.RESET.getFormat());
} else {
builder.append(modifier.getColor().getLegacyColor().getFormat());
}
}
builder.append(modifier.getMagicCodes());
return builder.toString();
}
/**
* Returns raw text without colors, only works correctly when component is organized
* @return raw text in this component and all child components
*/
public String toRawText() {
StringBuilder builder = new StringBuilder();
if (text != null) builder.append(text);
for (IChatBaseComponent child : getExtra()) {
if (child.text != null) builder.append(child.text);
}
return builder.toString();
}
/**
* Converts the component into flat text with used colors (including rgb) and magic codes
* @return converted text
*/
public String toFlatText() {
StringBuilder builder = new StringBuilder();
if (modifier.getColor() != null) builder.append("#").append(modifier.getColor().getHexCode());
builder.append(modifier.getMagicCodes());
if (text != null) builder.append(text);
for (IChatBaseComponent child : getExtra()) {
builder.append(child.toFlatText());
}
return builder.toString();
}
/**
* 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
*/
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;
}
}
@@ -0,0 +1,209 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing a component color, which can be either RGB or legacy code.
*/
public class TextColor {
/**
* RGB values as a single number of 3 8-bit numbers (0-255).
* It is only initialized if colors are actually used to avoid
* unnecessary memory allocations with string operations.
*/
private int rgb = -1;
/** Closest legacy color to this color object. */
private EnumChatFormat legacyColor;
/** HEX code of this color as a 6-digit hexadecimal string without "#" prefix. */
private String hexCode;
/**
* 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;
/**
* 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}
*/
public TextColor(TextColor color) {
Preconditions.checkNotNull(color, "color cannot be null");
rgb = color.rgb;
legacyColor = color.legacyColor;
hexCode = color.hexCode;
legacyColorForced = color.legacyColorForced;
}
/**
* 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}
*/
public TextColor(String hexCode) {
Preconditions.checkNotNull(hexCode, "hex code cannot be null");
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}
*/
public TextColor(String hexCode, EnumChatFormat legacyColor) {
Preconditions.checkNotNull(hexCode, "hex code cannot be null");
Preconditions.checkNotNull(legacyColor, "legacy color cannot be null");
this.hexCode = hexCode;
this.legacyColorForced = true;
this.legacyColor = legacyColor;
}
/**
* Constructs new instance from provided legacy color
* @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 cannot be null");
this.rgb = (legacyColor.getRed() << 16) + (legacyColor.getGreen() << 8) + legacyColor.getBlue();
this.hexCode = legacyColor.getHexCode();
}
/**
* 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})
*/
public TextColor(int red, int green, int blue) {
Preconditions.checkRange(red, 0, 255, "red color");
Preconditions.checkRange(green, 0, 255, "green color");
Preconditions.checkRange(blue, 0, 255, "blue color");
this.rgb = (red << 16) + (green << 8) + blue;
}
/**
* Loads the closest legacy color based currently provided values
*/
private EnumChatFormat loadClosestColor() {
double minMaxDist = 9999;
double maxDist;
EnumChatFormat closestColor = EnumChatFormat.WHITE;
for (EnumChatFormat color : EnumChatFormat.values()) {
int rDiff = Math.abs(color.getRed() - getRed());
int gDiff = Math.abs(color.getGreen() - getGreen());
int bDiff = Math.abs(color.getBlue() - getBlue());
maxDist = rDiff;
if (gDiff > maxDist) maxDist = gDiff;
if (bDiff > maxDist) maxDist = bDiff;
if (maxDist < minMaxDist) {
minMaxDist = maxDist;
closestColor = color;
}
}
return closestColor;
}
/**
* Returns {@code red} value
* @return red value
*/
public int getRed() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
return (rgb >> 16) & 0xFF;
}
/**
* Returns {@code green} value
* @return green value
*/
public int getGreen() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
return (rgb >> 8) & 0xFF;
}
/**
* Returns {@code blue} value
* @return blue value
*/
public int getBlue() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
return rgb & 0xFF;
}
/**
* 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
*/
public EnumChatFormat getLegacyColor() {
if (legacyColor == null) legacyColor = loadClosestColor();
return legacyColor;
}
/**
* Returns 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);
return hexCode;
}
/**
* 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
*/
public String toString() {
EnumChatFormat legacyEquivalent = EnumChatFormat.fromRGBExact(getRed(), getGreen(), getBlue());
if (legacyEquivalent != null) {
//not sending old colors as RGB to 1.16 clients if not needed as <1.16 servers will fail to apply color
return legacyEquivalent.toString().toLowerCase();
}
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
*/
public static TextColor fromString(String string) {
if (string == null) return null;
if (string.startsWith("#")) return new TextColor(string.substring(1));
return new TextColor(EnumChatFormat.valueOf(string.toUpperCase()));
}
}
@@ -0,0 +1,183 @@
package me.neznamy.tab.api.chat.rgb;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.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;
/**
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
*/
public class RGBUtils {
/** Instance of the class */
private static final RGBUtils instance = new RGBUtils();
/** Registered RGB formatters */
private final RGBFormatter[] formats;
/** Registered gradient patterns */
private final GradientPattern[] gradients;
/** TAB's RGB pattern, used to convert text to bukkit format for boss bar */
private final Pattern tabPattern = Pattern.compile("#[0-9a-fA-F]{6}");
/** RGB pattern for legacy codes */
private final Pattern tabPatternLegacy = Pattern.compile("#[0-9a-fA-F]{6}\\|.");
/**
* Constructs new instance and loads all RGB patterns and gradients
*/
public RGBUtils() {
formats = new RGBFormatter[] {
new BukkitFormat(),
new CMIFormat(),
new UnnamedFormat1(),
new HtmlFormat()
};
gradients = new GradientPattern[] {
//{#RRGGBB>}text{#RRGGBB<}
new CMIGradient(),
//<#RRGGBB>Text</#RRGGBB>
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>[^<]*</#[0-9a-fA-F]{6}>"),
Pattern.compile("<#[0-9a-fA-F]{6}\\|.>[^<]*</#[0-9a-fA-F]{6}>"),
"<#", 9, 2, 9, 7),
//<$#RRGGBB>Text<$#RRGGBB>
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>[^<]*<\\$#[0-9a-fA-F]{6}>"),
Pattern.compile("<\\$#[0-9a-fA-F]{6}\\|.>[^<]*<\\$#[0-9a-fA-F]{6}>"),
"<$", 10, 3, 10, 7),
new KyoriGradient()
};
}
/**
* 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
*/
public String applyFormats(String text, boolean ignorePlaceholders) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, ignorePlaceholders);
}
for (RGBFormatter formatter : formats) {
replaced = formatter.reformat(replaced);
}
return replaced;
}
/**
* Converts TAB's RGB format (#RRGGBB) into bukkit one (&x&r&r&g&g&b&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
*/
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);
for (Pattern p : new Pattern[]{tabPatternLegacy, tabPattern}) {
Matcher m = p.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = "&x&" + hexCode.charAt(1) + "&" + hexCode.charAt(2) + "&" + hexCode.charAt(3) + "&" + hexCode.charAt(4) + "&" + hexCode.charAt(5) + "&" + hexCode.charAt(6);
replaced = replaced.replace(hexCode, EnumChatFormat.color(fixed));
}
}
return replaced;
} else {
return convertRGBtoLegacy(text);
}
}
/**
* Converts all hex codes in given string to legacy codes
* @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();
}
/**
* 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
*/
public boolean isHexCode(String string) {
if (string.length() != 6) return false;
for (int i=0; i<6; i++) {
char c = string.charAt(i);
if (c < 48 || (c > 57 && c < 65) || (c > 70 && c < 97) || c > 102) return false;
}
return true;
}
/**
* 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
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
}
}
@@ -0,0 +1,27 @@
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.api.chat.EnumChatFormat;
/**
* Formatter for &x&R&R&G&G&B&B
*/
public class BukkitFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("[" + EnumChatFormat.COLOR_CHAR + "&]x[" + EnumChatFormat.COLOR_CHAR + "&\\p{XDigit}]{12}");
@Override
public String reformat(String text) {
if (!text.contains("&") && !text.contains(EnumChatFormat.COLOR_CHAR + "x")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = new String(new char[] {'#', hexCode.charAt(3), hexCode.charAt(5), hexCode.charAt(7), hexCode.charAt(9), hexCode.charAt(11), hexCode.charAt(13)});
replaced = replaced.replace(hexCode, fixed);
}
return replaced;
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.shared.rgb.format;
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -6,19 +6,19 @@ import java.util.regex.Pattern;
/**
* Formatter for {#RRGGBB}
*/
public class CMIFormat extends RGBFormatter {
public class CMIFormat implements RGBFormatter {
//pattern for {#RRGGBB}
private final Pattern pattern = Pattern.compile("\\{#[0-9a-fA-F]{6}\\}");
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);
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
@@ -1,24 +1,24 @@
package me.neznamy.tab.shared.rgb.format;
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for &x&R&R&G#<RRGGBB>
* Formatter for #<RRGGBB>
*/
public class UnnamedFormat2 extends RGBFormatter {
public class HtmlFormat implements RGBFormatter {
//pattern for #<RRGGBB>
private final Pattern pattern = Pattern.compile("#<[0-9a-fA-F]{6}>");
@Override
public String reformat(String text) {
if (!text.contains("#<")) return text;
Matcher m = pattern.matcher(text);
String replaced = text;
while (m.find()) {
String hexcode = m.group();
String fixed = hexcode.substring(2, 8);
replaced = replaced.replace(hexcode, "#" + fixed);
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
@@ -1,14 +1,14 @@
package me.neznamy.tab.shared.rgb.format;
package me.neznamy.tab.api.chat.rgb.format;
/**
* Abstract class for different RGB patterns
*/
public abstract class RGBFormatter {
public interface RGBFormatter {
/**
* Reformats RGB codes in provided text into #RRGGBB format
* @param text - text to format
* @return reformatted text
*/
public abstract String reformat(String text);
String reformat(String text);
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.chat.rgb.format;
/**
* Formatter for &#RRGGBB
*/
public class UnnamedFormat1 implements RGBFormatter {
@Override
public String reformat(String text) {
return text.contains("&#") ? text.replace("&#", "#") : text;
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CMIGradient extends CommonGradient {
//pattern for {#RRGGBB<>}
private final Pattern shortcutPattern = Pattern.compile("\\{#[0-9a-fA-F]{6}<>}");
public CMIGradient() {
super(Pattern.compile("\\{#[0-9a-fA-F]{6}>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
Pattern.compile("\\{#[0-9a-fA-F]{6}\\|.>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
"{#", 9, 2, 10, 8);
}
@Override
public String applyPattern(String text, boolean ignorePlaceholders) {
String replaced = text;
if (replaced.contains("<>}")) {
Matcher m = shortcutPattern.matcher(replaced);
while (m.find()) {
String format = m.group();
String code = format.substring(2, 8);
replaced = replaced.replace(format, "{#" + code + "<}{#" + code + ">}");
}
}
return super.applyPattern(replaced, ignorePlaceholders);
}
}
@@ -0,0 +1,57 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
public class CommonGradient implements GradientPattern {
private final Pattern pattern;
private final Pattern legacyPattern;
private final String containCheck;
private final int legacyCharPosition;
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;
String replaced = text;
Matcher m = legacyPattern.matcher(replaced);
while (m.find()) {
String format = m.group();
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(legacyCharPosition));
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6), legacyColor);
String message = format.substring(messageStart+2, format.length()-10);
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
}
m = pattern.matcher(replaced);
while (m.find()) {
String format = m.group();
if (ignorePlaceholders && format.contains("%")) continue;
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6));
String message = format.substring(messageStart, format.length()-10);
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
}
return replaced;
}
}
@@ -0,0 +1,51 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
/**
* Abstract class for applying different gradient patterns
*/
public interface GradientPattern {
/**
* Applies gradients in provided text and returns text using only #RRGGBB
* @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
*/
default String asGradient(TextColor start, String text, TextColor end) {
//lazy support for magic codes in gradients
String magicCodes = EnumChatFormat.getLastColors(text);
String deColorized = text.substring(magicCodes.length());
StringBuilder sb = new StringBuilder();
int length = deColorized.length();
if (length == 1) {
sb.append("#");
sb.append(new TextColor(start.getRed(), start.getGreen(), start.getBlue()).getHexCode());
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
sb.append(magicCodes);
sb.append(deColorized);
return sb.toString();
}
for (int i=0; i<length; i++) {
int red = (int) (start.getRed() + (float)(end.getRed() - start.getRed())/(length-1)*i);
int green = (int) (start.getGreen() + (float)(end.getGreen() - start.getGreen())/(length-1)*i);
int blue = (int) (start.getBlue() + (float)(end.getBlue() - start.getBlue())/(length-1)*i);
sb.append("#");
sb.append(new TextColor(red, green, blue).getHexCode());
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
sb.append(magicCodes);
sb.append(deColorized.charAt(i));
}
return sb.toString();
}
}
@@ -1,30 +1,29 @@
package me.neznamy.tab.shared.rgb.gradient;
package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.shared.packets.EnumChatFormat;
import me.neznamy.tab.shared.rgb.TextColor;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
/**
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient>
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient> and <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
*/
public class KyoriGradient extends GradientPattern {
public class KyoriGradient implements GradientPattern {
// <gradient:#RRGGBB:#RRGGBB>Text</gradient>
private final Pattern pattern = Pattern.compile("<gradient:#[0-9a-fA-F]{6}:#[0-9a-fA-F]{6}>[^<]*</gradient>");
// <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
private final Pattern legacy = Pattern.compile("<gradient:#[0-9a-fA-F]{6}\\|.:#[0-9a-fA-F]{6}>[^<]*</gradient>");
private final Pattern patternLegacy = Pattern.compile("<gradient:#[0-9a-fA-F]{6}\\|.:#[0-9a-fA-F]{6}>[^<]*</gradient>");
@Override
public String applyPattern(String text) {
public String applyPattern(String text, boolean ignorePlaceholders) {
if (!text.contains("<grad")) return text;
String replaced = text;
Matcher m = legacy.matcher(replaced);
Matcher m = patternLegacy.matcher(replaced);
while (m.find()) {
String format = m.group();
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(18));
if (legacyColor == null) continue;
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
TextColor start = new TextColor(format.substring(11, 17), legacyColor);
String message = format.substring(28, format.length()-11);
TextColor end = new TextColor(format.substring(21, 27));
@@ -34,6 +33,7 @@ public class KyoriGradient extends GradientPattern {
m = pattern.matcher(replaced);
while (m.find()) {
String format = m.group();
if (ignorePlaceholders && format.contains("%")) continue;
TextColor start = new TextColor(format.substring(11, 17));
String message = format.substring(26, format.length()-11);
TextColor end = new TextColor(format.substring(19, 25));
@@ -0,0 +1,427 @@
package me.neznamy.tab.api.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
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;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.api.TabAPI;
/**
* Abstract class for configuration file
*/
@SuppressWarnings("unchecked")
public abstract class ConfigurationFile {
/** Comments on top of the file */
protected List<String> header;
/** Configuration file content */
protected Map<String, Object> values;
/** File to use */
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
*/
protected ConfigurationFile(InputStream source, File destination) throws IOException {
if (destination == null) throw new IllegalArgumentException("Destination cannot be null");
this.file = destination;
if (file.getParentFile() != null) 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");
}
}
}
detectHeader();
}
/**
* Saves values from map to the file
*/
public abstract void save();
/**
* Returns 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
*/
public Object getObject(String path, Object defaultValue) {
Object value = values;
for (String section : path.contains(".") ? path.split("\\.") : new String[] {path}) {
if (!(value instanceof Map)) {
if (defaultValue != null) set(path, defaultValue);
return defaultValue;
}
value = getIgnoreCase((Map<Object, Object>) value, section);
}
if (value == null && defaultValue != null) {
TabAPI.getInstance().debug("Inserting missing config option \"" + path + "\" with value \"" + defaultValue + "\" into " + file.getName());
set(path, defaultValue);
return defaultValue;
}
return value;
}
/**
* 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
*/
public Object getObject(String path) {
return getObject(path, null);
}
public Object getObject(String[] path) {
Object value = values;
for (String section : path) {
if (!(value instanceof Map)) {
return null;
}
value = getIgnoreCase((Map<Object, Object>) value, section);
}
return value;
}
/**
* 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
*/
private Object getIgnoreCase(Map<Object, Object> map, String key) {
for (Entry<Object, Object> entry : map.entrySet()) {
if (entry.getKey().toString().equalsIgnoreCase(key)) return entry.getValue();
}
return map.get(key);
}
/**
* 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
*/
public String getString(String path) {
return getString(path, null);
}
/**
* Returns config option with specified path as {@code String}. 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 as {@code String}
*/
public String getString(String path, String defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
return String.valueOf(value);
}
/**
* 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
*/
public List<String> getStringList(String path) {
return getStringList(path, null);
}
/**
* Returns config option with specified path as {@code List<String>}. 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 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);
if (value == null) return defaultValue;
if (!(value instanceof List)) {
return new ArrayList<>();
}
List<String> fixedList = new ArrayList<>();
for (Object key : (List<Object>)value) {
fixedList.add(key.toString());
}
return fixedList;
}
/**
* 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
*/
public Integer getInt(String path) {
return getInt(path, null);
}
/**
* Returns config option with specified path as {@code Integer}. 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 as {@code Integer}
*/
public Integer getInt(String path, Integer defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try{
return Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Returns config option with specified path as {@code Boolean}. 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 as {@code Boolean}
*/
public boolean getBoolean(String path, boolean defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
return Boolean.parseBoolean(value.toString());
}
/**
* Returns config option with specified path as {@code Double}. 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 as {@code Double}
*/
public Double getDouble(String path, double defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try {
return Double.parseDouble(value.toString());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* 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>}
*/
public <K, V> Map<K, V> getConfigurationSection(String path) {
if (path == null || path.length() == 0) return (Map<K, V>) values;
Object value = getObject(path, null);
if (value instanceof Map) {
return (Map<K, V>) value;
} else {
return new LinkedHashMap<>();
}
}
/**
* 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
*/
public boolean hasConfigOption(String path) {
return getObject(path) != null;
}
/**
* 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
*/
public void set(String path, Object value) {
set(values, path, value);
save();
}
/**
* 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
*/
private Map<String, Object> set(Map<String, Object> map, String path, Object value) {
if (path.contains(".")) {
String keyWord = getRealKey(map, path.split("\\.")[0]);
Object subMap = map.get(keyWord);
if (!(subMap instanceof Map)) {
subMap = new LinkedHashMap<>();
}
map.put(keyWord, set((Map<String, Object>) subMap, path.substring(keyWord.length()+1), value));
} else {
if (value == null) {
map.remove(getRealKey(map, path));
} else {
map.put(path, value);
}
}
return map;
}
/**
* 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
*/
private String getRealKey(Map<?, ?> map, String key) {
for (Object mapKey : map.keySet()) {
if (mapKey.toString().equalsIgnoreCase(key)) return mapKey.toString();
}
return key;
}
/**
* Detects header of a file (first lines of file starting with #).
*
* @throws IOException
* if I/O operation fails
*/
private void detectHeader() throws IOException {
header = new ArrayList<>();
for (String line : Files.readAllLines(file.toPath())) {
if (line.startsWith("#")) {
header.add(line);
} else {
break;
}
}
}
/**
* Inserts header back into file. This is required after calling {@link #save()}, because
* it destroys the header.
*
* @throws IOException
* if I/O operation fails
*/
public void fixHeader() throws IOException {
if (header == null) return;
List<String> content = new ArrayList<>(header);
content.addAll(Files.readAllLines(file.toPath()));
Files.delete(file.toPath());
if (file.createNewFile()) {
Files.write(file.toPath(), content);
}
}
public File getFile() {
return file;
}
}
@@ -0,0 +1,87 @@
package me.neznamy.tab.api.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.yamlassist.YamlAssist;
/**
* YAML implementation of ConfigurationFile
*/
public class YamlConfigurationFile extends ConfigurationFile {
/** SnakeYAML instance */
private static final Yaml yaml;
static {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yaml = new Yaml(options);
}
/**
* 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
*/
public YamlConfigurationFile(InputStream source, File destination) throws YAMLException, IOException {
super(source, destination);
FileInputStream input = null;
try {
input = new FileInputStream(file);
values = yaml.load(input);
if (values == null) values = new LinkedHashMap<>();
input.close();
} catch (YAMLException e) {
if (input != null) input.close();
TabAPI tab = TabAPI.getInstance();
tab.sendConsoleMessage("&c[TAB] File " + destination + " has broken syntax.", true);
tab.sendConsoleMessage("&6[TAB] Error message from yaml parser: " + e.getMessage(), true);
List<String> suggestions = YamlAssist.getSuggestions(file);
if (!suggestions.isEmpty()) {
tab.sendConsoleMessage("&d[TAB] Suggestions to fix yaml syntax:", true);
for (String suggestion : suggestions) {
tab.sendConsoleMessage("&d[TAB] - " + suggestion, true);
}
}
throw e;
}
}
@Override
public void save() {
try {
Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
yaml.dump(values, writer);
writer.close();
fixHeader();
} catch (IOException e) {
TabAPI.getInstance().sendConsoleMessage("&c[TAB] Failed to save yaml file " + file.getPath() + " with content " + values.toString(), true);
}
}
}
@@ -0,0 +1,53 @@
package me.neznamy.tab.api.event;
/**
* The event bus. This is used to register event listeners/handlers for
* handling events that may be fired by TAB.
*/
public interface EventBus {
/**
* Scans the given listener for any method that is annotated with
* {@link Subscribe} and registers it as a listener to this event bus.
*
* All methods in this class that are annotated with {@link Subscribe}
* must meet the following criteria:
* <ul>
* <li>It must be <strong>public</strong>.</li>
* <li>It must <strong>not</strong> be abstract.</li>
* <li>It must have exactly <strong>one</strong> parameter.</li>
* <li>Its single parameter must be <strong>an event</strong>.</li>
* </ul>
* If a method does not meet the above criteria, an exception <strong>will</strong>
* be thrown.
*
* @param listener the listener to register
* @throws RuntimeException if a method does not meet the criteria
*/
void register(Object listener);
/**
* Registers the given handler to this event bus.
*
* @param type the class type of the event
* @param handler the handler to register
* @param <E> the type of the event
*/
<E extends TabEvent> void register(Class<E> type, EventHandler<E> handler);
/**
* Scans the given listener for any method that is annotated with
* {@link Subscribe} and unregisters it from this event bus.
*
* @param listener the listener to unregister
*/
void unregister(Object listener);
/**
* Unregisters the given handler from this event bus.
*
* @param handler the handler to unregister
* @param <E> the type of the event
*/
<E extends TabEvent> void unregister(EventHandler<E> handler);
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.event;
/**
* A handler for an event. This allows listening for events in a more
* functional style.
*
* @param <E> the type of event this handler handles
*/
@FunctionalInterface
public interface EventHandler<E> {
/**
* Called when the given event is fired from an event bus.
*
* @param event the event to handle
*/
void handle(E event);
}
@@ -0,0 +1,14 @@
package me.neznamy.tab.api.event;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Subscribes to a {@link TabEvent}.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
}
@@ -0,0 +1,10 @@
package me.neznamy.tab.api.event;
/**
* An event that may be fired by TAB when something occurs.
*
* This is a marker interface. All events <strong>must</strong> implement this
* interface.
*/
public interface TabEvent {
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.event.player;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.TabEvent;
/**
* Called when the player has been fully loaded by TAB.
*/
public interface PlayerLoadEvent extends TabEvent {
/**
* Gets 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
*/
boolean isJoin();
}
@@ -0,0 +1,11 @@
package me.neznamy.tab.api.event.plugin;
import me.neznamy.tab.api.event.TabEvent;
import me.neznamy.tab.api.placeholder.Placeholder;
public interface PlaceholderRegisterEvent extends TabEvent {
String getIdentifier();
void setPlaceholder(Placeholder placeholder);
}
@@ -0,0 +1,9 @@
package me.neznamy.tab.api.event.plugin;
import me.neznamy.tab.api.event.TabEvent;
/**
* Called when TAB is fully loaded.
*/
public interface TabLoadEvent extends TabEvent {
}
@@ -0,0 +1,16 @@
package me.neznamy.tab.api.placeholder;
public interface Placeholder {
int getRefresh();
String getIdentifier();
boolean isTriggerMode();
void enableTriggerMode();
void enableTriggerMode(Runnable onActivation, Runnable onDisable);
void unload();
}
@@ -0,0 +1,46 @@
package me.neznamy.tab.api.placeholder;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
public interface PlaceholderManager {
/**
* Registers a server placeholder (placeholder with same output for all players)
*/
ServerPlaceholder registerServerPlaceholder(String identifier, int refresh, Supplier<Object> supplier);
/**
* Registers a player placeholder (placeholder with player-specific output)
*/
PlayerPlaceholder registerPlayerPlaceholder(String identifier, int refresh, Function<TabPlayer, Object> function);
/**
* Registers a relational placeholder (different output for each player pair)
*/
RelationalPlaceholder registerRelationalPlaceholder(String identifier, int refresh, BiFunction<TabPlayer, TabPlayer, Object> function);
/**
* 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
*/
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
*/
void addUsedPlaceholder(String identifier, TabFeature feature);
String findReplacement(String placeholder, String output);
List<String> getUsedPlaceholders();
}
@@ -0,0 +1,10 @@
package me.neznamy.tab.api.placeholder;
import me.neznamy.tab.api.TabPlayer;
public interface PlayerPlaceholder extends Placeholder {
void updateValue(TabPlayer player, Object value);
Object request(TabPlayer p);
}
@@ -0,0 +1,10 @@
package me.neznamy.tab.api.placeholder;
import me.neznamy.tab.api.TabPlayer;
public interface RelationalPlaceholder extends Placeholder {
void updateValue(TabPlayer viewer, TabPlayer target, Object value);
Object request(TabPlayer viewer, TabPlayer target);
}
@@ -0,0 +1,8 @@
package me.neznamy.tab.api.placeholder;
public interface ServerPlaceholder extends Placeholder {
void updateValue(Object value);
Object request();
}
@@ -0,0 +1,240 @@
package me.neznamy.tab.api.protocol;
import java.util.HashMap;
import java.util.Map;
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;
/**
* An interface representing a platform-specific packet builder
*/
public abstract class PacketBuilder {
/** Function map turning custom packet class into platform-specific packets */
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
/**
* Constructs new instance and fills {@link #buildMap} with abstract build methods
* of packets present on all platforms.
*/
protected PacketBuilder() {
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
}
/**
* 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
*/
public Object build(TabPacket packet, 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
*/
public abstract Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Cuts given string to specified character length (or length-1 if last character is a color character)
* 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 <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
*/
public String cutTo(String string, int length) {
if (string == null) return "";
String legacyText = string;
if (string.contains("#")) {
//converting RGB to legacy colors
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
}
if (legacyText.length() <= length) return legacyText;
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
} else {
return legacyText.substring(0, length);
}
}
/**
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is < 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-
*/
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return cutTo(text, length);
}
}
/**
* 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
*/
public abstract PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* 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
*/
public abstract PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException;
/**
* 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
*/
public abstract PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException;
}
@@ -0,0 +1,280 @@
package me.neznamy.tab.api.protocol;
import java.util.UUID;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private final Action operation;
/** BossBar title */
private String name;
/** BossBar progress (0-1)*/
private float pct;
/** BossBar color */
private BarColor color;
/** BossBar style */
private BarStyle overlay;
/** Darken screen flag */
private boolean darkenScreen;
/** Play music flag */
private boolean playMusic;
/** Create fog flag */
private boolean createWorldFog;
/**
* 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
*/
public PacketPlayOutBoss(UUID id, String name, float pct, BarColor color, BarStyle overlay) {
this.operation = Action.ADD;
this.id = id;
this.name = name;
this.pct = pct;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.REMOVE action
*
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(UUID id) {
this.operation = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PCT action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(UUID id, float pct) {
this.operation = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_NAME action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(UUID id, String name) {
this.operation = Action.UPDATE_NAME;
this.id = id;
this.name = name;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_STYLE action
*
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, BarColor color, BarStyle overlay) {
this.operation = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@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
*/
public PacketPlayOutBoss(UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.operation = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
this.playMusic = playMusic;
this.createWorldFog = createWorldFog;
}
/**
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
* <p>
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
* to the final value.
*
* @return the bitmask
*/
public byte getFlags(){
byte value = 0;
if (darkenScreen) value += 1;
if (playMusic) value += 2;
if (createWorldFog) value += 4;
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, operation, 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 #operation}
* @return packet action
*/
public Action getOperation() {
return operation;
}
/**
* 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.
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}
@@ -0,0 +1,60 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
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) {
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;
}
/**
* An enum representing positions of a chat message
* Calling ordinal() will return type's network ID.
*/
public enum ChatMessageType {
CHAT,
SYSTEM,
GAME_INFO
}
}
@@ -0,0 +1,305 @@
package me.neznamy.tab.api.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet action */
private final EnumPlayerInfoAction action;
/** List of affected entries */
private final List<PlayerInfoData> entries;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
this.action = action;
this.entries = Arrays.asList(entries);
}
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
this.action = action;
this.entries = entries;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerInfo{action=%s,entries=%s}", action, entries);
}
/**
* Returns {@link #action}
* @return packet action
*/
public EnumPlayerInfoAction getAction() {
return action;
}
/**
* Returns {@link #entries}
* @return affected entries
*/
public List<PlayerInfoData> getEntries() {
return entries;
}
/**
* A subclass representing player list entry
*/
public static class PlayerInfoData {
/** Latency */
private int latency;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
private IChatBaseComponent displayName;
/** Real name of affected player */
private String name;
/** Player UUID */
private UUID uniqueId;
/** platform-specific skin object */
private Object skin;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.ADD_PLAYER 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
*/
public PlayerInfoData(String name, UUID uniqueId, Object skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName) {
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) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_LATENCY action
*
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
*/
public PlayerInfoData(UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_DISPLAY_NAME action
*
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
*/
public PlayerInfoData(UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction}.REMOVE_PLAYER action
*
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(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 Object getSkin() {
return skin;
}
/**
* Sets {@link #skin} to specified value
* @param skin
* Skin to use
*/
public void setSkin(Object skin) {
this.skin = skin;
}
}
/**
* En enum representing packet action
* Calling ordinal() will return action's network ID.
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
UPDATE_GAME_MODE,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
}
/**
* An enum representing GameMode
*/
public enum EnumGamemode {
NOT_SET,
SURVIVAL,
CREATIVE,
ADVENTURE,
SPECTATOR
}
}
@@ -0,0 +1,63 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
private final IChatBaseComponent header;
/** TabList footer */
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
*/
public PacketPlayOutPlayerListHeaderFooter(String header, 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) {
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;
}
}
@@ -0,0 +1,55 @@
package me.neznamy.tab.api.protocol;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
* Display slot.
* 0 = PlayerList,
* 1 = SideBar,
* 2 = BelowName.
*/
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) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
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;
}
}
@@ -0,0 +1,109 @@
package me.neznamy.tab.api.protocol;
/**
* A class representing platform specific packet class
*/
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,
* 1 = unregister,
* 2 = update title
*/
private final int method;
/**
* Constructs new instance with given parameters.
*
* @param method
* 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 method, String objectiveName, String displayName, EnumScoreboardHealthDisplay renderType) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
this.objectiveName = objectiveName;
this.displayName = displayName;
this.renderType = renderType;
this.method = method;
}
/**
* 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
*/
public PacketPlayOutScoreboardObjective(String objectiveName) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
this.method = 1;
this.renderType = null;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardObjective{objectiveName=%s,displayName=%s,renderType=%s,method=%s}",
objectiveName, displayName, renderType, method);
}
/**
* 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 #method}
* @return packet action
*/
public int getMethod() {
return method;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
*/
public enum EnumScoreboardHealthDisplay {
INTEGER, HEARTS
}
}
@@ -0,0 +1,89 @@
package me.neznamy.tab.api.protocol;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
private final Action action;
/** Objective name */
private final String objectiveName;
/** Affected player */
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) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
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
*/
public enum Action {
CHANGE,
REMOVE
}
}
@@ -0,0 +1,264 @@
package me.neznamy.tab.api.protocol;
import java.util.Collection;
import java.util.Collections;
import me.neznamy.tab.api.chat.EnumChatFormat;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Up to 16 characters long team name.
* It is used to sort players alphabetically in TabList by team names.
* Team-less players appear on top, sorted by usernames.
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
private final String name;
/** Team prefix, limited to 16 characters, on <1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on <1.13 */
private String playerSuffix;
/**
* NameTag visibility rule since 1.8. Possible options are:
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
*/
private String nameTagVisibility;
/**
* Collision rule added in 1.9. Possible options are:
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
*/
private String collisionRule;
/**
* Team color field added in 1.13. It determines name color,
* prefix and suffix color start.
*/
private EnumChatFormat color;
/**
* 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.
* Entity UUID option was added in 1.8.
*/
private Collection<String> players = Collections.emptyList();
/**
* Packet action.
* 0 = create team,
* 1 = remove team,
* 2 = update team info,
* 3 = add entries,
* 4 = remove entries
*/
private final int method;
/**
* Bit mask.
* 0x01: Allow friendly fire,
* 0x02: Can see invisible players on the same team.
*/
private int options;
/**
* Constructs new instance with given parameters. Private constructor used
* internally to validate team name and set action.
*
* @param method
* 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 method, String name) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Team name cannot be null/empty");
if (name.length() > 16) throw new IllegalArgumentException("name cannot be longer than 16 character (is " + name.length() + ")");
this.method = method;
this.name = name;
}
/**
* 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
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
this(0, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.players = players;
this.options = options;
}
/**
* 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
*/
public PacketPlayOutScoreboardTeam(String team) {
this(1, team);
}
/**
* 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
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, int options) {
this(2, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.options = options;
}
/**
* 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
*/
public PacketPlayOutScoreboardTeam(String team, Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
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, method, 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 #method}
* @return packet action
*/
public int getMethod() {
return method;
}
}
@@ -0,0 +1,16 @@
package me.neznamy.tab.api.protocol;
/**
* Interface representing a packet that can be sent to client independently of platform.
* It is technically empty, existence is only present to restrict type access
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
* be called with an actual packet.
*/
public interface TabPacket {
/**
* Returns user-friendly string representation of the packet
* @return user-friendly string representation of the packet
*/
String toString();
}
@@ -0,0 +1,29 @@
package me.neznamy.tab.api.scoreboard;
/**
* An interface allowing to work with a line of
* text in scoreboard.
*
*/
public interface Line {
/**
* Returns configured raw text of this line. Placeholders
* remain in raw format.
*
* @return raw text of this line
*/
String getText();
/**
* Changes text to new value. Supports placeholders, which are
* automatically registered if needed and refreshed periodically
* based on configuration. No need to call this method to try to
* keep placeholders up to date. If value is identical to previous
* one, nothing happens.
*
* @param text
* Raw text to display in the line
*/
void setText(String text);
}
@@ -0,0 +1,87 @@
package me.neznamy.tab.api.scoreboard;
import java.util.List;
/**
* An interface allowing to work with scoreboard, such as adding players,
* removing players and changing lines. New instance can be created using
* {@link ScoreboardManager#createScoreboard(String, String, List)},
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
*/
public interface Scoreboard {
/**
* Returns internal name of this scoreboard. Name is defined in registration,
* scoreboards from config use name they were defined with. This value is
* used internally and in /tab announce scoreboard command.
*
* @return custom name of this scoreboard
*/
String getName();
/**
* Returns raw title of this scoreboard. Placeholders stay in their
* raw format.
*
* @return scoreboard title
* @see #setTitle(String)
*/
String getTitle();
/**
* Sets title to specified value. Placeholders are refreshed automatically
* with refresh intervals defined in config. No need to call this method
* to try to keep placeholders up to date. Supports RGB codes using any of
* the supported formats.
* <p>
* Length is limited to 32 characters on <1.13. If the limit is exceeded,
* text will be cut to 32 characters.
* <p>
* Calling this method with same title as before will not do anything.
*
* @param title
* New title to use with placeholder support
* @see #getTitle()
*/
void setTitle(String title);
/**
* Returns list of lines in this scoreboard in the order they appear
* 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)
*/
List<Line> getLines();
/**
* 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)
*/
void addLine(String text);
/**
* 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 < 0 || index >= {@link #getLines()}.size())
* @see #getLines()
* @see #addLine(String)
*/
void removeLine(int index);
/**
* Unregisters this scoreboard from all players who can see it.
*/
void unregister();
}
@@ -0,0 +1,165 @@
package me.neznamy.tab.api.scoreboard;
import java.util.List;
import java.util.Map;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface allowing work with scoreboards, such as creating,
* sending, toggling and modifying.
* <p>
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getScoreboardManager()}.
* This requires the scoreboard feature to be enabled in config,
* the method will return null otherwise.
*/
public interface ScoreboardManager {
/**
* Creates new scoreboard, registers and returns it. {@code name}
* is internal name of the scoreboard, which it will be registered
* with and further work where scoreboard name is used allowed.
* <p>
* Scoreboard {@code title} is limited to 32 characters on <1.13. If needed,
* it will be cut to 32 character for those players (after replacing
* placeholders). RGB is supported using any of the supported formats.
* Placeholders are supported.
* <p>
* {@code lines} will display in the same order as contained in the list.
* 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}
*/
Scoreboard createScoreboard(String name, String title, List<String> lines);
/**
* Returns map of registered scoreboards via config and API.
* Map key is name of scoreboard, which is defined either in config
* or using {@link #createScoreboard(String, String, List)}.
* Map value is reference to the actual scoreboard.
*
* @return map of registered scoreboards
*/
Map<String, Scoreboard> getRegisteredScoreboards();
/**
* Displays scoreboard to defined player. This will disable all display
* condition checks until the scoreboard is unregistered using
* {@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)
*/
void showScoreboard(TabPlayer player, Scoreboard scoreboard);
/**
* 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)
*/
boolean hasCustomScoreboard(TabPlayer player);
/**
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
* 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)
*/
void resetScoreboard(TabPlayer player);
/**
* Returns {@code true} if player has scoreboard enabled, {@code false}
* if disabled (toggled) using either toggle command,
* {@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)
*/
boolean hasScoreboardVisible(TabPlayer player);
/**
* Sets scoreboard visibility of player to defined value. If visibility status
* is same as player had before, nothing happens. If this call changes
* 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)
*/
void setScoreboardVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Toggles scoreboard for specified player. If player had scoreboard visible,
* 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)
*/
void toggleScoreboard(TabPlayer player, boolean sendToggleMessage);
/**
* Temporarily displays scoreboard to all players for specified amount of
* 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 < 0.
*/
void announceScoreboard(String scoreboard, int duration);
/**
* 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
*/
Scoreboard getActiveScoreboard(TabPlayer player);
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.task;
public interface RepeatingTask {
int getInterval();
void setInterval(int paramInt);
void cancel();
void setRunnable(Runnable paramRunnable);
}
@@ -0,0 +1,56 @@
package me.neznamy.tab.api.task;
import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
public interface ThreadManager {
/**
* Starts a task in new thread and measures how long it took to process
* @param errorDescription - description to use if this task throws an error
* @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 errorDescription, TabFeature feature, String type, Runnable task);
/**
* Starts a task in new thread and measures how long it took to process
* @param errorDescription - description to use if this task throws an error
* @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 errorDescription, String feature, String type, Runnable task);
/**
* Runs task in a new thread
* @param errorDescription - description to use if this task throws an error
* @param task - the task
*/
Future<Void> runTask(String errorDescription, Runnable task);
/**
* Starts a new task with defined repeat interval that measures cpu usage
* @param intervalMilliseconds - task interval
* @param errorDescription - description to use if this task throws an error
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*/
RepeatingTask startRepeatingMeasuredTask(int intervalMilliseconds, String errorDescription, TabFeature feature, String type, 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 errorDescription - description to use if this task throws an error
* @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
*/
Future<?> runTaskLater(int delayMilliseconds, String errorDescription, TabFeature feature, String type, Runnable task);
Future<?> runTaskLater(int delayMilliseconds, String errorDescription, Runnable task);
}
@@ -0,0 +1,113 @@
package me.neznamy.tab.api.team;
import me.neznamy.tab.api.TabPlayer;
public interface TeamManager {
/**
* Makes player's NameTag globally invisible
* @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
*/
void hideNametag(TabPlayer player, TabPlayer viewer);
/**
* Makes player's NameTag visible again
* @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
*/
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)
*/
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
*/
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.
*/
void pauseTeamHandling(TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*/
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
*/
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
*/
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
*/
String getForcedTeamName(TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
* @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
*/
Boolean getCollisionRule(TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
* @param player - player to update team data of
*/
void updateTeamData(TabPlayer player);
void setPrefix(TabPlayer player, String prefix);
void setSuffix(TabPlayer player, String suffix);
void resetPrefix(TabPlayer player);
void resetSuffix(TabPlayer player);
String getCustomPrefix(TabPlayer player);
String getCustomSuffix(TabPlayer player);
String getOriginalPrefix(TabPlayer player);
String getOriginalSuffix(TabPlayer player);
}
@@ -0,0 +1,32 @@
package me.neznamy.tab.api.team;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
public interface UnlimitedNametagManager extends TeamManager {
void disableArmorStands(TabPlayer player);
void enableArmorStands(TabPlayer player);
boolean hasDisabledArmorStands(TabPlayer player);
void setName(TabPlayer player, String customName);
void setLine(TabPlayer player, String line, String value);
void resetName(TabPlayer player);
void resetLine(TabPlayer player, String line);
String getCustomName(TabPlayer player);
String getCustomLineValue(TabPlayer player, String line);
String getOriginalName(TabPlayer player);
String getOriginalLineValue(TabPlayer player, String line);
List<String> getDefinedLines();
}
@@ -0,0 +1,18 @@
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
* 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
*/
public interface BiFunctionWithException<A, B, C> {
C apply(A a, B b) throws ReflectiveOperationException;
}
@@ -0,0 +1,13 @@
package me.neznamy.tab.api.util;
public class Preconditions {
public static void checkNotNull(Object obj, String exceptionMessage){
if (obj == null) throw new IllegalArgumentException(exceptionMessage);
}
public static void checkRange(Number number, Number min, Number max, String variable){
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
throw new IllegalArgumentException("Number index out of range (" + min + " - " + max + ") for " + variable);
}
}
+12
View File
@@ -0,0 +1,12 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
dependencies {
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.0")
}
@@ -0,0 +1,30 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins {
`java-library`
}
tasks {
processResources {
filter<ReplaceTokens>("tokens" to mapOf(
"name" to rootProject.name,
"version" to project.version,
"description" to project.description
))
}
javadoc {
options.encoding = Charsets.UTF_8.name()
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
}
compileJava {
options.encoding = Charsets.UTF_8.name()
options.release.set(8)
options.compilerArgs.addAll(listOf("-nowarn", "-Xlint:-unchecked", "-Xlint:-deprecation"))
}
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
Whitespace-only changes.
@@ -0,0 +1,13 @@
plugins {
id("tab.standard-conventions")
id("com.github.johnrengelman.shadow")
}
tasks {
shadowJar {
archiveFileName.set("TAB-${project.name}-${project.version}.jar")
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
}
}
@@ -0,0 +1,31 @@
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`
}
java {
withSourcesJar()
withJavadocJar()
}
publishing {
repositories {
maven {
name = "krypton"
url = uri("https://repo.kryptonmc.org/releases")
credentials(PasswordCredentials::class)
}
}
publications.create<MavenPublication>("mavenJava") {
groupId = rootProject.group as String
artifactId = "tab-${project.name}"
version = rootProject.version as String
from(components["java"])
}
}
+29
View File
@@ -0,0 +1,29 @@
plugins {
id("tab.parent")
}
allprojects {
group = "me.neznamy"
version = property("projectVersion") as String // from gradle.properties
description = "An all-in-one solution that works"
}
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.krypton,
projects.velocity
).map { it.dependencyProject }
val special = setOf(
projects.api,
projects.shared
).map { it.dependencyProject }
subprojects {
when (this) {
in platforms -> plugins.apply("tab.platform-conventions")
in special -> plugins.apply("tab.standard-conventions")
else -> plugins.apply("tab.base-conventions")
}
}
+11
View File
@@ -0,0 +1,11 @@
dependencies {
implementation(projects.shared)
implementation(libs.bstats.bukkit)
compileOnly(libs.bukkit)
compileOnly(libs.placeholderapi)
compileOnly(libs.vault)
compileOnly(libs.via)
compileOnly(libs.authlib)
compileOnly(libs.libsDisguises)
compileOnly(libs.essentials)
}
@@ -3,13 +3,10 @@ package me.neznamy.tab.platforms.bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import me.neznamy.tab.shared.TAB;
@@ -18,6 +15,12 @@ import me.neznamy.tab.shared.TAB;
*/
public class BukkitEventListener implements Listener {
private final Main main;
public BukkitEventListener(Main main) {
this.main = main;
}
/**
* Listener to PlayerQuitEvent to remove player data and forward the event to features
* @param e quit event
@@ -35,7 +38,7 @@ public class BukkitEventListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent e) {
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask("processing PlayerJoinEvent", () -> TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer())));
TAB.getInstance().getCPUManager().runTask("processing PlayerJoinEvent", () -> TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), main.getProtocolVersion(e.getPlayer()))));
}
/**
@@ -57,34 +60,4 @@ public class BukkitEventListener implements Listener {
if (TAB.getInstance().isDisabled()) return;
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
}
/**
* Listener to AsyncPlayerChatEvent to forward the event to features
* @param e chat event
*/
@EventHandler
public void onChat(AsyncPlayerChatEvent e) {
if (TAB.getInstance().isDisabled()) return;
if (TAB.getInstance().getFeatureManager().onChat(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage(), e.isCancelled())) e.setCancelled(true);
}
/**
* Listener to PlayerRespawnEvent to forward the event to features
* @param e respawn event
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask("processing PlayerRespawnEvent", () -> TAB.getInstance().getFeatureManager().onRespawn(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
* Listener to PlayerToggleSneakEvent to forward the event to features
* @param e sneak event
*/
@EventHandler
public void onSneak(PlayerToggleSneakEvent e) {
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask("processing PlayerToggleSneakEvent", () -> TAB.getInstance().getFeatureManager().onSneak(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.isSneaking()));
}
}
@@ -0,0 +1,558 @@
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 org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
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.PacketBuilder;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
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.api.protocol.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
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;
@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));
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
//1.9+ server or client, handled by bukkit api or ViaVersion
return packet;
}
//<1.9 client and server
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().putAll((PropertyMap) data.getSkin());
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;
}
@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.getMethod());
}
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.getMethod());
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.getMethod()) {
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.getMethod());
}
}
return nms.newPacketPlayOutScoreboardTeam.newInstance(team, packet.getMethod());
}
/**
* 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);
PropertyMap map = new PropertyMap();
map.putAll(profile.getProperties());
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), map, (int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName));
}
return new PacketPlayOutPlayerInfo(action, listData);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardObjective(nms.PacketPlayOutScoreboardObjective_METHOD.getInt(nmsPacket),
(String) nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.get(nmsPacket), null, null
);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardDisplayObjective(
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.getInt(nmsPacket),
(String) nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.get(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
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (packet.getOperation() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getOperation() == Action.REMOVE) {
return build(new PacketPlayOutEntityDestroy(entityId));
}
DataWatcher w = new DataWatcher();
if (packet.getOperation() == Action.UPDATE_PCT || packet.getOperation() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.helper().setHealth(health);
}
if (packet.getOperation() == Action.UPDATE_NAME || packet.getOperation() == Action.ADD) {
w.helper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getOperation() == Action.ADD) {
w.helper().setEntityFlags((byte) 32);
return build(new PacketPlayOutSpawnEntityLiving(entityId, null, EntityType.WITHER, new Location(null, 0,0,0), w));
} else {
return build(new PacketPlayOutEntityMetadata(entityId, w));
}
}
/**
* 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));
}
}
@@ -0,0 +1,128 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Collection;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
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;
import me.neznamy.tab.shared.features.NickCompatibility;
import me.neznamy.tab.shared.features.PipelineInjector;
import org.jetbrains.annotations.NotNull;
public class BukkitPipelineInjector extends PipelineInjector {
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
/**
* Constructs new instance
*/
public BukkitPipelineInjector(){
super("packet_handler");
channelFunction = BukkitChannelDuplexHandler::new;
}
/**
* Custom channel duplex handler override
*/
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
//injected player
private final TabPlayer player;
/**
* Constructs new instance with given player
* @param player - player to inject
*/
public BukkitChannelDuplexHandler(TabPlayer 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){
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (nms.PacketPlayOutPlayerInfo.isInstance(packet)) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam != null && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
modifyPlayers(packet);
super.write(context, packet, channelPromise);
return;
}
if (nms.PacketPlayOutScoreboardDisplayObjective.isInstance(packet)){
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
}
if (nms.PacketPlayOutScoreboardObjective.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Exception e){
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
/**
* 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
*/
@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);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(p.getTeamName())) {
logTeamOverride(teamName, p.getName(), p.getTeamName());
} else {
newList.add(entry);
}
}
nms.setField(packetPlayOutScoreboardTeam, nms.PacketPlayOutScoreboardTeam_PLAYERS, newList);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
}
private TabPlayer getPlayer(String name) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (getName(p).equals(name)) return p;
}
return null;
}
private String getName(TabPlayer p) {
return ((NickCompatibility) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.NICK_COMPATIBILITY)).getNickname(p);
}
}
}
@@ -0,0 +1,209 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import com.earth2me.essentials.Essentials;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.placeholders.PlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
/**
* Bukkit registry to register bukkit-only placeholders
*/
public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
//formatter for 2 decimal places
public final DecimalFormat decimal2 = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.US));
private final Plugin plugin;
private Object chat;
private final Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
private Object server;
private Field recentTps;
private boolean paperTps;
private boolean paperMspt;
private boolean purpur;
private Method playerIsAfk;
private Listener healthListener = null;
/**
* Constructs new instance with given parameter
* @param plugin - plugin instance
*/
public BukkitPlaceholderRegistry(Plugin plugin) {
this.plugin = plugin;
decimal2.applyPattern("#.##");
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
}
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = server.getClass().getField("recentTps");
} 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");
purpur = true;
} catch (NoSuchMethodException e) {
//not purpur
}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(PlaceholderManager manager) {
NumberFormat roundDown = NumberFormat.getNumberInstance(Locale.ENGLISH);
roundDown.setRoundingMode(RoundingMode.DOWN);
roundDown.setMaximumFractionDigits(2);
manager.registerPlayerPlaceholder("%displayname%", 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps) {
manager.registerServerPlaceholder("%tps%", 1000, () -> formatTPS(Bukkit.getTPS()[0]));
} else if (recentTps != null) {
manager.registerServerPlaceholder("%tps%", 1000, () -> {
try {
return formatTPS(((double[]) recentTps.get(server))[0]);
} catch (IllegalAccessException e) {
return "-1";
}
});
} else {
manager.registerServerPlaceholder("%tps%", -1, () -> "-1").enableTriggerMode();
}
if (paperMspt) {
manager.registerServerPlaceholder("%mspt%", 1000, () -> roundDown.format(Bukkit.getAverageTickTime()));
}
manager.registerPlayerPlaceholder("%afk%", 500, p -> {
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
if (!purpur || playerIsAfk == null) return false;
try {
return playerIsAfk.invoke(p.getPlayer());
} catch (final IllegalAccessException | InvocationTargetException exception) {
TAB.getInstance().getErrorManager().printError("Failed to invoke isAfk!", exception);
}
return false;
});
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%", 500, p -> ((Chat) chat).getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 500, p -> ((Chat) chat).getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "").enableTriggerMode();
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "").enableTriggerMode();
}
registerOnlinePlaceholders(manager);
registerHealthPlaceholder(manager);
}
private String formatTPS(double tps) {
return decimal2.format(Math.min(20, tps));
}
private void registerOnlinePlaceholders(PlaceholderManager manager) {
manager.registerPlayerPlaceholder("%online%", 1000, p -> {
int count = 0;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()){
if (((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return count;
});
manager.registerPlayerPlaceholder("%staffonline%", 2000, p -> {
int count = 0;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()){
if (all.hasPermission(TabConstants.Permission.STAFF) && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return count;
});
manager.registerPlayerPlaceholder("%nonstaffonline%", 2000, p -> {
int count = 0;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()){
if (!all.hasPermission(TabConstants.Permission.STAFF) && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return count;
});
}
@SuppressWarnings("deprecation")
private void registerHealthPlaceholder(PlaceholderManager manager) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
PlayerPlaceholder health = manager.registerPlayerPlaceholder("%health%", -1, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
health.enableTriggerMode(() -> {
healthListener = new Listener() {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onDamage(EntityDamageEvent e) {
if (e.getEntity() instanceof Player) {
Player p = (Player) e.getEntity();
TabPlayer tabp = TAB.getInstance().getPlayer(e.getEntity().getUniqueId());
if (tabp == null) return;
health.updateValue(tabp, (int) Math.ceil(Math.max(p.getHealth() - e.getFinalDamage(), 0)));
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onRegain(EntityRegainHealthEvent e) {
if (e.getEntity() instanceof Player) {
Player p = (Player) e.getEntity();
TabPlayer tabp = TAB.getInstance().getPlayer(e.getEntity().getUniqueId());
if (tabp == null) return;
health.updateValue(tabp, (int) Math.ceil(Math.min(p.getHealth() + e.getAmount(), p.getMaxHealth())));
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onRespawn(PlayerRespawnEvent e) {
health.updateValue(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), (int) e.getPlayer().getMaxHealth());
}
};
Bukkit.getPluginManager().registerEvents(healthListener, plugin);
}, () -> HandlerList.unregisterAll(healthListener));
}
}
}
@@ -0,0 +1,274 @@
package me.neznamy.tab.platforms.bukkit;
import java.io.File;
import java.util.Collection;
import java.util.List;
import me.neznamy.tab.shared.TabConstants;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.earth2me.essentials.Essentials;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.protocol.PacketBuilder;
import me.neznamy.tab.platforms.bukkit.event.TabLoadEvent;
import me.neznamy.tab.platforms.bukkit.event.TabPlayerLoadEvent;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.PetFix;
import me.neznamy.tab.platforms.bukkit.features.TabExpansion;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.NameTagX;
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.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.permission.LuckPerms;
import me.neznamy.tab.shared.permission.None;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.permission.UltraPermissions;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.plugin.RegisteredServiceProvider;
/**
* Bukkit's implementation of Platform interface
*/
public class BukkitPlatform implements Platform {
//plugin instance
private final Main plugin;
private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
//booleans to check plugin presence
private boolean placeholderAPI;
private boolean libsDisguises;
private Plugin essentials;
/**
* Constructs new instance with given parameters
* @param plugin - plugin instance
*/
public BukkitPlatform(Main plugin) {
this.plugin = plugin;
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
return new LuckPerms(getPluginVersion("LuckPerms"));
} else if (Bukkit.getPluginManager().isPluginEnabled("UltraPermissions")) {
return new UltraPermissions(getPluginVersion("UltraPermissions"));
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider == null) return new None();
return new Vault(provider.getProvider(), getPluginVersion("Vault"));
} else {
return new None();
}
}
@Override
public void loadFeatures() {
if (Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
try {
Class.forName("com.viaversion.viaversion.api.Via");
} catch (ClassNotFoundException e) {
TAB.getInstance().sendConsoleMessage("&c[TAB] An outdated version of ViaVersion (" + getPluginVersion("ViaVersion") + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&c[TAB] TAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&c[TAB] This 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("&c[TAB] Detected plugin \"Tablisknu\", which causes TAB to not work properly. Consider removing the plugin.", true);
}
placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
libsDisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
essentials = Bukkit.getPluginManager().getPlugin("Essentials");
TAB tab = TAB.getInstance();
if (tab.getConfiguration().isPipelineInjection())
tab.getFeatureManager().registerFeature(TabConstants.Feature.PIPELINE_INJECTION, new BukkitPipelineInjector());
new BukkitPlaceholderRegistry(plugin).registerPlaceholders(tab.getPlaceholderManager());
new UniversalPlaceholderRegistry().registerPlaceholders(tab.getPlaceholderManager());
loadNameTagFeature(tab);
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) {
new TabExpansion(plugin);
}
for (Player p : getOnlinePlayers()) {
tab.addPlayer(new BukkitTabPlayer(p, plugin.getProtocolVersion(p)));
}
}
private String getPluginVersion(String plugin) {
Plugin pl = Bukkit.getPluginManager().getPlugin(plugin);
return pl == null ? "" : pl.getDescription().getVersion();
}
/**
* Loads NameTag feature from config
* @param tab - tab instance
*/
private void loadNameTagFeature(TAB tab) {
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 NameTagX(plugin));
} else {
tab.getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
}
/**
* Returns list of online players from Bukkit API
* @return list of online players from Bukkit API
*/
@SuppressWarnings("unchecked")
private Player[] getOnlinePlayers() {
try {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7.x
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get online players", e);
return new Player[0];
}
}
@Override
public void sendConsoleMessage(String message, boolean translateColors) {
Bukkit.getConsoleSender().sendMessage(translateColors ? EnumChatFormat.color(message) : message);
}
@Override
public void registerUnknownPlaceholder(String identifier) {
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
} else {
//normal placeholder
if (identifier.startsWith("%sync:")) {
int refresh = pl.getServerPlaceholderRefreshIntervals().getOrDefault(identifier,
pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh()));
pl.registerPlaceholder(new PlayerPlaceholderImpl(identifier, refresh, null) {
@Override
public Object request(TabPlayer p) {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
String syncedPlaceholder = identifier.substring(6, identifier.length()-1);
String value = placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), "%" + syncedPlaceholder + "%") : identifier;
getLastValues().put(p, value);
getForceUpdate().add(p);
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
});
String value = getLastValues().get(p);
return value == null ? identifier : value;
}
});
return;
}
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier), () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
} else {
int refresh = pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh());
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
}
}
}
@Override
public String getServerVersion() {
return Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
public File getDataFolder() {
return plugin.getDataFolder();
}
@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)));
}
@Override
public int getMaxPlayers() {
return Bukkit.getMaxPlayers();
}
public boolean isLibsDisguisesEnabled() {
return libsDisguises;
}
public void setLibsDisguisesEnabled(boolean enabled) {
libsDisguises = enabled;
}
public Essentials getEssentials() {
return (Essentials) essentials;
}
@Override
public PacketBuilder getPacketBuilder() {
return packetBuilder;
}
@SuppressWarnings("unchecked")
@Override
public Object getSkin(List<String> properties) {
PropertyMap map = new PropertyMap();
map.put("textures", new Property("textures", properties.get(0), properties.get(1)));
return map;
}
@Override
public boolean isProxy() {
return false;
}
@Override
public boolean isPluginEnabled(String plugin) {
return Bukkit.getPluginManager().isPluginEnabled(plugin);
}
@Override
public String getConfigName() {
return "bukkitconfig.yml";
}
}
@@ -0,0 +1,261 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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.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.ProtocolVersion;
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;
/**
* TabPlayer for Bukkit
*/
public class BukkitTabPlayer extends ITabPlayer {
//nms handle
private Object handle;
//nms player connection
private Object playerConnection;
//player's visible boss bars
private final Map<UUID, BossBar> bossBars = new HashMap<>();
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
/**
* Constructs new instance with given parameter
* @param p - bukkit player
*/
public BukkitTabPlayer(Player p, int protocolVersion){
super(p, p.getUniqueId(), p.getName(), "N/A", p.getWorld().getName());
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);
}
version = ProtocolVersion.fromNetworkId(protocolVersion);
}
@Override
public boolean hasPermission(String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
}
@Override
public int getPing() {
try {
int ping = NMSStorage.getInstance().PING.getInt(handle);
if (ping > 10000 || ping < 0) ping = -1;
return ping;
} catch (IllegalAccessException e) {
return -1;
}
}
@Override
public void sendPacket(Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
long time = System.nanoTime();
try {
if (nmsPacket instanceof PacketPlayOutBoss) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
handle((PacketPlayOutBoss) nmsPacket);
} else {
handleVia((PacketPlayOutBoss) nmsPacket);
}
} else {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
}
private void handle(PacketPlayOutBoss packet) {
BossBar bar = bossBars.get(packet.getId());
if (packet.getOperation() == PacketPlayOutBoss.Action.ADD) {
if (bossBars.containsKey(packet.getId())) return;
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BarColor.valueOf(packet.getColor().name()),
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
bar.setProgress(packet.getPct());
bossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer());
return;
}
if (bar == null) return; //no idea how
switch (packet.getOperation()) {
case REMOVE:
bar.removePlayer(getPlayer());
bossBars.remove(packet.getId());
break;
case UPDATE_PCT:
bar.setProgress(packet.getPct());
break;
case UPDATE_NAME:
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
bar.setColor(BarColor.valueOf(packet.getColor().name()));
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = bossBars.get(packet.getId());
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
private void handleVia(PacketPlayOutBoss packet) {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
switch (packet.getOperation()) {
case ADD:
if (viaBossBars.containsKey(packet.getId())) return;
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
packet.getPct(),
BossColor.valueOf(packet.getColor().name()),
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
//fog missing from via API
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
viaBossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer().getUniqueId());
break;
case REMOVE:
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
viaBossBars.remove(packet.getId());
break;
case UPDATE_PCT:
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
break;
case UPDATE_NAME:
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = viaBossBars.get(packet.getId());
//fog missing from via API
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue) {
if (!bar.hasFlag(flag)) {
bar.addFlag(flag);
}
} else {
if (bar.hasFlag(flag)) {
bar.removeFlag(flag);
}
}
}
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
if (targetValue) {
if (!bar.hasFlag(flag)) {
bar.addFlag(flag);
}
} else {
if (bar.hasFlag(flag)) {
bar.removeFlag(flag);
}
}
}
@Override
public boolean hasInvisibilityPotion() {
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
}
@Override
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
return DisguiseAPI.isDisguised(getPlayer());
} catch (LinkageError 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);
return false;
}
}
@Override
public Object getSkin() {
try {
return ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties();
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
return null;
}
}
@Override
public Player getPlayer() {
return (Player) player;
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@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();
}
@SuppressWarnings("deprecation")
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
}
@@ -0,0 +1,189 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import com.viaversion.viaversion.api.Via;
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;
/**
* Main class for Bukkit platform
*/
public class Main extends JavaPlugin {
private boolean viaVersion;
private boolean protocolSupport;
@Override
public void onEnable(){
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&7[TAB] Server version: " + Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")"));
if (!isVersionSupported()){
Bukkit.getPluginManager().disablePlugin(this);
return;
}
viaVersion = Bukkit.getPluginManager().isPluginEnabled("ViaVersion");
protocolSupport = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport");
TAB.setInstance(new TAB(new BukkitPlatform(this), ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0])));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(this), 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"));
PluginCommand cmd = Bukkit.getPluginCommand("tab");
if (cmd == null) return;
TABCommand command = new TABCommand();
cmd.setExecutor(command);
cmd.setTabCompleter(command);
}
@Override
public void onDisable() {
//null check due to L23 return making L30 not run
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
/**
* Initializes all used NMS classes, constructors, fields and methods and returns true if everything went successfully and version is marked as compatible
* @return true if compatible, false if not
*/
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");
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
long time = System.currentTimeMillis();
NMSStorage.setInstance(new NMSStorage());
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&7[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
return true;
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server version. This jar has only been tested on 1.5.x - 1.18. Disabling just to stay safe."));
}
} catch (Exception ex) {
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report the error below (include your server version & fork too)"));
Bukkit.getConsoleSender().sendMessage(ex.getClass().getName() + ": " + ex.getMessage());
for (StackTraceElement e : ex.getStackTrace()) {
Bukkit.getConsoleSender().sendMessage("\t" + e.toString());
}
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.x - 1.18. Disabling."));
}
}
return false;
}
/**
* Gets protocol version and returns it
* @return protocol version of this player
*/
public int getProtocolVersion(Player player) {
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) {
return getProtocolVersionVia(player, 0);
}
return TAB.getInstance().getServerVersion().getNetworkId();
}
/**
* Returns protocol version of this player using ProtocolSupport
* @return protocol version of this player using ProtocolSupport
*/
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() + ")");
return version;
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ProtocolSupport", player.getName()), e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
}
/**
* Returns protocol version of this player using ViaVersion
* @return protocol version of this player using ViaVersion
*/
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) {
Plugin via = Bukkit.getPluginManager().getPlugin("ViaVersion");
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ViaVersion v%s", player.getName(), via == null ? "" : via.getDescription().getVersion()), e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
}
public 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()) {
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));
}
} else {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return true; //player not loaded correctly
}
TAB.getInstance().getCommand().execute(p, args);
}
return false;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
}
}
@@ -0,0 +1,26 @@
package me.neznamy.tab.platforms.bukkit.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
/**
* 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 @NotNull HandlerList getHandlers(){
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,37 @@
package me.neznamy.tab.platforms.bukkit.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* 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 @NotNull HandlerList getHandlers(){
return getHandlerList();
}
public static HandlerList getHandlerList() {
return handlers;
}
public TabPlayer getPlayer() {
return player;
}
}
@@ -0,0 +1,98 @@
package me.neznamy.tab.platforms.bukkit.features;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
/**
* Per-world-PlayerList feature handler
*/
@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");
/**
* Constructs new instance with given parameters and loads config options
* @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);
}
@Override
public void load(){
Bukkit.getScheduler().runTask(plugin, () -> Bukkit.getOnlinePlayers().forEach(this::checkPlayer));
}
@Override
public void unload(){
for (Player p : Bukkit.getOnlinePlayers()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
p.showPlayer(pl);
}
}
HandlerList.unregisterAll(this);
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
private void checkPlayer(Player p) {
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);
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
}
}
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;
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
String targetWorldGroup = target.getWorld().getName() + "-default";
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
if (group.getValue() != null) {
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
}
}
return viewerWorldGroup.equals(targetWorldGroup);
}
}
@@ -0,0 +1,124 @@
package me.neznamy.tab.platforms.bukkit.features;
import java.util.*;
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.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.shared.TAB;
/**
* A feature to disable minecraft 1.9+ feature making tamed animals with custom names copy NameTag properties of their owner
* This is achieved by listening to entity spawn (<1.15) / entity metadata packets and removing owner field from the DataWatcher list
* Since 1.16 this results in client sending entity use packet twice, so we must cancel the 2nd one to prevent double toggle
*/
public class PetFix extends TabFeature {
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
//DataWatcher position of pet owner field
private final int petOwnerPosition = getPetOwnerPosition();
//logger of last interacts to prevent feature not working on 1.16
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
/**
* Constructs new instance with given parameter
*/
public PetFix() {
super("Pet name fix", null);
TAB.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
*/
private int getPetOwnerPosition() {
if (nms.getMinorVersion() >= 17) {
//1.17.x, 1.18.x
return 18;
} else if (nms.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 17;
} else if (nms.getMinorVersion() >= 14) {
//1.14.x
return 16;
} else if (nms.getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 14;
} else {
//1.9.x
return 13;
}
}
/**
* Cancels a packet if previous one arrived with no delay to prevent double toggle on 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) < 5)) {
//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());
}
}
return false;
}
private boolean isInteract(Object action) {
if (nms.getMinorVersion() >= 17) {
return nms.PacketPlayInUseEntity$d.isInstance(action);
} else {
return action.toString().equals("INTERACT");
}
}
/**
* Removes pet owner field from DataWatcher
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (nms.PacketPlayOutEntityMetadata.isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) nms.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) {
removedEntry = item;
}
}
}
} catch (ConcurrentModificationException e) {
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
onPacketSend(receiver, packet);
}
if (removedEntry != null) items.remove(removedEntry);
} else if (nms.PacketPlayOutSpawnEntityLiving.isInstance(packet) && nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER != null) {
//<1.15
DataWatcher watcher = DataWatcher.fromNMS(nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER.get(packet));
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
if (petOwner != null && (petOwner.getValue() instanceof java.util.Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
watcher.removeValue(petOwnerPosition);
nms.setField(packet, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, watcher.toNMS());
}
}
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.shared.TabConstants;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.api.scoreboard.Scoreboard;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
/**
* TAB's expansion for PlaceholderAPI
*/
public class TabExpansion extends PlaceholderExpansion {
//plugin to take plugin.yml data from
private final JavaPlugin plugin;
/**
* Constructs new instance of the class and registers it
* @param plugin - plugin to take plugin.yml data from
*/
public TabExpansion(JavaPlugin plugin) {
this.plugin = plugin;
register();
}
@Override
public boolean persist(){
return true;
}
@Override
public boolean canRegister(){
return true;
}
@Override
public @NotNull String getAuthor(){
return plugin.getDescription().getAuthors().toString();
}
@Override
public @NotNull String getIdentifier(){
return "tab";
}
@Override
public @NotNull String getVersion(){
return plugin.getDescription().getVersion();
}
@Override
public String onPlaceholderRequest(Player player, @NotNull String identifier){
if (player == null) return "";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if ("scoreboard_visible".equals(identifier)) {
return translate(hasScoreboardVisible(p));
}
if ("scoreboard_name".equals(identifier)) {
return getActiveScoreboard(p);
}
if ("bossbar_visible".equals(identifier)) {
return translate(hasBossBarVisible(p));
}
if ("ntpreview".equals(identifier)) {
return translate(p.isPreviewingNametag());
}
if (identifier.startsWith("replace_")) {
return findReplacement("%" + identifier.substring(8) + "%", player);
}
if (identifier.startsWith("placeholder_")) {
return TAB.getInstance().getPlaceholderManager().getPlaceholder("%" + identifier.substring(12) + "%").getLastValue(p);
}
return getProperty(identifier, p);
}
private boolean hasBossBarVisible(TabPlayer p) {
BossBarManager boss = (BossBarManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.BOSS_BAR);
if (boss == null) return false;
return boss.hasBossBarVisible(p);
}
private boolean hasScoreboardVisible(TabPlayer p) {
ScoreboardManager scoreboard = (ScoreboardManager) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SCOREBOARD);
if (scoreboard == null) return false;
return scoreboard.hasScoreboardVisible(p);
}
private String getActiveScoreboard(TabPlayer p) {
ScoreboardManager sb = TabAPI.getInstance().getScoreboardManager();
if (sb == null) return "";
Scoreboard active = sb.getActiveScoreboard(p);
if (active == null) return "";
return active.getName();
}
private String translate(boolean b) {
return b ? "Enabled" : "Disabled";
}
/**
* Find replacement for specified placeholder
* @param placeholder - placeholder to find replacement for
* @param player - player to set placeholder for
* @return replacement
*/
private String findReplacement(String placeholder, Player player) {
String output = PlaceholderAPI.setPlaceholders(player, placeholder);
return TAB.getInstance().getPlaceholderManager().findReplacement(placeholder, output).replace("%value%", output);
}
/**
* Returns value of defined property if found
* @param name - property name
* @param player - player to get property of
* @return value of specified property
*/
private String getProperty(String name, TabPlayer player) {
String propName = name.replace("_raw", "");
Property prop = player.getProperty(propName);
if (prop != null) {
if (name.endsWith("_raw")) {
return prop.getCurrentRawValue();
}
return prop.get();
}
return null;
}
}
@@ -0,0 +1,72 @@
package me.neznamy.tab.platforms.bukkit.features;
import java.util.Collections;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.java.JavaPlugin;
import 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.shared.TAB;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
/**
* An additional class with additional code for <1.9 servers due to an entity being required
*/
public class WitherBossBar extends BossBarManagerImpl implements Listener {
//distance of wither in blocks
private static final int WITHER_DISTANCE = 60;
/**
* Constructs a new instance of the class
* @param plugin - plugin instance
*/
public WitherBossBar(JavaPlugin 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
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder("%location%", 100, p -> ((Player)p.getPlayer()).getLocation());
addUsedPlaceholders(Collections.singletonList("%location%"));
}
@Override
public void load() {
super.load();
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
refresh(p, false);
}
}
@Override
public void refresh(TabPlayer p, boolean force) {
for (BossBar line : getRegisteredBossBars().values()) {
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
}
}
@Override
public void unload() {
super.unload();
HandlerList.unregisterAll(this);
}
/**
* Respawning wither as respawn screen destroys all entities in client
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerRespawnEvent", this, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
}
@@ -0,0 +1,339 @@
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.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;
/**
* A class representing an armor stand attached to a player (if the feature is enabled)
*/
public class BukkitArmorStand implements ArmorStand {
private final boolean alwaysVisible = TAB.getInstance().getConfiguration().isArmorStandsAlwaysVisible();
//entity id counter to pick unique entity IDs
private static int idCounter = 2000000000;
//NameTag feature
private final NameTagX manager = (NameTagX) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS);
//owner of the armor stand
private final TabPlayer owner;
//instance of Bukkit player
private final Player player;
//offset in blocks, 0 for original height
private double yOffset;
//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);
/**
* Constructs new instance with given parameters
* @param owner - armor stand owner
* @param property - property for armor stand's name
* @param yOffset - offset in blocks
* @param staticOffset - if offset is static or not
*/
public BukkitArmorStand(TabPlayer owner, Property property, double yOffset, boolean staticOffset) {
this.owner = owner;
this.staticOffset = staticOffset;
player = (Player) owner.getPlayer();
this.yOffset = yOffset;
this.property = property;
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 : owner.getArmorStandManager().getNearbyPlayers()) {
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_OFFSET_CHANGE);
}
}
@Override
public void spawn(TabPlayer viewer) {
for (TabPacket packet : getSpawnPackets(viewer)) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
}
}
@Override
public void destroy() {
for (TabPlayer all : owner.getArmorStandManager().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 : owner.getArmorStandManager().getNearbyPlayers()) {
all.sendCustomPacket(getTeleportPacket(all), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
}
@Override
public void teleport(TabPlayer viewer) {
if (!owner.getArmorStandManager().isNearby(viewer) && viewer != owner) {
owner.getArmorStandManager().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 : owner.getArmorStandManager().getNearbyPlayers()) {
if (viewer.getVersion().getMinorVersion() == 14 && !alwaysVisible) {
//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) {
visible = visibility;
updateMetadata();
}
}
@Override
public int getEntityId() {
return entityId;
}
/**
* Returns teleport packet for specified viewer
* @param viewer - player to get location for
* @return teleport packet
*/
public PacketPlayOutEntityTeleport getTeleportPacket(TabPlayer viewer) {
return new PacketPlayOutEntityTeleport(entityId, getArmorStandLocationFor(viewer));
}
/**
* Updates armor stand's metadata
*/
public void updateMetadata() {
for (TabPlayer viewer : owner.getArmorStandManager().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 (owner.isDisguised() || manager.getVehicleManager().isOnBoat(owner)) return false;
if (alwaysVisible) return true;
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().equals("DONKEY")) { //1.11+
return vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
return vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("STRIDER")) { //1.16+
return 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)) {
visibility = false;
} else {
visibility = visible;
}
datawatcher.helper().setCustomNameVisible(visibility);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x()) datawatcher.helper().setArmorStandFlags((byte)16);
return datawatcher;
}
/**
* 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
*/
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;
}
/**
* 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();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getArmorStandLocationFor(viewer), dataWatcher),
};
}
}
/**
* Returns location where armor stand should be for specified viewer
* @param viewer - player to get location for
* @return location of armor stand
*/
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, "compensating for 1.8.0 bugs", manager, TabConstants.CpuUsageCategory.V1_8_0_BUG_COMPENSATION, spawn);
} else {
spawn.run();
}
}
}
@@ -0,0 +1,81 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.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
*/
public class EventListener implements Listener {
//the NameTag feature handler
private final NameTagX feature;
/**
* Constructs new instance with given parameters
* @param feature - NameTag feature handler
*/
public EventListener(NameTagX feature) {
this.feature = feature;
}
/**
* Sneak event listener to de-spawn & spawn armor stands to skip animation
* @param e - sneak event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || feature.isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerToggleSneakEvent", feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> {
if (p.getArmorStandManager() != null) p.getArmorStandManager().sneak(e.isSneaking());
});
}
/**
* Respawning armor stands as respawn screen destroys all entities in client
* @param e - respawn event
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerRespawnEvent", feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isPlayerDisabled(respawned)) return;
respawned.getArmorStandManager().teleport();
});
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || !p.isLoaded()) return;
long time = System.nanoTime();
String to = e.getPlayer().getWorld().getName();
if (feature.isDisabled(to)) {
feature.getPlayersInDisabledUnlimitedWorlds().add(p);
} else {
feature.getPlayersInDisabledUnlimitedWorlds().remove(p);
}
//TODO delete the block below
TabPlayer[] nearby = p.getArmorStandManager().getNearbyPlayers();
p.getArmorStandManager().destroy();
feature.loadArmorStands(p);
feature.getVehicleManager().loadPassengers(p);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer.getArmorStandManager() != null) viewer.getArmorStandManager().destroy(p);
if (!to.equals(viewer.getWorld())) continue;
for (TabPlayer player : nearby) {
if (player == viewer) feature.spawnArmorStands(p, viewer, true);
}
}
TAB.getInstance().getCPUManager().addTime(feature, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
}
@@ -0,0 +1,413 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.ArmorStandManager;
import me.neznamy.tab.api.Property;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.team.UnlimitedNametagManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.nametags.NameTag;
/**
* The core class for unlimited NameTag mode
*/
public class NameTagX extends NameTag implements UnlimitedNametagManager {
//config options
private final boolean markerFor18x = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.use-marker-tag-for-1-8-x-clients", false);
private final boolean disableOnBoats = TAB.getInstance().getConfiguration().getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.disable-on-boats", true);
private final double spaceBetweenLines = TAB.getInstance().getConfiguration().getConfig().getDouble("scoreboard-teams.unlimited-nametag-mode.space-between-lines", 0.22);
protected final List<String> disabledUnlimitedWorlds = TAB.getInstance().getConfiguration().getConfig().getStringList("scoreboard-teams.unlimited-nametag-mode.disable-in-worlds", new ArrayList<>());
private final List<String> dynamicLines = new ArrayList<>(TAB.getInstance().getConfiguration().getConfig().getStringList("scoreboard-teams.unlimited-nametag-mode.dynamic-lines", Arrays.asList(TabConstants.Property.ABOVENAME, TabConstants.Property.NAMETAG, TabConstants.Property.BELOWNAME, "another")));
private final Map<String, Object> staticLines = TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("scoreboard-teams.unlimited-nametag-mode.static-lines");
//player data by entityId, used for better performance
private final Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
//bukkit event listener
private final EventListener eventListener = new EventListener(this);
private final Set<TabPlayer> playersInDisabledUnlimitedWorlds = Collections.newSetFromMap(new WeakHashMap<>());
private final String[] disabledUnlimitedWorldsArray = disabledUnlimitedWorlds.toArray(new String[0]);
private final boolean unlimitedWorldWhitelistMode = disabledUnlimitedWorlds.contains("WHITELIST");
private final Set<TabPlayer> playersDisabledWithAPI = Collections.newSetFromMap(new WeakHashMap<>());
private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
/**
* Constructs new instance with given parameters and loads config options
* @param plugin - plugin instance
*/
public NameTagX(JavaPlugin plugin) {
Collections.reverse(dynamicLines);
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);
TAB.getInstance().debug(String.format("Loaded Unlimited NameTag feature with parameters markerFor18x=%s, disableOnBoats=%s, spaceBetweenLines=%s, disabledUnlimitedWorlds=%s",
markerFor18x, disableOnBoats, spaceBetweenLines, disabledUnlimitedWorlds));
}
@Override
public void load() {
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
getEntityIdMap().put(((Player) all.getPlayer()).getEntityId(), all);
updateProperties(all);
loadArmorStands(all);
if (isDisabled(all.getWorld())) {
playersInDisabledUnlimitedWorlds.add(all);
}
if (isPlayerDisabled(all)) continue;
vehicleManager.loadPassengers(all);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(all, viewer, false);
}
}
super.load();
startVisibilityRefreshTask();
}
private void startVisibilityRefreshTask() {
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(500, "refreshing NameTag visibility", this, TabConstants.CpuUsageCategory.REFRESHING_NAME_TAG_VISIBILITY, () -> {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (isPlayerDisabled(p)) continue;
p.getArmorStandManager().updateVisibility(false);
}
});
}
@Override
public void unload() {
super.unload();
HandlerList.unregisterAll(eventListener);
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getArmorStandManager() != null) p.getArmorStandManager().destroy();
}
getEntityIdMap().clear();
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
if (isDisabled(connectedPlayer.getWorld()))
playersInDisabledUnlimitedWorlds.add(connectedPlayer);
super.onJoin(connectedPlayer);
getEntityIdMap().put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
loadArmorStands(connectedPlayer);
if (isPlayerDisabled(connectedPlayer)) return;
vehicleManager.loadPassengers(connectedPlayer);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(connectedPlayer, viewer, true);
}
}
/**
* 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) {
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 void onQuit(TabPlayer disconnectedPlayer) {
super.onQuit(disconnectedPlayer);
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
if (all.getArmorStandManager() != null) all.getArmorStandManager().unregisterPlayer(disconnectedPlayer);
}
entityIdMap.remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
if (disconnectedPlayer.getArmorStandManager() != null) { //player was not loaded yet
disconnectedPlayer.getArmorStandManager().destroy();
TAB.getInstance().getCPUManager().runTaskLater(500, "processing onQuit", this, TabConstants.CpuUsageCategory.PLAYER_QUIT, () -> disconnectedPlayer.getArmorStandManager().destroy());
}
}
/**
* Spawns armor stands of specified owner to viewer and, mutually if set
* @param owner - armor stand owner
* @param viewer - player to spawn armor stands for
* @param sendMutually - if packets should be sent both ways
*/
public void spawnArmorStands(TabPlayer owner, TabPlayer viewer, boolean sendMutually) {
if (owner == viewer) return; //not displaying own armor stands
if (((Player) viewer.getPlayer()).getWorld() != ((Player) owner.getPlayer()).getWorld()) return; //different world
if (isPlayerDisabled(owner)) return;
if (getDistance(viewer, owner) <= 48) {
if (((Player)viewer.getPlayer()).canSee((Player)owner.getPlayer()) && !owner.isVanished()) owner.getArmorStandManager().spawn(viewer);
if (sendMutually && viewer.getArmorStandManager() != null && ((Player)owner.getPlayer()).canSee((Player)viewer.getPlayer())
&& !viewer.isVanished()) viewer.getArmorStandManager().spawn(owner);
}
}
/**
* Restarts and loads armor stands from config
* @param pl - player to load
*/
public void loadArmorStands(TabPlayer pl) {
pl.setArmorStandManager(new ArmorStandManager());
pl.setProperty(this, TabConstants.Property.NAMETAG, pl.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue()
+ pl.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue()
+ pl.getProperty(TabConstants.Property.TAGSUFFIX).getCurrentRawValue());
double height = 0;
for (String line : dynamicLines) {
Property p = pl.getProperty(line);
if (p.getCurrentRawValue().length() == 0) continue;
pl.getArmorStandManager().addArmorStand(line, new BukkitArmorStand(pl, p, height, false));
height += spaceBetweenLines;
}
for (Entry<String, Object> line : staticLines.entrySet()) {
Property p = pl.getProperty(line.getKey());
if (p.getCurrentRawValue().length() == 0) continue;
pl.getArmorStandManager().addArmorStand(line.getKey(), new BukkitArmorStand(pl, p, Double.parseDouble(line.getValue().toString()), true));
}
fixArmorStandHeights(pl);
}
/**
* Fixes heights of all armor stands of specified player due to dynamic lines
* @param p - player to fix armor stands heights for
*/
public void fixArmorStandHeights(TabPlayer p) {
double currentY = -spaceBetweenLines;
for (ArmorStand as : p.getArmorStandManager().getArmorStands()) {
if (as.hasStaticOffset()) continue;
if (as.getProperty().get().length() != 0) {
currentY += spaceBetweenLines;
as.setOffset(currentY);
}
}
}
@Override
public void refresh(TabPlayer refreshed, boolean force) {
super.refresh(refreshed, force);
if (isPlayerDisabled(refreshed)) return;
if (force) {
refreshed.getArmorStandManager().destroy();
loadArmorStands(refreshed);
vehicleManager.loadPassengers(refreshed);
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
if (viewer == refreshed) continue;
if (viewer.getWorld().equals(refreshed.getWorld())) {
refreshed.getArmorStandManager().spawn(viewer);
}
}
} else {
boolean fix = false;
for (ArmorStand as : refreshed.getArmorStandManager().getArmorStands()) {
if (as.getProperty().update()) {
as.refresh();
fix = true;
}
}
if (fix) fixArmorStandHeights(refreshed);
}
}
/**
* Updates raw values of properties for specified player
* @param p - player to update
*/
@Override
public boolean updateProperties(TabPlayer p) {
boolean changed = super.updateProperties(p);
if (p.loadPropertyFromConfig(this, TabConstants.Property.CUSTOMTAGNAME, p.getName())) changed = true;
if (p.setProperty(this, TabConstants.Property.NAMETAG, p.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue() +
p.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue() + p.getProperty(TabConstants.Property.TAGSUFFIX).getCurrentRawValue())) changed = true;
for (String property : dynamicLines) {
if (!property.equals(TabConstants.Property.NAMETAG) && p.loadPropertyFromConfig(this, property)) changed = true;
}
for (String property : staticLines.keySet()) {
if (!property.equals(TabConstants.Property.NAMETAG) && p.loadPropertyFromConfig(this, property)) changed = true;
}
return changed;
}
@Override
public String getFeatureName() {
return "Unlimited NameTags";
}
@Override
public boolean getTeamVisibility(TabPlayer p, TabPlayer viewer) {
if (p.hasInvisibilityPotion()) return false; //1.8.x client sided bug
if (vehicleManager == null) return false; //class is still being initialized and this was called from super constructor
return vehicleManager.isOnBoat(p) || isPlayerDisabled(p);
}
public Map<Integer, TabPlayer> getEntityIdMap() {
return entityIdMap;
}
public boolean isDisabled(String world) {
boolean contains = contains(disabledUnlimitedWorldsArray, world);
if (unlimitedWorldWhitelistMode) contains = !contains;
return contains;
}
public boolean isPlayerDisabled(TabPlayer p) {
return isDisabledPlayer(p) || playersInDisabledUnlimitedWorlds.contains(p) || hasTeamHandlingPaused(p) || hasDisabledArmorStands(p);
}
public boolean isMarkerFor18x() {
return markerFor18x;
}
public Set<TabPlayer> getPlayersInDisabledUnlimitedWorlds() {
return playersInDisabledUnlimitedWorlds;
}
public VehicleRefresher getVehicleManager() {
return vehicleManager;
}
@Override
public void pauseTeamHandling(TabPlayer player) {
if (teamHandlingPaused.contains(player)) return;
if (!isDisabledPlayer(player)) unregisterTeam(player);
teamHandlingPaused.add(player); //adding after, so unregisterTeam method runs
player.getArmorStandManager().destroy();
}
@Override
public void resumeTeamHandling(TabPlayer player) {
if (!teamHandlingPaused.contains(player)) return;
teamHandlingPaused.remove(player); //removing before, so registerTeam method runs
if (!isDisabledPlayer(player)) registerTeam(player);
if (!isPlayerDisabled(player)) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(player, viewer, false);
}
}
}
@Override
public void disableArmorStands(TabPlayer player) {
if (playersDisabledWithAPI.contains(player)) return;
playersDisabledWithAPI.add(player);
player.getArmorStandManager().destroy();
updateTeamData(player);
}
@Override
public void enableArmorStands(TabPlayer player) {
if (!playersDisabledWithAPI.contains(player)) return;
playersDisabledWithAPI.remove(player);
if (!isPlayerDisabled(player)) {
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
spawnArmorStands(player, viewer, false);
}
}
updateTeamData(player);
}
@Override
public boolean hasDisabledArmorStands(TabPlayer player) {
return playersDisabledWithAPI.contains(player);
}
@Override
public void setPrefix(TabPlayer player, String prefix) {
player.getProperty(TabConstants.Property.TAGPREFIX).setTemporaryValue(prefix);
rebuildNameTagLine(player);
player.forceRefresh();
}
@Override
public void setSuffix(TabPlayer player, String suffix) {
player.getProperty(TabConstants.Property.TAGSUFFIX).setTemporaryValue(suffix);
rebuildNameTagLine(player);
player.forceRefresh();
}
@Override
public void resetPrefix(TabPlayer player) {
player.getProperty(TabConstants.Property.TAGPREFIX).setTemporaryValue(null);
rebuildNameTagLine(player);
player.forceRefresh();
}
@Override
public void resetSuffix(TabPlayer player) {
player.getProperty(TabConstants.Property.TAGSUFFIX).setTemporaryValue(null);
rebuildNameTagLine(player);
player.forceRefresh();
}
@Override
public void setName(TabPlayer player, String customName) {
player.getProperty(TabConstants.Property.CUSTOMTAGNAME).setTemporaryValue(customName);
rebuildNameTagLine(player);
player.forceRefresh();
}
@Override
public void setLine(TabPlayer player, String line, String value) {
player.getProperty(line).setTemporaryValue(value);
player.forceRefresh();
}
@Override
public void resetName(TabPlayer player) {
player.getProperty(TabConstants.Property.CUSTOMTAGNAME).setTemporaryValue(null);
rebuildNameTagLine(player);
player.forceRefresh();
}
@Override
public void resetLine(TabPlayer player, String line) {
player.getProperty(line).setTemporaryValue(null);
player.forceRefresh();
}
@Override
public String getCustomName(TabPlayer player) {
return player.getProperty(TabConstants.Property.CUSTOMTAGNAME).getTemporaryValue();
}
@Override
public String getCustomLineValue(TabPlayer player, String line) {
return player.getProperty(line).getTemporaryValue();
}
@Override
public String getOriginalName(TabPlayer player) {
return player.getProperty(TabConstants.Property.CUSTOMTAGNAME).getOriginalRawValue();
}
@Override
public String getOriginalLineValue(TabPlayer player, String line) {
return player.getProperty(line).getOriginalRawValue();
}
private void rebuildNameTagLine(TabPlayer player) {
player.setProperty(this, TabConstants.Property.NAMETAG, player.getProperty(TabConstants.Property.TAGPREFIX).getCurrentRawValue() +
player.getProperty(TabConstants.Property.CUSTOMTAGNAME).getCurrentRawValue() + player.getProperty(TabConstants.Property.TAGSUFFIX).getCurrentRawValue());
}
public boolean isDisableOnBoats() {
return disableOnBoats;
}
@Override
public List<String> getDefinedLines() {
List<String> lines = new ArrayList<>(dynamicLines);
lines.addAll(staticLines.keySet());
return lines;
}
}
@@ -0,0 +1,126 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import java.util.List;
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 NameTagX nameTagX;
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
/**
* Constructs new instance with given parameters and loads config options
* @param nameTagX - main feature
*/
public PacketListener(NameTagX nameTagX) {
super(nameTagX.getFeatureName(), null);
this.nameTagX = nameTagX;
}
@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() && all.getArmorStandManager().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.getPlayersInDisabledUnlimitedWorlds().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 = nameTagX.getEntityIdMap().get(entityId);
List<Entity> vehicleList;
if (pl != null) {
//player moved
if (nameTagX.isPlayerDisabled(pl)) return;
TAB.getInstance().getCPUManager().runMeasuredTask("processing EntityMove", nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE, () -> pl.getArmorStandManager().teleport(receiver));
} else if ((vehicleList = nameTagX.getVehicleManager().getVehicles().get(entityId)) != null){
//a vehicle carrying something moved
for (Entity entity : vehicleList) {
TabPlayer passenger = nameTagX.getEntityIdMap().get(entity.getEntityId());
if (passenger != null && passenger.getArmorStandManager() != null) {
TAB.getInstance().getCPUManager().runMeasuredTask("processing EntityMove", nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_MOVE_PASSENGER, () -> passenger.getArmorStandManager().teleport(receiver));
}
}
}
}
private void onEntitySpawn(TabPlayer receiver, int entityId) {
TabPlayer spawnedPlayer = nameTagX.getEntityIdMap().get(entityId);
if (spawnedPlayer != null && spawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(spawnedPlayer)) {
TAB.getInstance().getCPUManager().runMeasuredTask("processing NamedEntitySpawn", nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_SPAWN, () -> spawnedPlayer.getArmorStandManager().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 = nameTagX.getEntityIdMap().get(entity);
if (deSpawnedPlayer != null && deSpawnedPlayer.isLoaded() && !nameTagX.isPlayerDisabled(deSpawnedPlayer))
TAB.getInstance().getCPUManager().runMeasuredTask("processing EntityDestroy", nameTagX, TabConstants.CpuUsageCategory.PACKET_ENTITY_DESTROY, () -> deSpawnedPlayer.getArmorStandManager().destroy(receiver));
}
}
@@ -0,0 +1,140 @@
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 NameTagX feature;
public VehicleRefresher(NameTagX feature) {
super(feature.getFeatureName(), "Refreshing vehicles");
this.feature = feature;
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(50, "processing player movement",
this, TabConstants.CpuUsageCategory.PROCESSING_PLAYER_MOVEMENT, () -> {
for (TabPlayer inVehicle : playersInVehicle.keySet()) {
inVehicle.getArmorStandManager().teleport();
// feature.getVehicleManager().processPassengers((Entity) inVehicle.getPlayer());
}
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.isPreviewingNametag()) {
p.getArmorStandManager().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 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());
p.getArmorStandManager().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));
p.getArmorStandManager().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);
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
if (playersInVehicle.containsKey(disconnectedPlayer)) vehicles.remove(playersInVehicle.get(disconnectedPlayer).getEntityId());
}
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<>();
}
}
}
/**
* Loads all passengers riding this player and adds them to vehicle list
* @param p - player to load passengers of
*/
public void loadPassengers(TabPlayer p) {
Entity vehicle = ((Entity) p.getPlayer()).getVehicle();
if (vehicle == null) return;
vehicles.put(vehicle.getEntityId(), getPassengers(vehicle));
}
/**
* 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) {
TAB.getInstance().getPlayer(passenger.getUniqueId()).getArmorStandManager().teleport();
}
processPassengers(passenger);
}
}
}
@@ -0,0 +1,677 @@
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.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;
}
}
@@ -0,0 +1,23 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.util.Arrays;
import me.neznamy.tab.api.protocol.TabPacket;
public class PacketPlayOutEntityDestroy implements TabPacket {
private final int[] 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;
}
}
@@ -0,0 +1,29 @@
package me.neznamy.tab.platforms.bukkit.nms;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
public class PacketPlayOutEntityMetadata implements TabPacket {
private final int entityId;
private final DataWatcher dataWatcher;
public PacketPlayOutEntityMetadata(int entityId, DataWatcher dataWatcher) {
this.entityId = entityId;
this.dataWatcher = dataWatcher;
}
@Override
public String toString() {
return String.format("PacketPlayOutEntityMetadata{entityId=%s,dataWatcher=%s}", entityId, dataWatcher);
}
public int getEntityId() {
return entityId;
}
public DataWatcher getDataWatcher() {
return dataWatcher;
}
}
@@ -0,0 +1,30 @@
package me.neznamy.tab.platforms.bukkit.nms;
import org.bukkit.Location;
import me.neznamy.tab.api.protocol.TabPacket;
public class PacketPlayOutEntityTeleport implements TabPacket {
private final int entityId;
private final Location location;
public PacketPlayOutEntityTeleport(int entityId, Location location) {
this.entityId = entityId;
this.location = location;
}
@Override
public String toString() {
return String.format("PacketPlayOutEntityTeleport{entityId=%s,location=%s}", entityId, location);
}
public int getEntityId() {
return entityId;
}
public Location getLocation() {
return location;
}
}
@@ -0,0 +1,56 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.util.UUID;
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;
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
private final int entityId;
private final UUID uuid;
private final EntityType entityType;
private final Location location;
private final DataWatcher dataWatcher;
public PacketPlayOutSpawnEntityLiving(int entityId, UUID uuid, EntityType entityType, Location location, DataWatcher dataWatcher) {
this.entityId = entityId;
this.uuid = uuid;
this.entityType = entityType;
this.location = location;
this.dataWatcher = dataWatcher;
}
@Override
public String toString() {
return String.format("PacketPlayOutSpawnEntityLiving{entityId=%s,uuid=%s,entityType=%s,location=%s,dataWatcher=%s}",
entityId, uuid, entityType, location, dataWatcher);
}
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;
}
}
@@ -5,18 +5,17 @@ import java.util.List;
import java.util.Map;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
public class DataWatcher {
//datawatcher data
private Map<Integer, DataWatcherItem> dataValues = new HashMap<Integer, DataWatcherItem>();
//DataWatcher data
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
//a helper for easier data write
private DataWatcherHelper helper = new DataWatcherHelper(this);
private final DataWatcherHelper helper = new DataWatcherHelper(this);
/**
* Sets value into data values
@@ -24,7 +23,7 @@ public class DataWatcher {
* @param value - value
*/
public void setValue(DataWatcherObject type, Object value){
dataValues.put(type.position, new DataWatcherItem(type, value));
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
}
/**
@@ -55,37 +54,45 @@ public class DataWatcher {
/**
* Converts the class into an instance of NMS.DataWatcher
* @return an instance of NMS.DataWatcher with same data
* @throws Exception - if something fails
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object toNMS() throws Exception {
public Object toNMS() throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher = nms.newDataWatcher.newInstance(new Object[] {null});
Object nmsWatcher;
if (nms.newDataWatcher.getParameterCount() == 1) { //1.7+
Object[] args = new Object[] {null};
nmsWatcher = nms.newDataWatcher.newInstance(args);
} else {
nmsWatcher = nms.newDataWatcher.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object position;
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 9) {
position = nms.newDataWatcherObject.newInstance(item.type.position, item.type.classType);
if (nms.getMinorVersion() >= 9) {
position = nms.newDataWatcherObject.newInstance(item.getType().getPosition(), item.getType().getClassType());
} else {
position = item.type.position;
position = item.getType().getPosition();
}
nms.DataWatcher_REGISTER.invoke(nmsWatcher, position, item.value);
nms.DataWatcher_REGISTER.invoke(nmsWatcher, position, item.getValue());
}
return nmsWatcher;
}
/**
* Reads NMS data watcher and returns and instance of this class with same data
* @param nmsWatcher - NMS datawatcher to read
* @param nmsWatcher - NMS DataWatcher to read
* @return an instance of this class with same values
* @throws Exception - if something fails
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@SuppressWarnings("unchecked")
public static DataWatcher fromNMS(Object nmsWatcher) throws Exception{
public static DataWatcher fromNMS(Object nmsWatcher) throws ReflectiveOperationException {
DataWatcher watcher = new DataWatcher();
List<Object> items = (List<Object>) nmsWatcher.getClass().getMethod("c").invoke(nmsWatcher);
if (items != null) {
for (Object watchableObject : items) {
DataWatcherItem w = DataWatcherItem.fromNMS(watchableObject);
watcher.setValue(w.type, w.value);
watcher.setValue(w.getType(), w.getValue());
}
}
return watcher;
Loaded 100 of 457 files, more files were not shown because too many files have changed in this diff. Show more