Compare commits

...
512 Commits
Author SHA1 Message Date
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
NEZNAMY a1565ed481 2.8.11-pre10 2021-04-04 15:09:19 +02:00
NEZNAMY ae056b312f put 0.5 for characters that wide and added some missing values 2021-04-04 13:39:52 +02:00
NEZNAMY b20c1158b0 allow decimal values (0.5) 2021-04-04 13:39:26 +02:00
NEZNAMY 210f567b74 added debug messages to loading widths 2021-04-04 13:12:09 +02:00
NEZNAMY 82b91c7bd9 added empty lines for missing chars 2021-04-04 12:55:21 +02:00
NEZNAMY 5168a99c3b Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-04-04 12:49:55 +02:00
NEZNAMY e437917841 added permission required to use tabcomplete #290 2021-04-04 12:49:47 +02:00
MrGraycat 6543440926 added 55001-60000
added 55001-60000
2021-04-04 09:03:14 +02:00
NEZNAMY 30c53c6048 fixed disabling in worlds not really working 2021-04-04 01:32:42 +02:00
NEZNAMY 87412eb4ff another attempt to avoid concurrent modification 2021-04-04 00:24:43 +02:00
NEZNAMY f1ad5fc57d restructure, cleanup 2021-04-03 22:50:37 +02:00
NEZNAMY dd3a457ec0 fixed tablist entries not getting shorter after maxPlayer quits 2021-04-03 22:50:14 +02:00
NEZNAMY 069f2aba8b updated info 2021-04-03 21:40:29 +02:00
NEZNAMY 9e19553150 added option to disable unlimited nametags in worlds 2021-04-03 17:26:24 +02:00
NEZNAMY 2d7b009442 no longer refreshing if value did not change 2021-04-03 16:42:59 +02:00
NEZNAMY 68817e3595 post-recode fix 2021-04-03 16:42:36 +02:00
NEZNAMY 3e229d6b82 do not refresh unused placeholders 2021-04-03 12:15:29 +02:00
NEZNAMY 5fce471577 fixed empty packets being sent causing white names with anti-override disabled 2021-04-01 22:58:55 +02:00
NEZNAMY 68b1e7cb16 nametag classes refactor, NameTag is base class which NameTagX extends if enabled
also made invisible-nametags public
2021-04-01 22:15:18 +02:00
NEZNAMY 761e02ff6e fixed unsupported operation from previous commit 2021-04-01 16:59:08 +02:00
NEZNAMY 5fe4408631 using in-built java function instead of google one 2021-04-01 16:05:27 +02:00
NEZNAMY bae0685ac1 added check for team name length 2021-04-01 11:32:31 +02:00
NEZNAMY 6322a0c045 fixed concurrent modification 2021-04-01 11:32:07 +02:00
NEZNAMY 0b89de905e added null check 2021-03-31 21:05:32 +02:00
NEZNAMY 49c4453678 removed unnecessary full path 2021-03-31 08:25:49 +02:00
NEZNAMY c056cafda6 fixed error when left side of condition was empty 2021-03-30 22:02:40 +02:00
NEZNAMY b375c24340 added API methods to force team name 2021-03-30 15:16:26 +02:00
NEZNAMY 5a76131f24 setBossbarVisible method now performs all actions 2021-03-30 15:12:27 +02:00
NEZNAMY 764769a6ca fixed startup error caused by misconfiguration 2021-03-30 11:20:02 +02:00
NEZNAMY b0226df3ab updated comments 2021-03-29 22:39:25 +02:00
NEZNAMY 8c86120b33 removed unused method 2021-03-29 22:01:58 +02:00
NEZNAMY 8805d0666b 2.8.11-pre9 2021-03-29 12:02:34 +02:00
NEZNAMY ae259a894e added config option to disable anti-override for nicknames 2021-03-29 12:02:03 +02:00
NEZNAMY 88d3c94c26 lowercased static method names 2021-03-28 17:00:07 +02:00
NEZNAMY 93dd0e1c72 using existing variable 2021-03-28 16:53:51 +02:00
NEZNAMY af1a762048 fixed startup on 1.8.0 2021-03-28 16:50:13 +02:00
NEZNAMY 980daa6f64 small change in method 2021-03-28 00:32:07 +01:00
NEZNAMY 814ba371cb accessing balance only through vault now since it supports essentials too 2021-03-28 00:25:33 +01:00
NEZNAMY 027e1c7330 saving player handle for small optimization 2021-03-28 00:12:19 +01:00
NEZNAMY 585af9656b dropped BungeePerms support as according to bStats no one is using it + assigning by permissions is still available 2021-03-28 00:07:38 +01:00
NEZNAMY 101e84d837 cleanup using String.format 2021-03-27 23:58:49 +01:00
NEZNAMY c0de6137b0 getFeatureType() now returns Object 2021-03-27 23:30:04 +01:00
NEZNAMY eb93a5ae2d fixed NPE 2021-03-27 23:18:02 +01:00
NEZNAMY 77124cd922 small cleanup 2021-03-27 00:56:30 +01:00
NEZNAMY 3b5c844743 apparently this doesn't throw anything 2021-03-27 00:49:42 +01:00
NEZNAMY 8e909011a0 preloading fields for better performance 2021-03-27 00:47:35 +01:00
NEZNAMY 9607720a5d fixed NPE 2021-03-27 00:46:45 +01:00
NEZNAMY 95ac332c85 added a little comment 2021-03-27 00:03:44 +01:00
NEZNAMY df615f1aba small optimization 2021-03-27 00:00:05 +01:00
NEZNAMY 1a60e097f2 moved packet listener to separate anonymous class to not deserialize packet if not needed 2021-03-26 23:51:44 +01:00
NEZNAMY ebcd758e68 split usage type of packet reading into in/out 2021-03-26 23:51:12 +01:00
NEZNAMY 15201af923 added config options to disable anti-override 2021-03-25 20:06:12 +01:00
NEZNAMY e4b43abf11 hiding the error now 2021-03-25 15:02:57 +01:00
NEZNAMY f63cccdb0c readded code that disappeared in previous commits 2021-03-25 15:02:42 +01:00
NEZNAMY b5fd465788 attempt to avoid an issue when hooking into other plugins 2021-03-25 14:59:37 +01:00
NEZNAMY 6c2d05742d added missing check resulting in error spam 2021-03-25 14:59:09 +01:00
NEZNAMY 838422b568 fix from previous vanish fix causing entity tracking range to be ignored 2021-03-24 20:58:01 +01:00
NEZNAMY 4ca327d3d9 ignoring self 2021-03-24 12:45:35 +01:00
NEZNAMY f5dce3db0a 2.8.11-pre8 2021-03-24 12:11:47 +01:00
NEZNAMY aee7de283a removed error message caused by papi placeholders without initial value on bungeecord 2021-03-24 09:13:26 +01:00
NEZNAMY 7d9c65cb41 fixed compatibility with vanishing 2021-03-24 08:33:55 +01:00
NEZNAMY 6140b11185 fixed out of bounds exception 2021-03-24 08:33:38 +01:00
NEZNAMY b8394d2d9a fixed error caused by misconfiguration 2021-03-24 08:10:12 +01:00
NEZNAMY d615f0a5ab adding to used to make sure it updates if used in other plugins via placeholder 2021-03-24 08:09:59 +01:00
NEZNAMY d2249f6788 running position check every 50ms instead of spamming threads on move to fix performance issue 2021-03-23 18:09:44 +01:00
NEZNAMY 7a7707f712 Update README.md 2021-03-22 11:24:02 +01:00
NEZNAMY 54af3d4a4a fixed spacing 2021-03-22 11:23:23 +01:00
NEZNAMY e5fd46a3fa fixed spacing 2021-03-22 11:22:50 +01:00
NEZNAMY 26e5cb6f14 matching limit set by PS bug 2021-03-20 16:30:29 +01:00
NEZNAMY 2c01f64a06 added %player-version-id% 2021-03-20 15:11:59 +01:00
NEZNAMY 79654c344c mighty replace function 2021-03-19 19:28:27 +01:00
NEZNAMY ed9a17320c fixed concurrent modification 2021-03-18 22:51:27 +01:00
NEZNAMY 093b795407 changed instantiation and added register methods 2021-03-16 15:53:07 +01:00
NEZNAMY cf0b0ed8d2 added missing legacy code pattern from PR 2021-03-16 15:47:31 +01:00
NEZNAMY 53503883d1 split class into multiple 2021-03-16 15:40:07 +01:00
NEZNAMY b4eaf9ff1d Merge pull request #284 from VQ9/master
Added an extra gradient pattern in the style of Kyori
2021-03-15 19:44:38 +01:00
NEZNAMY 5f286322cb better performance check 2021-03-15 19:44:39 +01:00
NEZNAMY a1e938626b added try/catch 2021-03-15 18:05:42 +01:00
NEZNAMY 13c0fb769d suffix 2021-03-15 18:03:31 +01:00
NEZNAMY 67f92c9e4c fixed concurrent modification 2021-03-15 18:03:24 +01:00
NEZNAMY f438627063 avoiding weird NPE 2021-03-15 18:03:08 +01:00
NEZNAMY 252458b6d5 fixed NPE 2021-03-15 18:02:46 +01:00
NEZNAMY 5be109f13c removed unnecessary value returning and fixed NPE on reload 2021-03-15 16:22:55 +01:00
NEZNAMY 0ab6691ea6 hiding weird error 2021-03-15 13:44:20 +01:00
NEZNAMY 111b105466 cleanup and color fix 2021-03-15 13:23:48 +01:00
NEZNAMY 1dba805c95 not delaying scoreboard on reload 2021-03-15 13:08:56 +01:00
NEZNAMY a691d69011 more info in error message 2021-03-15 13:08:41 +01:00
NEZNAMY 83e645c1d9 improved structure & NPE fix 2021-03-15 13:08:12 +01:00
NEZNAMY 1090fbb390 added death event listener for cleaner armor stand destroy 2021-03-13 23:25:21 +01:00
NEZNAMY 7669fec40a fixed error leading to RGB not working 2021-03-13 21:58:04 +01:00
NEZNAMY a8ee2a47cb fixed dead players still showing 2021-03-13 21:27:26 +01:00
NEZNAMY 3a159159b0 feature request made in my private messages on discord # 2 2021-03-13 21:07:21 +01:00
NEZNAMY 3827fcd306 feature request made in my private messages on discord 2021-03-13 21:06:40 +01:00
NEZNAMY 1a925b407d added null check 2021-03-13 20:39:48 +01:00
VQ9 568b6bd14d Fixed an issue with legacy 2021-03-13 11:37:32 +00:00
VQ9 94106404ad Added the kyori gradient as a pattern for TAB 2021-03-12 22:59:54 +00:00
NEZNAMY 07d5a52004 2.8.11-pre7 2021-03-11 22:05:22 +01:00
NEZNAMY 60e59c2c9d fixing #258 2021-03-11 20:17:54 +01:00
NEZNAMY d9ab1cf2c8 matching link tree on discord 2021-03-11 19:48:29 +01:00
NEZNAMY 3ab85750ed forgotten change that fixes error on reload 2021-03-10 20:43:28 +01:00
NEZNAMY 13a98f8618 another try/catch to LP hook 2021-03-10 18:15:14 +01:00
NEZNAMY 4cf64beae0 no longer overriding other plugins with toggle command 2021-03-10 18:12:38 +01:00
NEZNAMY 28bba220ad added missing method call from recode 2021-03-10 17:57:21 +01:00
NEZNAMY c71f7645a5 fixed wrong world used when using a plugin that instaTPs someone on join 2021-03-10 17:56:47 +01:00
NEZNAMY 6537c6fcde added command option for silent scoreboard toggle 2021-03-10 17:56:15 +01:00
NEZNAMY 7165c3941a fixed random error 2021-03-10 17:55:10 +01:00
NEZNAMY 31969aa076 apparently this helps 2021-03-10 16:21:30 +01:00
NEZNAMY 7e098cd7d9 random NPE fix 2021-03-10 16:21:15 +01:00
NEZNAMY 8850812994 multi-thread access 2021-03-10 16:20:59 +01:00
NEZNAMY 657d03aaf8 replaced entity id with uuid.hashCode() 2021-03-09 19:46:05 +01:00
NEZNAMY cb293f5f9a reformat 2021-03-08 20:57:57 +01:00
NEZNAMY 388290a90f didn't mean to keep this in 2021-03-08 20:54:33 +01:00
NEZNAMY 218e5964c3 split method into multiple 2021-03-08 20:48:52 +01:00
NEZNAMY 6a9ec46488 moved code to separate method 2021-03-08 20:36:36 +01:00
NEZNAMY 4577fa5d40 created nested class for command and added check for console message 2021-03-08 20:32:58 +01:00
NEZNAMY d571f7de9e minor improvements 2021-03-08 20:29:54 +01:00
NEZNAMY 408cf2ff08 now sending correct tab format immediately without requiring packet interception (still using it for anti-override) 2021-03-08 20:25:22 +01:00
NEZNAMY 4be47b175a added debug message 2021-03-08 20:16:53 +01:00
NEZNAMY d339e41210 NPE fix 2021-03-08 20:15:00 +01:00
NEZNAMY dca7d229ba cleanup 2021-03-08 20:09:22 +01:00
NEZNAMY edb16c3b46 more docs I guess 2021-03-08 17:17:55 +01:00
NEZNAMY f2a5461e15 NPE fix 2021-03-08 14:10:18 +01:00
NEZNAMY 7d325ade95 fixed armor stands being invisible due to old per world playerlist mark on world switch 2021-03-06 18:09:26 +01:00
NEZNAMY 669924fa2a moved code from ITabPlayer to NameTag 2021-03-06 15:41:17 +01:00
NEZNAMY 480af187ac removed unnecessary method definitions 2021-03-06 13:44:06 +01:00
NEZNAMY ceb18d5f1d 2.8.11-pre6 2021-03-05 22:36:31 +01:00
NEZNAMY 93ae2b970b added clone() method 2021-03-05 22:31:01 +01:00
NEZNAMY e55a389d60 added missing parameter forwarding 2021-03-05 21:54:14 +01:00
NEZNAMY 5ff2524e6e changed display name of function 2021-03-05 17:05:26 +01:00
NEZNAMY 35619744b4 removed unused field 2021-03-05 17:05:10 +01:00
NEZNAMY c47e6dc32e more specific function name 2021-03-05 17:02:32 +01:00
NEZNAMY 3f04b185f6 removed fields that are no longer used 2021-03-05 17:01:27 +01:00
NEZNAMY 61818099a1 detecting mount with repeating task instead of packets 2021-03-05 16:55:36 +01:00
NEZNAMY 1bca58ffda added missing check for disabled worlds 2021-03-05 15:13:12 +01:00
NEZNAMY cf506d0f57 no longer sending packets on head rotation only 2021-03-05 15:10:31 +01:00
NEZNAMY 4b16cf5463 moved code to avoid duplication 2021-03-05 15:00:08 +01:00
NEZNAMY 46be09df53 changed 3 packet listeners to event listeners 2021-03-05 14:58:02 +01:00
NEZNAMY 0e564f01d1 added new chart since automatic classes do not offer "premium" in version like manually adding the IF 2021-03-02 21:45:05 +01:00
NEZNAMY 9326231d21 added method to register custom condition types 2021-03-02 13:34:54 +01:00
NEZNAMY 0bd3098fc9 changed plugin id in metrics (submitted plugin under different name) 2021-03-02 13:33:49 +01:00
NEZNAMY eb5286756d added cancelled parameter to chat event forwarding 2021-03-01 23:26:56 +01:00
NEZNAMY 116b8d45c1 split method into 2 2021-03-01 18:55:01 +01:00
NEZNAMY bff4c83f30 cleanup? 2021-03-01 18:51:18 +01:00
NEZNAMY be9316de10 they didn't understand that message anyway 2021-03-01 18:45:44 +01:00
NEZNAMY 98d87a9e44 changed ifs to switch 2021-03-01 18:45:00 +01:00
NEZNAMY ac3e1cc524 moved announce thread to separate method 2021-03-01 18:28:11 +01:00
NEZNAMY 18c8260a95 split method into multiple 2021-03-01 18:24:18 +01:00
NEZNAMY 18b88bb6f7 renamed package bungee -> bungeecord to match bStats folder name 2021-03-01 18:17:44 +01:00
NEZNAMY a90383c79e deleted class and moved content to main class 2021-03-01 18:14:56 +01:00
NEZNAMY 144b47ec78 removed unused parameter 2021-03-01 17:35:01 +01:00
NEZNAMY 8cefe8a179 split method into multiple 2021-03-01 17:24:08 +01:00
NEZNAMY d3f15538e3 removed manual bstats classes and using maven shade where possible 2021-03-01 17:06:05 +01:00
NEZNAMY 759a4929de moved variables on top of class 2021-03-01 16:17:05 +01:00
NEZNAMY eca1c5a920 moved duplicated code to a separate method 2021-03-01 16:12:57 +01:00
NEZNAMY 03fce279e7 merged ifs 2021-03-01 16:04:12 +01:00
NEZNAMY 1d225022b4 created subclasses instead of using anonymous classes 2021-03-01 16:02:51 +01:00
NEZNAMY d5db4da02e merged ifs 2021-03-01 15:54:20 +01:00
NEZNAMY 553595a0b4 added helper method that significantly reduces method complexity 2021-03-01 15:52:38 +01:00
NEZNAMY 3aff395370 updated message 2021-03-01 15:49:50 +01:00
NEZNAMY 4e71189bd8 merged ifs 2021-03-01 15:31:22 +01:00
NEZNAMY 81eb4b1a19 method cleanup 2021-03-01 15:30:14 +01:00
NEZNAMY a640bcaa87 moved code into separate method 2021-03-01 15:21:48 +01:00
NEZNAMY be24019cd5 added 55001-60000 2021-03-01 13:48:14 +01:00
NEZNAMY 7be675d101 added 50001-55000 2021-03-01 12:52:00 +01:00
NEZNAMY c6b39a9650 more user-friendly messages when file creation fails 2021-03-01 12:51:46 +01:00
NEZNAMY 5ac1ad0975 added try/catch because it's needed apparently 2021-03-01 12:51:11 +01:00
NEZNAMY 69f4744640 PlaceholderManager is null on server startup into broken file 2021-03-01 10:28:29 +01:00
NEZNAMY 46a34dd765 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2021-02-27 16:23:29 +01:00
NEZNAMY c2a68ef8ad Merge pull request #281 from msengbusch/master
Fix using wrong cpu usage deserialization packet type
2021-02-27 16:23:04 +01:00
minecraftplayer456 75dc5db149 Fix using wrong cpu deserialization packet usage type 2021-02-27 16:08:20 +01:00
NEZNAMY 81a483c74b fixed per-world-playerlist bug when several players were teleported at once 2021-02-27 15:16:52 +01:00
NEZNAMY 9f3995198b added more checks in LP hook 2021-02-27 13:56:45 +01:00
NEZNAMY 18f0384fd1 added isOnline() method to TabPlayer 2021-02-27 13:56:28 +01:00
NEZNAMY 87d43c08fe fixed #278 2021-02-27 12:03:47 +01:00
NEZNAMY 2f0c8cccd6 fixed error when text was equal to " 2021-02-27 11:12:18 +01:00
NEZNAMY d8796b291a moved some code 2021-02-21 15:39:44 +01:00
NEZNAMY 9a321e4106 2.8.11-pre5 2021-02-21 15:21:49 +01:00
NEZNAMY 0e7a445fbe added support for thermos & forks 2021-02-21 15:10:13 +01:00
NEZNAMY 84dd4b0571 shorter alternative 2021-02-21 12:32:17 +01:00
NEZNAMY 12f5c4b33d added check for animation refresh interval instead of throwing an error on load 2021-02-19 22:12:36 +01:00
NEZNAMY 0ab9063d30 added unregisterFeature method 2021-02-19 22:12:15 +01:00
NEZNAMY 9981f1c64c added check to prevent errors 2021-02-19 13:56:19 +01:00
NEZNAMY c0768a40af added info into console 2021-02-19 13:55:56 +01:00
NEZNAMY af343c80c0 no longer sending the message for every online player 2021-02-19 13:33:56 +01:00
NEZNAMY 29c6035fb8 moved same code to universal class and cleanups 2021-02-18 12:39:58 +01:00
NEZNAMY bd3b8c3fc3 fixed compatibility with player riding plugins 2021-02-18 10:48:29 +01:00
NEZNAMY 2742a12de1 added and used SneakEventListener interface
will also be used in addon
2021-02-17 21:30:45 +01:00
NEZNAMY b49e38c19b fixed bug - world specific armor stands didn't load 2021-02-17 20:55:00 +01:00
NEZNAMY 357436a3ca added metrics to that thing 2021-02-17 17:02:39 +01:00
NEZNAMY a0bbc9cdd7 added check for invalid PS API result 2021-02-17 10:32:24 +01:00
NEZNAMY 0501bb5c33 added console warn 2021-02-17 10:31:55 +01:00
NEZNAMY def58303fa renamed the option and made it public 2021-02-16 22:38:09 +01:00
NEZNAMY d57b03a1cd added ChatEventListener feature interface 2021-02-16 19:18:01 +01:00
NEZNAMY e3c30d3b3e changed feature interface structure 2021-02-16 18:56:41 +01:00
NEZNAMY 5671f80cf5 fixed NPE when action doe not require text 2021-02-16 17:51:21 +01:00
NEZNAMY d7714f4145 fixed NPE when plugin didn't load due to compatibility issue and didn't set instance 2021-02-16 17:51:07 +01:00
304 changed files with 81388 additions and 63668 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://paypal.me/neznamy1'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+36
View File
@@ -0,0 +1,36 @@
name: Build
on:
push:
branches:
- master
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 Maven packages
uses: actions/cache@v1
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
+4 -5
View File
@@ -1,8 +1,7 @@
# Compiled class file
*.class
/bin/
/target/
/src/test/
bin/
target/
# Log file
*.log
@@ -31,8 +30,8 @@ hs_err_pid*
.project
# intellij
/.idea/
.idea/
# other
.gitattributes
*.iml
*.iml
+18 -20
View File
@@ -1,28 +1,26 @@
## 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.
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. Or just get premium version for free.
## Compiling
Plugin can be compiled using maven.
## 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).
Plugin can be compiled using maven. Once you've purchased the premium version, you can enable premium features by setting [this line to return true](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/TAB.java#L82) and apply your code improvements. Enabling it without purchasing the plugin may result in the features not working correctly.
**No support is provided for self-compiled jars, only for official compilations.** If you compiled it yourself, it is highly likely you modified something, which might be the reason why is the plugin not working. If you didn't modify anything, you can just get the plugin from official download locations.
## Links
Discord: https://discord.gg/EaSvdk6
SpigotMC (free): https://www.spigotmc.org/resources/57806/
MC-Market (paid): https://www.mc-market.org/resources/14009/
Wiki: https://github.com/NEZNAMY/TAB/wiki
## Downloads
Official release: https://www.spigotmc.org/resources/57806/
Pre-releases: https://github.com/NEZNAMY/TAB/releases
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
## Maven Repository
```
<repositories>
@@ -36,8 +34,8 @@ Pre-releases: https://github.com/NEZNAMY/TAB/releases
<!-- TAB -->
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>TAB-API</artifactId>
<version>2.8.10</version>
<artifactId>TAB</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
```
```
+150
View File
@@ -0,0 +1,150 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.neznamy</groupId>
<artifactId>TAB</artifactId>
<version>2.9.2-pre2</version>
</parent>
<artifactId>bukkit</artifactId>
<repositories>
<!-- Spigot API -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<!-- PlaceholderAPI -->
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
<!-- Vault -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<!-- ViaVersion -->
<repository>
<id>viaversion</id>
<url>https://repo.viaversion.com/</url>
</repository>
<repository>
<id>aikar</id>
<url>https://repo.aikar.co/content/groups/aikar/</url>
</repository>
</repositories>
<dependencies>
<!-- TAB Core -->
<dependency>
<groupId>me.neznamy</groupId>
<artifactId>shared</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>YamlAssist</artifactId>
</exclusion>
<exclusion>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PlaceholderAPI -->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.10.9</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Vault -->
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- ViaVersion -->
<dependency>
<groupId>com.viaversion</groupId>
<artifactId>viaversion-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>
<version>1.11</version>
<scope>provided</scope>
</dependency>
<!-- Bukkit Metrics -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -7,7 +7,6 @@ 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 me.neznamy.tab.shared.TAB;
@@ -33,7 +32,22 @@ 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", () -> {
try {
int i = 0;
int version;
while ((version = Main.getProtocolVersion(e.getPlayer())) == -1 && ++i < 100) {
Thread.sleep(2);
}
if (!e.getPlayer().isOnline()) return;
if (i == 50) {
TAB.getInstance().getErrorManager().printError("Failed to get protocol version of " + e.getPlayer().getName() + " after 50 retries");
}
TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), version));
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
}
});
}
/**
@@ -43,7 +57,7 @@ public class BukkitEventListener implements Listener {
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e){
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask("processing PlayerChangedWorldEvent", () -> TAB.getInstance().getFeatureManager().onWorldChange(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getPlayer().getWorld().getName()));
TAB.getInstance().getCPUManager().runTask("processing PlayerChangedWorldEvent", () -> TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
}
/**
@@ -55,14 +69,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 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())));
}
}
@@ -0,0 +1,593 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
import com.viaversion.viaversion.api.type.Type;
import com.viaversion.viaversion.libs.gson.JsonParser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.packets.EnumChatFormat;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
import me.neznamy.tab.shared.packets.IChatBaseComponent.ClickAction;
import me.neznamy.tab.shared.packets.PacketBuilder;
import me.neznamy.tab.shared.packets.PacketPlayOutBoss;
import me.neznamy.tab.shared.packets.PacketPlayOutBoss.Action;
import me.neznamy.tab.shared.packets.PacketPlayOutChat;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardScore;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.shared.rgb.TextColor;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitPacketBuilder implements PacketBuilder {
//nms storage
private NMSStorage nms;
//entity type ids
private EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/**
* Constructs new instance with given parameter
* @param nms - nms storage
*/
public BukkitPacketBuilder(NMSStorage nms) {
this.nms = nms;
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);
}
}
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
if (nms.getMinorVersion() >= 9) {
//1.9+ server, handled using bukkit api
return packet;
}
if (clientVersion.getMinorVersion() >= 9 && Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
//1.9+ client on 1.8 server
//technically redundant VV check as there is no other way to get 1.9 client on 1.8 server
return buildBossPacketVia(packet, clientVersion);
}
//<1.9 client and server
return buildBossPacketEntity(packet, clientVersion);
}
/**
* Builds 1.9 bossbar packet bytebuf using viaversion
* @param packet - packet to build
* @param clientVersion - client version
* @return bytebuf with 1.9 bossbar packet content
*/
private ByteBuf buildBossPacketVia(PacketPlayOutBoss packet, ProtocolVersion clientVersion) {
if (clientVersion == ProtocolVersion.UNKNOWN) return null; //preventing disconnect if packet ID changes and users do not update
try {
ByteBuf buf = Unpooled.buffer();
Type.VAR_INT.writePrimitive(buf, clientVersion.getMinorVersion() == 15 || clientVersion.getMinorVersion() >= 17 ? 0x0D : 0x0C);
Type.UUID.write(buf, packet.getId());
Type.VAR_INT.writePrimitive(buf, packet.getOperation().ordinal());
switch (packet.getOperation()) {
case ADD:
Type.COMPONENT.write(buf, JsonParser.parseString(IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion)));
Type.FLOAT.writePrimitive(buf, packet.getPct());
Type.VAR_INT.writePrimitive(buf, packet.getColor().ordinal());
Type.VAR_INT.writePrimitive(buf, packet.getOverlay().ordinal());
Type.BYTE.write(buf, packet.getFlags());
break;
case REMOVE:
break;
case UPDATE_PCT:
Type.FLOAT.writePrimitive(buf, packet.getPct());
break;
case UPDATE_NAME:
Type.COMPONENT.write(buf, JsonParser.parseString(IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion)));
break;
case UPDATE_STYLE:
Type.VAR_INT.writePrimitive(buf, packet.getColor().ordinal());
Type.VAR_INT.writePrimitive(buf, packet.getOverlay().ordinal());
break;
case UPDATE_PROPERTIES:
Type.BYTE.write(buf, packet.getFlags());
break;
default:
break;
}
return buf;
} catch (Exception t) {
return TAB.getInstance().getErrorManager().printError(null, "Failed to create 1.9 bossbar packet using ViaVersion v" + Bukkit.getPluginManager().getPlugin("ViaVersion").getDescription().getVersion() + ". Is it the latest version?", t);
}
}
/**
* Builds entity bossbar packet
* @param packet - packet to build
* @param clientVersion - client version
* @return entity bossbar packet
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
if (packet.getOperation() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getOperation() == Action.REMOVE) {
return buildEntityDestroyPacket(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 buildEntitySpawnPacket(entityId, null, EntityType.WITHER, new Location(null, 0,0,0), w);
} else {
return buildEntityMetadataPacket(entityId, w);
}
}
@Override
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
Object component = stringToComponent(packet.getMessage().toString(clientVersion));
Constructor<?> c = nms.getConstructor("PacketPlayOutChat");
if (nms.getMinorVersion() >= 16) {
return c.newInstance(component, nms.getEnum("ChatMessageType")[packet.getType().ordinal()], UUID.randomUUID());
}
if (nms.getMinorVersion() >= 12) {
return c.newInstance(component, Enum.valueOf((Class<Enum>) nms.getClass("ChatMessageType"), packet.getType().toString()));
}
if (nms.getMinorVersion() >= 8) {
return c.newInstance(component, (byte) packet.getType().ordinal());
}
if (nms.getMinorVersion() == 7) {
return c.newInstance(component);
}
throw new IllegalStateException("Not supported on <1.7");
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException, NegativeArraySizeException {
if (nms.getMinorVersion() < 8) return null;
Object nmsPacket = nms.getConstructor("PacketPlayOutPlayerInfo").newInstance(nms.getEnum("EnumPlayerInfoAction")[packet.getAction().ordinal()], Array.newInstance(nms.getClass("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.getConstructor("PlayerInfoData").getParameterCount() == 5) {
parameters.add(nmsPacket);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : nms.getEnum("EnumGamemode")[nms.getEnum("EnumGamemode").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.getConstructor("PlayerInfoData").newInstance(parameters.toArray()));
}
nms.setField(nmsPacket, "PacketPlayOutPlayerInfo_PLAYERS", items);
return nmsPacket;
}
@Override
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
if (nms.getMinorVersion() < 8) return null;
if (nms.getMinorVersion() >= 17) {
return nms.getConstructor("PacketPlayOutPlayerListHeaderFooter").newInstance(toNMSComponent(packet.getHeader(), clientVersion), toNMSComponent(packet.getFooter(), clientVersion));
}
Object nmsPacket = nms.getConstructor("PacketPlayOutPlayerListHeaderFooter").newInstance();
nms.setField(nmsPacket, "PacketPlayOutPlayerListHeaderFooter_HEADER", toNMSComponent(packet.getHeader(), clientVersion));
nms.setField(nmsPacket, "PacketPlayOutPlayerListHeaderFooter_FOOTER", toNMSComponent(packet.getFooter(), clientVersion));
return nmsPacket;
}
@Override
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
return nms.getConstructor("PacketPlayOutScoreboardDisplayObjective").newInstance(packet.getSlot(), newScoreboardObjective(packet.getObjectiveName()));
}
@Override
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
String displayName = clientVersion.getMinorVersion() < 13 ? cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
if (nms.getMinorVersion() >= 13) {
return nms.getConstructor("PacketPlayOutScoreboardObjective").newInstance(nms.getConstructor("ScoreboardObjective").newInstance(null, packet.getObjectiveName(), null,
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
packet.getRenderType() == null ? null : nms.getEnum("EnumScoreboardHealthDisplay")[packet.getRenderType().ordinal()]),
packet.getMethod());
}
Object nmsPacket = nms.getConstructor("PacketPlayOutScoreboardObjective").newInstance();
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_OBJECTIVENAME", packet.getObjectiveName());
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_DISPLAYNAME", displayName);
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_RENDERTYPE", Enum.valueOf((Class<Enum>) nms.getClass("EnumScoreboardHealthDisplay"), packet.getRenderType().toString()));
}
nms.setField(nmsPacket, "PacketPlayOutScoreboardObjective_METHOD", packet.getMethod());
return nmsPacket;
}
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
if (nms.getMinorVersion() >= 13) {
return nms.getConstructor("PacketPlayOutScoreboardScore_1_13").newInstance(nms.getEnum("EnumScoreboardAction")[packet.getAction().ordinal()], packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
}
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
return nms.getConstructor("PacketPlayOutScoreboardScore_String").newInstance(packet.getPlayer());
}
Object score = nms.getConstructor("ScoreboardScore").newInstance(nms.getConstructor("Scoreboard").newInstance(), newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
nms.getMethod("ScoreboardScore_setScore").invoke(score, packet.getScore());
if (nms.getMinorVersion() >= 8) {
return nms.getConstructor("PacketPlayOutScoreboardScore").newInstance(score);
}
return nms.getConstructor("PacketPlayOutScoreboardScore").newInstance(score, 0);
}
@Override
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, SecurityException {
Object team;
if (nms.getMinorVersion() >= 13) {
team = createTeamModern(packet, clientVersion);
} else {
team = createTeamLegacy(packet, clientVersion);
}
if (nms.getMinorVersion() >= 17) {
switch (packet.getMethod()) {
case 0:
return nms.getMethod("PacketPlayOutScoreboardTeam_ofBoolean").invoke(null, team, true);
case 1:
return nms.getMethod("PacketPlayOutScoreboardTeam_of").invoke(null, team);
case 2:
return nms.getMethod("PacketPlayOutScoreboardTeam_ofBoolean").invoke(null, team, false);
case 3:
return nms.getMethod("PacketPlayOutScoreboardTeam_ofString").invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.getEnum("PacketPlayOutScoreboardTeam_a")[0]);
case 4:
return nms.getMethod("PacketPlayOutScoreboardTeam_ofString").invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.getEnum("PacketPlayOutScoreboardTeam_a")[1]);
default:
throw new IllegalArgumentException("Invalid action: " + packet.getMethod());
}
}
return nms.getConstructor("PacketPlayOutScoreboardTeam").newInstance(team, packet.getMethod());
}
private Object createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException {
String prefix = packet.getPlayerPrefix();
String suffix = packet.getPlayerSuffix();
if (clientVersion.getMinorVersion() < 13) {
prefix = cutTo(prefix, 16);
suffix = cutTo(suffix, 16);
}
Object team = nms.getConstructor("ScoreboardTeam").newInstance(nms.getConstructor("Scoreboard").newInstance(), packet.getName());
((Collection<String>)nms.getMethod("ScoreboardTeam_getPlayerNameSet").invoke(team)).addAll(packet.getPlayers());
if (prefix != null && prefix.length() > 0) nms.getMethod("ScoreboardTeam_setPrefix").invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null && suffix.length() > 0) nms.getMethod("ScoreboardTeam_setSuffix").invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
nms.getMethod("ScoreboardTeam_setColor").invoke(team, nms.getEnum("EnumChatFormat")[format.ordinal()]);
nms.getMethod("ScoreboardTeam_setNameTagVisibility").invoke(team, String.valueOf(packet.getNametagVisibility()).equals("always") ? nms.getEnum("EnumNameTagVisibility")[0] : nms.getEnum("EnumNameTagVisibility")[1]);
nms.getMethod("ScoreboardTeam_setCollisionRule").invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.getEnum("EnumTeamPush")[0] : nms.getEnum("EnumTeamPush")[1]);
return team;
}
private Object createTeamLegacy(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException, InstantiationException, SecurityException {
String prefix = packet.getPlayerPrefix();
String suffix = packet.getPlayerSuffix();
if (clientVersion.getMinorVersion() < 13) {
prefix = cutTo(prefix, 16);
suffix = cutTo(suffix, 16);
}
Object team = nms.getConstructor("ScoreboardTeam").newInstance(nms.getConstructor("Scoreboard").newInstance(), packet.getName());
((Collection<String>)nms.getMethod("ScoreboardTeam_getPlayerNameSet").invoke(team)).addAll(packet.getPlayers());
if (prefix != null) nms.getMethod("ScoreboardTeam_setPrefix").invoke(team, prefix);
if (suffix != null) nms.getMethod("ScoreboardTeam_setSuffix").invoke(team, suffix);
if (nms.getMinorVersion() >= 8) nms.getMethod("ScoreboardTeam_setNameTagVisibility").invoke(team, String.valueOf(packet.getNametagVisibility()).equals("always") ? nms.getEnum("EnumNameTagVisibility")[0] : nms.getEnum("EnumNameTagVisibility")[1]);
if (nms.getMinorVersion() >= 9) nms.getMethod("ScoreboardTeam_setCollisionRule").invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.getEnum("EnumTeamPush")[0] : nms.getEnum("EnumTeamPush")[1]);
return team;
}
/**
* Builds entity destroy packet with given parameter
* @param id - entity id to destroy
* @return destroy packet
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Object buildEntityDestroyPacket(int id) throws InstantiationException, IllegalAccessException, InvocationTargetException {
if (nms.getMinorVersion() >= 17) {
return nms.getConstructor("PacketPlayOutEntityDestroy").newInstance(id);
}
return nms.getConstructor("PacketPlayOutEntityDestroy").newInstance(new int[] {id});
}
/**
* Builds entity metadata packet with given parameters
* @param entityId - entity id
* @param dataWatcher - datawatcher
* @return metadata packet
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Object buildEntityMetadataPacket(int entityId, DataWatcher dataWatcher) throws InstantiationException, IllegalAccessException, InvocationTargetException {
return nms.getConstructor("PacketPlayOutEntityMetadata").newInstance(entityId, dataWatcher.toNMS(), true);
}
/**
* Builds entity spawn packet with given parameters
* @param entityId - entity id
* @param uuid - entity uuid
* @param entityType - entity type
* @param loc - location to spawn at
* @param dataWatcher - datawatcher
* @return entity spawn packet
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
public Object buildEntitySpawnPacket(int entityId, UUID uuid, EntityType entityType, Location loc, DataWatcher dataWatcher) throws IllegalAccessException, InstantiationException, InvocationTargetException {
Object nmsPacket = nms.getConstructor("PacketPlayOutSpawnEntityLiving").newInstance(nms.getMethod("getHandle").invoke(Bukkit.getOnlinePlayers().iterator().next()));
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_ENTITYID", entityId);
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_ENTITYTYPE", entityIds.get(entityType));
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_YAW", (byte)(loc.getYaw() * 256.0f / 360.0f));
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_PITCH", (byte)(loc.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_DATAWATCHER", dataWatcher.toNMS());
}
if (nms.getMinorVersion() >= 9) {
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_UUID", uuid);
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_X", loc.getX());
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Y", loc.getY());
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Z", loc.getZ());
} else {
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_X", floor(loc.getX()*32));
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Y", floor(loc.getY()*32));
nms.setField(nmsPacket, "PacketPlayOutSpawnEntityLiving_Z", floor(loc.getZ()*32));
}
return nmsPacket;
}
/**
* Builds entity teleport packet with given parameters
* @param entityId - entity id
* @param location - location to teleport to
* @return entity teleport packet
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
public Object buildEntityTeleportPacket(int entityId, Location location) throws IllegalAccessException, InstantiationException, InvocationTargetException {
Object nmsPacket = nms.getConstructor("PacketPlayOutEntityTeleport").newInstance(nms.getMethod("getHandle").invoke(Bukkit.getOnlinePlayers().iterator().next()));
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_ENTITYID", entityId);
if (nms.getMinorVersion() >= 9) {
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_X", location.getX());
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Y", location.getY());
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Z", location.getZ());
} else {
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_X", floor(location.getX()*32));
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Y", floor(location.getY()*32));
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_Z", floor(location.getZ()*32));
}
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_YAW", (byte) (location.getYaw()/360*256));
nms.setField(nmsPacket, "PacketPlayOutEntityTeleport_PITCH", (byte) (location.getPitch()/360*256));
return nmsPacket;
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws IllegalAccessException, InvocationTargetException {
if (nms.getMinorVersion() < 8) return null;
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.getField("PacketPlayOutPlayerInfo_ACTION").get(nmsPacket).toString());
List<PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) nms.getField("PacketPlayOutPlayerInfo_PLAYERS").get(nmsPacket)) {
Object nmsGamemode = nms.getMethod("PlayerInfoData_getGamemode").invoke(nmsData);
EnumGamemode gamemode = (nmsGamemode == null) ? null : EnumGamemode.valueOf(nmsGamemode.toString());
GameProfile profile = (GameProfile) nms.getMethod("PlayerInfoData_getProfile").invoke(nmsData);
Object nmsComponent = nms.getMethod("PlayerInfoData_getDisplayName").invoke(nmsData);
IChatBaseComponent listName = nmsComponent == null ? null : fromNMSComponent(nmsComponent);
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), profile.getProperties(), (int) nms.getMethod("PlayerInfoData_getLatency").invoke(nmsData), gamemode, listName));
}
return new PacketPlayOutPlayerInfo(action, listData);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket, ProtocolVersion clientVersion) throws IllegalAccessException {
String objective = (String) nms.getField("PacketPlayOutScoreboardObjective_OBJECTIVENAME").get(nmsPacket);
String displayName;
Object component = nms.getField("PacketPlayOutScoreboardObjective_DISPLAYNAME").get(nmsPacket);
if (nms.getMinorVersion() >= 13) {
IChatBaseComponent c = component == null ? null : fromNMSComponent(component);
displayName = c == null ? null : c.toLegacyText();
} else {
displayName = (String) component;
}
EnumScoreboardHealthDisplay renderType = null;
if (nms.getMinorVersion() >= 8) {
Object nmsRender = nms.getField("PacketPlayOutScoreboardObjective_RENDERTYPE").get(nmsPacket);
if (nmsRender != null) {
renderType = EnumScoreboardHealthDisplay.valueOf(nmsRender.toString());
}
}
int method = nms.getField("PacketPlayOutScoreboardObjective_METHOD").getInt(nmsPacket);
return new PacketPlayOutScoreboardObjective(method, objective, displayName, renderType);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket, ProtocolVersion clientVersion) throws IllegalAccessException {
return new PacketPlayOutScoreboardDisplayObjective(
nms.getField("PacketPlayOutScoreboardDisplayObjective_POSITION").getInt(nmsPacket),
(String) nms.getField("PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME").get(nmsPacket)
);
}
/**
* A method yoinked 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 json string into a component
* @param json json as string
* @return NMS component
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public Object stringToComponent(String json) throws IllegalAccessException, InvocationTargetException {
if (json == null) return null;
return nms.getMethod("ChatSerializer_DESERIALIZE").invoke(null, json);
}
/**
* Converts minecraft IChatBaseComponent into TAB's component class. Currently does not support hover event.
* @param component - component to convert
* @return converted component
* @throws IllegalAccessException
*/
public IChatBaseComponent fromNMSComponent(Object component) throws IllegalAccessException {
long time = System.nanoTime();
IChatBaseComponent obj = fromNMSComponent0(component);
TAB.getInstance().getCPUManager().addMethodTime("fromNMSComponent", System.nanoTime()-time);
return obj;
}
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
public IChatBaseComponent fromNMSComponent0(Object component) throws IllegalAccessException {
if (!nms.getClass("ChatComponentText").isInstance(component)) return null; //paper
IChatBaseComponent chat = new IChatBaseComponent((String) nms.getField("ChatComponentText_text").get(component));
Object modifier = nms.getField("ChatBaseComponent_modifier").get(component);
if (modifier != null) {
Object color = nms.getField("ChatModifier_color").get(modifier);
if (color != null) {
if (nms.getMinorVersion() >= 16) {
String name = (String) nms.getField("ChatHexColor_name").get(color);
if (name != null) {
//legacy code
chat.setColor(new TextColor(EnumChatFormat.valueOf(name.toUpperCase())));
} else {
int rgb = (int) nms.getField("ChatHexColor_rgb").get(color);
chat.setColor(new TextColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF));
}
} else {
chat.setColor(new TextColor(EnumChatFormat.valueOf(((Enum)color).name())));
}
}
chat.setBold((Boolean) nms.getField("ChatModifier_bold").get(modifier));
chat.setItalic((Boolean) nms.getField("ChatModifier_italic").get(modifier));
chat.setObfuscated((Boolean) nms.getField("ChatModifier_obfuscated").get(modifier));
chat.setStrikethrough((Boolean) nms.getField("ChatModifier_strikethrough").get(modifier));
chat.setUnderlined((Boolean) nms.getField("ChatModifier_underlined").get(modifier));
Object clickEvent = nms.getField("ChatModifier_clickEvent").get(modifier);
if (clickEvent != null) {
chat.onClick(ClickAction.valueOf(nms.getField("ChatClickable_action").get(clickEvent).toString().toUpperCase()), (String) nms.getField("ChatClickable_value").get(clickEvent));
}
}
for (Object extra : (List<Object>) nms.getField("ChatBaseComponent_extra").get(component)) {
chat.addExtra(fromNMSComponent0(extra));
}
return chat;
}
/**
* 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 InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws InstantiationException, IllegalAccessException, InvocationTargetException {
long time = System.nanoTime();
Object obj = toNMSComponent0(component, clientVersion);
TAB.getInstance().getCPUManager().addMethodTime("toNMSComponent", System.nanoTime()-time);
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 InstantiationException, IllegalAccessException, InvocationTargetException {
if (component == null) return null;
Object chat = nms.getConstructor("ChatComponentText").newInstance(component.getText());
Object modifier;
if (nms.getMinorVersion() >= 16) {
Object color = null;
if (component.getColor() != null) {
if (clientVersion.getMinorVersion() >= 16) {
color = nms.getMethod("ChatHexColor_ofInt").invoke(null, (component.getColor().getRed() << 16) + (component.getColor().getGreen() << 8) + component.getColor().getBlue());
} else {
color = nms.getMethod("ChatHexColor_ofString").invoke(null, component.getColor().getLegacyColor().toString().toLowerCase());
}
}
modifier = nms.getConstructor("ChatModifier").newInstance(
color,
component.getBold(),
component.getItalic(),
component.getUnderlined(),
component.getStrikethrough(),
component.getObfuscated(),
component.getClickAction() == null ? null : nms.getConstructor("ChatClickable").newInstance(nms.getMethod("EnumClickAction_a").invoke(null, component.getClickAction().toString().toLowerCase()), component.getClickValue().toString()),
null, //need to make it for 1.16
null, null);
} else {
modifier = nms.getConstructor("ChatModifier").newInstance();
if (component.getClickAction() != null){
nms.setField(modifier, "ChatModifier_clickEvent", nms.getConstructor("ChatClickable").newInstance(nms.getMethod("EnumClickAction_a").invoke(null, component.getClickAction().toString().toLowerCase()), component.getClickValue().toString()));
}
if (component.getColor() != null) nms.setField(modifier, "ChatModifier_color", Enum.valueOf((Class<Enum>) nms.getClass("EnumChatFormat"), component.getColor().getLegacyColor().toString()));
nms.setField(modifier, "ChatModifier_bold", component.getBold());
nms.setField(modifier, "ChatModifier_italic", component.getItalic());
nms.setField(modifier, "ChatModifier_underlined", component.getUnderlined());
nms.setField(modifier, "ChatModifier_strikethrough", component.getStrikethrough());
nms.setField(modifier, "ChatModifier_obfuscated", component.getObfuscated());
}
nms.setField(chat, "ChatBaseComponent_modifier", modifier);
for (IChatBaseComponent extra : component.getExtra()) {
nms.getMethod("ChatComponentText_addSibling").invoke(chat, toNMSComponent0(extra, clientVersion));
}
return chat;
}
private Object newScoreboardObjective(String objectiveName) throws InstantiationException, IllegalAccessException, InvocationTargetException {
if (nms.getMinorVersion() >= 13) {
return nms.getConstructor("ScoreboardObjective").newInstance(null, objectiveName, null, stringToComponent("{\"text\":\"\"}"), null);
}
return nms.getConstructor("ScoreboardObjective").newInstance(null, objectiveName, nms.getFields(nms.getClass("IScoreboardCriteria"), nms.getClass("IScoreboardCriteria")).get(0).get(null));
}
}
@@ -0,0 +1,144 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.NoSuchElementException;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.PipelineInjector;
public class BukkitPipelineInjector extends PipelineInjector {
//handler to inject before
private static final String INJECT_POSITION = "packet_handler";
//nms storage
private NMSStorage nms;
/**
* Constructs new instance with given parameters
* @param tab - tab instance
* @param nms - nms storage
*/
public BukkitPipelineInjector(TAB tab, NMSStorage nms){
super(tab);
this.nms = nms;
}
@Override
public void inject(TabPlayer player) {
if (!player.getChannel().pipeline().names().contains(INJECT_POSITION)) {
//fake player or waterfall bug
return;
}
uninject(player);
try {
player.getChannel().pipeline().addBefore(INJECT_POSITION, DECODER_NAME, new BukkitChannelDuplexHandler(player));
} catch (NoSuchElementException | IllegalArgumentException e) {
//idk how does this keep happening but whatever
}
}
@Override
public void uninject(TabPlayer player) {
try {
if (player.getChannel().pipeline().names().contains(DECODER_NAME)) player.getChannel().pipeline().remove(DECODER_NAME);
} catch (NoSuchElementException e) {
//for whatever reason this rarely throws
//java.util.NoSuchElementException: TABReader
}
}
/**
* Custom channel duplex handler override
*/
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
//injected player
private TabPlayer player;
/**
* Constructs new instance with given player
* @param player - player to inject
*/
public BukkitChannelDuplexHandler(TabPlayer player) {
this.player = player;
}
@Override
public void channelRead(ChannelHandlerContext context, Object packet) {
try {
Object modifiedPacket = tab.getFeatureManager().onPacketReceive(player, packet);
if (modifiedPacket != null) super.channelRead(context, modifiedPacket);
} catch (Exception e){
tab.getErrorManager().printError("An error occurred when reading packets", e);
}
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (nms.getClass("PacketPlayOutPlayerInfo").isInstance(packet)) {
super.write(context, tab.getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (tab.getFeatureManager().getNameTagFeature() != null && antiOverrideTeams && nms.getClass("PacketPlayOutScoreboardTeam").isInstance(packet)) {
modifyPlayers(packet);
super.write(context, packet, channelPromise);
return;
}
if (nms.getClass("PacketPlayOutScoreboardDisplayObjective").isInstance(packet) && antiOverrideObjectives && tab.getFeatureManager().onDisplayObjective(player, packet)) {
return;
}
if (antiOverrideObjectives && nms.getClass("PacketPlayOutScoreboardObjective").isInstance(packet)) {
tab.getFeatureManager().onObjective(player, packet);
}
tab.getFeatureManager().onPacketSend(player, packet);
} catch (Exception e){
tab.getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
tab.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 IllegalAccessException
*/
@SuppressWarnings("unchecked")
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws IllegalAccessException {
long time = System.nanoTime();
Collection<String> players = (Collection<String>) nms.getField("PacketPlayOutScoreboardTeam_PLAYERS").get(packetPlayOutScoreboardTeam);
String teamName = (String) nms.getField("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 = tab.getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
if (!tab.getFeatureManager().getNameTagFeature().getPlayersInDisabledWorlds().contains(p) && !p.hasTeamHandlingPaused() &&
!p.getTeamName().equals(teamName)) {
logTeamOverride(teamName, entry);
} else {
newList.add(entry);
}
}
nms.setField(packetPlayOutScoreboardTeam, "PacketPlayOutScoreboardTeam_PLAYERS", newList);
tab.getCPUManager().addTime(TabFeature.NAMETAGS, UsageType.ANTI_OVERRIDE, System.nanoTime()-time);
}
}
}
@@ -1,11 +1,9 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
@@ -13,10 +11,12 @@ import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.Placeholder;
@@ -31,40 +31,60 @@ import net.milkbowl.vault.economy.Economy;
*/
public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
//formatter for 2 decimal places
public final DecimalFormat decimal2 = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.US));
//plugin instance
private JavaPlugin plugin;
//vault economy
private Economy economy;
//vault chat
private Chat chat;
//placeholder registry buffer
private List<Placeholder> placeholders;
private Object essentials;
//essentials methods
private Method essGetUser;
private Method essGetNickname;
private Method essIsAfk;
private Method purpurIsAfk;
/**
* Constructs new instance with given parameter
* @param plugin - plugin instance
*/
public BukkitPlaceholderRegistry(JavaPlugin plugin) {
decimal2.applyPattern("#.##");
this.plugin = plugin;
essentials = Bukkit.getPluginManager().getPlugin("Essentials");
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Economy> rspEconomy = Bukkit.getServicesManager().getRegistration(Economy.class);
if (rspEconomy != null) economy = rspEconomy.getProvider();
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
if (essentials != null) {
try {
essGetUser = Class.forName("com.earth2me.essentials.Essentials").getMethod("getUser", Player.class);
essGetNickname = Class.forName("com.earth2me.essentials.User").getMethod("getNickname");
essIsAfk = Class.forName("com.earth2me.essentials.User").getMethod("isAfk");
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to load essentials methods", e);
}
}
}
@Override
public List<Placeholder> registerPlaceholders() {
placeholders = new ArrayList<Placeholder>();
placeholders = new ArrayList<>();
placeholders.add(new PlayerPlaceholder("%money%", 1000) {
public String get(TabPlayer p) {
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
try {
Object essentials = Bukkit.getPluginManager().getPlugin("Essentials");
Object user = essentials.getClass().getMethod("getUser", Player.class).invoke(essentials, p.getPlayer());
BigDecimal money = (BigDecimal) user.getClass().getMethod("getMoney").invoke(user);
return decimal2.format(money.doubleValue());
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get player balance of " + p.getName() + " using Essentials", e);
return "-";
}
}
if (economy != null) return decimal2.format(economy.getBalance((Player) p.getPlayer()));
return "-";
}
@@ -77,12 +97,7 @@ public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
});
placeholders.add(new PlayerPlaceholder("%deaths%", 1000) {
public String get(TabPlayer p) {
return ((Player) p.getPlayer()).getStatistic(Statistic.DEATHS)+"";
}
});
placeholders.add(new PlayerPlaceholder("%health%", 100) {
public String get(TabPlayer p) {
return (int) Math.ceil(((Player) p.getPlayer()).getHealth())+"";
return String.valueOf(((Player) p.getPlayer()).getStatistic(Statistic.DEATHS));
}
});
placeholders.add(new ServerPlaceholder("%tps%", 1000) {
@@ -91,29 +106,36 @@ public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
Object nmsServer = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
double value = ((double[]) nmsServer.getClass().getField("recentTps").get(nmsServer))[0];
return decimal2.format(Math.min(20, value));
} catch (Throwable t) {
} catch (Exception t) {
return "-1";
}
}
});
placeholders.add(new PlayerPlaceholder("%canseeonline%", 2000) {
public String get(TabPlayer p) {
int var = 0;
int count = 0;
for (TabPlayer all : TAB.getInstance().getPlayers()){
if (((Player) p.getPlayer()).canSee((Player) all.getPlayer())) var++;
if (((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return var+"";
return String.valueOf(count);
}
});
placeholders.add(new PlayerPlaceholder("%canseestaffonline%", 2000) {
public String get(TabPlayer p) {
int var = 0;
int count = 0;
for (TabPlayer all : TAB.getInstance().getPlayers()){
if (all.isStaff() && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) var++;
if (all.isStaff() && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return var+"";
return String.valueOf(count);
}
});
if (NMSStorage.getInstance().getMinorVersion() >= 6) {
placeholders.add(new PlayerPlaceholder("%health%", 100) {
public String get(TabPlayer p) {
return String.valueOf((int) Math.ceil(((Player) p.getPlayer()).getHealth()));
}
});
}
registerAFKPlaceholder();
registerVaultPlaceholders();
registerPositionPlaceholders();
@@ -122,40 +144,43 @@ public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
return placeholders;
}
/**
* Registers AFK placeholder
*/
private void registerAFKPlaceholder() {
String noAfk = TAB.getInstance().getConfiguration().config.getString("placeholders.afk-no", "");
String yesAfk = TAB.getInstance().getConfiguration().config.getString("placeholders.afk-yes", " &4*&4&lAFK&4*&r");
Plugin afkplus = Bukkit.getPluginManager().getPlugin("AFKPlus");
boolean antiafkplus = Bukkit.getPluginManager().isPluginEnabled("AntiAFKPlus");
try {
purpurIsAfk = Player.class.getMethod("isAfk");
} catch (NoSuchMethodException | SecurityException e1) {
//not purpur
}
String noAfk = TAB.getInstance().getConfiguration().getConfig().getString("placeholders.afk-no", "");
String yesAfk = TAB.getInstance().getConfiguration().getConfig().getString("placeholders.afk-yes", " &4*&4&lAFK&4*&r");
placeholders.add(new PlayerPlaceholder("%afk%", 500) {
public String get(TabPlayer p) {
boolean afk = false;
try {
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
Object user = Bukkit.getPluginManager().getPlugin("Essentials").getClass().getMethod("getUser", Player.class).invoke(Bukkit.getPluginManager().getPlugin("Essentials"), p.getPlayer());
afk = (boolean) user.getClass().getMethod("isAfk").invoke(user);
if (essentials != null) {
Object user = essGetUser.invoke(Bukkit.getPluginManager().getPlugin("Essentials"), p.getPlayer());
if ((boolean) essIsAfk.invoke(user)) return yesAfk;
}
if (Bukkit.getPluginManager().isPluginEnabled("AFKPlus")) {
Object AFKPlus = Bukkit.getPluginManager().getPlugin("AFKPlus");
Object AFKPlusPlayer = AFKPlus.getClass().getMethod("getPlayer", UUID.class).invoke(AFKPlus, p.getUniqueId());
afk = (boolean) AFKPlusPlayer.getClass().getMethod("isAFK").invoke(AFKPlusPlayer);
if (afkplus != null) {
Object afkplusplayer = afkplus.getClass().getMethod("getPlayer", UUID.class).invoke(afkplus, p.getUniqueId());
if ((boolean) afkplusplayer.getClass().getMethod("isAFK").invoke(afkplusplayer)) return yesAfk;
}
if (Bukkit.getPluginManager().isPluginEnabled("AntiAFKPlus")) {
if (antiafkplus) {
Object api = Class.forName("de.kinglol12345.AntiAFKPlus.api.AntiAFKPlusAPI").getDeclaredMethod("getAPI").invoke(null);
afk = (boolean) api.getClass().getMethod("isAFK", Player.class).invoke(api, p.getPlayer());
if ((boolean) api.getClass().getMethod("isAFK", Player.class).invoke(api, p.getPlayer())) return yesAfk;
}
if (Bukkit.getPluginManager().isPluginEnabled("xAntiAFK")) {
afk = (boolean) Class.forName("ch.soolz.xantiafk.xAntiAFKAPI").getMethod("isAfk", Player.class).invoke(null, p.getPlayer());
}
if (Bukkit.getPluginManager().isPluginEnabled("AutoAFK")) {
Object plugin = Bukkit.getPluginManager().getPlugin("AutoAFK");
Field f = plugin.getClass().getDeclaredField("afkList");
f.setAccessible(true);
HashMap<?, ?> map = (HashMap<?, ?>) f.get(plugin);
afk = map.containsKey(p.getPlayer());
}
} catch (Throwable t) {
return TAB.getInstance().getErrorManager().printError("", "Failed to check AFK status of " + p.getName(), t);
} catch (Exception e) {
return TAB.getInstance().getErrorManager().printError("", "Failed to check AFK status of " + p.getName(), e);
}
return afk ? yesAfk : noAfk;
try {
if (purpurIsAfk != null && (boolean) purpurIsAfk.invoke(p.getPlayer())) return yesAfk;
} catch (Exception e) {
//not purpur
}
return noAfk;
}
@Override
public String[] getNestedStrings(){
@@ -164,20 +189,21 @@ public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
});
}
/**
* Registers vault placeholders
*/
private void registerVaultPlaceholders() {
if (Bukkit.getPluginManager().isPluginEnabled("Vault") && chat != null) {
placeholders.add(new PlayerPlaceholder("%vault-prefix%", 500) {
public String get(TabPlayer p) {
String prefix = chat.getPlayerPrefix((Player) p.getPlayer());
return prefix != null ? prefix : "";
return chat.getPlayerPrefix((Player) p.getPlayer());
}
});
placeholders.add(new PlayerPlaceholder("%vault-suffix%", 500) {
public String get(TabPlayer p) {
String suffix = chat.getPlayerSuffix((Player) p.getPlayer());
return suffix != null ? suffix : "";
return chat.getPlayerSuffix((Player) p.getPlayer());
}
});
} else {
@@ -194,40 +220,43 @@ public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
}
}
/**
* Registers position placeholders
*/
private void registerPositionPlaceholders() {
placeholders.add(new PlayerPlaceholder("%xPos%", 100) {
public String get(TabPlayer p) {
return ((Player) p.getPlayer()).getLocation().getBlockX()+"";
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockX());
}
});
placeholders.add(new PlayerPlaceholder("%yPos%", 100) {
public String get(TabPlayer p) {
return ((Player) p.getPlayer()).getLocation().getBlockY()+"";
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockY());
}
});
placeholders.add(new PlayerPlaceholder("%zPos%", 100) {
public String get(TabPlayer p) {
return ((Player) p.getPlayer()).getLocation().getBlockZ()+"";
return String.valueOf(((Player) p.getPlayer()).getLocation().getBlockZ());
}
});
}
/**
* Registers essentials placeholders
*/
private void registerEssentialsPlaceholders() {
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
if (essentials != null) {
placeholders.add(new PlayerPlaceholder("%essentialsnick%", 1000) {
public String get(TabPlayer p) {
if (essGetNickname == null) return "<Internal plugin error>";
String name = null;
if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
try {
Object essentials = Bukkit.getPluginManager().getPlugin("Essentials");
Object user = essentials.getClass().getMethod("getUser", Player.class).invoke(essentials, p.getPlayer());
name = (String) user.getClass().getMethod("getNickname").invoke(user);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get Essentials nickname of " + p.getName(), e);
}
try {
name = (String) essGetNickname.invoke(essGetUser.invoke(essentials, p.getPlayer()));
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get Essentials nickname of " + p.getName(), e);
}
if (name == null || name.length() == 0) return p.getName();
return TAB.getInstance().getConfiguration().getSecretOption("essentials-nickname-prefix", "") + name;
return TAB.getInstance().getConfiguration().getEssentialsNickPrefix() + name;
}
});
} else {
@@ -239,35 +268,35 @@ public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
}
}
/**
* Registers synchronous placeholders
*/
private void registerSyncPlaceholders() {
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
for (String identifier : pl.allUsedPlaceholderIdentifiers) {
for (String identifier : pl.getAllUsedPlaceholderIdentifiers()) {
if (identifier.startsWith("%sync:")) {
int refresh;
if (pl.serverPlaceholderRefreshIntervals.containsKey(identifier)) {
refresh = pl.serverPlaceholderRefreshIntervals.get(identifier);
} else if (pl.playerPlaceholderRefreshIntervals.containsKey(identifier)) {
refresh = pl.playerPlaceholderRefreshIntervals.get(identifier);
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
refresh = pl.getServerPlaceholderRefreshIntervals().get(identifier);
} else if (pl.getPlayerPlaceholderRefreshIntervals().containsKey(identifier)) {
refresh = pl.getPlayerPlaceholderRefreshIntervals().get(identifier);
} else {
refresh = pl.defaultRefresh;
refresh = pl.getDefaultRefresh();
}
placeholders.add(new PlayerPlaceholder(identifier, refresh) {
@Override
public String get(TabPlayer p) {
Bukkit.getScheduler().runTask(plugin, new Runnable() {
Bukkit.getScheduler().runTask(plugin, () -> {
@Override
public void run() {
long time = System.nanoTime();
String syncedPlaceholder = identifier.substring(6, identifier.length()-1);
String value = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders((Player) p.getPlayer(), "%" + syncedPlaceholder + "%");
lastValue.put(p.getName(), value);
if (!forceUpdate.contains(p.getName())) forceUpdate.add(p.getName());
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
}
long time = System.nanoTime();
String syncedPlaceholder = identifier.substring(6, identifier.length()-1);
String value = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders((Player) p.getPlayer(), "%" + syncedPlaceholder + "%");
getLastValues().put(p.getName(), value);
if (!getForceUpdate().contains(p.getName())) getForceUpdate().add(p.getName());
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
});
return getLastValue(p);
return getLastValues().get(p.getName());
}
});
}
@@ -0,0 +1,312 @@
package me.neznamy.tab.platforms.bukkit;
import java.io.File;
import java.util.Collection;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.BukkitTABLoadEvent;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
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.unlimitedtags.NameTagX;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.Platform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.BelowName;
import me.neznamy.tab.shared.features.NameTag;
import me.neznamy.tab.shared.features.PingSpoof;
import me.neznamy.tab.shared.features.PlaceholderManager;
import me.neznamy.tab.shared.features.SpectatorFix;
import me.neznamy.tab.shared.features.TabObjective;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
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.Placeholder;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
import me.neznamy.tab.shared.placeholders.RelationalPlaceholder;
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.permission.Permission;
/**
* Bukkit implementation of Platform
*/
public class BukkitPlatform implements Platform {
//plugin instance
private JavaPlugin plugin;
//nms storage
private NMSStorage nms;
//booleans to check plugin presence
private Plugin placeholderAPI;
private boolean viaversion;
private boolean idisguise;
private boolean libsdisguises;
private boolean essentials;
/**
* Constructs new instance with given parameters
* @param plugin - plugin instance
* @param nms - nms storage
*/
public BukkitPlatform(JavaPlugin plugin, NMSStorage nms) {
this.plugin = plugin;
this.nms = nms;
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
return new LuckPerms(Bukkit.getPluginManager().getPlugin("LuckPerms").getDescription().getVersion());
} else if (Bukkit.getPluginManager().isPluginEnabled("UltraPermissions")) {
return new UltraPermissions(Bukkit.getPluginManager().getPlugin("UltraPermissions").getDescription().getVersion());
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
return new Vault(Bukkit.getServicesManager().getRegistration(Permission.class).getProvider(), Bukkit.getPluginManager().getPlugin("Vault").getDescription().getVersion());
} else {
return new None();
}
}
@Override
public void loadFeatures() {
placeholderAPI = Bukkit.getPluginManager().getPlugin("PlaceholderAPI");
viaversion = Bukkit.getPluginManager().isPluginEnabled("ViaVersion");
idisguise = Bukkit.getPluginManager().isPluginEnabled("iDisguise");
libsdisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
essentials = Bukkit.getPluginManager().isPluginEnabled("Essentials");
TAB tab = TAB.getInstance();
tab.getPlaceholderManager().addRegistry(new BukkitPlaceholderRegistry(plugin));
tab.getPlaceholderManager().addRegistry(new UniversalPlaceholderRegistry());
tab.getPlaceholderManager().registerPlaceholders();
if (nms.getMinorVersion() >= 8 && tab.getConfiguration().isPipelineInjection()) {
tab.getFeatureManager().registerFeature("injection", new BukkitPipelineInjector(tab, nms));
}
loadNametagFeature(tab);
loadUniversalFeatures();
if (tab.getConfiguration().getConfig().getBoolean("ping-spoof.enabled", false)) tab.getFeatureManager().registerFeature("pingspoof", new PingSpoof());
if (tab.getConfiguration().getConfig().getString("yellow-number-in-tablist", "%ping%").length() > 0) tab.getFeatureManager().registerFeature("tabobjective", new TabObjective(tab));
if (tab.getConfiguration().getConfig().getBoolean("do-not-move-spectators", false)) tab.getFeatureManager().registerFeature("spectatorfix", new SpectatorFix());
if (tab.getConfiguration().getConfig().getBoolean("classic-vanilla-belowname.enabled", true)) tab.getFeatureManager().registerFeature("belowname", new BelowName(tab));
if (tab.getConfiguration().getPremiumConfig() != null && tab.getConfiguration().getPremiumConfig().getBoolean("scoreboard.enabled", false)) tab.getFeatureManager().registerFeature("scoreboard", new ScoreboardManager(tab));
if (tab.getConfiguration().getBossbarConfig().getBoolean("bossbar-enabled", false) && nms.getMinorVersion() < 9) tab.getFeatureManager().registerFeature("bossbar1.8", new WitherBossBar(tab, plugin));
if (nms.getMinorVersion() >= 9 && tab.getConfiguration().getConfig().getBoolean("fix-pet-names", false)) tab.getFeatureManager().registerFeature("petfix", new PetFix(nms));
if (tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.enabled", false)) tab.getFeatureManager().registerFeature("pwp", new PerWorldPlayerlist(plugin, tab));
if (placeholderAPI != null) {
new TabExpansion(plugin);
}
for (Player p : getOnlinePlayers()) {
tab.addPlayer(new BukkitTabPlayer(p, Main.getProtocolVersion(p)));
}
}
/**
* Loads nametag feature from config
* @param tab - tab instance
*/
private void loadNametagFeature(TAB tab) {
if (tab.getConfiguration().getConfig().getBoolean("change-nametag-prefix-suffix", true)) {
if (tab.getConfiguration().getConfig().getBoolean("unlimited-nametag-prefix-suffix-mode.enabled", false) && nms.getMinorVersion() >= 8) {
tab.getFeatureManager().registerFeature("nametagx", new NameTagX(plugin, nms, tab));
} else {
tab.getFeatureManager().registerFeature("nametag16", new NameTag(tab));
}
}
}
/**
* 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 (Exception 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 ? message.replace('&', '\u00a7') : message);
}
@Override
public void registerUnknownPlaceholder(String identifier) {
if (identifier.contains("_")) {
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
if (identifier.startsWith("%rel_")) {
//relational placeholder
registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier));
} else {
//normal placeholder
String expansion = identifier.split("_")[0].substring(1).toLowerCase();
if (expansion.equals("some")) return;
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier));
return;
}
if (pl.getPlayerPlaceholderRefreshIntervals().containsKey(identifier)) {
registerPlayerPlaceholder(identifier, pl.getPlayerPlaceholderRefreshIntervals().get(identifier));
return;
}
registerPlayerPlaceholder(identifier, pl.getDefaultRefresh());
}
}
}
/**
* Registers server placeholder
* @param identifier - placeholder identifier
* @param refresh - refresh interval in milliseconds
*/
private void registerServerPlaceholder(String identifier, int refresh) {
BukkitPlatform pl = this;
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new ServerPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)){
@Override
public String get() {
return pl.setPlaceholders(null, identifier);
}
});
}
/**
* Registers player placeholder
* @param identifier - placeholder identifier
* @param refresh - refresh interval in milliseconds
*/
private void registerPlayerPlaceholder(String identifier, int refresh) {
BukkitPlatform pl = this;
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new PlayerPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)) {
@Override
public String get(TabPlayer p) {
return pl.setPlaceholders((Player) p.getPlayer(), identifier);
}
});
}
/**
* Registers relational placeholder
* @param identifier - placeholder identifier
* @param refresh - refresh interval in milliseconds
*/
private void registerRelationalPlaceholder(String identifier, int refresh) {
TAB.getInstance().getPlaceholderManager().registerPlaceholder(new RelationalPlaceholder(identifier, TAB.getInstance().getErrorManager().fixPlaceholderInterval(identifier, refresh)) {
@Override
public String get(TabPlayer viewer, TabPlayer target) {
if (placeholderAPI == null) return identifier;
try {
return PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier);
} catch (Exception t) {
TAB.getInstance().getErrorManager().printError("PlaceholderAPI v" + placeholderAPI.getDescription().getVersion() + " generated an error when setting relational placeholder " + identifier + " for viewer " + viewer.getName() + " and target " + target.getName(), t, false, TAB.getInstance().getErrorManager().getPapiErrorLog());
}
return identifier;
}
});
}
/**
* Runs PlaceholderAPI call and returns the output. If the task fails, error is logged and "ERROR" returned instead
* @param player - player to set placeholder for
* @param placeholder - placeholder
* @return result from PlaceholderAPI
*/
public String setPlaceholders(Player player, String placeholder) {
if (placeholderAPI == null) return placeholder;
try {
return PlaceholderAPI.setPlaceholders(player, placeholder);
} catch (Exception t) {
String playername = (player == null ? "<null>" : player.getName());
TAB.getInstance().getErrorManager().printError("PlaceholderAPI v" + placeholderAPI.getDescription().getVersion() + " generated an error when setting placeholder " + placeholder + " for player " + playername, t, false, TAB.getInstance().getErrorManager().getPapiErrorLog());
return "ERROR";
}
}
@Override
public String getServerVersion() {
return Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
public String getSeparatorType() {
return "world";
}
@Override
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public String replaceAllPlaceholders(String string, TabPlayer sender) {
if (string == null) return null;
String replaced = string;
for (Placeholder p : TAB.getInstance().getPlaceholderManager().getAllPlaceholders()) {
if (replaced.contains(p.getIdentifier())) {
if (p instanceof ServerPlaceholder) {
((ServerPlaceholder)p).update();
}
if (p instanceof PlayerPlaceholder) {
((PlayerPlaceholder)p).update(sender);
}
replaced = p.set(replaced, sender);
}
}
replaced = setPlaceholders(sender == null ? null : (Player) sender.getPlayer(), replaced);
return replaced;
}
@Override
public void callLoadEvent() {
Bukkit.getPluginManager().callEvent(new BukkitTABLoadEvent());
}
@Override
public int getMaxPlayers() {
return Bukkit.getMaxPlayers();
}
@Override
public String getConfigName() {
return "bukkitconfig.yml";
}
public boolean isViaversionEnabled() {
return viaversion;
}
public boolean isLibsdisguisesEnabled() {
return libsdisguises;
}
public boolean isIdisguiseEnabled() {
return idisguise;
}
public boolean isEssentialsEnabled() {
return essentials;
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import com.viaversion.viaversion.api.Via;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.packets.PacketPlayOutBoss;
import me.neznamy.tab.shared.rgb.RGBUtils;
/**
* TabPlayer for Bukkit
*/
public class BukkitTabPlayer extends ITabPlayer {
//bukkit player
private Player player;
//nms handle
private Object handle;
//nms player connection
private Object playerConnection;
//player's visible boss bars
private Map<UUID, BossBar> bossbars = new HashMap<>();
/**
* Constructs new instance with given parameter
* @param p - bukkit player
*/
public BukkitTabPlayer(Player p, int protocolVersion){
player = p;
world = p.getWorld().getName();
try {
handle = NMSStorage.getInstance().getMethod("getHandle").invoke(player);
playerConnection = NMSStorage.getInstance().getField("PLAYER_CONNECTION").get(handle);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection of " + p.getName(), e);
}
try {
if (NMSStorage.getInstance().getField("CHANNEL") != null)
channel = (Channel) NMSStorage.getInstance().getField("CHANNEL").get(NMSStorage.getInstance().getField("NETWORK_MANAGER").get(playerConnection));
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + p.getName(), e);
}
uniqueId = p.getUniqueId();
name = p.getName();
version = ProtocolVersion.fromNetworkId(protocolVersion);
init();
}
@Override
public boolean hasPermission(String permission) {
return player.hasPermission(permission);
}
@Override
public long getPing() {
try {
int ping = NMSStorage.getInstance().getField("PING").getInt(handle);
if (ping > 10000 || ping < 0) ping = -1;
return ping;
} catch (Exception e) {
return -1;
}
}
@Override
public void sendPacket(Object nmsPacket) {
if (nmsPacket == null || !player.isOnline()) return;
try {
if (((BukkitPlatform)TAB.getInstance().getPlatform()).isViaversionEnabled() && nmsPacket instanceof ByteBuf) {
Via.getAPI().sendRawPacket(uniqueId, (ByteBuf) nmsPacket);
return;
}
if (nmsPacket instanceof PacketPlayOutBoss) {
handle((PacketPlayOutBoss) nmsPacket);
return;
}
NMSStorage.getInstance().getMethod("sendPacket").invoke(playerConnection, nmsPacket);
} catch (IllegalArgumentException e) {
//java.lang.IllegalArgumentException: This player is not controlled by ViaVersion!
//this is only used to send 1.9 bossbar packets on 1.8 servers, no idea why it does this sometimes
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
}
private void handle(PacketPlayOutBoss packet) {
Set<BarFlag> flags = new HashSet<>();
BossBar bar;
switch (packet.getOperation()) {
case ADD:
if (packet.isCreateWorldFog()) flags.add(BarFlag.CREATE_FOG);
if (packet.isDarkenScreen()) flags.add(BarFlag.DARKEN_SKY);
if (packet.isPlayMusic()) flags.add(BarFlag.PLAY_BOSS_MUSIC);
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && NMSStorage.getInstance().getMinorVersion() >= 16),
BarColor.valueOf(packet.getColor().name()),
BarStyle.valueOf(packet.getOverlay().name().replace("PROGRESS", "SOLID").replace("NOTCHED", "SEGMENTED")),
flags.toArray(new BarFlag[0]));
bar.setProgress(packet.getPct());
bossbars.put(packet.getId(), bar);
bar.addPlayer(player);
break;
case REMOVE:
bossbars.get(packet.getId()).removePlayer(player);
bossbars.remove(packet.getId());
break;
case UPDATE_PCT:
bossbars.get(packet.getId()).setProgress(packet.getPct());
break;
case UPDATE_NAME:
bossbars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && NMSStorage.getInstance().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
bossbars.get(packet.getId()).setColor(BarColor.valueOf(packet.getColor().name()));
bossbars.get(packet.getId()).setStyle(BarStyle.valueOf(packet.getOverlay().name().replace("PROGRESS", "SOLID").replace("NOTCHED", "SEGMENTED")));
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 processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue) {
if (!bar.hasFlag(flag)) {
bar.addFlag(flag);
}
} else {
if (bar.hasFlag(flag)) {
bar.removeFlag(flag);
}
}
}
@Override
public boolean hasInvisibilityPotion() {
return player.hasPotionEffect(PotionEffectType.INVISIBILITY);
}
@Override
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsdisguisesEnabled()) return false;
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, player);
} catch (Exception e) {
return TAB.getInstance().getErrorManager().printError(false, "Failed to check disguise status using LibsDisguises", e);
}
}
@Override
public Object getSkin() {
try {
return Class.forName("com.mojang.authlib.GameProfile").getMethod("getProperties").invoke(NMSStorage.getInstance().getMethod("getProfile").invoke(handle));
} catch (Exception e) {
return TAB.getInstance().getErrorManager().printError(null, "Failed to get skin of " + getName(), e);
}
}
@Override
public Player getPlayer() {
return player;
}
@Override
public boolean isOnline() {
return player.isOnline();
}
@Override
public boolean isVanished() {
try {
if (((BukkitPlatform)TAB.getInstance().getPlatform()).isEssentialsEnabled()) {
Object essentials = Bukkit.getPluginManager().getPlugin("Essentials");
Object user = essentials.getClass().getMethod("getUser", Player.class).invoke(essentials, player);
boolean vanished = (boolean) user.getClass().getMethod("isVanished").invoke(user);
if (vanished) return true;
}
List<MetadataValue> metadata = player.getMetadata("vanished");
return !metadata.isEmpty() && metadata.get(0).asBoolean();
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to check vanish status of " + player.getName(), e);
}
return false;
}
@SuppressWarnings("deprecation")
@Override
public int getGamemode() {
return player.getGameMode().getValue();
}
}
@@ -0,0 +1,167 @@
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.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import com.viaversion.viaversion.api.Via;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
/**
* Main class for Bukkit platform
*/
public class Main extends JavaPlugin {
@Override
public void onEnable(){
Bukkit.getConsoleSender().sendMessage("\u00a77[TAB] Server version: " + Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")");
if (!isVersionSupported()){
Bukkit.getPluginManager().disablePlugin(this);
return;
}
TAB.setInstance(new TAB(new BukkitPlatform(this, NMSStorage.getInstance()), new BukkitPacketBuilder(NMSStorage.getInstance()), ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0])));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN) {
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly.");
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
TABCommand command = new TABCommand();
Bukkit.getPluginCommand("tab").setExecutor(command);
Bukkit.getPluginCommand("tab").setTabCompleter(command);
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 5304);
metrics.addCustomChart(new SimplePie("unlimited_nametag_mode_enabled", () -> TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx") ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie("placeholderapi", () -> Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI") ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie("permission_system", () -> TAB.getInstance().getPermissionPlugin().getName()));
metrics.addCustomChart(new SimplePie("server_version", () -> "1." + NMSStorage.getInstance().getMinorVersion() + ".x"));
metrics.addCustomChart(new SimplePie("using_premium_version", () -> TAB.getInstance().isPremium() ? "Yes" : "No"));
}
@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");
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
NMSStorage.setInstance(new NMSStorage());
if (supportedVersions.contains(serverPackage)) {
return true;
} else {
Bukkit.getConsoleSender().sendMessage("\u00a7c[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.17. Disabling just to stay safe.");
}
} catch (Exception ex) {
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage("\u00a7c[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(e.toString());
}
} else {
Bukkit.getConsoleSender().sendMessage("\u00a7c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.x - 1.17. Disabling.");
}
}
return false;
}
/**
* Gets protocol version and returns it
* @return protocol version of this player
*/
public static int getProtocolVersion(Player player) {
if (Bukkit.getPluginManager().isPluginEnabled("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 (Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
return getProtocolVersionVia(player);
}
return TAB.getInstance().getServerVersion().getNetworkId();
}
/**
* Returns protocol version of this player using ProtocolSupport
* @return protocol version of this player using ProtocolSupport
*/
private static 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 (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get protocol version of " + player.getName() + " using ProtocolSupport", e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
}
/**
* Returns protocol version of this player using ViaVersion
* @return protocol version of this player using ViaVersion
*/
private static int getProtocolVersionVia(Player player){
try {
int version = Via.getAPI().getPlayerVersion(player.getUniqueId());
TAB.getInstance().debug("ViaVersion returned protocol version " + version + " for " + player.getName() + "(online=" + player.isOnline() + ")");
return version;
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to get protocol version of " + player.getName() + " using ViaVersion v" + Bukkit.getPluginManager().getPlugin("ViaVersion").getDescription().getVersion(), e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
}
public class TABCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (TAB.getInstance().isDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission("tab.reload"), sender.hasPermission("tab.admin"))) {
sender.sendMessage(message.replace('&', '\u00a7'));
}
} 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(CommandSender sender, Command command, 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,110 @@
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.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.types.Loadable;
/**
* Per-world-playerlist feature handler
*/
@SuppressWarnings("deprecation")
public class PerWorldPlayerlist implements Loadable, Listener {
//plugin instance
private JavaPlugin plugin;
//config options
private boolean allowBypass;
private List<String> ignoredWorlds;
private Map<String, List<String>> sharedWorlds;
/**
* Constructs new instance with given parameters and loads config options
* @param plugin - plugin instance
* @param tab - tab instance
*/
public PerWorldPlayerlist(JavaPlugin plugin, TAB tab) {
this.plugin = plugin;
allowBypass = tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
ignoredWorlds = tab.getConfiguration().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
sharedWorlds = tab.getConfiguration().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
for (Entry<String, List<String>> group : sharedWorlds.entrySet()) {
if (group.getValue() == null) {
tab.getErrorManager().startupWarn("World group \"" + group + "\" in per-world-playerlist does not contain any worlds. You can just remove the group.");
} else if (group.getValue().size() == 1) {
tab.getErrorManager().startupWarn("World group \"" + group + "\" in per-world-playerlist only contain a single world (\"" + group.getValue().get(0) +
"\"), which has no effect and only makes config less readable. Delete the group entirely for a cleaner config.");
}
}
tab.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(TabFeature.PER_WORLD_PLAYERLIST, UsageType.PLAYER_JOIN_EVENT, System.nanoTime()-time);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(TabFeature.PER_WORLD_PLAYERLIST, UsageType.WORLD_SWITCH_EVENT, 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("tab.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,144 @@
package me.neznamy.tab.platforms.bukkit.features;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
/**
* 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 implements RawPacketListener, QuitEventListener {
//datawatcher position of pet owner field
private int petOwnerPosition;
//logger of last interacts to prevent feature not working on 1.16
private Map<String, Long> lastInteractFix = new HashMap<>();
//nms storage
private NMSStorage nms;
/**
* Constructs new instance with given parameter
* @param nms
*/
public PetFix(NMSStorage nms) {
this.nms = nms;
petOwnerPosition = getPetOwnerPosition();
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
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 IllegalAccessException
*/
@Override
public Object onPacketReceive(TabPlayer sender, Object packet) throws IllegalAccessException {
if (nms.getClass("PacketPlayInUseEntity").isInstance(packet)) {
if (lastInteractFix.containsKey(sender.getName()) && (System.currentTimeMillis() - lastInteractFix.get(sender.getName()) < 5)) {
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
return null;
}
if (isInteract(nms.getField("PacketPlayInUseEntity_ACTION").get(packet))) {
//this is the first packet, saving player so the next packet can be cancelled
lastInteractFix.put(sender.getName(), System.currentTimeMillis());
}
}
return packet;
}
private boolean isInteract(Object action) {
if (nms.getMinorVersion() >= 17) {
return nms.getClass("PacketPlayInUseEntity$d").isInstance(action);
} else {
return action.toString().equals("INTERACT");
}
}
/**
* Removes pet owner field from datawatcher
* @throws IllegalAccessException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException {
if (nms.getClass("PacketPlayOutEntityMetadata").isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) nms.getField("PacketPlayOutEntityMetadata_LIST").get(packet);
if (items == null) return;
for (Object item : items) {
if (nms.getField("DataWatcherObject_SLOT").getInt(nms.getField("DataWatcherItem_TYPE").get(item)) == petOwnerPosition) {
Object value = nms.getField("DataWatcherItem_VALUE").get(item);
if (value instanceof java.util.Optional || value instanceof com.google.common.base.Optional) {
removedEntry = item;
break;
}
}
}
if (removedEntry != null) items.remove(removedEntry);
}
//<1.15
Field datawatcher = nms.getField("PacketPlayOutSpawnEntityLiving_DATAWATCHER");
if (nms.getClass("PacketPlayOutSpawnEntityLiving").isInstance(packet) && datawatcher != null) {
DataWatcher watcher = DataWatcher.fromNMS(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, "PacketPlayOutSpawnEntityLiving_DATAWATCHER", watcher.toNMS());
}
}
}
@Override
public TabFeature getFeatureType() {
return TabFeature.PET_NAME_FIX;
}
/**
* Removing player data to not have a memory leak
*/
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
lastInteractFix.remove(disconnectedPlayer.getName());
}
}
@@ -60,31 +60,55 @@ public class TabExpansion extends PlaceholderExpansion {
if (player == null) return "";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if (identifier.equals("scoreboard_visible")) {
return p.isScoreboardVisible() ? "Enabled" : "Disabled";
return translate(p.isScoreboardVisible());
}
if (identifier.equals("bossbar_visible")) {
return p.hasBossbarVisible() ? "Enabled" : "Disabled";
return translate(p.hasBossbarVisible());
}
if (identifier.equals("ntpreview")) {
return p.isPreviewingNametag() ? "Enabled" : "Disabled";
return translate(p.isPreviewingNametag());
}
if (identifier.startsWith("replace_") && TAB.getInstance().getConfiguration().premiumconfig != null) {
String placeholder = "%" + identifier.substring(8) + "%";
String output = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders(player, placeholder);
Map<Object, String> replacements = TAB.getInstance().getConfiguration().premiumconfig.getConfigurationSection("placeholder-output-replacements." + placeholder);
return Placeholder.findReplacement(replacements, output).replace("%value%", output);
if (identifier.startsWith("replace_")) {
return findReplacement("%" + identifier.substring(8) + "%", player);
}
if (identifier.startsWith("placeholder_")) {
//using Property function for fast & easy handling of nested placeholders and different placeholder types
return new Property(p, "%" + identifier.substring(12) + "%").get();
}
String placeholder = identifier.replace("_raw", "");
Property prop = p.getProperty(placeholder);
return getProperty(identifier, p);
}
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) {
if (!TAB.getInstance().isPremium()) return "Placeholder output replacements require premium version to work";
String output = ((BukkitPlatform) TAB.getInstance().getPlatform()).setPlaceholders(player, placeholder);
Map<Object, String> replacements = TAB.getInstance().getConfiguration().getPremiumConfig().getConfigurationSection("placeholder-output-replacements." + placeholder);
return Placeholder.findReplacement(replacements, 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 (identifier.endsWith("_raw")) {
if (name.endsWith("_raw")) {
return prop.getCurrentRawValue();
}
return prop.lastReplacedValue;
return prop.get();
}
return null;
}
@@ -0,0 +1,87 @@
package me.neznamy.tab.platforms.bukkit.features;
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.platforms.bukkit.BukkitPacketBuilder;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.bossbar.BossBar;
import me.neznamy.tab.shared.features.types.Loadable;
/**
* An additional class with additional code for <1.9 servers due to an entity being required
*/
public class WitherBossBar implements Listener, Loadable {
//distance of wither in blocks
private static final int WITHER_DISTANCE = 75;
//main bossbar feature
private BossBar mainFeature;
/**
* Constructs a new instance of the class
* @param mainFeature - main bossbar feature
*/
public WitherBossBar(TAB tab, JavaPlugin plugin) {
this.mainFeature = (BossBar) tab.getFeatureManager().getFeature("bossbar");
Bukkit.getPluginManager().registerEvents(this, plugin);
//bar disappears in client after ~1 second of not seeing boss entity
tab.getCPUManager().startRepeatingMeasuredTask(900, "refreshing bossbar", TabFeature.BOSSBAR, UsageType.TELEPORTING_ENTITY, this::teleport);
}
@Override
public void load() {
teleport();
}
/**
* Updates wither location for all players
*/
private void teleport() {
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (all.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (me.neznamy.tab.api.bossbar.BossBar l : all.getActiveBossBars()) {
try {
all.sendPacket(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityTeleportPacket(l.getUniqueId().hashCode(), getWitherLocation(all)), TabFeature.BOSSBAR);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to create PacketPlayOutEntityTeleport", e);
}
}
}
}
@Override
public void 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", TabFeature.BOSSBAR, UsageType.PLAYER_RESPAWN_EVENT, () -> mainFeature.detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
* Returns location where wither should be located based on where player is looking
* @param p - player to get wither location for
* @return location of wither
*/
public Location getWitherLocation(TabPlayer p) {
Player pl = (Player) p.getPlayer();
Location loc = pl.getEyeLocation().add(pl.getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
return loc;
}
}
@@ -10,15 +10,16 @@ import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.potion.PotionEffectType;
import me.neznamy.tab.api.ArmorStand;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
@@ -69,14 +70,14 @@ public class BukkitArmorStand implements ArmorStand {
* @param yOffset - offset in blocks
* @param staticOffset - if offset is static or not
*/
public BukkitArmorStand(int entityId, TabPlayer owner, Property property, double yOffset, boolean staticOffset) {
public BukkitArmorStand(int entityId, TabPlayer owner, Property property, double yOffset, boolean staticOffset, boolean markerFor18x) {
this.entityId = entityId;
this.owner = owner;
this.staticOffset = staticOffset;
player = (Player) owner.getPlayer();
this.yOffset = yOffset;
this.property = property;
markerFor18x = ((NameTagX)TAB.getInstance().getFeatureManager().getFeature("nametagx")).markerFor18x;
this.markerFor18x = markerFor18x;
visible = getVisibility();
}
@@ -120,7 +121,7 @@ public class BukkitArmorStand implements ArmorStand {
visible = getVisibility();
nearbyPlayers.add(viewer);
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 15) {
if (NMSStorage.getInstance().getMinorVersion() >= 15) {
return new Object[] {
getSpawnPacket(getArmorStandLocationFor(viewer), null),
getMetadataPacket(dataWatcher)
@@ -144,7 +145,7 @@ public class BukkitArmorStand implements ArmorStand {
* @param viewer - player to get location for
* @return location of armor stand
*/
private Location getArmorStandLocationFor(TabPlayer viewer) {
protected Location getArmorStandLocationFor(TabPlayer viewer) {
return viewer.getVersion().getMinorVersion() == 8 && !markerFor18x ? getLocation().clone().add(0,-2,0) : getLocation();
}
@@ -171,7 +172,7 @@ public class BukkitArmorStand implements ArmorStand {
if (this.sneaking == sneaking) return; //idk
this.sneaking = sneaking;
for (TabPlayer viewer : getNearbyPlayers()) {
if (viewer.getVersion().getMinorVersion() == 14 && !(boolean) TAB.getInstance().getConfiguration().getSecretOption("unlimited-nametag-prefix-suffix-mode.always-visible", false)) {
if (viewer.getVersion().getMinorVersion() == 14 && !TAB.getInstance().getConfiguration().isArmorStandsAlwaysVisible()) {
//1.14.x client sided bug, despawning completely
if (sneaking) {
viewer.sendPacket(getDestroyPacket(), TabFeature.NAMETAGX);
@@ -184,7 +185,7 @@ public class BukkitArmorStand implements ArmorStand {
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", TabFeature.NAMETAGX, UsageType.v1_8_0_BUG_COMPENSATION, spawn);
TAB.getInstance().getCPUManager().runTaskLater(50, "compensating for 1.8.0 bugs", TabFeature.NAMETAGX, UsageType.V1_8_0_BUG_COMPENSATION, spawn);
} else {
spawn.run();
}
@@ -200,9 +201,9 @@ public class BukkitArmorStand implements ArmorStand {
@Override
public void updateVisibility(boolean force) {
boolean visible = getVisibility();
if (this.visible != visible || force) {
this.visible = visible;
boolean visibility = getVisibility();
if (visible != visibility || force) {
visible = visibility;
updateMetadata();
}
}
@@ -213,7 +214,7 @@ public class BukkitArmorStand implements ArmorStand {
* @param viewer - player to get location for
* @return teleport packet
*/
private Object getTeleportPacket(TabPlayer viewer) {
protected Object getTeleportPacket(TabPlayer viewer) {
try {
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityTeleportPacket(entityId, getArmorStandLocationFor(viewer));
} catch (Exception e) {
@@ -221,7 +222,11 @@ public class BukkitArmorStand implements ArmorStand {
}
}
private Object getDestroyPacket() {
/**
* Returns destroy packet
* @return destroy packet
*/
protected Object getDestroyPacket() {
try {
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityDestroyPacket(entityId);
} catch (Exception e) {
@@ -229,7 +234,12 @@ public class BukkitArmorStand implements ArmorStand {
}
}
private Object getMetadataPacket(DataWatcher dataWatcher) {
/**
* Returns metadata packet with specified datawatcher
* @param dataWatcher - datawatcher
* @return metadata packet
*/
protected Object getMetadataPacket(DataWatcher dataWatcher) {
try {
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntityMetadataPacket(entityId, dataWatcher);
} catch (Exception e) {
@@ -237,7 +247,13 @@ public class BukkitArmorStand implements ArmorStand {
}
}
private Object getSpawnPacket(Location loc, DataWatcher dataWatcher) {
/**
* Returns spawn packet with specified parameters
* @param loc - location to spawn at
* @param dataWatcher - datawatcher
* @return spawn packet
*/
protected Object getSpawnPacket(Location loc, DataWatcher dataWatcher) {
try {
return ((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).buildEntitySpawnPacket(entityId, uuid, EntityType.ARMOR_STAND, loc, dataWatcher);
} catch (Exception e) {
@@ -248,7 +264,7 @@ public class BukkitArmorStand implements ArmorStand {
/**
* Updates armor stand's metadata
*/
private void updateMetadata() {
protected void updateMetadata() {
for (TabPlayer viewer : getNearbyPlayers()) {
viewer.sendPacket(getMetadataPacket(createDataWatcher(property.getFormat(viewer), viewer)), TabFeature.NAMETAGX);
}
@@ -258,24 +274,24 @@ public class BukkitArmorStand implements ArmorStand {
* Returns general visibility rule for everyone with limited info
* @return true if should be visible, false if not
*/
private boolean getVisibility() {
if (((BukkitTabPlayer)owner).isDisguised() || (((NameTagX)TAB.getInstance().getFeatureManager().getFeature("nametagx")).disableOnBoats && owner.isOnBoat())) return false;
if ((boolean) TAB.getInstance().getConfiguration().getSecretOption("unlimited-nametag-prefix-suffix-mode.always-visible", false)) return true;
return !player.hasPotionEffect(PotionEffectType.INVISIBILITY) && player.getGameMode() != GameMode.SPECTATOR && !owner.hasHiddenNametag() && property.get().length() > 0 && !owner.isOnBoat();
protected boolean getVisibility() {
if (((BukkitTabPlayer)owner).isDisguised() || (((NameTagX)TAB.getInstance().getFeatureManager().getFeature("nametagx")).getPlayersOnBoats().contains(owner))) return false;
if (TAB.getInstance().getConfiguration().isArmorStandsAlwaysVisible()) return true;
return !player.hasPotionEffect(PotionEffectType.INVISIBILITY) && player.getGameMode() != GameMode.SPECTATOR && !owner.hasHiddenNametag() && 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
*/
private Location getLocation() {
protected 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 (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 9) {
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
y -= (sneaking ? 0.45 : 0.18);
} else {
y -= (sneaking ? 0.30 : 0.18);
@@ -288,29 +304,34 @@ public class BukkitArmorStand implements ArmorStand {
* Returns Y where player is based on player's vehicle due to bukkit API bug
* @return correct player's Y
*/
private double getY() {
//1.14+ bukkit api bug
protected double getY() {
//1.14+ server sided bug
Entity vehicle = player.getVehicle();
if (vehicle != null) {
if (vehicle.getType() == EntityType.HORSE) {
if (vehicle.getType().toString().contains("HORSE")) { //covering all 3 horse types
return vehicle.getLocation().getY() + 0.85;
}
if (vehicle.getType().toString().equals("DONKEY")) {
if (vehicle.getType() == EntityType.DONKEY) {
return vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
return vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("STRIDER")) { //preventing errors on <1.16
if (vehicle.getType().toString().equals("STRIDER")) { //added in 1.16
return vehicle.getLocation().getY() + 1.15;
}
}
//1.13+ swimming
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 13 && player.isSwimming()) {
return player.getLocation().getY()-1.22;
}
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (NMSStorage.getInstance().getMinorVersion() >= 9 && player.isGliding())) {
return player.getLocation().getY()-1.22;
}
return player.getLocation().getY();
}
private boolean isSwimming() {
if (NMSStorage.getInstance().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
return NMSStorage.getInstance().getMinorVersion() == 13 && player.isSwimming();
}
@Override
public int getEntityId() {
@@ -328,7 +349,7 @@ public class BukkitArmorStand implements ArmorStand {
* @param viewer - player to apply checks against
* @return datawatcher for viewer
*/
private DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
protected DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
byte flag = 32; //invisible
@@ -336,25 +357,22 @@ public class BukkitArmorStand implements ArmorStand {
datawatcher.helper().setEntityFlags(flag);
datawatcher.helper().setCustomName(displayName, viewer.getVersion());
boolean visible;
boolean visibility;
if (isNameVisiblyEmpty(displayName) || !((Player) viewer.getPlayer()).canSee(player) || owner.hasHiddenNametag(viewer.getUniqueId())) {
visible = false;
visibility = false;
} else {
visible = this.visible;
visibility = visible;
}
datawatcher.helper().setCustomNameVisible(visible);
datawatcher.helper().setCustomNameVisible(visibility);
if (viewer.getVersion().getMinorVersion() > 8 || markerFor18x) datawatcher.helper().setArmorStandFlags((byte)16);
return datawatcher;
}
/**
* Returns list of players in entity tracking range (48 blocks)
* @return
*/
private Set<TabPlayer> getNearbyPlayers(){
@Override
public Set<TabPlayer> getNearbyPlayers(){
synchronized (nearbyPlayers) {
return new HashSet<TabPlayer>(nearbyPlayers);
return new HashSet<>(nearbyPlayers);
}
}
@@ -0,0 +1,55 @@
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.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
/**
* The event listener part for securing proper functionality of armor stands
*/
public class EventListener implements Listener {
//the nametag feature handler
private NameTagX feature;
/**
* Constructs new instance with given parameters
* @param feature - nametagx feature handler
*/
public EventListener(NameTagX feature) {
this.feature = feature;
}
/**
* Sneak event listener to despawn & 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 || !p.isLoaded() || feature.isInDisabledWorld(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask("processing PlayerToggleSneakEvent", TabFeature.NAMETAGX, UsageType.PLAYER_TOGGLE_SNEAK_EVENT, () -> {
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", TabFeature.NAMETAGX, UsageType.PLAYER_RESPAWN_EVENT, () -> {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isInDisabledWorld(respawned)) return;
respawned.getArmorStandManager().teleport();
});
}
}
@@ -0,0 +1,441 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.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.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.NameTag;
/**
* The core class for unlimited nametag mode
*/
public class NameTagX extends NameTag {
//config options
private boolean markerFor18x;
private boolean disableOnBoats;
private double spaceBetweenLines;
//list of worlds with unlimited nametag mode disabled
protected List<String> disabledUnlimitedWorlds;
//list of defined dynamic lines
private List<String> dynamicLines = Arrays.asList(PropertyUtils.BELOWNAME, PropertyUtils.NAMETAG, PropertyUtils.ABOVENAME);
//map of defined static lines
private Map<String, Object> staticLines = new ConcurrentHashMap<>();
//entity id counter to pick unique entity IDs
private int idCounter = 2000000000;
//player data by entityId, used for better performance
private Map<Integer, TabPlayer> entityIdMap = new ConcurrentHashMap<>();
//map of vehicles carrying players
private Map<Integer, List<Entity>> vehicles = new ConcurrentHashMap<>();
//bukkit event listener
private EventListener eventListener;
//list of players currently on boats
private List<TabPlayer> playersOnBoats = new ArrayList<>();
//list of players currently in a vehicle
private Map<TabPlayer, Entity> playersInVehicle = new ConcurrentHashMap<>();
private Map<TabPlayer, Location> playerLocations = new ConcurrentHashMap<>();
private Set<TabPlayer> playersInDisabledUnlimitedWorlds = new HashSet<>();
/**
* Constructs new instance with given parameters and loads config options
* @param plugin - plugin instance
* @param nms - nms storage
* @param tab - tab instance
*/
public NameTagX(JavaPlugin plugin, NMSStorage nms, TAB tab) {
super(tab);
markerFor18x = tab.getConfiguration().getConfig().getBoolean("unlimited-nametag-prefix-suffix-mode.use-marker-tag-for-1-8-x-clients", false);
disableOnBoats = tab.getConfiguration().getConfig().getBoolean("unlimited-nametag-prefix-suffix-mode.disable-on-boats", true);
spaceBetweenLines = tab.getConfiguration().getConfig().getDouble("unlimited-nametag-prefix-suffix-mode.space-between-lines", 0.22);
disabledUnlimitedWorlds = tab.getConfiguration().getConfig().getStringList("disable-features-in-worlds.unlimited-nametags", Arrays.asList("disabledworld"));
if (tab.getConfiguration().getPremiumConfig() != null) {
List<String> realList = tab.getConfiguration().getPremiumConfig().getStringList("unlimited-nametag-mode-dynamic-lines", Arrays.asList(PropertyUtils.ABOVENAME, PropertyUtils.NAMETAG, PropertyUtils.BELOWNAME, "another"));
dynamicLines = new ArrayList<>();
dynamicLines.addAll(realList);
Collections.reverse(dynamicLines);
staticLines = tab.getConfiguration().getPremiumConfig().getConfigurationSection("unlimited-nametag-mode-static-lines");
}
refreshUsedPlaceholders();
eventListener = new EventListener(this);
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
tab.getFeatureManager().registerFeature("nametagx-packet", new PacketListener(this, nms, tab));
tab.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() {
super.load();
for (TabPlayer all : tab.getPlayers()) {
getEntityIdMap().put(((Player) all.getPlayer()).getEntityId(), all);
loadArmorStands(all);
if (isDisabledWorld(disabledUnlimitedWorlds, all.getWorldName())) {
playersInDisabledUnlimitedWorlds.add(all);
}
if (isInDisabledWorld(all)) continue;
loadPassengers(all);
for (TabPlayer viewer : tab.getPlayers()) {
spawnArmorStands(all, viewer, false);
}
}
startVisibilityRefreshTask();
startVehicleTickingTask();
}
private void startVisibilityRefreshTask() {
tab.getCPUManager().startRepeatingMeasuredTask(500, "refreshing nametag visibility", getFeatureType(), UsageType.REFRESHING_NAMETAG_VISIBILITY_AND_COLLISION, () -> {
for (TabPlayer p : tab.getPlayers()) {
if (!p.isLoaded() || isInDisabledWorld(p)) continue;
p.getArmorStandManager().updateVisibility(false);
if (disableOnBoats) processBoats(p);
}
});
}
private void processBoats(TabPlayer p) {
boolean onBoat = ((Player)p.getPlayer()).getVehicle() != null && ((Player)p.getPlayer()).getVehicle().getType() == EntityType.BOAT;
if (onBoat) {
if (!getPlayersOnBoats().contains(p)) {
getPlayersOnBoats().add(p);
updateTeamData(p);
}
} else {
if (getPlayersOnBoats().contains(p)) {
getPlayersOnBoats().remove(p);
updateTeamData(p);
}
}
}
private void startVehicleTickingTask() {
tab.getCPUManager().startRepeatingMeasuredTask(100, "ticking vehicles", TabFeature.NAMETAGX, UsageType.TICKING_VEHICLES, () -> {
for (TabPlayer p : TAB.getInstance().getPlayers()) {
if (!p.isLoaded()) continue;
if (isInDisabledWorld(p)) {
playersInVehicle.remove(p);
playerLocations.remove(p);
} else {
processVehicles(p);
if (!playerLocations.containsKey(p) || !playerLocations.get(p).equals(((Player)p.getPlayer()).getLocation())) {
playerLocations.put(p, ((Player)p.getPlayer()).getLocation());
processPassengers((Entity) p.getPlayer());
//also updating position if player is previewing since we're here as the code would be same if we want to avoid listening to move event
if (p.isPreviewingNametag() && p.getArmorStandManager() != null) {
p.getArmorStandManager().teleport(p);
}
}
}
}
});
}
/**
* Checks for vehicle changes of player and sends packets if needed
* @param p - player to check
*/
private void processVehicles(TabPlayer p) {
Entity vehicle = ((Player)p.getPlayer()).getVehicle();
if (playersInVehicle.containsKey(p) && vehicle == null) {
//vehicle exit
getVehicles().remove(playersInVehicle.get(p).getEntityId());
p.getArmorStandManager().teleport();
playersInVehicle.remove(p);
}
if (!playersInVehicle.containsKey(p) && vehicle != null) {
//vehicle enter
getVehicles().put(vehicle.getEntityId(), getPassengers(vehicle));
p.getArmorStandManager().teleport();
playersInVehicle.put(p, vehicle);
}
}
/**
* Teleports armor stands of all passengers on specified vehicle
* @param vehicle - entity to check passengers of
*/
private void processPassengers(Entity vehicle) {
for (Entity passenger : getPassengers(vehicle)) {
if (passenger instanceof Player) {
TabPlayer pl = TAB.getInstance().getPlayer(passenger.getUniqueId());
pl.getArmorStandManager().teleport();
} else {
processPassengers(passenger);
}
}
}
@Override
public void unload() {
super.unload();
HandlerList.unregisterAll(eventListener);
for (TabPlayer p : tab.getPlayers()) {
if (p.getArmorStandManager() != null) p.getArmorStandManager().destroy();
}
getEntityIdMap().clear();
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
super.onJoin(connectedPlayer);
getEntityIdMap().put(((Player) connectedPlayer.getPlayer()).getEntityId(), connectedPlayer);
loadArmorStands(connectedPlayer);
if (isDisabledWorld(disabledUnlimitedWorlds, connectedPlayer.getWorldName())) {
playersInDisabledUnlimitedWorlds.add(connectedPlayer);
return;
}
if (isInDisabledWorld(connectedPlayer)) return;
loadPassengers(connectedPlayer);
for (TabPlayer viewer : tab.getPlayers()) {
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));
}
/**
* Loads all passengers riding this player and adds them to vehicle list
* @param p - player to load passengers of
*/
private void loadPassengers(TabPlayer p) {
if (((Entity) p.getPlayer()).getVehicle() == null) return;
Entity vehicle = ((Entity) p.getPlayer()).getVehicle();
getVehicles().put(vehicle.getEntityId(), getPassengers(vehicle));
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
super.onQuit(disconnectedPlayer);
for (TabPlayer all : tab.getPlayers()) {
if (all.getArmorStandManager() != null) all.getArmorStandManager().unregisterPlayer(disconnectedPlayer);
}
getEntityIdMap().remove(((Player) disconnectedPlayer.getPlayer()).getEntityId());
playersInVehicle.remove(disconnectedPlayer);
playerLocations.remove(disconnectedPlayer);
playersInDisabledUnlimitedWorlds.remove(disconnectedPlayer);
tab.getCPUManager().runTaskLater(100, "processing onQuit", getFeatureType(), UsageType.PLAYER_QUIT_EVENT, () -> disconnectedPlayer.getArmorStandManager().destroy());
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
super.onWorldChange(p, from, to);
if (isDisabledWorld(disabledWorlds, p.getWorldName())) {
playersInDisabledWorlds.add(p);
} else {
playersInDisabledWorlds.remove(p);
}
Set<TabPlayer> nearby = p.getArmorStandManager().getNearbyPlayers();
p.getArmorStandManager().destroy();
loadArmorStands(p);
loadPassengers(p);
for (TabPlayer viewer : tab.getPlayers()) {
viewer.getArmorStandManager().destroy(p);
if (nearby.contains(viewer) && to.equals(viewer.getWorldName())) spawnArmorStands(p, viewer, true);
}
}
/**
* 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
*/
private void spawnArmorStands(TabPlayer owner, TabPlayer viewer, boolean sendMutually) {
if (owner == viewer) return; //not displaying own armorstands
if (((Player) viewer.getPlayer()).getWorld() != ((Player) owner.getPlayer()).getWorld()) return; //different world
if (isInDisabledWorld(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(PropertyUtils.NAMETAG, pl.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue() + pl.getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue() + pl.getProperty(PropertyUtils.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(idCounter++, pl, p, height, false, markerFor18x));
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(idCounter++, pl, p, Double.parseDouble(line.getValue().toString()), true, markerFor18x));
}
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) {
p.getArmorStandManager().refresh();
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 (isInDisabledWorld(refreshed)) return;
if (force) {
refreshed.getArmorStandManager().destroy();
loadArmorStands(refreshed);
loadPassengers(refreshed);
for (TabPlayer viewer : tab.getPlayers()) {
if (viewer == refreshed) continue;
if (viewer.getWorldName().equals(refreshed.getWorldName())) {
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 void updateProperties(TabPlayer p) {
super.updateProperties(p);
p.loadPropertyFromConfig(PropertyUtils.CUSTOMTAGNAME, p.getName());
p.setProperty(PropertyUtils.NAMETAG, p.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue() + p.getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue() + p.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue());
for (String property : dynamicLines) {
if (!property.equals(PropertyUtils.NAMETAG)) p.loadPropertyFromConfig(property);
}
for (String property : staticLines.keySet()) {
if (!property.equals(PropertyUtils.NAMETAG)) p.loadPropertyFromConfig(property);
}
}
/**
* 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 (NMSStorage.getInstance().getMinorVersion() >= 11) {
return vehicle.getPassengers();
} else {
if (vehicle.getPassenger() != null) {
return Arrays.asList(vehicle.getPassenger());
} else {
return new ArrayList<>();
}
}
}
@Override
public void refreshUsedPlaceholders() {
usedPlaceholders = new HashSet<>(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(PropertyUtils.TAGPREFIX, PropertyUtils.CUSTOMTAGNAME, PropertyUtils.TAGSUFFIX));
for (String line : dynamicLines) {
usedPlaceholders.addAll(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(line));
}
for (String line : staticLines.keySet()) {
usedPlaceholders.addAll(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(line));
}
for (TabPlayer p : tab.getPlayers()) {
usedPlaceholders.addAll(tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(p.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue(),
p.getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue(), p.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue(),
p.getProperty(PropertyUtils.ABOVENAME).getCurrentRawValue(), p.getProperty(PropertyUtils.BELOWNAME).getCurrentRawValue()));
}
}
@Override
public TabFeature getFeatureType() {
return TabFeature.NAMETAGX;
}
@Override
public boolean getTeamVisibility(TabPlayer p, TabPlayer viewer) {
//only visible if player is on boat & config option is enabled and player is not invisible (1.8 bug) or feature is disabled
return (getPlayersOnBoats().contains(p) && !invisiblePlayers.contains(p.getName())) || isInDisabledWorld(p);
}
public Map<Integer, TabPlayer> getEntityIdMap() {
return entityIdMap;
}
public Map<Integer, List<Entity>> getVehicles() {
return vehicles;
}
public List<TabPlayer> getPlayersOnBoats() {
return playersOnBoats;
}
public boolean isInDisabledWorld(TabPlayer p) {
return playersInDisabledWorlds.contains(p) || isDisabledWorld(disabledUnlimitedWorlds, p.getWorldName());
}
}
@@ -0,0 +1,122 @@
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.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
/**
* The packet listening part for securing proper functionality of armor stands
* Bukkit events are too unreliable and delayed/ahead which causes desync
*/
public class PacketListener implements RawPacketListener {
//main feature
private NameTagX nameTagX;
//nms storage
private NMSStorage nms;
//tab instance
private TAB tab;
/**
* Constructs new instance with given parameters and loads config options
* @param nameTagX - main feature
* @param nms - nms storage
* @param tab - tab instance
*/
public PacketListener(NameTagX nameTagX, NMSStorage nms, TAB tab) {
this.nameTagX = nameTagX;
this.nms = nms;
this.tab = tab;
}
@Override
public Object onPacketReceive(TabPlayer sender, Object packet) throws IllegalAccessException {
if (sender.getVersion().getMinorVersion() == 8 && nms.getClass("PacketPlayInUseEntity").isInstance(packet)) {
int entityId = nms.getField("PacketPlayInUseEntity_ENTITY").getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : tab.getPlayers()) {
if (all.isLoaded() && all.getArmorStandManager().hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.setField(packet, "PacketPlayInUseEntity_ENTITY", ((Player) attacked.getPlayer()).getEntityId());
}
}
return packet;
}
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws IllegalAccessException {
if (receiver.getVersion().getMinorVersion() < 8) return;
//using bukkit player to check world due to old data on world change due to asynchronous processing & world name changing
String world = ((Player)receiver.getPlayer()).getWorld().getName();
if (!receiver.isLoaded() || nameTagX.getPlayersInDisabledWorlds().contains(receiver) || nameTagX.isDisabledWorld(nameTagX.disabledUnlimitedWorlds, world)) return;
if (nms.getClass("PacketPlayOutEntity").isInstance(packet) && !nms.getClass("PacketPlayOutEntityLook").isInstance(packet)) { //ignoring head rotation only packets
onEntityMove(receiver, nms.getField("PacketPlayOutEntity_ENTITYID").getInt(packet));
} else if (nms.getClass("PacketPlayOutEntityTeleport").isInstance(packet)) {
onEntityMove(receiver, nms.getField("PacketPlayOutEntityTeleport_ENTITYID").getInt(packet));
} else if (nms.getClass("PacketPlayOutNamedEntitySpawn").isInstance(packet)) {
onEntitySpawn(receiver, nms.getField("PacketPlayOutNamedEntitySpawn_ENTITYID").getInt(packet));
} else if (nms.getClass("PacketPlayOutEntityDestroy").isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
onEntityDestroy(receiver, nms.getField("PacketPlayOutEntityDestroy_ENTITIES").getInt(packet));
} else {
onEntityDestroy(receiver, (int[]) nms.getField("PacketPlayOutEntityDestroy_ENTITIES").get(packet));
}
}
}
/**
* Processes entity move packet
* @param receiver - packet receiver
* @param entityId - entity that moved
*/
public void onEntityMove(TabPlayer receiver, int entityId) {
TabPlayer pl = nameTagX.getEntityIdMap().get(entityId);
List<Entity> vehicleList;
if (pl != null) {
//player moved
tab.getCPUManager().runMeasuredTask("processing EntityMove", getFeatureType(), UsageType.PACKET_ENTITY_MOVE, () -> pl.getArmorStandManager().teleport(receiver));
} else if ((vehicleList = nameTagX.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.getCPUManager().runMeasuredTask("processing EntityMove", getFeatureType(), UsageType.PACKET_ENTITY_MOVE, () -> passenger.getArmorStandManager().teleport(receiver));
}
}
}
}
public void onEntitySpawn(TabPlayer receiver, int entityId) {
TabPlayer spawnedPlayer = nameTagX.getEntityIdMap().get(entityId);
if (spawnedPlayer != null && spawnedPlayer.isLoaded()) {
tab.getCPUManager().runMeasuredTask("processing NamedEntitySpawn", getFeatureType(), UsageType.PACKET_NAMED_ENTITY_SPAWN, () -> spawnedPlayer.getArmorStandManager().spawn(receiver));
}
}
public void onEntityDestroy(TabPlayer receiver, int... entities) {
for (int entity : entities) {
TabPlayer despawnedPlayer = nameTagX.getEntityIdMap().get(entity);
if (despawnedPlayer != null && despawnedPlayer.isLoaded())
tab.getCPUManager().runMeasuredTask("processing EntityDestroy", getFeatureType(), UsageType.PACKET_ENTITY_DESTROY, () -> despawnedPlayer.getArmorStandManager().destroy(receiver));
}
}
@Override
public TabFeature getFeatureType() {
return TabFeature.NAMETAGX;
}
}
@@ -0,0 +1,520 @@
package me.neznamy.tab.platforms.bukkit.nms;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import io.netty.channel.Channel;
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 class NMSStorage {
//instance of this class
private static NMSStorage instance;
//data watcher registry
private DataWatcherRegistry dataWatcherRegistry;
//server package, such as "v1_16_R3"
private String serverPackage;
//server minor version such as "16"
private int minorVersion;
private Map<String, Class<?>> classes = new HashMap<>();
private Map<String, Constructor<?>> constructors = new HashMap<>();
private Map<String, Field> fields = new HashMap<>();
private Map<String, Method> methods = new HashMap<>();
private Map<String, Enum[]> enums = new HashMap<>();
/**
* Creates new instance, initializes required NMS classes and fields
* @throws NoSuchFieldException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws ClassNotFoundException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public NMSStorage() throws NoSuchFieldException, NoSuchMethodException, SecurityException, ClassNotFoundException, IllegalAccessException, InvocationTargetException {
serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
classes.put("EnumChatFormat", getNMSClass("net.minecraft.EnumChatFormat", "EnumChatFormat"));
classes.put("EntityPlayer", getNMSClass("net.minecraft.server.level.EntityPlayer", "EntityPlayer"));
classes.put("Entity", getNMSClass("net.minecraft.world.entity.Entity", "Entity"));
classes.put("EntityLiving", getNMSClass("net.minecraft.world.entity.EntityLiving", "EntityLiving"));
classes.put("Packet", getNMSClass("net.minecraft.network.protocol.Packet", "Packet"));
classes.put("PlayerConnection", getNMSClass("net.minecraft.server.network.PlayerConnection", "PlayerConnection"));
classes.put("NetworkManager", getNMSClass("net.minecraft.network.NetworkManager", "NetworkManager"));
fields.put("PING", getField(getClass("EntityPlayer"), "ping", "latency", "e"));
fields.put("PLAYER_CONNECTION", getFields(getClass("EntityPlayer"), getClass("PlayerConnection")).get(0));
methods.put("getHandle", Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle"));
methods.put("sendPacket", getMethod(getClass("PlayerConnection"), new String[]{"sendPacket", "func_147359_a"}, getClass("Packet")));
if (minorVersion >= 7) {
fields.put("NETWORK_MANAGER", getFields(getClass("PlayerConnection"), getClass("NetworkManager")).get(0));
}
if (minorVersion >= 8) {
fields.put("CHANNEL", getFields(getClass("NetworkManager"), Channel.class).get(0));
methods.put("getProfile", getClass("EntityPlayer").getMethod("getProfile"));
}
initializeChatComponents();
initializeChatPacket();
initializeDataWatcher();
initializeEntitySpawnPacket();
initializeEntityTeleportPacket();
initializeHeaderFooterPacket();
initializeOtherEntityPackets();
initializePlayerInfoPacket();
initializeScoreboardPackets();
initializeEnums();
}
/**
* 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 initializeEnums() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException {
for (String name : Arrays.asList("ChatMessageType", "EnumPlayerInfoAction", "EnumGamemode", "EnumScoreboardHealthDisplay", "EnumScoreboardAction",
"PacketPlayOutScoreboardTeam_a", "EnumChatFormat", "EnumNameTagVisibility", "EnumTeamPush")) {
if (classes.containsKey(name)) enums.put(name, (Enum[]) getClass(name).getMethod("values").invoke(null));
}
}
private void initializeChatComponents() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
if (minorVersion >= 7) {
classes.put("ChatBaseComponent", getNMSClass("net.minecraft.network.chat.ChatBaseComponent", "ChatBaseComponent"));
classes.put("ChatClickable", getNMSClass("net.minecraft.network.chat.ChatClickable", "ChatClickable"));
classes.put("ChatComponentText", getNMSClass("net.minecraft.network.chat.ChatComponentText", "ChatComponentText"));
classes.put("ChatHoverable", getNMSClass("net.minecraft.network.chat.ChatHoverable", "ChatHoverable"));
classes.put("ChatModifier", getNMSClass("net.minecraft.network.chat.ChatModifier", "ChatModifier"));
classes.put("ChatSerializer", getNMSClass("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer"));
classes.put("EnumClickAction", getNMSClass("net.minecraft.network.chat.ChatClickable$EnumClickAction", "ChatClickable$EnumClickAction", "EnumClickAction"));
classes.put("EnumHoverAction", getNMSClass("net.minecraft.network.chat.ChatHoverable$EnumHoverAction", "ChatHoverable$EnumHoverAction", "EnumHoverAction"));
classes.put("IChatBaseComponent", getNMSClass("net.minecraft.network.chat.IChatBaseComponent", "IChatBaseComponent"));
constructors.put("ChatComponentText", getClass("ChatComponentText").getConstructor(String.class));
constructors.put("ChatClickable", getClass("ChatClickable").getConstructor(getClass("EnumClickAction"), String.class));
constructors.put("ChatHoverable", getClass("ChatHoverable").getConstructors()[0]);
fields.put("ChatBaseComponent_extra", getFields(getClass("ChatBaseComponent"), List.class).get(0));
fields.put("ChatBaseComponent_modifier", getFields(getClass("ChatBaseComponent"), getClass("ChatModifier")).get(0));
fields.put("ChatComponentText_text", getFields(getClass("ChatComponentText"), String.class).get(0));
fields.put("ChatClickable_action", getFields(getClass("ChatClickable"), getClass("EnumClickAction")).get(0));
fields.put("ChatClickable_value", getFields(getClass("ChatClickable"), String.class).get(0));
fields.put("ChatHoverable_action", getFields(getClass("ChatHoverable"), getClass("EnumHoverAction")).get(0));
List<Field> booleans = getFields(getClass("ChatModifier"), Boolean.class);
fields.put("ChatModifier_bold", booleans.get(0));
fields.put("ChatModifier_italic", booleans.get(1));
fields.put("ChatModifier_underlined", booleans.get(2));
fields.put("ChatModifier_strikethrough", booleans.get(3));
fields.put("ChatModifier_obfuscated", booleans.get(4));
fields.put("ChatModifier_clickEvent", getFields(getClass("ChatModifier"), getClass("ChatClickable")).get(0));
fields.put("ChatModifier_hoverEvent", getFields(getClass("ChatModifier"), getClass("ChatHoverable")).get(0));
methods.put("ChatComponentText_addSibling", getMethod(getClass("ChatComponentText"), new String[]{"addSibling", "a", "func_150257_a"}, getClass("IChatBaseComponent"))); //v1.7.R4+, v1.7.R3-
methods.put("ChatSerializer_DESERIALIZE", getMethod(getClass("ChatSerializer"), new String[]{"a", "func_150699_a"}, String.class));
methods.put("EnumClickAction_a", getMethod(getClass("EnumClickAction"), new String[]{"a", "func_150672_a"}, String.class));
methods.put("EnumHoverAction_a", getMethod(getClass("EnumHoverAction"), new String[]{"a", "func_150684_a"}, String.class));
}
if (minorVersion >= 16) {
classes.put("ChatHexColor", getNMSClass("net.minecraft.network.chat.ChatHexColor", "ChatHexColor"));
constructors.put("ChatModifier", getConstructor(getClass("ChatModifier"), 10));
List<Field> list = getFields(getClass("ChatHexColor"), String.class);
fields.put("ChatHexColor_name", list.get(list.size()-1));
fields.put("ChatHexColor_rgb", getFields(getClass("ChatHexColor"), int.class).get(0));
fields.put("ChatHoverable_value", getFields(getClass("ChatHoverable"), Object.class).get(0));
fields.put("ChatModifier_color", getFields(getClass("ChatModifier"), getClass("ChatHexColor")).get(0));
methods.put("ChatHexColor_ofInt", getClass("ChatHexColor").getMethod("a", int.class));
methods.put("ChatHexColor_ofString", getClass("ChatHexColor").getMethod("a", String.class));
} else if (minorVersion >= 7) {
constructors.put("ChatModifier", getClass("ChatModifier").getConstructor());
fields.put("ChatHoverable_value", getFields(getClass("ChatHoverable"), getClass("IChatBaseComponent")).get(0));
fields.put("ChatModifier_color", getFields(getClass("ChatModifier"), getClass("EnumChatFormat")).get(0));
}
}
private void initializeChatPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
classes.put("PacketPlayOutChat", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutChat", "PacketPlayOutChat", "Packet3Chat"));
if (minorVersion >= 12) {
classes.put("ChatMessageType", getNMSClass("net.minecraft.network.chat.ChatMessageType", "ChatMessageType"));
}
if (minorVersion >= 16) {
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), getClass("ChatMessageType"), UUID.class));
} else if (minorVersion >= 12) {
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), getClass("ChatMessageType")));
} else if (minorVersion >= 8) {
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent"), byte.class));
} else if (minorVersion >= 7){
constructors.put("PacketPlayOutChat", getClass("PacketPlayOutChat").getConstructor(getClass("IChatBaseComponent")));
}
}
private void initializeDataWatcher() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
classes.put("DataWatcher", getNMSClass("net.minecraft.network.syncher.DataWatcher", "DataWatcher"));
classes.put("DataWatcherItem", getNMSClass("net.minecraft.network.syncher.DataWatcher$Item", "DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject"));
if (minorVersion >= 7) {
constructors.put("DataWatcher", getClass("DataWatcher").getConstructor(getClass("Entity")));
} else {
constructors.put("DataWatcher", getClass("DataWatcher").getConstructor());
}
if (minorVersion >= 9) {
classes.put("DataWatcherObject", getNMSClass("net.minecraft.network.syncher.DataWatcherObject", "DataWatcherObject"));
classes.put("DataWatcherRegistry", getNMSClass("net.minecraft.network.syncher.DataWatcherRegistry", "DataWatcherRegistry"));
classes.put("DataWatcherSerializer", getNMSClass("net.minecraft.network.syncher.DataWatcherSerializer", "DataWatcherSerializer"));
constructors.put("DataWatcherObject", getClass("DataWatcherObject").getConstructor(int.class, getClass("DataWatcherSerializer")));
fields.put("DataWatcherItem_TYPE", getFields(getClass("DataWatcherItem"), getClass("DataWatcherObject")).get(0));
fields.put("DataWatcherItem_VALUE", getFields(getClass("DataWatcherItem"), Object.class).get(0));
fields.put("DataWatcherObject_SLOT", getFields(getClass("DataWatcherObject"), int.class).get(0));
fields.put("DataWatcherObject_SERIALIZER", getFields(getClass("DataWatcherObject"), getClass("DataWatcherSerializer")).get(0));
methods.put("DataWatcher_REGISTER", getClass("DataWatcher").getMethod("register", getClass("DataWatcherObject"), Object.class));
} else {
fields.put("DataWatcherItem_TYPE", getFields(getClass("DataWatcherItem"), int.class).get(1));
fields.put("DataWatcherItem_VALUE", getFields(getClass("DataWatcherItem"), Object.class).get(0));
methods.put("DataWatcher_REGISTER", getMethod(getClass("DataWatcher"), new String[]{"a", "func_75682_a"}, int.class, Object.class));
}
dataWatcherRegistry = new DataWatcherRegistry(this, getClass("DataWatcherRegistry"));
}
private void initializeEntitySpawnPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
classes.put("PacketPlayOutSpawnEntityLiving", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn"));
constructors.put("PacketPlayOutSpawnEntityLiving", getClass("PacketPlayOutSpawnEntityLiving").getConstructor(getClass("EntityLiving")));
fields.put("PacketPlayOutSpawnEntityLiving_ENTITYID", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(0));
fields.put("PacketPlayOutSpawnEntityLiving_ENTITYTYPE", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(1));
fields.put("PacketPlayOutSpawnEntityLiving_YAW", getFields(getClass("PacketPlayOutSpawnEntityLiving"), byte.class).get(0));
fields.put("PacketPlayOutSpawnEntityLiving_PITCH", getFields(getClass("PacketPlayOutSpawnEntityLiving"), byte.class).get(0));
if (minorVersion >= 9) {
fields.put("PacketPlayOutSpawnEntityLiving_UUID", getFields(getClass("PacketPlayOutSpawnEntityLiving"), UUID.class).get(0));
fields.put("PacketPlayOutSpawnEntityLiving_X", getFields(getClass("PacketPlayOutSpawnEntityLiving"), double.class).get(0));
fields.put("PacketPlayOutSpawnEntityLiving_Y", getFields(getClass("PacketPlayOutSpawnEntityLiving"), double.class).get(1));
fields.put("PacketPlayOutSpawnEntityLiving_Z", getFields(getClass("PacketPlayOutSpawnEntityLiving"), double.class).get(2));
} else {
fields.put("PacketPlayOutSpawnEntityLiving_X", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(2));
fields.put("PacketPlayOutSpawnEntityLiving_Y", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(3));
fields.put("PacketPlayOutSpawnEntityLiving_Z", getFields(getClass("PacketPlayOutSpawnEntityLiving"), int.class).get(4));
}
if (minorVersion <= 14) {
fields.put("PacketPlayOutSpawnEntityLiving_DATAWATCHER", getFields(getClass("PacketPlayOutSpawnEntityLiving"), getClass("DataWatcher")).get(0));
}
}
private void initializeEntityTeleportPacket() throws NoSuchMethodException, SecurityException, ClassNotFoundException {
classes.put("PacketPlayOutEntityTeleport", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport"));
constructors.put("PacketPlayOutEntityTeleport", getClass("PacketPlayOutEntityTeleport").getConstructor(getClass("Entity")));
fields.put("PacketPlayOutEntityTeleport_ENTITYID", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(0));
fields.put("PacketPlayOutEntityTeleport_YAW", getFields(getClass("PacketPlayOutEntityTeleport"), byte.class).get(0));
fields.put("PacketPlayOutEntityTeleport_PITCH", getFields(getClass("PacketPlayOutEntityTeleport"), byte.class).get(1));
if (minorVersion >= 9) {
fields.put("PacketPlayOutEntityTeleport_X", getFields(getClass("PacketPlayOutEntityTeleport"), double.class).get(0));
fields.put("PacketPlayOutEntityTeleport_Y", getFields(getClass("PacketPlayOutEntityTeleport"), double.class).get(1));
fields.put("PacketPlayOutEntityTeleport_Z", getFields(getClass("PacketPlayOutEntityTeleport"), double.class).get(2));
} else {
fields.put("PacketPlayOutEntityTeleport_X", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(1));
fields.put("PacketPlayOutEntityTeleport_Y", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(2));
fields.put("PacketPlayOutEntityTeleport_Z", getFields(getClass("PacketPlayOutEntityTeleport"), int.class).get(3));
}
}
private void initializeHeaderFooterPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
if (minorVersion >= 8) {
classes.put("PacketPlayOutPlayerListHeaderFooter", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter"));
fields.put("PacketPlayOutPlayerListHeaderFooter_HEADER", getFields(getClass("PacketPlayOutPlayerListHeaderFooter"), getClass("IChatBaseComponent")).get(0));
fields.put("PacketPlayOutPlayerListHeaderFooter_FOOTER", getFields(getClass("PacketPlayOutPlayerListHeaderFooter"), getClass("IChatBaseComponent")).get(1));
if (minorVersion >= 17) {
constructors.put("PacketPlayOutPlayerListHeaderFooter", getClass("PacketPlayOutPlayerListHeaderFooter").getConstructor(getClass("IChatBaseComponent"), getClass("IChatBaseComponent")));
} else {
constructors.put("PacketPlayOutPlayerListHeaderFooter", getClass("PacketPlayOutPlayerListHeaderFooter").getConstructor());
}
}
}
private void initializeOtherEntityPackets() throws ClassNotFoundException, NoSuchMethodException {
classes.put("PacketPlayInUseEntity", getNMSClass("net.minecraft.network.protocol.game.PacketPlayInUseEntity", "PacketPlayInUseEntity", "Packet7UseEntity"));
classes.put("PacketPlayOutEntity", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity"));
classes.put("PacketPlayOutEntityDestroy", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity"));
classes.put("PacketPlayOutEntityLook", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook"));
classes.put("PacketPlayOutEntityMetadata", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata"));
classes.put("PacketPlayOutNamedEntitySpawn", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn"));
constructors.put("PacketPlayOutEntityMetadata", getClass("PacketPlayOutEntityMetadata").getConstructor(int.class, getClass("DataWatcher"), boolean.class));
constructors.put("PacketPlayOutEntityDestroy", getConstructor(getClass("PacketPlayOutEntityDestroy"), 1));
fields.put("PacketPlayOutEntity_ENTITYID", getFields(getClass("PacketPlayOutEntity"), int.class).get(0));
fields.put("PacketPlayOutEntityMetadata_LIST", getFields(getClass("PacketPlayOutEntityMetadata"), List.class).get(0));
fields.put("PacketPlayOutNamedEntitySpawn_ENTITYID", getFields(getClass("PacketPlayOutNamedEntitySpawn"), int.class).get(0));
if (minorVersion >= 7) {
classes.put("EnumEntityUseAction", getNMSClass("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction", "PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction"));
fields.put("PacketPlayInUseEntity_ENTITY", getFields(getClass("PacketPlayInUseEntity"), int.class).get(0));
fields.put("PacketPlayInUseEntity_ACTION", getFields(getClass("PacketPlayInUseEntity"), getClass("EnumEntityUseAction")).get(0));
}
if (minorVersion >= 17) {
classes.put("PacketPlayInUseEntity$d", Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$d"));
fields.put("PacketPlayOutEntityDestroy_ENTITIES", getFields(getClass("PacketPlayOutEntityDestroy"), int.class).get(0));
} else {
fields.put("PacketPlayOutEntityDestroy_ENTITIES", getFields(getClass("PacketPlayOutEntityDestroy"), int[].class).get(0));
}
}
private void initializePlayerInfoPacket() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
if (minorVersion >= 8) {
classes.put("PacketPlayOutPlayerInfo", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo"));
classes.put("EnumPlayerInfoAction", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction"));
classes.put("PlayerInfoData", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"));
classes.put("EnumGamemode", getNMSClass("net.minecraft.world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode"));
constructors.put("PacketPlayOutPlayerInfo", getClass("PacketPlayOutPlayerInfo").getConstructor(getClass("EnumPlayerInfoAction"), Array.newInstance(getClass("EntityPlayer"), 0).getClass()));
constructors.put("PlayerInfoData", getClass("PlayerInfoData").getConstructors()[0]);
fields.put("PacketPlayOutPlayerInfo_ACTION", getFields(getClass("PacketPlayOutPlayerInfo"), getClass("EnumPlayerInfoAction")).get(0));
fields.put("PacketPlayOutPlayerInfo_PLAYERS", getFields(getClass("PacketPlayOutPlayerInfo"), List.class).get(0));
methods.put("PlayerInfoData_getProfile", getClass("PlayerInfoData").getMethod("a"));
methods.put("PlayerInfoData_getLatency", getClass("PlayerInfoData").getMethod("b"));
methods.put("PlayerInfoData_getGamemode", getClass("PlayerInfoData").getMethod("c"));
methods.put("PlayerInfoData_getDisplayName", getClass("PlayerInfoData").getMethod("d"));
}
}
private void initializeScoreboardPackets() throws ClassNotFoundException, NoSuchMethodException, SecurityException {
classes.put("PacketPlayOutScoreboardDisplayObjective", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective", "PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective"));
classes.put("PacketPlayOutScoreboardObjective", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective", "PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective"));
classes.put("PacketPlayOutScoreboardScore", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore", "PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore"));
classes.put("PacketPlayOutScoreboardTeam", getNMSClass("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam", "PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam"));
classes.put("Scoreboard", getNMSClass("net.minecraft.world.scores.Scoreboard", "Scoreboard"));
classes.put("ScoreboardObjective", getNMSClass("net.minecraft.world.scores.ScoreboardObjective", "ScoreboardObjective"));
classes.put("ScoreboardTeam", getNMSClass("net.minecraft.world.scores.ScoreboardTeam", "ScoreboardTeam"));
classes.put("ScoreboardScore", getNMSClass("net.minecraft.world.scores.ScoreboardScore", "ScoreboardScore"));
classes.put("IScoreboardCriteria", getNMSClass("net.minecraft.world.scores.criteria.IScoreboardCriteria", "IScoreboardCriteria"));
constructors.put("ScoreboardObjective", getClass("ScoreboardObjective").getConstructors()[0]);
constructors.put("Scoreboard", getClass("Scoreboard").getConstructor());
constructors.put("ScoreboardTeam", getClass("ScoreboardTeam").getConstructor(getClass("Scoreboard"), String.class));
constructors.put("ScoreboardScore", getClass("ScoreboardScore").getConstructor(getClass("Scoreboard"), getClass("ScoreboardObjective"), String.class));
constructors.put("PacketPlayOutScoreboardDisplayObjective", getClass("PacketPlayOutScoreboardDisplayObjective").getConstructor(int.class, getClass("ScoreboardObjective")));
fields.put("PacketPlayOutScoreboardDisplayObjective_POSITION", getFields(getClass("PacketPlayOutScoreboardDisplayObjective"), int.class).get(0));
fields.put("PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME", getFields(getClass("PacketPlayOutScoreboardDisplayObjective"), String.class).get(0));
fields.put("PacketPlayOutScoreboardObjective_OBJECTIVENAME", getFields(getClass("PacketPlayOutScoreboardObjective"), String.class).get(0));
List<Field> list = getFields(getClass("PacketPlayOutScoreboardObjective"), int.class);
fields.put("PacketPlayOutScoreboardObjective_METHOD", list.get(list.size()-1));
fields.put("PacketPlayOutScoreboardTeam_NAME", getFields(getClass("PacketPlayOutScoreboardTeam"), String.class).get(0));
fields.put("PacketPlayOutScoreboardTeam_PLAYERS", getFields(getClass("PacketPlayOutScoreboardTeam"), Collection.class).get(0));
methods.put("ScoreboardTeam_getPlayerNameSet", getMethod(getClass("ScoreboardTeam"), new String[]{"getPlayerNameSet", "func_96670_d"}));
methods.put("ScoreboardScore_setScore", getMethod(getClass("ScoreboardScore"), new String[]{"setScore", "func_96647_c"}, int.class));
if (minorVersion >= 8) {
classes.put("EnumScoreboardHealthDisplay", getNMSClass("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay", "IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay"));
classes.put("EnumScoreboardAction", getNMSClass("net.minecraft.server.ScoreboardServer$Action", "ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction"));
classes.put("EnumNameTagVisibility", getNMSClass("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility", "ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility"));
fields.put("PacketPlayOutScoreboardObjective_RENDERTYPE", getFields(getClass("PacketPlayOutScoreboardObjective"), getClass("EnumScoreboardHealthDisplay")).get(0));
methods.put("ScoreboardTeam_setNameTagVisibility", getMethod(getClass("ScoreboardTeam"), new String[]{"setNameTagVisibility", "a"}, getClass("EnumNameTagVisibility")));
}
if (minorVersion >= 9) {
classes.put("EnumTeamPush", getNMSClass("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush"));
methods.put("ScoreboardTeam_setCollisionRule", getClass("ScoreboardTeam").getMethod("setCollisionRule", getClass("EnumTeamPush")));
}
if (minorVersion >= 13) {
constructors.put("PacketPlayOutScoreboardObjective", getClass("PacketPlayOutScoreboardObjective").getConstructor(getClass("ScoreboardObjective"), int.class));
constructors.put("PacketPlayOutScoreboardScore_1_13", getClass("PacketPlayOutScoreboardScore").getConstructor(getClass("EnumScoreboardAction"), String.class, String.class, int.class));
fields.put("PacketPlayOutScoreboardObjective_DISPLAYNAME", getFields(getClass("PacketPlayOutScoreboardObjective"), getClass("IChatBaseComponent")).get(0));
methods.put("ScoreboardTeam_setPrefix", getClass("ScoreboardTeam").getMethod("setPrefix", getClass("IChatBaseComponent")));
methods.put("ScoreboardTeam_setSuffix", getClass("ScoreboardTeam").getMethod("setSuffix", getClass("IChatBaseComponent")));
methods.put("ScoreboardTeam_setColor", getClass("ScoreboardTeam").getMethod("setColor", getClass("EnumChatFormat")));
} else {
constructors.put("PacketPlayOutScoreboardObjective", getClass("PacketPlayOutScoreboardObjective").getConstructor());
constructors.put("PacketPlayOutScoreboardScore_String", getClass("PacketPlayOutScoreboardScore").getConstructor(String.class));
fields.put("PacketPlayOutScoreboardObjective_DISPLAYNAME", getFields(getClass("PacketPlayOutScoreboardObjective"), String.class).get(1));
methods.put("ScoreboardTeam_setPrefix", getMethod(getClass("ScoreboardTeam"), new String[]{"setPrefix", "func_96666_b"}, String.class));
methods.put("ScoreboardTeam_setSuffix", getMethod(getClass("ScoreboardTeam"), new String[]{"setSuffix", "func_96662_c"}, String.class));
if (minorVersion >= 8) {
constructors.put("PacketPlayOutScoreboardScore", getClass("PacketPlayOutScoreboardScore").getConstructor(getClass("ScoreboardScore")));
} else {
constructors.put("PacketPlayOutScoreboardScore", getClass("PacketPlayOutScoreboardScore").getConstructor(getClass("ScoreboardScore"), int.class));
}
}
if (minorVersion >= 17) {
classes.put("PacketPlayOutScoreboardTeam_a", Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$a"));
methods.put("PacketPlayOutScoreboardTeam_of", getClass("PacketPlayOutScoreboardTeam").getMethod("a", getClass("ScoreboardTeam")));
methods.put("PacketPlayOutScoreboardTeam_ofBoolean", getClass("PacketPlayOutScoreboardTeam").getMethod("a", getClass("ScoreboardTeam"), boolean.class));
methods.put("PacketPlayOutScoreboardTeam_ofString", getClass("PacketPlayOutScoreboardTeam").getMethod("a", getClass("ScoreboardTeam"), String.class, getClass("PacketPlayOutScoreboardTeam_a")));
} else {
constructors.put("PacketPlayOutScoreboardTeam", getClass("PacketPlayOutScoreboardTeam").getConstructor(getClass("ScoreboardTeam"), int.class));
}
}
/**
* A helper method that prints all methods of class into console, including their return type, name and parameters
* Useful for modded servers which code I can not access
* @param clazz - class to show methods of
*/
@SuppressWarnings("unused")
private void showMethods(Class<?> clazz) {
TAB.getInstance().getPlatform().sendConsoleMessage("--- " + clazz.getSimpleName() + " ---", false);
for (Method m : clazz.getMethods()) {
TAB.getInstance().getPlatform().sendConsoleMessage(m.getReturnType().getName() + " " + m.getName() + "(" + Arrays.toString(m.getParameterTypes()) + ")", false);
}
}
/**
* 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... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getNMSClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + 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<?> getNMSClass(String name) throws ClassNotFoundException {
if (minorVersion >= 17) {
return Class.forName(name);
} else {
try {
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
} catch (ClassNotFoundException e) {
//modded server?
return Main.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
//nested class in modded server
throw new ClassNotFoundException(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 clazz.getMethod(name, parameterTypes);
} catch (Exception e) {
//not the first method in array
}
}
throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(names) + " in class " + clazz.getName());
}
/**
* 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
*/
public 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) {
field.setAccessible(true);
list.add(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))) {
f.setAccessible(true);
return f;
}
}
if (name.equals("ping")){
return getField(clazz, "field_71138_i"); //thermos
}
throw new NoSuchFieldException("Field \"" + name + "\" was not found in class " + clazz.getName());
}
private Constructor<?> getConstructor(Class<?> clazz, int parameterCount) throws NoSuchMethodException {
for (Constructor<?> c : clazz.getDeclaredConstructors()) {
if (c.getParameterCount() == parameterCount) {
c.setAccessible(true);
return c;
}
}
throw new NoSuchMethodException("No constructor found in class " + clazz.getName() + " with " + parameterCount + " parameters");
}
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));
}
public int getMinorVersion() {
return minorVersion;
}
public Class<?> getClass(String name){
return classes.get(name);
}
public Constructor getConstructor(String name){
return constructors.get(name);
}
public Field getField(String name){
return fields.get(name);
}
public Method getMethod(String name) {
return methods.get(name);
}
public Enum[] getEnum(String name) {
return enums.get(name);
}
public void setField(Object obj, String field, Object value) throws IllegalAccessException {
fields.get(field).set(obj, value);
}
public DataWatcherRegistry getDataWatcherRegistry() {
return dataWatcherRegistry;
}
}
@@ -1,12 +1,11 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import me.neznamy.tab.platforms.bukkit.nms.NMSHook;
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
@@ -14,7 +13,7 @@ import me.neznamy.tab.shared.ProtocolVersion;
public class DataWatcher {
//datawatcher data
private Map<Integer, DataWatcherItem> dataValues = new HashMap<Integer, DataWatcherItem>();
private Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
//a helper for easier data write
private DataWatcherHelper helper = new DataWatcherHelper(this);
@@ -25,7 +24,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));
}
/**
@@ -56,17 +55,26 @@ 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 InvocationTargetException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Object toNMS() throws Exception {
NMSStorage nms = NMSHook.nms;
Object nmsWatcher = nms.newDataWatcher.newInstance(new Object[] {null});
public Object toNMS() throws InstantiationException, IllegalAccessException, InvocationTargetException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (nms.getMinorVersion() >= 7) {
nmsWatcher = nms.getConstructor("DataWatcher").newInstance(new Object[] {null});
} else {
nmsWatcher = nms.getConstructor("DataWatcher").newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 9) {
nms.DataWatcher_REGISTER.invoke(nmsWatcher, nms.newDataWatcherObject.newInstance(item.type.position, item.type.classType), item.value);
Object position;
if (nms.getMinorVersion() >= 9) {
position = nms.getConstructor("DataWatcherObject").newInstance(item.getType().getPosition(), item.getType().getClassType());
} else {
nms.DataWatcher_REGISTER.invoke(nmsWatcher, item.type.position, item.value);
position = item.getType().getPosition();
}
nms.getMethod("DataWatcher_REGISTER").invoke(nmsWatcher, position, item.getValue());
}
return nmsWatcher;
}
@@ -75,16 +83,19 @@ public class DataWatcher {
* Reads NMS data watcher and returns and instance of this class with same data
* @param nmsWatcher - NMS datawatcher to read
* @return an instance of this class with same values
* @throws Exception - if something fails
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@SuppressWarnings("unchecked")
public static DataWatcher fromNMS(Object nmsWatcher) throws Exception{
public static DataWatcher fromNMS(Object nmsWatcher) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException{
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;
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import java.util.Optional;
import me.neznamy.tab.platforms.bukkit.BukkitPacketBuilder;
import me.neznamy.tab.platforms.bukkit.nms.NMSHook;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
@@ -14,11 +14,12 @@ import me.neznamy.tab.shared.packets.IChatBaseComponent;
public class DataWatcherHelper {
//position of armor stand flags
private final int ARMOR_STAND_BYTEFLAGS_POSITION = getArmorStandFlagsPosition();
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
//original datawatcher to write to
private DataWatcher data;
//data watcher registry
private DataWatcherRegistry registry;
/**
@@ -27,7 +28,7 @@ public class DataWatcherHelper {
*/
public DataWatcherHelper(DataWatcher data) {
this.data = data;
this.registry = NMSHook.registry;
this.registry = NMSStorage.getInstance().getDataWatcherRegistry();
}
/**
@@ -35,13 +36,16 @@ public class DataWatcherHelper {
* @return armor stand flags position based on server version
*/
private int getArmorStandFlagsPosition() {
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 15) {
if (NMSStorage.getInstance().getMinorVersion() >= 17) {
//1.17.x
return 15;
} else if (NMSStorage.getInstance().getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 14) {
} else if (NMSStorage.getInstance().getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 10) {
} else if (NMSStorage.getInstance().getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
@@ -55,7 +59,7 @@ public class DataWatcherHelper {
* @param flags - flags to write
*/
public void setEntityFlags(byte flags) {
data.setValue(new DataWatcherObject(0, registry.Byte), flags);
data.setValue(new DataWatcherObject(0, registry.getByte()), flags);
}
/**
@@ -64,17 +68,22 @@ public class DataWatcherHelper {
* @param clientVersion - client version
*/
public void setCustomName(String customName, ProtocolVersion clientVersion) {
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 13) {
if (NMSStorage.getInstance().getMinorVersion() >= 13) {
try {
data.setValue(new DataWatcherObject(2, registry.Optional_IChatBaseComponent), Optional.ofNullable(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).stringToComponent(IChatBaseComponent.optimizedComponent(customName).toString(clientVersion))));
data.setValue(new DataWatcherObject(2, registry.getOptionalComponent()), Optional.ofNullable(((BukkitPacketBuilder)TAB.getInstance().getPacketBuilder()).toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to create component", e);
}
} else if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 8){
data.setValue(new DataWatcherObject(2, registry.String), customName);
} else if (NMSStorage.getInstance().getMinorVersion() >= 8){
data.setValue(new DataWatcherObject(2, registry.getString()), customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
data.setValue(new DataWatcherObject(10, registry.String), cutName);
if (NMSStorage.getInstance().getMinorVersion() >= 6){
data.setValue(new DataWatcherObject(10, registry.getString()), cutName);
} else {
data.setValue(new DataWatcherObject(5, registry.getString()), cutName);
}
}
}
@@ -84,10 +93,10 @@ public class DataWatcherHelper {
* @param visible - if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (ProtocolVersion.SERVER_VERSION.getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, registry.Boolean), visible);
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, registry.getBoolean()), visible);
} else {
data.setValue(new DataWatcherObject(3, registry.Byte), (byte)(visible?1:0));
data.setValue(new DataWatcherObject(3, registry.getByte()), (byte)(visible?1:0));
}
}
@@ -96,7 +105,11 @@ public class DataWatcherHelper {
* @param health - health of entity
*/
public void setHealth(float health) {
data.setValue(new DataWatcherObject(6, registry.Float), health);
if (NMSStorage.getInstance().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(6, registry.getFloat()), health);
} else {
data.setValue(new DataWatcherObject(16, registry.getInteger()), (int)health);
}
}
/**
@@ -104,6 +117,6 @@ public class DataWatcherHelper {
* @param flags - flags to write
*/
public void setArmorStandFlags(byte flags) {
data.setValue(new DataWatcherObject(ARMOR_STAND_BYTEFLAGS_POSITION, registry.Byte), flags);
data.setValue(new DataWatcherObject(armorStandFlagsPosition, registry.getByte()), flags);
}
}
@@ -0,0 +1,46 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
public class DataWatcherItem {
//type of value (position + data type (1.9+))
private DataWatcherObject type;
//actual data value
private Object value;
/**
* Constructs new instance of the object with given parameters
* @param type - value type
* @param value - value
*/
public DataWatcherItem(DataWatcherObject type, Object value){
this.type = type;
this.value = value;
}
/**
* Returns and instance of this class from given NMS item
* @param nmsItem - NMS item
* @return instance of this class with same data
* @throws IllegalAccessException
*/
public static DataWatcherItem fromNMS(Object nmsItem) throws IllegalAccessException {
NMSStorage nms = NMSStorage.getInstance();
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
Object nmsObject = nms.getField("DataWatcherItem_TYPE").get(nmsItem);
return new DataWatcherItem(new DataWatcherObject(nms.getField("DataWatcherObject_SLOT").getInt(nmsObject), nms.getField("DataWatcherObject_SERIALIZER").get(nmsObject)), nms.getField("DataWatcherItem_VALUE").get(nmsItem));
} else {
return new DataWatcherItem(new DataWatcherObject(nms.getField("DataWatcherItem_TYPE").getInt(nmsItem), null), nms.getField("DataWatcherItem_VALUE").get(nmsItem));
}
}
public DataWatcherObject getType() {
return type;
}
public Object getValue() {
return value;
}
}
@@ -6,10 +6,10 @@ package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
public class DataWatcherObject {
//position in datawatcher
public int position;
private int position;
//value class type used since 1.9
public Object classType;
private Object classType;
/**
* Constructs a new instance of this class with given parameters
@@ -20,4 +20,12 @@ public class DataWatcherObject {
this.position = position;
this.classType = classType;
}
public int getPosition() {
return position;
}
public Object getClassType() {
return classType;
}
}
@@ -0,0 +1,85 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
/**
* A class representing the n.m.s.DataWatcherRegistry class to make work with it much easier
*/
public class DataWatcherRegistry {
//used registry types
private Object registryByte;
private Object registryInteger;
private Object registryFloat;
private Object registryString;
private Object registryOptionalComponent;
private Object registryBoolean;
/**
* Initializes required NMS classes and fields
*/
public DataWatcherRegistry(NMSStorage nms, Class<?> registry) {
if (nms.getMinorVersion() >= 9) {
Map<String, Object> fields = getStaticFields(registry);
registryByte = fields.get("a");
registryInteger = fields.get("b");
registryFloat = fields.get("c");
registryString = fields.get("d");
if (nms.getMinorVersion() >= 13) {
registryOptionalComponent = fields.get("f");
registryBoolean = fields.get("i");
} else {
registryBoolean = fields.get("h");
}
}
}
/**
* Gets values of all static fields in a class
* @param clazz class to return field values from
* @return map of values
*/
private Map<String, Object> getStaticFields(Class<?> clazz){
Map<String, Object> fields = new HashMap<>();
if (clazz == null) return fields;
for (Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())) {
try {
fields.put(field.getName(), field.get(null));
} catch (Exception e) {
//this will never happen
}
}
}
return fields;
}
public Object getBoolean() {
return registryBoolean;
}
public Object getByte() {
return registryByte;
}
public Object getFloat() {
return registryFloat;
}
public Object getInteger() {
return registryInteger;
}
public Object getString() {
return registryString;
}
public Object getOptionalComponent() {
return registryOptionalComponent;
}
}
@@ -1,233 +1,250 @@
# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml
#changing nametags, affecting sorting, collision rule
change-nametag-prefix-suffix: true
#prefix/suffix in tablist
change-tablist-prefix-suffix: true
#header/footer
enable-header-footer: true
header:
- "&3&lServer name"
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
- "&7Online players: &f%online%"
- "&6Online staff: &e%staffonline%"
- "%animation:ping-tps%"
- ""
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
- "%animation:MyAnimation1%"
footer:
- "%animation:MyAnimation1%"
- "%animation:time%"
- ""
- "&7Visit our webpage %animation:web%"
#supports any numeric placeholder, set to "" to disable the feature
yellow-number-in-tablist: "%ping%"
#literally what it says
group-sorting-priority-list:
- owner
- admin
- mod
- helper
- builder
- premium
- player
- default
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
Admin:
tabprefix: "&0&l[&4&lAdmin&0&l] &1"
tagprefix: "%animation:admin_prefix%"
Player:
tabprefix: "&0&l[&7&lPlayer&0&l] &3"
tagprefix: "&2&lPlayer &3"
example_group:
header:
- "This is an example of per-group header/footer"
footer:
- "applied to a group"
#default settings for all groups, all groups will take properties from this section unless player's primary group overrides a specific setting
_OTHER_:
tabprefix: "%vault-prefix%"
tagprefix: "%vault-prefix%"
tabsuffix: "%afk%"
tagsuffix: "%afk%"
customtabname: "%essentialsnick%"
customtagname: "%essentialsnick%"
Users:
_NEZNAMY_:
tabprefix: "&6&lTAB Author &a"
tagprefix: "&6&lTAB &r&a"
abovename: "&bOh look, 3 lines of text !"
belowname: "&eThere's only one plugin out there supporting it"
#additional very limited line of text below nametag
#don't forget this enables on all player entities, which includes NPCs
#tags so you can find this section using the mighty ctrl + f feature: [NPC, Citizens, below_name, health]
classic-vanilla-belowname:
#enabling the feature
enabled: true
#number or numeric placeholder, supports per-player placeholders
number: "%health%"
#text after number, must be same on all players
text: "Health"
#controlling the collision rule
enable-collision: true
#using the opposite of the setting above for worlds
revert-collision-rule-in-worlds:
- revertedworld
#preventing players with spectator gamemode from being moved to the bottom of tablist with transparent name
do-not-move-spectators: false
#allows players with tab.spectatorbypass permission to see spectators moved despite the option above enabled
allow-spectator-bypass-permission: false
#using armor stands for nametags, bypassing all limits
#THEY ARE CLIENT SIDED AND DO NOT EXIST ON SERVER SO STOP SAYING YOUR SERVER IS LAGGING FROM THIS
unlimited-nametag-prefix-suffix-mode:
#enabling the feature
enabled: false
#enabling will result in NPCs ignoring nametag prefix/suffix/visibility to prevent NPCs with names of online players from being invisible
modify-npc-names: false
#using a feature introduced in 1.8.1 to make hitbox zero so you can't cry it affects pvp despite hit redirection
#1.8.0 clients will see wild things
#automatically enabled for 1.9+
use-marker-tag-for-1-8-x-clients: false
#disabling the feature for players riding boats to prevent massive desync
disable-on-boats: true
per-world-settings:
world1:
header:
- "an example of world with custom"
footer:
- "header/footer and prefix/suffix"
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
#separating multiple worlds with "-"
world2-world3:
header:
- "This is a shared header for"
- "world2 and world3"
placeholders:
#output of %afk%
afk-yes: " &4*&4&lAFK&4*&r"
afk-no: ""
#%date%
date-format: "dd.MM.yyyy"
#%time%
time-format: "[HH:mm:ss / h:mm a]"
#in hours, in case of wrong timezone
time-offset: 0
#don't forget color codes
remove-strings:
- "[]"
- "< >"
#aliases for %rank%, this is not the place where group prefix is defined!
rank-aliases:
Admin: "&4&lADMIN"
Mod: "&b&lMOD"
Premium: "&6&lPREMIUM"
Ultra: "&b&lULTRA"
Legend: "&a&lLEGEND"
Titan: "&c&lTITAN"
Youtuber: "&c&lYOUTUBE"
_OTHER_: "&7No Rank"
disable-features-in-worlds:
header-footer:
- disabledworld
tablist-names:
- disabledworld
nametag:
- disabledworld
yellow-number:
- disabledworld
bossbar:
- disabledworld
belowname:
- disabledworld
placeholderapi-refresh-intervals:
default-refresh-interval: 100
server:
"%server_uptime%": 1000
"%server_tps_1_colored%": 1000
player:
"%player_health%": 200
"%player_ping%": 1000
"%vault_prefix%": 1000
relational:
"%rel_factionsuuid_relation_color%": 500
#different playerlist in different worlds
per-world-playerlist:
#to enable the feature
enabled: false
#allows players with tab.bypass permission to see everyone in tablist despite this option enabled
allow-bypass-permission: false
#players in these worlds will see everyone despite the feature enabled
ignore-effect-in-worlds:
- ignoredworld
- build
#groups of worlds to share playerlist
shared-playerlist-world-groups:
#group name, can be anything
lobby:
- lobby1
- lobby2
minigames:
- paintball
- bedwars
#permission node is tab.sort.<permission group from sorting list>
sort-players-by-permissions: false
#making tamed animals no longer copy nametag properties from their owner (prefix/suffix/visibility)
#do not enable this if you're on paper, you'll feed the trolls even more
fix-pet-names: false
#taking primary group from permission plugin
use-primary-group: true
#if the option above is false, all groups are taken from permission plugin and the one higher in this list is used as primary
primary-group-finding-list:
- Owner
- Admin
- Helper
- default
#if none of the above fits your needs, you can enable this to give groups using tab.group.<group name from the list above> permission
assign-groups-by-permissions: false
# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml
#changing nametags, affecting sorting, collision rule
change-nametag-prefix-suffix: true
#prefix/suffix in tablist
change-tablist-prefix-suffix: true
#header/footer
enable-header-footer: true
header:
- "&3&lServer name"
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
- "&7Online players: &f%online%"
- "&6Online staff: &e%staffonline%"
- "%animation:ping-tps%"
- ""
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
- "%animation:MyAnimation1%"
footer:
- "%animation:MyAnimation1%"
- "%animation:time%"
- ""
- "&7Visit our webpage %animation:web%"
#supports any numeric placeholder, set to "" to disable the feature
yellow-number-in-tablist: "%ping%"
group-sorting-priority-list:
- owner
- admin
- mod
- helper
- builder
- premium
- player
- default
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
Admin:
tabprefix: "&0&l[&4&lAdmin&0&l] &1"
tagprefix: "%animation:admin_prefix%"
Player:
tabprefix: "&0&l[&7&lPlayer&0&l] &3"
tagprefix: "&2&lPlayer &3"
example_group:
header:
- "This is an example of per-group header/footer"
footer:
- "applied to a group"
#default settings for all groups, all groups will take properties from this section unless player's primary group overrides a specific setting
_OTHER_:
tabprefix: "%vault-prefix%"
tagprefix: "%vault-prefix%"
tabsuffix: "%afk%"
tagsuffix: "%afk%"
customtabname: "%essentialsnick%"
customtagname: "%essentialsnick%"
Users:
_NEZNAMY_:
tabprefix: "&6&lTAB Author &a"
tagprefix: "&6&lTAB &r&a"
abovename: "&bOh look, 3 lines of text !"
belowname: "&eThere's only one plugin out there supporting it"
#additional very limited line of text below nametag
#don't forget this enables on all player entities, which includes NPCs
#tags so you can find this section using the mighty ctrl + f feature: [NPC, Citizens, below_name, health]
classic-vanilla-belowname:
#enabling the feature
enabled: true
#number or numeric placeholder, supports per-player placeholders
number: "%health%"
#text after number, must be same on all players
text: "Health"
#controlling the collision rule
enable-collision: true
#using the opposite of the setting above for worlds
revert-collision-rule-in-worlds:
- revertedworld
#preventing players with spectator gamemode from being moved to the bottom of tablist with transparent name
do-not-move-spectators: false
#allows players with tab.spectatorbypass permission to see spectators moved despite the option above enabled
allow-spectator-bypass-permission: false
#using armor stands for nametags, bypassing all limits (they are not on server sided & asynchronous - no TPS loss)
unlimited-nametag-prefix-suffix-mode:
#enabling the feature
enabled: false
#using a feature introduced in 1.8.1 to make hitbox zero so you can't cry it affects pvp despite hit redirection
#1.8.0 clients will see wild things
#automatically enabled for 1.9+
use-marker-tag-for-1-8-x-clients: false
#disabling the feature for players riding boats to prevent massive desync
disable-on-boats: true
#in blocks
space-between-lines: 0.22
per-world-settings:
world1:
header:
- "an example of world with custom"
footer:
- "header/footer and prefix/suffix"
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
#separating multiple worlds with "-"
world2-world3:
header:
- "This is a shared header for"
- "world2 and world3"
placeholders:
#output of %afk%
afk-yes: " &4*&4&lAFK&4*&r"
afk-no: ""
#%date%
date-format: "dd.MM.yyyy"
#%time%
time-format: "[HH:mm:ss / h:mm a]"
#in hours, in case of wrong timezone
time-offset: 0
#don't forget color codes
remove-strings:
- "[]"
- "< >"
#aliases for %rank%, this is not the place where group prefix is defined!
rank-aliases:
Admin: "&4&lADMIN"
Mod: "&b&lMOD"
Premium: "&6&lPREMIUM"
Ultra: "&b&lULTRA"
Legend: "&a&lLEGEND"
Titan: "&c&lTITAN"
Youtuber: "&c&lYOUTUBE"
_OTHER_: "&7No Rank"
disable-features-in-worlds:
header-footer:
- disabledworld
tablist-names:
- disabledworld
nametag:
- disabledworld
yellow-number:
- disabledworld
bossbar:
- disabledworld
belowname:
- disabledworld
unlimited-nametags:
- disabledworld
placeholderapi-refresh-intervals:
default-refresh-interval: 500
server:
"%server_uptime%": 1000
"%server_tps_1_colored%": 1000
player:
"%player_health%": 200
"%player_ping%": 1000
"%vault_prefix%": 1000
relational:
"%rel_factionsuuid_relation_color%": 500
#different playerlist in different worlds
per-world-playerlist:
#to enable the feature
enabled: false
#allows players with tab.bypass permission to see everyone in tablist despite this option enabled
allow-bypass-permission: false
#players in these worlds will see everyone despite the feature enabled
ignore-effect-in-worlds:
- ignoredworld
- build
#groups of worlds to share playerlist
shared-playerlist-world-groups:
#group name, can be anything
lobby:
- lobby1
- lobby2
minigames:
- paintball
- bedwars
#permission node is tab.sort.<permission group from sorting list>
sort-players-by-permissions: false
#making tamed animals no longer copy nametag properties from their owner (prefix/suffix/visibility)
fix-pet-names: false
#taking primary group from permission plugin
use-primary-group: true
#if the option above is false, all groups are taken from permission plugin and the one higher in this list is used as primary
primary-group-finding-list:
- Owner
- Admin
- Helper
- default
#if none of the above fits your needs, you can enable this to give groups using tab.group.<group name from the list above> permission
assign-groups-by-permissions: false
#preventing other plugins from overriding TAB
anti-override:
scoreboard-teams: true
scoreboard-objectives: true
tablist-names: true
usernames: true
#permanently invisible nametags
invisible-nametags: false
#faking ping of players
ping-spoof:
enabled: false
value: 0
#unlocks extra console messages
debug: false
@@ -1,10 +1,10 @@
name: ${project.name}
version: ${project.version}
author: NEZNAMY
main: me.neznamy.tab.platforms.bukkit.Main
api-version: 1.13
softdepend: [PlaceholderAPI, Vault, UltraPermissions, LuckPerms, Essentials, LibsDisguises]
website: "https://www.spigotmc.org/resources/57806/"
description: ${project.description}
commands:
name: ${project.parent.name}
version: ${project.parent.version}
author: NEZNAMY
main: me.neznamy.tab.platforms.bukkit.Main
api-version: 1.13
softdepend: [PlaceholderAPI, Vault, UltraPermissions, LuckPerms, Essentials, LibsDisguises, ProtocolSupport, ViaVersion]
website: "https://www.spigotmc.org/resources/57806/"
description: ${project.parent.description}
commands:
tab:
+164
View File
@@ -0,0 +1,164 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.neznamy</groupId>
<artifactId>TAB</artifactId>
<version>2.9.2-pre2</version>
</parent>
<artifactId>bungeecord</artifactId>
<repositories>
<!-- PremiumVanishAPI -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<!-- BungeeCord -->
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything/</url>
</repository>
</repositories>
<dependencies>
<!-- TAB Core -->
<dependency>
<groupId>me.neznamy</groupId>
<artifactId>shared</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
</exclusion>
<exclusion>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>YamlAssist</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- BungeeCord -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>brigadier</artifactId>
</exclusion>
<exclusion>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.trove4j</groupId>
<artifactId>core</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-query</artifactId>
</exclusion>
<exclusion>
<groupId>se.llbit</groupId>
<artifactId>jo-nbt</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
</exclusion>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.maven</groupId>
<artifactId>maven-resolver-provider</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>
maven-resolver-connector-basic
</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>
maven-resolver-transport-http
</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
</exclusion>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PremiumVanishAPI -->
<dependency>
<groupId>com.github.MyzelYam</groupId>
<artifactId>PremiumVanishAPI</artifactId>
<version>2.0.3</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Bungee Metrics -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bungeecord</artifactId>
<version>2.2.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PluginMessageHandler;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
@@ -14,6 +15,12 @@ import net.md_5.bungee.event.EventPriority;
*/
public class BungeeEventListener implements Listener {
private PluginMessageHandler plm;
public BungeeEventListener(PluginMessageHandler plm) {
this.plm = plm;
}
/**
* Disconnect event listener to forward the event to all features
* @param e - disconnect event
@@ -32,12 +39,12 @@ public class BungeeEventListener implements Listener {
public void onSwitch(ServerSwitchEvent e){
if (TAB.getInstance().isDisabled()) return;
try {
if (!TAB.getInstance().data.containsKey(e.getPlayer().getUniqueId())) {
TAB.getInstance().getFeatureManager().onJoin(new BungeeTabPlayer(e.getPlayer()));
if (TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()) == null) {
TAB.getInstance().getFeatureManager().onJoin(new BungeeTabPlayer(e.getPlayer(), plm));
} else {
TAB.getInstance().getFeatureManager().onWorldChange(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getPlayer().getServer().getInfo().getName());
TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
}
} catch (Throwable ex){
} catch (Exception ex){
TAB.getInstance().getErrorManager().criticalError("An error occurred when player joined/changed server", ex);
}
}
@@ -49,6 +56,6 @@ public class BungeeEventListener implements Listener {
@EventHandler
public void onChat(ChatEvent e) {
if (TAB.getInstance().isDisabled()) return;
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(((ProxiedPlayer)e.getSender()).getUniqueId()), e.getMessage())) e.setCancelled(true);
if (e.getMessage().startsWith("/") && TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(((ProxiedPlayer)e.getSender()).getUniqueId()), e.getMessage())) e.setCancelled(true);
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import java.util.ArrayList;
import java.util.List;
@@ -19,18 +19,16 @@ import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardScore;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.shared.packets.PacketPlayOutTitle;
import net.md_5.bungee.protocol.packet.BossBar;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardObjective.HealthDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import net.md_5.bungee.protocol.packet.Title;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
/**
* Packet builder for BungeeCord platform
@@ -40,96 +38,85 @@ public class BungeePacketBuilder implements PacketBuilder {
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() < 9) return null;
BossBar bungeePacket = new BossBar(packet.id, packet.operation.ordinal());
bungeePacket.setHealth(packet.pct);
bungeePacket.setTitle(packet.name == null ? null : IChatBaseComponent.optimizedComponent(packet.name).toString(clientVersion));
bungeePacket.setColor(packet.color == null ? 0 : packet.color.ordinal());
bungeePacket.setDivision(packet.overlay == null ? 0: packet.overlay.ordinal());
BossBar bungeePacket = new BossBar(packet.getId(), packet.getOperation().ordinal());
bungeePacket.setHealth(packet.getPct());
bungeePacket.setTitle(packet.getName() == null ? null : IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion));
bungeePacket.setColor(packet.getColor() == null ? 0 : packet.getColor().ordinal());
bungeePacket.setDivision(packet.getOverlay() == null ? 0: packet.getOverlay().ordinal());
bungeePacket.setFlags(packet.getFlags());
return bungeePacket;
}
@Override
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
return new Chat(packet.message.toString(clientVersion), (byte) packet.type.ordinal());
return new Chat(packet.getMessage().toString(clientVersion), (byte) packet.getType().ordinal());
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) {
List<Item> items = new ArrayList<Item>();
for (PlayerInfoData data : packet.entries) {
List<Item> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
Item item = new Item();
if (data.displayName != null) {
if (clientVersion.getNetworkId() >= ProtocolVersion.v1_8.getNetworkId()) {
item.setDisplayName(data.displayName.toString(clientVersion));
if (data.getDisplayName() != null) {
if (clientVersion.getMinorVersion() >= 8) {
item.setDisplayName(data.getDisplayName().toString(clientVersion));
} else {
item.setDisplayName(data.displayName.toLegacyText());
item.setDisplayName(data.getDisplayName().toLegacyText());
}
} else if (clientVersion.getNetworkId() < ProtocolVersion.v1_8.getNetworkId()) {
item.setDisplayName(data.name); //avoiding NPE, 1.7 client requires this, 1.8 added a leading boolean
} else if (clientVersion.getMinorVersion() < 8) {
item.setDisplayName(data.getName()); //avoiding NPE, 1.7 client requires this, 1.8 added a leading boolean
}
if (data.gameMode != null) item.setGamemode(data.gameMode.ordinal()-1);
item.setPing(data.latency);
if (data.skin != null) {
item.setProperties((String[][]) data.skin);
if (data.getGameMode() != null) item.setGamemode(data.getGameMode().ordinal()-1);
item.setPing(data.getLatency());
if (data.getSkin() != null) {
item.setProperties((String[][]) data.getSkin());
} else {
item.setProperties(new String[0][0]);
}
item.setUsername(data.name);
item.setUuid(data.uniqueId);
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
items.add(item);
}
PlayerListItem bungeePacket = new PlayerListItem();
bungeePacket.setAction(PlayerListItem.Action.valueOf(packet.action.toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setAction(PlayerListItem.Action.valueOf(packet.getAction().toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
}
@Override
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return new PlayerListHeaderFooter(packet.header.toString(clientVersion, true), packet.footer.toString(clientVersion, true));
return new PlayerListHeaderFooter(packet.getHeader().toString(clientVersion, true), packet.getFooter().toString(clientVersion, true));
}
@Override
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return new ScoreboardDisplay((byte)packet.slot, packet.objectiveName);
return new ScoreboardDisplay((byte)packet.getSlot(), packet.getObjectiveName());
}
@Override
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return new ScoreboardObjective(packet.objectiveName, jsonOrCut(packet.displayName, clientVersion, 32), packet.renderType == null ? null : HealthDisplay.valueOf(packet.renderType.toString()), (byte)packet.method);
return new ScoreboardObjective(packet.getObjectiveName(), jsonOrCut(packet.getDisplayName(), clientVersion, 32), packet.getRenderType() == null ? null : HealthDisplay.valueOf(packet.getRenderType().toString()), (byte)packet.getMethod());
}
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return new ScoreboardScore(packet.player, (byte) packet.action.ordinal(), packet.objectiveName, packet.score);
return new ScoreboardScore(packet.getPlayer(), (byte) packet.getAction().ordinal(), packet.getObjectiveName(), packet.getScore());
}
@Override
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
int color = 0;
if (clientVersion.getMinorVersion() >= 13) {
color = (packet.color != null ? packet.color : EnumChatFormat.lastColorsOf(packet.playerPrefix)).getNetworkId();
color = (packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(packet.getPlayerPrefix())).getNetworkId();
}
return new Team(packet.name, (byte)packet.method, jsonOrCut(packet.name, clientVersion, 16), jsonOrCut(packet.playerPrefix, clientVersion, 16), jsonOrCut(packet.playerSuffix, clientVersion, 16),
packet.nametagVisibility, packet.collisionRule, color, (byte)packet.options, packet.players.toArray(new String[0]));
return new Team(packet.getName(), (byte)packet.getMethod(), jsonOrCut(packet.getName(), clientVersion, 16), jsonOrCut(packet.getPlayerPrefix(), clientVersion, 16), jsonOrCut(packet.getPlayerSuffix(), clientVersion, 16),
packet.getNametagVisibility(), packet.getCollisionRule(), color, (byte)packet.getOptions(), packet.getPlayers().toArray(new String[0]));
}
@Override
public Object build(PacketPlayOutTitle packet, ProtocolVersion clientVersion) throws Exception {
Title bungeePacket = new Title();
bungeePacket.setAction(Title.Action.valueOf(packet.action.toString()));
bungeePacket.setText(IChatBaseComponent.optimizedComponent(packet.text).toString(clientVersion));
bungeePacket.setFadeIn(packet.fadeIn);
bungeePacket.setStay(packet.stay);
bungeePacket.setFadeOut(packet.fadeOut);
return bungeePacket;
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object bungeePacket, ProtocolVersion clientVersion){
public PacketPlayOutPlayerInfo readPlayerInfo(Object bungeePacket, ProtocolVersion clientVersion) {
PlayerListItem item = (PlayerListItem) bungeePacket;
List<PlayerInfoData> listData = new ArrayList<PlayerInfoData>();
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : item.getItems()) {
listData.add(new PlayerInfoData(i.getUsername(), i.getUuid(), i.getProperties(), i.getPing(), EnumGamemode.values()[i.getGamemode()+1], IChatBaseComponent.fromString(i.getDisplayName())));
}
@@ -137,7 +124,7 @@ public class BungeePacketBuilder implements PacketBuilder {
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object bungeePacket, ProtocolVersion clientVersion) throws Exception {
public PacketPlayOutScoreboardObjective readObjective(Object bungeePacket, ProtocolVersion clientVersion) {
ScoreboardObjective packet = (ScoreboardObjective) bungeePacket;
String title;
if (clientVersion.getMinorVersion() >= 13) {
@@ -150,15 +137,7 @@ public class BungeePacketBuilder implements PacketBuilder {
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object bungeePacket, ProtocolVersion clientVersion) throws Exception {
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object bungeePacket, ProtocolVersion clientVersion){
return new PacketPlayOutScoreboardDisplayObjective(((ScoreboardDisplay) bungeePacket).getPosition(), ((ScoreboardDisplay) bungeePacket).getName());
}
@Override
public PacketPlayOutPlayerListHeaderFooter readHeaderFooter(Object packet, ProtocolVersion clientVersion) throws Exception {
return new PacketPlayOutPlayerListHeaderFooter(
IChatBaseComponent.fromString(((PlayerListHeaderFooter)packet).getHeader()),
IChatBaseComponent.fromString(((PlayerListHeaderFooter)packet).getFooter())
);
}
}
@@ -0,0 +1,161 @@
package me.neznamy.tab.platforms.bungeecord;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.function.Supplier;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.PipelineInjector;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.Login;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.Team;
public class BungeePipelineInjector extends PipelineInjector {
//handler to inject before
private static final String INJECT_POSITION = "inbound-boss";
//packets that must be deserialized and bungeecord does not do it automatically
private Map<Class<? extends DefinedPacket>, Supplier<DefinedPacket>> extraPackets = new HashMap<>();
/**
* Constructs new instance with given parameter
* @param tab - tab instance
*/
public BungeePipelineInjector(TAB tab) {
super(tab);
extraPackets.put(Team.class, Team::new);
extraPackets.put(ScoreboardDisplay.class, ScoreboardDisplay::new);
extraPackets.put(ScoreboardObjective.class, ScoreboardObjective::new);
}
@Override
public void inject(TabPlayer player) {
if (player.getVersion().getMinorVersion() < 8) return;
uninject(player);
try {
player.getChannel().pipeline().addBefore(INJECT_POSITION, DECODER_NAME, new BungeeChannelDuplexHandler(player));
} catch (NoSuchElementException | IllegalArgumentException e) {
//idk how does this keep happening but whatever
}
}
@Override
public void uninject(TabPlayer player) {
if (player.getVersion().getMinorVersion() < 8) return;
if (player.getChannel().pipeline().names().contains(DECODER_NAME)) player.getChannel().pipeline().remove(DECODER_NAME);
}
/**
* Custom channel duplex handler override
*/
public class BungeeChannelDuplexHandler extends ChannelDuplexHandler {
//injected player
private TabPlayer player;
/**
* Constructs new instance with given player
* @param player - player to inject
*/
public BungeeChannelDuplexHandler(TabPlayer player) {
this.player = player;
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
long time = System.nanoTime();
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
tab.getCPUManager().addTime(TabFeature.PACKET_DESERIALIZING, UsageType.PACKET_READING_OUT, System.nanoTime()-time);
try {
if (modifiedPacket instanceof PlayerListItem) {
super.write(context, tab.getFeatureManager().onPacketPlayOutPlayerInfo(player, modifiedPacket), channelPromise);
return;
}
if (tab.getFeatureManager().isFeatureEnabled("nametag16") && antiOverrideTeams) {
time = System.nanoTime();
if (modifiedPacket instanceof Team) {
modifyPlayers((Team) modifiedPacket);
}
tab.getCPUManager().addTime(TabFeature.NAMETAGS, UsageType.ANTI_OVERRIDE, System.nanoTime()-time);
}
if (modifiedPacket instanceof ScoreboardDisplay && antiOverrideObjectives && tab.getFeatureManager().onDisplayObjective(player, modifiedPacket)) {
return;
}
if (modifiedPacket instanceof ScoreboardObjective && antiOverrideObjectives) {
tab.getFeatureManager().onObjective(player, modifiedPacket);
}
//client reset packet
if (modifiedPacket instanceof Login) {
//making sure to not send own packets before login packet is actually sent
super.write(context, modifiedPacket, channelPromise);
tab.getFeatureManager().onLoginPacket(player);
return;
}
} catch (Exception e){
tab.getErrorManager().printError("An error occurred when analyzing packets for player " + player.getName() + " with client version " + player.getVersion().getFriendlyName(), e);
}
try {
super.write(context, modifiedPacket, channelPromise);
} catch (Exception e) {
tab.getErrorManager().printError("Failed to forward packet " + modifiedPacket.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
/**
* Removes all real players from packet if the packet doesn't come from TAB
* @param packet - packet to modify
*/
private void modifyPlayers(Team packet){
long time = System.nanoTime();
if (packet.getPlayers() == null) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
for (TabPlayer p : tab.getPlayers()) {
if (col.contains(p.getName()) && !tab.getFeatureManager().getNameTagFeature().getPlayersInDisabledWorlds().contains(p) &&
!p.hasTeamHandlingPaused() && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName());
col.remove(p.getName());
}
}
packet.setPlayers(col.toArray(new String[0]));
tab.getCPUManager().addTime(TabFeature.NAMETAGS, UsageType.ANTI_OVERRIDE, System.nanoTime()-time);
}
/**
* Deserializes bytebuf in case it is one of the tracked packets coming from backend server and returns it.
* If the packet is not one of them, returns input
* @param buf - bytebuf to deserialize
* @return deserialized packet or input bytebuf if packet is not tracked
*/
private Object deserialize(ByteBuf buf) {
int marker = buf.readerIndex();
int packetId = buf.readByte();
for (Entry<Class<? extends DefinedPacket>, Supplier<DefinedPacket>> e : extraPackets.entrySet()) {
if (packetId == ((BungeeTabPlayer)player).getPacketId(e.getKey())) {
DefinedPacket packet = e.getValue().get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
buf.release();
return packet;
}
}
buf.readerIndex(marker);
return buf;
}
}
}
@@ -1,16 +1,14 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.placeholders.Placeholder;
import me.neznamy.tab.shared.placeholders.PlaceholderRegistry;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
import me.neznamy.tab.shared.proxy.ProxyPlaceholderRegistry;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -18,27 +16,11 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* Bungeecord registry to register bungeecord-only placeholders
*/
public class BungeePlaceholderRegistry implements PlaceholderRegistry {
public class BungeePlaceholderRegistry extends ProxyPlaceholderRegistry {
@Override
public List<Placeholder> registerPlaceholders() {
List<Placeholder> placeholders = new ArrayList<Placeholder>();
if (ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null) {
placeholders.add(new ServerPlaceholder("%canseeonline%", 1000) {
public String get() {
return TAB.getInstance().getPlayers().size() - BungeeVanishAPI.getInvisiblePlayers().size()+"";
}
});
placeholders.add(new ServerPlaceholder("%canseestaffonline%", 1000) {
public String get() {
int count = 0;
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (!((BungeeTabPlayer)all).isVanished() && all.isStaff()) count++;
}
return count+"";
}
});
}
List<Placeholder> placeholders = super.registerPlaceholders();
placeholders.add(new PlayerPlaceholder("%displayname%", 500) {
public String get(TabPlayer p) {
return ((ProxiedPlayer) p.getPlayer()).getDisplayName();
@@ -47,7 +29,7 @@ public class BungeePlaceholderRegistry implements PlaceholderRegistry {
for (Entry<String, ServerInfo> server : ProxyServer.getInstance().getServers().entrySet()) {
placeholders.add(new ServerPlaceholder("%online_" + server.getKey() + "%", 1000) {
public String get() {
return server.getValue().getPlayers().size()+"";
return String.valueOf(server.getValue().getPlayers().size());
}
});
placeholders.add(new ServerPlaceholder("%canseeonline_" + server.getKey() + "%", 1000) {
@@ -56,7 +38,7 @@ public class BungeePlaceholderRegistry implements PlaceholderRegistry {
for (ProxiedPlayer p : server.getValue().getPlayers()) {
if (((BungeeTabPlayer)TAB.getInstance().getPlayer(p.getUniqueId())).isVanished()) count--;
}
return count+"";
return String.valueOf(count);
}
});
}
@@ -0,0 +1,110 @@
package me.neznamy.tab.platforms.bungeecord;
import java.io.File;
import me.neznamy.tab.api.event.BungeeTABLoadEvent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.BelowName;
import me.neznamy.tab.shared.features.GlobalPlayerlist;
import me.neznamy.tab.shared.features.NameTag;
import me.neznamy.tab.shared.features.PingSpoof;
import me.neznamy.tab.shared.features.PluginMessageHandler;
import me.neznamy.tab.shared.features.SpectatorFix;
import me.neznamy.tab.shared.features.TabObjective;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
import me.neznamy.tab.shared.permission.LuckPerms;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.permission.UltraPermissions;
import me.neznamy.tab.shared.permission.VaultBridge;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
/**
* Bungeecord implementation of Platform
*/
public class BungeePlatform extends ProxyPlatform {
//instance of plugin
private Plugin plugin;
/**
* Constructs new instance with given parameter
* @param plugin - main class
*/
public BungeePlatform(Plugin plugin, PluginMessageHandler plm) {
super(plm);
this.plugin = plugin;
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (TAB.getInstance().getConfiguration().isBukkitPermissions()) {
return new VaultBridge(plm);
} else if (ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null) {
return new LuckPerms(ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms").getDescription().getVersion());
} else if (ProxyServer.getInstance().getPluginManager().getPlugin("UltraPermissions") != null) {
return new UltraPermissions(ProxyServer.getInstance().getPluginManager().getPlugin("UltraPermissions").getDescription().getVersion());
} else {
return new VaultBridge(plm);
}
}
@Override
public void loadFeatures() throws IllegalAccessException {
TAB tab = TAB.getInstance();
tab.getPlaceholderManager().addRegistry(new BungeePlaceholderRegistry());
tab.getPlaceholderManager().addRegistry(new UniversalPlaceholderRegistry());
tab.getPlaceholderManager().registerPlaceholders();
if (tab.getConfiguration().isPipelineInjection()) tab.getFeatureManager().registerFeature("injection", new BungeePipelineInjector(tab));
if (tab.getConfiguration().getConfig().getBoolean("change-nametag-prefix-suffix", true)) tab.getFeatureManager().registerFeature("nametag16", new NameTag(tab));
loadUniversalFeatures();
if (tab.getConfiguration().getConfig().getBoolean("ping-spoof.enabled", false)) tab.getFeatureManager().registerFeature("pingspoof", new PingSpoof());
if (tab.getConfiguration().getConfig().getString("yellow-number-in-tablist", "%ping%").length() > 0) tab.getFeatureManager().registerFeature("tabobjective", new TabObjective(tab));
if (tab.getConfiguration().getConfig().getBoolean("do-not-move-spectators", false)) tab.getFeatureManager().registerFeature("spectatorfix", new SpectatorFix());
if (tab.getConfiguration().getConfig().getBoolean("classic-vanilla-belowname.enabled", true)) tab.getFeatureManager().registerFeature("belowname", new BelowName(tab));
if (tab.getConfiguration().getPremiumConfig() != null && tab.getConfiguration().getPremiumConfig().getBoolean("scoreboard.enabled", false)) tab.getFeatureManager().registerFeature("scoreboard", new ScoreboardManager(tab));
if (tab.getConfiguration().getConfig().getBoolean("global-playerlist.enabled", false)) tab.getFeatureManager().registerFeature("globalplayerlist", new GlobalPlayerlist(tab));
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
tab.addPlayer(new BungeeTabPlayer(p, plm));
}
}
@Override
@SuppressWarnings("deprecation")
public void sendConsoleMessage(String message, boolean translateColors) {
ProxyServer.getInstance().getConsole().sendMessage(translateColors ? message.replace('&', '\u00a7') : message);
}
@Override
public String getServerVersion() {
return ProxyServer.getInstance().getVersion();
}
@Override
public String getSeparatorType() {
return "server";
}
@Override
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public void callLoadEvent() {
ProxyServer.getInstance().getPluginManager().callEvent(new BungeeTABLoadEvent());
}
@Override
public int getMaxPlayers() {
return ProxyServer.getInstance().getConfigurationAdapter().getListeners().iterator().next().getMaxPlayers();
}
@Override
public String getConfigName() {
return "bungeeconfig.yml";
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import com.google.common.io.ByteStreams;
@@ -39,9 +39,8 @@ public class BungeePluginMessageHandler implements Listener, PluginMessageHandle
long time = System.nanoTime();
TabPlayer receiver = TAB.getInstance().getPlayer(((ProxiedPlayer) event.getReceiver()).getUniqueId());
if (receiver == null) return;
if (onPluginMessage(receiver, ByteStreams.newDataInput(event.getData()))) {
event.setCancelled(true);
}
onPluginMessage(receiver, ByteStreams.newDataInput(event.getData()));
event.setCancelled(true);
TAB.getInstance().getCPUManager().addTime(TabFeature.PLUGIN_MESSAGE_HANDLING, UsageType.PLUGIN_MESSAGE_EVENT, System.nanoTime()-time);
}
}
@@ -1,13 +1,14 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.PluginMessageHandler;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler;
@@ -19,12 +20,16 @@ import net.md_5.bungee.protocol.Protocol;
/**
* TabPlayer for BungeeCord
*/
public class BungeeTabPlayer extends ITabPlayer {
public class BungeeTabPlayer extends ProxyTabPlayer {
//bungee internals to get player channel
private static Field wrapperField;
private static Object directionData;
private static Method getId;
/**
* Initializes fields to get player channel later
*/
static {
try {
Field f = Protocol.class.getDeclaredField("TO_CLIENT");
@@ -32,7 +37,8 @@ public class BungeeTabPlayer extends ITabPlayer {
directionData = f.get(Protocol.GAME);
getId = directionData.getClass().getDeclaredMethod("getId", Class.class, int.class);
getId.setAccessible(true);
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
wrapperField = InitialHandler.class.getDeclaredField("ch");
wrapperField.setAccessible(true);
} catch (Exception e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize fields for packet analysis", e);
}
@@ -44,9 +50,10 @@ public class BungeeTabPlayer extends ITabPlayer {
/**
* Constructs new instance for given player
* @param p - velocity player
* @throws Exception - if reflection fails
* @throws IllegalAccessException
*/
public BungeeTabPlayer(ProxiedPlayer p) throws Exception {
public BungeeTabPlayer(ProxiedPlayer p, PluginMessageHandler plm) throws IllegalAccessException {
super (plm);
player = p;
if (p.getServer() != null) {
world = p.getServer().getInfo().getName();
@@ -60,7 +67,7 @@ public class BungeeTabPlayer extends ITabPlayer {
}
@Override
public boolean hasPermission(String permission) {
public boolean hasPermission0(String permission) {
return player.hasPermission(permission);
}
@@ -74,14 +81,6 @@ public class BungeeTabPlayer extends ITabPlayer {
if (nmsPacket != null && player.isConnected()) player.unsafe().sendPacket((DefinedPacket) nmsPacket);
}
@Override
public void sendPacket(Object nmsPacket, TabFeature feature) {
if (nmsPacket != null && player.isConnected()) {
player.unsafe().sendPacket((DefinedPacket) nmsPacket);
TAB.getInstance().getCPUManager().packetSent(feature);
}
}
@Override
public Object getSkin() {
LoginResult loginResult = ((InitialHandler)player.getPendingConnection()).getLoginProfile();
@@ -101,6 +100,11 @@ public class BungeeTabPlayer extends ITabPlayer {
return player;
}
/**
* Returns packet ID for this player of provided packet class
* @param clazz - packet class
* @return - packet ID
*/
public int getPacketId(Class<? extends DefinedPacket> clazz) {
try {
return (int) getId.invoke(directionData, clazz, player.getPendingConnection().getVersion());
@@ -117,20 +121,17 @@ public class BungeeTabPlayer extends ITabPlayer {
@Override
public boolean isVanished() {
return ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null && BungeeVanishAPI.isInvisible(player);
}
@Override
public boolean isDisguised() {
Main.plm.requestAttribute(this, "disguised");
if (!attributes.containsKey("disguised")) return false;
return Boolean.parseBoolean(attributes.get("disguised"));
if (ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null && BungeeVanishAPI.isInvisible(player)) return true;
return super.isVanished();
}
@Override
public boolean hasInvisibilityPotion() {
Main.plm.requestAttribute(this, "invisible");
if (!attributes.containsKey("invisible")) return false;
return Boolean.parseBoolean(attributes.get("invisible"));
public boolean isOnline() {
return player.isConnected();
}
@Override
public int getGamemode() {
return ((UserConnection)player).getGamemode();
}
}
@@ -1,7 +1,11 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import java.util.ArrayList;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PluginMessageHandler;
@@ -17,9 +21,6 @@ import net.md_5.bungee.api.plugin.TabExecutor;
*/
public class Main extends Plugin {
//plugin message handler
public static PluginMessageHandler plm;
@SuppressWarnings("deprecation")
@Override
public void onEnable(){
@@ -27,14 +28,16 @@ public class Main extends Plugin {
ProxyServer.getInstance().getConsole().sendMessage("\u00a7c[TAB] The plugin requires BungeeCord build #1330 and up to work. Get it at https://ci.md-5.net/job/BungeeCord/");
return;
}
ProtocolVersion.SERVER_VERSION = ProtocolVersion.values()[1];
TAB.setInstance(new TAB(new BungeePlatform(this), new BungeePacketBuilder()));
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
PluginMessageHandler plm = new BungeePluginMessageHandler(this);
TAB.setInstance(new TAB(new BungeePlatform(this, plm), new BungeePacketBuilder(), ProtocolVersion.values()[1]));
getProxy().getPluginManager().registerListener(this, new BungeeEventListener(plm));
if (getProxy().getPluginManager().getPlugin("PremiumVanish") != null) getProxy().getPluginManager().registerListener(this, new PremiumVanishListener());
getProxy().getPluginManager().registerCommand(this, new BTABCommand());
plm = new BungeePluginMessageHandler(this);
TAB.getInstance().load();
new BungeeMetrics(this);
Metrics metrics = new Metrics(this, 10535);
metrics.addCustomChart(new SimplePie("permission_system", () -> TAB.getInstance().getPermissionPlugin().getName()));
metrics.addCustomChart(new SimplePie("global_playerlist_enabled", () -> TAB.getInstance().getFeatureManager().isFeatureEnabled("globalplayerlist") ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie("using_premium_version", () -> TAB.getInstance().isPremium() ? "Yes" : "No"));
}
/**
@@ -52,11 +55,17 @@ public class Main extends Plugin {
@Override
public void onDisable() {
TAB.getInstance().unload();
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
/**
* TAB command for bungeecord
*/
public class BTABCommand extends Command implements TabExecutor {
/**
* Constructs new instance
*/
public BTABCommand() {
super("btab", null);
}
@@ -65,18 +74,27 @@ public class Main extends Plugin {
@Override
public void execute(CommandSender sender, String[] args) {
if (TAB.getInstance().isDisabled()) {
for (String message : TAB.getInstance().disabledCommand.execute(args, sender.hasPermission("tab.reload"), sender.hasPermission("tab.admin"))) {
sender.sendMessage(TAB.getInstance().getPlaceholderManager().color(message));
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission("tab.reload"), sender.hasPermission("tab.admin"))) {
sender.sendMessage(message.replace('&', '\u00a7'));
}
} else {
TAB.getInstance().command.execute(sender instanceof ProxiedPlayer ? TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId()) : null, args);
TabPlayer p = null;
if (sender instanceof ProxiedPlayer) {
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
if (p == null) return; //player not loaded correctly
}
TAB.getInstance().getCommand().execute(p, args);
}
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
if (TAB.getInstance().isDisabled()) return new ArrayList<String>();
return TAB.getInstance().command.complete(sender instanceof ProxiedPlayer ? TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId()) : null, args);
TabPlayer p = null;
if (sender instanceof ProxiedPlayer) {
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.bungee;
package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeePlayerHideEvent;
import de.myzelyam.api.vanish.BungeePlayerShowEvent;
@@ -28,9 +28,9 @@ public class PremiumVanishListener implements Listener {
if (list == null) return;
TabPlayer vanished = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
for (TabPlayer all : TAB.getInstance().getPlayers()) {
boolean perm = all.hasPermission(list.PREMIUMVANISH_SEE_VANISHED_PERMISSION);
boolean perm = all.hasPermission("tab.seevanished");
if (all == vanished || perm) {
if (perm && list.vanishedAsSpectators) {
if (perm && list.isVanishedAsSpectators()) {
all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_GAME_MODE, new PlayerInfoData(vanished.getUniqueId(), EnumGamemode.SPECTATOR)), TabFeature.GLOBAL_PLAYERLIST);
}
} else {
@@ -50,7 +50,7 @@ public class PremiumVanishListener implements Listener {
if (list == null) return;
TabPlayer unvanished = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
for (TabPlayer viewer : TAB.getInstance().getPlayers()) {
if (list.shouldSee(viewer, unvanished)) viewer.sendCustomPacket(list.getAddPacket(unvanished), TabFeature.GLOBAL_PLAYERLIST);
if (list.shouldSee(viewer, unvanished)) viewer.sendCustomPacket(list.getAddPacket(unvanished, viewer), TabFeature.GLOBAL_PLAYERLIST);
}
}
}
+4
View File
@@ -0,0 +1,4 @@
name: ${project.parent.name}
main: me.neznamy.tab.platforms.bungeecord.Main
version: ${project.parent.version}
author: NEZNAMY
@@ -1,197 +1,217 @@
# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml
#changing nametags, affecting sorting, collision rule
change-nametag-prefix-suffix: true
#prefix/suffix in tablist
change-tablist-prefix-suffix: true
#header/footer
enable-header-footer: true
global-playerlist:
enabled: false
display-others-as-spectators: false
display-vanished-players-as-spectators: true
isolate-unlisted-servers: false
spy-servers:
- spyserver1
- spyserver2
server-groups:
lobbies:
- lobby1
- lobby2
group2:
- server1
- server2
header:
- "&3&lServer name"
- "&6&lRank: %rank%"
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
- "&7Online players: &f%online%"
- "&6Online staff: &e%staffonline%"
- ""
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
- "%animation:MyAnimation1%"
footer:
- "%animation:MyAnimation1%"
- "%animation:time%"
- ""
- "&7Visit our webpage %animation:web%"
#supports any numeric placeholder, set to "" to disable the feature
yellow-number-in-tablist: "%ping%"
#literally what it says
group-sorting-priority-list:
- owner
- admin
- mod
- helper
- builder
- premium
- player
- default
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
Admin:
tabprefix: "&0&l[&4&lAdmin&0&l] &1"
tagprefix: "%animation:admin_prefix%"
Player:
tabprefix: "&0&l[&7&lPlayer&0&l] &3"
tagprefix: "&2&lPlayer &3"
example_group:
header:
- "This is an example of per-group header/footer"
footer:
- "applied to a group"
#default settings for all groups, all groups will take properties from this section unless player's primary group overrides a specific setting
_OTHER_:
tabprefix: "%rank% "
tagprefix: "%rank% "
Users:
_NEZNAMY_:
tabprefix: "&6&lTAB Author &a"
tagprefix: "&6&lTAB &r&a"
#additional very limited line of text below nametag
#don't forget this enables on all player entities, which includes NPCs
#tags so you can find this section using the mighty ctrl + f feature: [NPC, Citizens, below_name, health]
classic-vanilla-belowname:
#enabling the feature
enabled: true
#number or numeric placeholder, supports per-player placeholders
number: "%ping%"
#text after number, must be same on all players
text: "Ping"
#controlling the collision rule
enable-collision: true
#using the opposite of the setting above for worlds
revert-collision-rule-in-worlds:
- revertedworld
#preventing players with spectator gamemode from being moved to the bottom of tablist with transparent name
do-not-move-spectators: false
#allows players with tab.spectatorbypass permission to see spectators moved despite the option above enabled
allow-spectator-bypass-permission: false
per-server-settings:
server1:
header:
- "an example of world with custom"
footer:
- "header/footer and prefix/suffix"
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
server2-server3:
header:
- "This is a shared header for"
- "server2 and server3"
placeholders:
#%date%
date-format: "dd.MM.yyyy"
#%time%
time-format: "[HH:mm:ss / h:mm a]"
#in hours, in case of wrong timezone
time-offset: 0
#don't forget color codes
remove-strings:
- "[]"
- "< >"
#aliases for %server%
server-aliases:
hub: "&eHub"
survival: "&bSurvival"
plot: "&cPlot"
#aliases for %rank%, this is not the place where group prefix is defined!
rank-aliases:
Admin: "&4&lADMIN"
Mod: "&b&lMOD"
Premium: "&6&lPREMIUM"
Ultra: "&b&lULTRA"
Legend: "&a&lLEGEND"
Titan: "&c&lTITAN"
Youtuber: "&c&lYOUTUBE"
_OTHER_: "&7No Rank"
disable-features-in-servers:
header-footer:
- disabledserver
tablist-names:
- disabledserver
nametag:
- disabledserver
yellow-number:
- disabledserver
bossbar:
- disabledserver
belowname:
- disabledserver
placeholderapi-refresh-intervals:
default-refresh-interval: 100
server:
"%server_uptime%": 1000
"%server_tps_1_colored%": 1000
player:
"%player_health%": 200
"%player_ping%": 1000
"%vault_prefix%": 1000
relational:
"%rel_factionsuuid_relation_color%": 500
#permission node is tab.sort.<permission group from sorting list>
sort-players-by-permissions: false
#taking primary group from permission plugin
use-primary-group: true
#if the option above is false, all groups are taken from permission plugin and the one higher in this list is used as primary
primary-group-finding-list:
- Owner
- Admin
- Helper
- default
#if none of the above fits your needs, you can enable this to give groups using tab.group.<group name from the list above> permission
assign-groups-by-permissions: false
# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml
#taking permission nodes and groups from backend servers
use-bukkit-permissions-manager: false
#changing nametags, affecting sorting, collision rule
change-nametag-prefix-suffix: true
#prefix/suffix in tablist
change-tablist-prefix-suffix: true
#header/footer
enable-header-footer: true
global-playerlist:
enabled: false
display-others-as-spectators: false
display-vanished-players-as-spectators: true
isolate-unlisted-servers: false
spy-servers:
- spyserver1
- spyserver2
server-groups:
lobbies:
- lobby1
- lobby2
group2:
- server1
- server2
header:
- "&3&lServer name"
- "&6&lRank: %rank%"
- "&7&l>> %animation:Welcome%&3 &l%player%&7&l! &7&l<<"
- "&7Online players: &f%online%"
- "&6Online staff: &e%staffonline%"
- ""
- "&7&l Used memory: %memory-used% MB / %memory-max% MB"
- "%animation:MyAnimation1%"
footer:
- "%animation:MyAnimation1%"
- "%animation:time%"
- ""
- "&7Visit our webpage %animation:web%"
#supports any numeric placeholder, set to "" to disable the feature
yellow-number-in-tablist: "%ping%"
group-sorting-priority-list:
- owner
- admin
- mod
- helper
- builder
- premium
- player
- default
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
Admin:
tabprefix: "&0&l[&4&lAdmin&0&l] &1"
tagprefix: "%animation:admin_prefix%"
Player:
tabprefix: "&0&l[&7&lPlayer&0&l] &3"
tagprefix: "&2&lPlayer &3"
example_group:
header:
- "This is an example of per-group header/footer"
footer:
- "applied to a group"
#default settings for all groups, all groups will take properties from this section unless player's primary group overrides a specific setting
_OTHER_:
tabprefix: "%rank% "
tagprefix: "%rank% "
Users:
_NEZNAMY_:
tabprefix: "&6&lTAB Author &a"
tagprefix: "&6&lTAB &r&a"
#additional very limited line of text below nametag
#don't forget this enables on all player entities, which includes NPCs
#tags so you can find this section using the mighty ctrl + f feature: [NPC, Citizens, below_name, health]
classic-vanilla-belowname:
#enabling the feature
enabled: true
#number or numeric placeholder, supports per-player placeholders
number: "%ping%"
#text after number, must be same on all players
text: "Ping"
#controlling the collision rule
enable-collision: true
#using the opposite of the setting above for servers
revert-collision-rule-in-servers:
- revertedserver
#preventing players with spectator gamemode from being moved to the bottom of tablist with transparent name
do-not-move-spectators: false
#allows players with tab.spectatorbypass permission to see spectators moved despite the option above enabled
allow-spectator-bypass-permission: false
per-server-settings:
server1:
header:
- "an example of world with custom"
footer:
- "header/footer and prefix/suffix"
Groups:
Owner:
tabprefix: "&0&l[&a&lOwner&0&l] &a"
tagprefix: "&2&lOwner &a"
server2-server3:
header:
- "This is a shared header for"
- "server2 and server3"
placeholders:
#%date%
date-format: "dd.MM.yyyy"
#%time%
time-format: "[HH:mm:ss / h:mm a]"
#in hours, in case of wrong timezone
time-offset: 0
#don't forget color codes
remove-strings:
- "[]"
- "< >"
#aliases for %server%
server-aliases:
hub: "&eHub"
survival: "&bSurvival"
plot: "&cPlot"
#aliases for %rank%, this is not the place where group prefix is defined!
rank-aliases:
Admin: "&4&lADMIN"
Mod: "&b&lMOD"
Premium: "&6&lPREMIUM"
Ultra: "&b&lULTRA"
Legend: "&a&lLEGEND"
Titan: "&c&lTITAN"
Youtuber: "&c&lYOUTUBE"
_OTHER_: "&7No Rank"
disable-features-in-servers:
header-footer:
- disabledserver
tablist-names:
- disabledserver
nametag:
- disabledserver
yellow-number:
- disabledserver
bossbar:
- disabledserver
belowname:
- disabledserver
placeholderapi-refresh-intervals:
default-refresh-interval: 500
server:
"%server_uptime%": 1000
"%server_tps_1_colored%": 1000
player:
"%player_health%": 200
"%player_ping%": 1000
"%vault_prefix%": 1000
relational:
"%rel_factionsuuid_relation_color%": 500
#permission node is tab.sort.<permission group from sorting list>
sort-players-by-permissions: false
#taking primary group from permission plugin
use-primary-group: true
#if the option above is false, all groups are taken from permission plugin and the one higher in this list is used as primary
primary-group-finding-list:
- Owner
- Admin
- Helper
- default
#if none of the above fits your needs, you can enable this to give groups using tab.group.<group name from the list above> permission
assign-groups-by-permissions: false
#preventing other plugins from overriding TAB
anti-override:
scoreboard-teams: true
scoreboard-objectives: true
tablist-names: true
usernames: true
#permanently invisible nametags
invisible-nametags: false
#faking ping of players
ping-spoof:
enabled: false
value: 0
#unlocks extra console messages
debug: false
+96
View File
@@ -0,0 +1,96 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.neznamy</groupId>
<artifactId>TAB</artifactId>
<version>2.9.2-pre2</version>
</parent>
<artifactId>jar</artifactId>
<build>
<finalName>TAB-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactSet>
<includes>
<include>com.googlecode.json-simple:json-simple</include>
<include>com.github.NEZNAMY:YamlAssist</include>
<include>com.github.NEZNAMY:VelocityPacketInjector</include>
<include>org.bstats:bstats-base</include>
<include>org.bstats:bstats-bukkit</include>
<include>org.bstats:bstats-bungeecord</include>
<include>org.bstats:bstats-velocity</include>
<include>me.neznamy:*</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>me.neznamy.tab.libs.org.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>org.json.simple</pattern>
<shadedPattern>me.neznamy.tab.libs.org.json.simple</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>me.neznamy</groupId>
<artifactId>bukkit</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>me.neznamy</groupId>
<artifactId>bungeecord</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>me.neznamy</groupId>
<artifactId>shared</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</exclusion>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>me.neznamy</groupId>
<artifactId>velocity-1.1.0</artifactId>
<version>${project.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>
+20 -408
View File
@@ -1,13 +1,29 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.neznamy</groupId>
<artifactId>TAB</artifactId>
<version>2.8.11-pre4</version>
<name>TAB</name>
<version>2.9.2-pre2</version>
<packaging>pom</packaging>
<description>An all-in-one solution that works</description>
<modules>
<module>shared</module>
<module>bukkit</module>
<module>bungeecord</module>
<module>velocity-1.1.0</module>
<module>jar</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputEncoding>UTF-8</project.build.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<sonar.projectKey>NEZNAMY_TAB</sonar.projectKey>
<sonar.moduleKey>${project.artifactId}</sonar.moduleKey>
<sonar.organization>neznamy</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>
<build>
<resources>
<resource>
@@ -15,409 +31,5 @@
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<artifactSet>
<includes>
<include>com.googlecode.json-simple:json-simple</include>
<include>com.github.NEZNAMY:YamlAssist</include>
<include>com.github.NEZNAMY:VelocityPacketInjector</include>
</includes>
</artifactSet>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<!-- Spigot API -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<!-- PlaceholderAPI -->
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
<!-- Vault -->
<!-- PremiumVanishAPI -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<!-- LuckPerms5 -->
<!-- BungeeCord -->
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything/</url>
</repository>
<!-- ViaVersion -->
<repository>
<id>viaversion</id>
<url>https://repo.viaversion.com/</url>
</repository>
<repository>
<id>velocity</id>
<url>https://nexus.velocitypowered.com/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- json-simple, shaded -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- BungeeCord -->
<!-- com.google.common.io -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>1.16-R0.5-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec-haproxy</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>brigadier</artifactId>
</exclusion>
<exclusion>
<groupId>jline</groupId>
<artifactId>jline</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.trove4j</groupId>
<artifactId>core</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-query</artifactId>
</exclusion>
<exclusion>
<groupId>se.llbit</groupId>
<artifactId>jo-nbt</artifactId>
</exclusion>
<exclusion>
<groupId>net.sf.jopt-simple</groupId>
<artifactId>jopt-simple</artifactId>
</exclusion>
<exclusion>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PlaceholderAPI -->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.10.9</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Vault -->
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>org.bukkit</groupId>
<artifactId>bukkit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- LuckPerms5 -->
<dependency>
<groupId>net.luckperms</groupId>
<artifactId>api</artifactId>
<version>5.2</version>
<scope>provided</scope>
</dependency>
<!-- ViaVersion -->
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion</artifactId>
<version>3.2.0</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>us.myles</groupId>
<artifactId>viaversion-common</artifactId>
</exclusion>
<exclusion>
<groupId>us.myles</groupId>
<artifactId>viaversion-bukkit</artifactId>
</exclusion>
<exclusion>
<groupId>us.myles</groupId>
<artifactId>viaversion-bungee</artifactId>
</exclusion>
<exclusion>
<groupId>us.myles</groupId>
<artifactId>viaversion-fabric</artifactId>
</exclusion>
<exclusion>
<groupId>us.myles</groupId>
<artifactId>viaversion-sponge</artifactId>
</exclusion>
<exclusion>
<groupId>us.myles</groupId>
<artifactId>viaversion-velocity</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- PremiumVanishAPI -->
<dependency>
<groupId>com.github.MyzelYam</groupId>
<artifactId>PremiumVanishAPI</artifactId>
<version>2.0.3</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- YamlAssist -->
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>YamlAssist</artifactId>
<version>1.0.3</version>
</dependency>
<!-- Velocity API -->
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>com.moandjiezana.toml</groupId>
<artifactId>toml4j</artifactId>
</exclusion>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-gson</artifactId>
</exclusion>
<exclusion>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-hocon</artifactId>
</exclusion>
<exclusion>
<groupId>com.mojang</groupId>
<artifactId>brigadier</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.spongepowered</groupId>
<artifactId>configurate-yaml</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>
adventure-text-serializer-legacy-text3
</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>
adventure-text-serializer-legacy
</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>
adventure-text-serializer-plain
</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>text-serializer-plain</artifactId>
</exclusion>
<exclusion>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.j2objc</groupId>
<artifactId>j2objc-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_annotations</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>text-serializer-legacy</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>text-serializer-gson</artifactId>
</exclusion>
<exclusion>
<groupId>net.kyori</groupId>
<artifactId>examination-string</artifactId>
</exclusion>
<exclusion>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Missing velocity packets -->
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>VelocityPacketInjector</artifactId>
<version>1.0.0_fix</version>
</dependency>
</dependencies>
</project>
+121
View File
@@ -0,0 +1,121 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.neznamy</groupId>
<artifactId>TAB</artifactId>
<version>2.9.2-pre2</version>
</parent>
<artifactId>shared</artifactId>
<repositories>
<!-- LuckPerms5 -->
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything/</url>
</repository>
<!-- YamlAssist -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.63.Final</version>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.17-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId>
</exclusion>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>
netty-transport-native-unix-common
</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-protocol</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-event</artifactId>
</exclusion>
<exclusion>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- json-simple, shaded -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- LuckPerms5 -->
<dependency>
<groupId>net.luckperms</groupId>
<artifactId>api</artifactId>
<version>5.2</version>
<scope>provided</scope>
</dependency>
<!-- YamlAssist -->
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>YamlAssist</artifactId>
<version>1.0.4</version>
</dependency>
</dependencies>
</project>
@@ -1,5 +1,7 @@
package me.neznamy.tab.api;
import java.util.Set;
import me.neznamy.tab.shared.Property;
public interface ArmorStand {
@@ -85,4 +87,10 @@ public interface ArmorStand {
* @param addToRegistered - if player should be added to players in range
*/
public void spawn(TabPlayer viewer);
/**
* Returns list of players in entity tracking range (48 blocks)
* @return list of nearby players
*/
public Set<TabPlayer> getNearbyPlayers();
}
@@ -0,0 +1,157 @@
package me.neznamy.tab.api;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* A helper class for easy management of armor stands of a player
*/
public class ArmorStandManager {
//map of registered armor stands
private Map<String, ArmorStand> armorStands = Collections.synchronizedMap(new LinkedHashMap<String, ArmorStand>());
/**
* Adds armor stand into list
* @param name - key name of the armor stand
* @param as - armor stand to add
*/
public void addArmorStand(String name, ArmorStand as) {
armorStands.put(name, as);
}
/**
* 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.remove(name);
}
/**
* 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) {
if (viewer.getVersion().getMinorVersion() < 8) return;
synchronized (armorStands) {
armorStands.values().forEach(a -> a.spawn(viewer));
}
}
/**
* Sets sneak value of armor stands to specified value
* @param sneaking - new sneaking status
*/
public void sneak(boolean sneaking) {
synchronized (armorStands) {
armorStands.values().forEach(a -> a.sneak(sneaking));
}
}
/**
* Teleports armor stands to player's current location for all nearby players
*/
public void teleport() {
synchronized (armorStands) {
armorStands.values().forEach(ArmorStand::teleport);
}
}
/**
* Teleports armor stands to player's current location for specified viewer
* @param viewer - player to teleport armor stands for
*/
public void teleport(TabPlayer viewer) {
synchronized (armorStands) {
armorStands.values().forEach(a -> a.teleport(viewer));
}
}
/**
* Refreshes name of all armor stands for all nearby players
*/
public void refresh() {
synchronized (armorStands) {
armorStands.values().forEach(ArmorStand::refresh);
}
}
/**
* Updates armor stand visibility for all armor stands
* @param force - true if packets should be sent despite seemingly not needed
*/
public void updateVisibility(boolean force) {
synchronized (armorStands) {
armorStands.values().forEach(a -> a.updateVisibility(force));
}
}
/**
* Removes specified player from list of nearby players
* @param viewer - player to remove
*/
public void unregisterPlayer(TabPlayer viewer) {
synchronized (armorStands) {
armorStands.values().forEach(a -> a.removeFromRegistered(viewer));
}
}
/**
* Sends destroy packet of all armor stands to everyone and clears nearby players list
*/
public void destroy() {
synchronized (armorStands) {
armorStands.values().forEach(ArmorStand::destroy);
}
}
/**
* Sends destroy packet of all armor stands to specified player and removes them from nearby players list
* @param viewer - player to destroy armor stands for
*/
public void destroy(TabPlayer viewer) {
synchronized (armorStands) {
armorStands.values().forEach(a -> a.destroy(viewer));
}
}
/**
* Returns true if manager contains armor stand with specified entity id, false if not
* @param entityId - entity id
* @return true if armor stand with specified entity id exists, false if not
*/
public boolean hasArmorStandWithID(int entityId) {
synchronized (armorStands) {
for (ArmorStand as : armorStands.values()) {
if (as.getEntityId() == entityId) {
return true;
}
}
return false;
}
}
/**
* Returns a copy of list of all registered armor stands
* @return copy of armor stands
*/
public Collection<ArmorStand> getArmorStands(){
return armorStands.values();
}
/**
* Returns list of nearby players
* @return list of nearby players
*/
public Set<TabPlayer> getNearbyPlayers(){
if (armorStands.isEmpty()) return new HashSet<>(); //not initialized yet
synchronized (armorStands) {
return armorStands.values().iterator().next().getNearbyPlayers();
}
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.api;
import java.util.Set;
/**
* An interface to be worked with to manipulate scoreboard
*/
public interface Scoreboard {
/**
* Unregisters this scoreboard from specified player
* @param p - player to unregister scoreboard for
*/
public void unregister(TabPlayer p);
/**
* Registers this scoreboard to specified player
* @param p - player to register scoreboard for
*/
public void register(TabPlayer p);
/**
* Returns name of this scoreboard
* @return name of this scoreboard
*/
public String getName();
/**
* Returns list of all players who can see this scoreboard
* @return list of players who can see this scoreboard
*/
public Set<TabPlayer> getRegisteredUsers();
}
@@ -5,132 +5,122 @@ import java.util.List;
import java.util.Map;
import java.util.UUID;
import com.google.common.base.Preconditions;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PlaceholderManager;
import me.neznamy.tab.shared.features.bossbar.BossBarLine;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.placeholders.Placeholder;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
import me.neznamy.tab.shared.placeholders.RelationalPlaceholder;
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
import me.neznamy.tab.shared.placeholders.conditions.simple.SimpleCondition;
/**
* The primary API class to get instances of other API classes
*/
public class TABAPI {
//placeholders registered via API
public static Map<String, Placeholder> APIPlaceholders = new HashMap<String, Placeholder>();
private static final Map<String, Placeholder> apiPlaceholders = new HashMap<>();
private TABAPI() {
}
/**
* Returns player object from given UUID
* @return player object from given UUID
* @param id - Player UUID
* @since 2.8.3
*/
public static TabPlayer getPlayer(UUID id) {
return TAB.getInstance().getPlayer(id);
}
/**
* Returns player object from given name
* @return player object from given name
* @param name - Player name
* @since 2.8.3
*/
public static TabPlayer getPlayer(String name) {
return TAB.getInstance().getPlayer(name);
}
/**
* Returns true if enabled, false if disabled
* @return Whether unlimited nametag mode is enabled or not
* @see enableUnlimitedNameTagModePermanently
* @since 2.4.12
*/
public static boolean isUnlimitedNameTagModeEnabled() {
return TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx");
}
/**
* Enables unlimited nametag mode permanently in config
* @throws IllegalStateException if called from a proxy
* @see isUnlimitedNameTagModeEnabled
* @since 2.4.12
*/
public static void enableUnlimitedNameTagModePermanently() {
if (isUnlimitedNameTagModeEnabled()) return;
TAB.getInstance().getConfiguration().config.set("change-nametag-prefix-suffix", true);
TAB.getInstance().getConfiguration().config.set("unlimited-nametag-prefix-suffix-mode.enabled", true);
TAB.getInstance().getConfiguration().getConfig().set("change-nametag-prefix-suffix", true);
TAB.getInstance().getConfiguration().getConfig().set("unlimited-nametag-prefix-suffix-mode.enabled", true);
TAB.getInstance().unload();
TAB.getInstance().load();
}
/**
* Registers a player placeholder (placeholder with player-specific output)
* @param placeholder - Placeholder handler
* @since 2.6.5
* @see registerServerPlaceholder
* @see registerServerConstant
*/
public static void registerPlayerPlaceholder(PlayerPlaceholder placeholder) {
APIPlaceholders.put(placeholder.getIdentifier(), placeholder);
Preconditions.checkNotNull(placeholder, "placeholder");
apiPlaceholders.put(placeholder.getIdentifier(), placeholder);
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
pl.registerPlaceholder(placeholder);
pl.allUsedPlaceholderIdentifiers.add(placeholder.getIdentifier());
pl.getAllUsedPlaceholderIdentifiers().add(placeholder.getIdentifier());
pl.refreshPlaceholderUsage();
}
/**
* Registers a server placeholder (placeholder with same output for all players)
* @param placeholder - Placeholder handler
* @since 2.6.5
* @see registerPlayerPlaceholder
* @see registerServerConstant
*/
public static void registerServerPlaceholder(ServerPlaceholder placeholder) {
APIPlaceholders.put(placeholder.getIdentifier(), placeholder);
Preconditions.checkNotNull(placeholder, "placeholder");
apiPlaceholders.put(placeholder.getIdentifier(), placeholder);
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
pl.registerPlaceholder(placeholder);
pl.allUsedPlaceholderIdentifiers.add(placeholder.getIdentifier());
pl.getAllUsedPlaceholderIdentifiers().add(placeholder.getIdentifier());
pl.refreshPlaceholderUsage();
}
/**
* Registers a relational placeholder
* @param placeholder - Placeholder handler
* @since 2.8.0
*/
public static void registerRelationalPlaceholder(RelationalPlaceholder placeholder) {
APIPlaceholders.put(placeholder.getIdentifier(), placeholder);
Preconditions.checkNotNull(placeholder, "placeholder");
apiPlaceholders.put(placeholder.getIdentifier(), placeholder);
PlaceholderManager pl = TAB.getInstance().getPlaceholderManager();
pl.registerPlaceholder(placeholder);
pl.allUsedPlaceholderIdentifiers.add(placeholder.getIdentifier());
pl.getAllUsedPlaceholderIdentifiers().add(placeholder.getIdentifier());
pl.refreshPlaceholderUsage();
}
@Deprecated
public static Scoreboard createScoreboard(String title, List<String> lines) {
return createScoreboard("Unnamed scoreboard", title, lines);
}
/**
* Creates a new scoreboard
* @param name - name of the scoreboard
* @param title - the scoreboard title
* @param lines - up to 15 lines of text (supports placeholders)
* @return The new scoreboard
* @since 2.8.8
*/
public static Scoreboard createScoreboard(String name, String title, List<String> lines) {
for (String line : lines) {
@@ -138,37 +128,65 @@ public class TABAPI {
}
ScoreboardManager sbm = (ScoreboardManager) TAB.getInstance().getFeatureManager().getFeature("scoreboard");
if (sbm == null) throw new IllegalStateException("Scoreboard feature is not enabled");
Scoreboard sb = new me.neznamy.tab.shared.features.scoreboard.Scoreboard(sbm, name, title, lines);
sbm.APIscoreboards.add(sb);
Scoreboard sb = new ScoreboardImpl(sbm, name, title, lines);
sbm.getApiScoreboards().add(sb);
return sb;
}
/**
* Registers a custom permission plugin
* @param permission - permission plugin provider
* @since 2.8.3
*/
public static void registerPermissionPlugin(PermissionPlugin permission) {
TAB.getInstance().setPermissionPlugin(permission);
}
/**
* Creates bossbar with specified parameters
* @param name - internal name of bossbar
* @param title - title
* @param progress - progress (0-1)
* @param color - color
* @param style - style
* @return the bossbar
*/
public static BossBar createBossBar(String name, String title, float progress, BarColor color, BarStyle style) {
return createBossBar(name, title, progress+"", color.toString(), style.toString());
return createBossBar(name, title, String.valueOf(progress), color.toString(), style.toString());
}
/**
* Creates bossbar with specified parameters as strings to allow placeholder support
* @param name - internal name of bossbar
* @param title - title
* @param progress - progress
* @param color - color
* @param style - style
* @return the bossbar
*/
public static BossBar createBossBar(String name, String title, String progress, String color, String style) {
me.neznamy.tab.shared.features.bossbar.BossBar feature = (me.neznamy.tab.shared.features.bossbar.BossBar) TAB.getInstance().getFeatureManager().getFeature("bossbar");
if (feature == null) throw new IllegalStateException("Bossbar feature is not enabled");
BossBar bar = new BossBarLine(TAB.getInstance(), name, null, color, style, title, progress);
feature.lines.put(bar.getName(), (BossBarLine) bar);
BossBar bar = new BossBarLine(name, null, color, style, title, progress);
feature.getLines().put(bar.getName(), (BossBarLine) bar);
return bar;
}
/**
* Returns placeholders registered via API
* @return placeholders registered via API
*/
public static Map<String, Placeholder> getAPIPlaceholders(){
return APIPlaceholders;
return apiPlaceholders;
}
/**
* Registers custom condition type and return true if it was registered or false if it was already registered. Useful since
* /tab reload does not remove registered condition types.
* @param identifier - identifier to detect if condition is of this type
* @param clazz - class that handles the condition
* @return true if it was registered, false if it already was registered before
*/
public static boolean registerSubCondition(String identifier, Class<? extends SimpleCondition> clazz) {
return SimpleCondition.getConditionTypes().put(identifier, clazz) == null;
}
}
@@ -0,0 +1,467 @@
package me.neznamy.tab.api;
import java.util.Set;
import java.util.UUID;
import io.netty.channel.Channel;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
import me.neznamy.tab.shared.packets.UniversalPacketPlayOut;
/**
* An interface representing a player
*/
public interface TabPlayer {
/**
* Changes the requested property of a player temporarily (until next restart, reload or /tab reload)
* @param type Type of property
* @param value The value to be used
*/
public void setValueTemporarily(EnumProperty type, String value);
/**
* Changes the requested property of a player permanently (saved into config too)
* @param type Type of property
* @param value The value to be used
*/
public void setValuePermanently(EnumProperty type, String value);
/**
* Returns temporary value of player's property or null if not set
* @param type Type of property
* @return Temporary value of player's property or null if not set
* @see hasTemporaryValue
* @see setValueTemporarily
*/
public String getTemporaryValue(EnumProperty type);
/**
* Returns Whether player has temporary value or not
* @param type Type of property
* @return Whether player has temporary value or not
*/
public boolean hasTemporaryValue(EnumProperty type);
/**
* Removes temporary value from player if set
* @param type Type of property
*/
public void removeTemporaryValue(EnumProperty type);
/**
* Returns original value of property of player
* @param type Type of property
* @return Original value of property of player
*/
public String getOriginalValue(EnumProperty type);
/**
* Makes player's nametag globally invisible
* @see showNametag
* @see hasHiddenNametag
*/
public void hideNametag();
/**
* Hides player's nametag for specified player until it's shown again
* @param viewer - player to hide nametag for
*/
public void hideNametag(UUID viewer);
/**
* Makes player's nametag visible again
* @see hideNametag
* @see hasHiddenNametag
*/
public void showNametag();
/**
* Shows player's nametag for specified viewer if it was hidden before
* @param viewer - player to show nametag back for
*/
public void showNametag(UUID viewer);
/**
* Return whether player has hidden nametag or not
* @return Whether player has hidden nametag or not
* @see hideNametag
* @see showNametag
*/
public boolean hasHiddenNametag();
/**
* 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
*/
public boolean hasHiddenNametag(UUID viewer);
/**
* Refreshes all visuals on the player
*/
public void forceRefresh();
/**
* Displays a scoreboard created using TABAPI.createScoreboard method and disables
* automatic scoreboard assigning until this is reverted using removeCustomScoreboard()
* @see removeCustomScoreboard
*/
public void showScoreboard(Scoreboard scoreboard);
/**
* Displays a scoreboard defined in premiumconfig.yml and disabled automatic
* scoreboard assignment until this is reverted using removeCustomScoreboard()
* @see removeCustomScoreboard
*/
public void showScoreboard(String name);
/**
* Removes forced scoreboard sent using one of the showScoreboard methods
* @see showScoreboard
*/
public void removeCustomScoreboard();
/**
* Returns player's name
* @return Player's name
*/
public String getName();
/**
* Returns player's UUID
* @return Player's UUID
*/
public UUID getUniqueId();
/**
* Returns player's uuid used in tablist. This may only be different than real uuid if
* TAB is installed on velocity with some specific velocity setup
* @return player's uuid in tablist
*/
public UUID getTablistUUID();
/**
* Returns player's protocol version
* @return player's protocol version
*/
public ProtocolVersion getVersion();
/**
* Returns platform-specific entity
* @return an instance of bukkit/bungee/velocity player depending on platform
*/
public Object getPlayer();
/**
* Returns player's current world name (server on bungeecord)
* @return name of world (server on bungeecord) where player is currently in
*/
public String getWorldName();
/**
* Performs platform-specific API call to check for permission and returns the result
* @param permission - the permission to check for
* @return - true if has, false if not
*/
public boolean hasPermission(String permission);
/**
* Sends the player a custom universal packet
* @param packet - packet to send
*/
public void sendCustomPacket(UniversalPacketPlayOut 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
*/
public void sendCustomPacket(UniversalPacketPlayOut packet, TabFeature feature);
/**
* Sends the player a platform-specific packet
* @param packet - an instance of packet depending on platform
*/
public 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
*/
public void sendPacket(Object packet, TabFeature feature);
/**
* Returns player's property by name
* @param name - name of property
* @return the property or null if not found
*/
public 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
*/
public void sendMessage(String message, boolean translateColors);
/**
* Sends specified component as a chat message
* @param message - message to send
*/
public void sendMessage(IChatBaseComponent message);
/**
* Returns player's primary permission group
* @return player's group depending on configuration
*/
public String getGroup();
/**
* Toggles armor stands preview mode for the player
*/
public void toggleNametagPreview();
/**
* Returns whether player is previewing nametag or not
* @return true if previewing, false if not
*/
public boolean isPreviewingNametag();
/**
* Returns whether player has tab.staff permission or not
* @return whether player is staff or not
*/
public boolean isStaff();
/**
* Returns player's channel or null if server is 1.7 or older
* @return player's channel
*/
public Channel getChannel();
/**
* Returns player's ping calculated by server
* @return player's ping
*/
public long getPing();
/**
* Returns player's platform-specific skin data
* @return player's skin
*/
public 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
*/
public boolean isLoaded();
/**
* Returns true if player has bossbar visible, false if player hid bossbar(s) using bossbar
* toggle command
* @return true if visible, false if toggled
*/
public boolean hasBossbarVisible();
/**
* Sets bossbar visibility for the player and performs all packet sending if new value is
* different than previous value (true->false, false->true)
* @param visible - whether bossbar should be visible or not
* @param sendToggleMessage - if toggle message should be sent or not
*/
public void setBossbarVisible(boolean visible, boolean sendToggleMessage);
/**
* Returns list of all bossbars player can currently see
* @return list of all bossbars player can see
*/
public Set<BossBar> getActiveBossBars();
/**
* Sets property with specified name to new value. If property did no exist before, it is
* created. If it existed, it is overridden
* @param identifier - property name
* @param rawValue - new raw value
*/
public void setProperty(String identifier, String rawValue);
/**
* Loads property from config using standard property loading algorithm
* @param property - property name to load
*/
public void loadPropertyFromConfig(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
*/
public void loadPropertyFromConfig(String property, String ifNotSet);
/**
* Returns name of player's scoreboard team or null if nametag feature is disabled
* @return name of player's team
*/
public String getTeamName();
/**
* Returns user-friendly explanation of team name
* @return explanation behind team name
*/
public String getTeamNameNote();
/**
* Forces collision rule for the player. Setting it to null will remove forced value
* @param collision - forced collision rule
*/
public void setCollisionRule(Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
* @return forced value or null if not forced
*/
public Boolean getCollisionRule();
/**
* Returns true if player is disguised using iDisguide or LibsDisguises on bukkit, LibsDisguises
* only on bungeecord via bukkit bridge
* @return true if player is disguised, false if not
*/
public boolean isDisguised();
/**
* Sets scoreboard visibility and sends packets if value changed. Sends toggle message configured
* in premiumconfig if value changed and sendToggleMessage is true
* @param visible - new visibility value
* @param sendToggleMessage - whether toggle message should be sent or not
*/
public void setScoreboardVisible(boolean visible, boolean sendToggleMessage);
/**
* Toggles scoreboard visibility and sends toggle message if sendToggleMessage is true
* @param sendToggleMessage - whether toggle message should be sent or not
*/
public void toggleScoreboard(boolean sendToggleMessage);
/**
* Returns true if player can see scoreboard, false if toggled
* @return true if visible, false if not
*/
public boolean isScoreboardVisible();
/**
* Returns true if player has invisiblity potion, false if not. For bukkit, API is used, for bungeecord
* bukkit bridge is used
* @return true if has invisiblity potion, false if not
*/
public boolean hasInvisibilityPotion();
/**
* Returns true if scoreboard is forced using API method, false if not
* @return true if forced via API, false if not
*/
public boolean hasForcedScoreboard();
/**
* 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
*/
public boolean isVanished();
/**
* Shows specificed bossbar to the player
* @param bossbar - bossbar to show
*/
public void showBossBar(BossBar bossbar);
/**
* Hides specified bossbar if previously shown using showBossBar method
* @param bossbar - bossbar to hide back
*/
public void removeBossBar(BossBar bossbar);
/**
* Internal method for bukkit bridge data collection (disguise status, invisiblity status)
* @param attribute - attribute name
* @param value - attribute value
*/
public void setAttribute(String attribute, String value);
/**
* Returns true if player is online according to server
* @return true if online, false if not
*/
public boolean isOnline();
/**
* Returns player's armor stand manager if unlimited nametag mode is enabled, null if disabled
* @return player's armor stand manager
*/
public ArmorStandManager getArmorStandManager();
/**
* Sets player's armor stands manager to new instance
* @param armorStandManager - new instance
*/
public void setArmorStandManager(ArmorStandManager armorStandManager);
/**
* Internal method that returns player's active scoreboard
* @return player's active scoreboard
*/
public Scoreboard getActiveScoreboard();
/**
* 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 name - forced team name
*/
public void forceTeamName(String name);
/**
* Returns forced team name or null if not forced
* @return forced team name or null if not forced
*/
public String getForcedTeamName();
/**
* Returns gamemode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
* @return gamemode of the player
*/
public int getGamemode();
/**
* 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.
*/
public void pauseTeamHandling();
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*/
public void resumeTeamHandling();
/**
* 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
*/
public boolean hasTeamHandlingPaused();
}
@@ -0,0 +1,87 @@
package me.neznamy.tab.api.bossbar;
import java.util.UUID;
public interface BossBar {
/**
* Returns name of this bossbar
* @return name of bossbar
*/
public String getName();
/**
* Returns uuid of this bossbar
* @return uuid of this bossbar
*/
public UUID getUniqueId();
/**
* Sets bossbar title to specified string, supporting placeholders
* @param title - new title
*/
public void setTitle(String title);
/**
* Sets progress to specified string with placeholder support
* @param progress - progress placeholder
*/
public void setProgress(String progress);
/**
* Sets progress to specified value
* @param progress - new progress
*/
public void setProgress(float progress);
/**
* Sets color to specified value supporting placeholders
* @param color - color placeholder / value
*/
public void setColor(String color);
/**
* Sets color to specified value
* @param color - new color
*/
public void setColor(BarColor color);
/**
* Sets style to specified value supporting placeholders
* @param style - style placeholder / value
*/
public void setStyle(String style);
/**
* Sets style to specified value
* @param style - new style
*/
public void setStyle(BarStyle style);
/**
* Returns title of the bossbar
* @return title of the bossbar
*/
public String getTitle();
/**
* Returns progress of the bossbar as string, which is either entered string
* containing placeholders or entered number converted to string
* @return entered progress as string
*/
public String getProgress();
/**
* Returns color of the bossbar as string, which is either entered string
* containing placeholders or entered enum value converted to string
* @return entered color as string
*/
public String getColor();
/**
* Returns style of the bossbar as string, which is either entered string
* containing placeholders or entered enum value converted to string
* @return entered style as string
*/
public String getStyle();
}
@@ -3,6 +3,10 @@ package me.neznamy.tab.api.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* Bukkit event that is called each time TAB fully loads. This includes server startup, reload,
* /tab reload and /plugman reload tab
*/
public class BukkitTABLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
@@ -11,8 +15,8 @@ public class BukkitTABLoadEvent extends Event {
public HandlerList getHandlers(){
return handlers;
}
public static HandlerList getHandlerList(){
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,11 @@
package me.neznamy.tab.api.event;
import net.md_5.bungee.api.plugin.Event;
/**
* Bungee event that is called each time TAB fully loads. This includes server startup, reload,
* /tab reload and /plugman reload tab
*/
public class BungeeTABLoadEvent extends Event {
}
@@ -10,6 +10,7 @@ import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import me.neznamy.tab.api.bossbar.BarColor;
@@ -24,26 +25,30 @@ public class ErrorManager {
private final SimpleDateFormat dateformat = new SimpleDateFormat("dd.MM.yyyy - HH:mm:ss - ");
//one time messages already sent into console so they are not sent again
private Set<String> oneTimeMessages = new HashSet<String>();
private Set<String> oneTimeMessages = new HashSet<>();
//amount of logged startup warns
private int startupWarns = 0;
//error logs
public File errorLog;
public File papiErrorLog;
public File antiOverrideLog;
private File errorLog;
private File papiErrorLog;
private File antiOverrideLog;
//plugin instance
private TAB tab;
/**
* Constructs new instance
* @param tab - tab instance
*/
public ErrorManager(TAB tab) {
this.tab = tab;
errorLog = new File(tab.getPlatform().getDataFolder(), "errors.log");
papiErrorLog = new File(tab.getPlatform().getDataFolder(), "PlaceholderAPI.errors.log");
antiOverrideLog = new File(tab.getPlatform().getDataFolder(), "anti-override.log");
if (errorLog.exists() && errorLog.length() > 10) {
startupWarn("File &e" + errorLog.getPath() + "&c exists and is not empty. Take a look at the error messages and try to resolve them. After you do, delete the file.");
if (getErrorLog().exists() && getErrorLog().length() > 10) {
startupWarn("File &e" + getErrorLog().getPath() + "&c exists and is not empty. Take a look at the error messages and try to resolve them. After you do, delete the file.");
}
}
@@ -55,16 +60,6 @@ public class ErrorManager {
printError(message, null, false);
}
/**
* Prints an error message into errors.txt file and returns first argument
* @param defaultValue - value to return
* @param message - message to print
* @return first argument
*/
public <T> T printError(T defaultValue, String message) {
return printError(defaultValue, message, null);
}
/**
* Prints an error message and stack trace into errors.txt file and returns first argument
* @param defaultValue - value to return
@@ -93,7 +88,7 @@ public class ErrorManager {
* @param intoConsoleToo - if the message should be printed into console as well
*/
public void printError(String message, Throwable t, boolean intoConsoleToo) {
printError(message, t, intoConsoleToo, errorLog);
printError(message, t, intoConsoleToo, getErrorLog());
}
/**
@@ -109,24 +104,48 @@ public class ErrorManager {
error = error.getCause();
}
try {
if (!file.exists()) file.createNewFile();
if (!createFile(file)) return;
if (file.length() > 1000000) return; //not going over 1 MB
BufferedWriter buf = new BufferedWriter(new FileWriter(file, true));
if (message != null) {
write(buf, "&c[TAB v" + tab.getPluginVersion() + (tab.isPremium() ? " Premium": "") + "] " + message, intoConsoleToo);
}
if (error != null) {
write(buf, "&c" + error.getClass().getName() + ": " + error.getMessage(), intoConsoleToo);
for (StackTraceElement ste : error.getStackTrace()) {
write(buf, "&c at " + ste.toString(), intoConsoleToo);
try (BufferedWriter buf = new BufferedWriter(new FileWriter(file, true))){
if (message != null) {
write(buf, "&c[TAB v" + tab.getPluginVersion() + (tab.isPremium() ? " Premium": "") + "] ", message.replace('\u00a7', '&'), intoConsoleToo);
}
if (error != null) {
write(buf, "&c", error.getClass().getName() + ": " + error.getMessage(), intoConsoleToo);
for (StackTraceElement ste : error.getStackTrace()) {
write(buf, "&c at ", ste.toString(), intoConsoleToo);
}
}
}
buf.close();
} catch (Throwable ex) {
} catch (Exception ex) {
tab.getPlatform().sendConsoleMessage("&c[TAB] An error occurred when printing error message into file", true);
ex.printStackTrace();
tab.getPlatform().sendConsoleMessage(ex.getClass().getName() + ": " + ex.getMessage(), true);
for (StackTraceElement e : ex.getStackTrace()) {
tab.getPlatform().sendConsoleMessage(e.toString(), true);
}
tab.getPlatform().sendConsoleMessage("&c[TAB] Original error: " + message, true);
if (error != null) error.printStackTrace();
if (error != null) {
tab.getPlatform().sendConsoleMessage(error.getClass().getName() + ": " + error.getMessage(), true);
for (StackTraceElement e : error.getStackTrace()) {
tab.getPlatform().sendConsoleMessage(e.toString(), true);
}
}
}
}
/**
* Creates the file if it does not exist. Returns true if file already existed or creation was successful
* or false if file does not exist and creation failed due to an exception which is then printed into console
* @param file - file to create
* @return true if file already exists / was successfully created, false if creation failed due to an IOException
*/
private boolean createFile(File file) {
if (file.exists()) return true;
try {
return file.createNewFile();
} catch (IOException e) {
tab.getPlatform().sendConsoleMessage("&c[TAB] Failed to create file " + file.getPath() + ": " + e.getMessage(), true);
return false;
}
}
@@ -137,12 +156,17 @@ public class ErrorManager {
* @param forceConsole - send into console even without debug mode
* @throws IOException - if IO writer operation fails
*/
private void write(BufferedWriter buf, String message, boolean forceConsole) throws IOException {
buf.write(getCurrentTime() + removeColors(message) + System.getProperty("line.separator"));
if (tab.debugMode || forceConsole) tab.getPlatform().sendConsoleMessage(message, true);
private void write(BufferedWriter buf, String prefix, String message, boolean forceConsole) throws IOException {
buf.write(getCurrentTime() + removeColors(prefix) + message + System.getProperty("line.separator"));
if (tab.isDebugMode() || forceConsole) tab.getPlatform().sendConsoleMessage(prefix.replace('&', '\u00a7') + message, false);
}
private static String removeColors(String text) {
/**
* Removes all color codes from defined text
* @param text - text to remove colors from
* @return text without colors
*/
private String removeColors(String text) {
StringBuilder sb = new StringBuilder();
for (int i=0; i<text.length(); i++) {
char c = text.charAt(i);
@@ -204,12 +228,8 @@ public class ErrorManager {
if (string == null || string.length() == 0) return 0; //preventing error message on bungee with papi placeholders due to them not being initialized yet
try {
return (int) Math.round(Double.parseDouble(string));
} catch (Throwable e) {
if (string.contains("%")) {
return oneTimeConsoleError(defaultValue, "Value \"" + string + "\" used in " + place + " still has unparsed placeholders! Did you forget to download an expansion ?");
} else {
return oneTimeConsoleError(defaultValue, place + " only accepts numeric values! (Attempted to use \"" + string + "\")");
}
} catch (NumberFormatException e) {
return oneTimeConsoleError(defaultValue, formatNumberError(place, string));
}
}
@@ -223,12 +243,8 @@ public class ErrorManager {
public float parseFloat(String string, float defaultValue, String place) {
try {
return Float.parseFloat(string);
} catch (Throwable e) {
if (string.contains("%")) {
return oneTimeConsoleError(defaultValue, "Value \"" + string + "\" used in " + place + " still has unparsed placeholders! Did you forget to download an expansion ?");
} else {
return oneTimeConsoleError(defaultValue, place + " only accepts numeric values! (Attempted to use \"" + string + "\")");
}
} catch (NumberFormatException e) {
return oneTimeConsoleError(defaultValue, formatNumberError(place, string));
}
}
@@ -240,16 +256,17 @@ public class ErrorManager {
* @return parsed double
*/
public double parseDouble(String string, double defaultValue, String place) {
if (string == null || string.length() == 0) return 0; //preventing error message on bungee with papi placeholders due to them not being initialized yet
try {
return Double.parseDouble(string);
} catch (Throwable e) {
if (string.contains("%")) {
return oneTimeConsoleError(defaultValue, "Value \"" + string + "\" used in " + place + " still has unparsed placeholders! Did you forget to download an expansion ?");
} else {
return oneTimeConsoleError(defaultValue, place + " only accepts numeric values! (Attempted to use \"" + string + "\")");
}
} catch (NumberFormatException e) {
return oneTimeConsoleError(defaultValue, formatNumberError(place, string));
}
}
private String formatNumberError(String place, String value) {
return String.format("%s only accepts numeric values! (Attempted to use \"%s\")", place, value);
}
/**
* Parses bar color in given string, returns second argument if string is not valid
@@ -261,12 +278,8 @@ public class ErrorManager {
public BarColor parseColor(String string, BarColor defaultValue, String place) {
try {
return BarColor.valueOf(string);
} catch (Throwable e) {
if (string.contains("%")) {
return oneTimeConsoleError(defaultValue, "Value \"" + string + "\" used in " + place + " still has unparsed placeholders! Did you forget to download an expansion ?");
} else {
return oneTimeConsoleError(defaultValue, place + " only accepts one of the defined colors! (Attempted to use \"" + string + "\")");
}
} catch (Exception e) {
return oneTimeConsoleError(defaultValue, String.format("%s only accepts one of the defined colors! (Attempted to use \"%s\")", place, string));
}
}
@@ -280,12 +293,8 @@ public class ErrorManager {
public BarStyle parseStyle(String string, BarStyle defaultValue, String place) {
try {
return BarStyle.valueOf(string);
} catch (Throwable e) {
if (string.contains("%")) {
return oneTimeConsoleError(defaultValue, "Value \"" + string + "\" used in " + place + " still has unparsed placeholders! Did you forget to download an expansion ?");
} else {
return oneTimeConsoleError(defaultValue, place + " only accepts one of the defined styles! (Attempted to use \"" + string + "\")");
}
} catch (Exception e) {
return oneTimeConsoleError(defaultValue, String.format("%s only accepts one of the defined styles! (Attempted to use \"%s\")", place, string));
}
}
@@ -307,7 +316,31 @@ public class ErrorManager {
if (interval % 50 != 0) {
int newInterval = interval - interval%50;
if (newInterval == 0) newInterval = 50;
startupWarn("Animation \"&e" + name + "&c\" has refresh interval of "+interval+" which is not divisible by 50! Using " + newInterval + ".");
startupWarn("Animation \"&e" + name + "&c\" has refresh interval of "+interval+" which is not divisible by 50! &bUsing " + newInterval + ".");
return newInterval;
}
return interval;
}
/**
* Makes interval divisible by 50 and sends error message if it was not already or was 0 or less
* @param identifier - placeholder identifier
* @param interval - configured interval
* @return fixed interval
*/
public int fixPlaceholderInterval(String identifier, int interval) {
if (interval == 0) {
startupWarn("Placeholder \"&e" + identifier + "&c\" has refresh interval of 0 milliseconds! Is that misconfiguration? &bUsing 100.");
return 100;
}
if (interval < 0) {
startupWarn("Placeholder \"&e" + identifier + "&c\" has refresh interval of "+interval+". Refresh cannot be negative! &bUsing 100.");
return 100;
}
if (interval % 50 != 0) {
int newInterval = interval - interval%50;
if (newInterval == 0) newInterval = 50;
startupWarn("Placeholder \"&e" + identifier + "&c\" has refresh interval of "+interval+" which is not divisible by 50! &bUsing " + newInterval + ".");
return newInterval;
}
return interval;
@@ -369,10 +402,22 @@ public class ErrorManager {
*/
public SimpleDateFormat createDateFormat(String value, String defaultValue) {
try {
return new SimpleDateFormat(value);
return new SimpleDateFormat(value, Locale.ENGLISH);
} catch (Exception e) {
startupWarn("Format \"" + value + "\" is not a valid date/time format. Did you try to use color codes?");
return new SimpleDateFormat(defaultValue);
}
}
public File getAntiOverrideLog() {
return antiOverrideLog;
}
public File getErrorLog() {
return errorLog;
}
public File getPapiErrorLog() {
return papiErrorLog;
}
}
@@ -1,31 +1,31 @@
package me.neznamy.tab.shared;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.NameTag;
import me.neznamy.tab.shared.features.interfaces.CommandListener;
import me.neznamy.tab.shared.features.interfaces.DisplayObjectivePacketListener;
import me.neznamy.tab.shared.features.interfaces.Feature;
import me.neznamy.tab.shared.features.interfaces.HeaderFooterPacketListener;
import me.neznamy.tab.shared.features.interfaces.JoinEventListener;
import me.neznamy.tab.shared.features.interfaces.Loadable;
import me.neznamy.tab.shared.features.interfaces.LoginPacketListener;
import me.neznamy.tab.shared.features.interfaces.ObjectivePacketListener;
import me.neznamy.tab.shared.features.interfaces.PlayerInfoPacketListener;
import me.neznamy.tab.shared.features.interfaces.QuitEventListener;
import me.neznamy.tab.shared.features.interfaces.RawPacketFeature;
import me.neznamy.tab.shared.features.interfaces.Refreshable;
import me.neznamy.tab.shared.features.interfaces.RespawnEventListener;
import me.neznamy.tab.shared.features.interfaces.WorldChangeListener;
import me.neznamy.tab.shared.features.types.Feature;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.Refreshable;
import me.neznamy.tab.shared.features.types.event.CommandListener;
import me.neznamy.tab.shared.features.types.event.JoinEventListener;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.WorldChangeListener;
import me.neznamy.tab.shared.features.types.packet.DisplayObjectivePacketListener;
import me.neznamy.tab.shared.features.types.packet.LoginPacketListener;
import me.neznamy.tab.shared.features.types.packet.ObjectivePacketListener;
import me.neznamy.tab.shared.features.types.packet.PlayerInfoPacketListener;
import me.neznamy.tab.shared.features.types.packet.RawPacketListener;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
@@ -35,10 +35,15 @@ import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
public class FeatureManager {
//list of registered features
private Map<String, Feature> features = new LinkedHashMap<String, Feature>();
private Map<String, Feature> features = new LinkedHashMap<>();
//tab instance
private TAB tab;
/**
* Constructs new instance
* @param tab - tab instance
*/
public FeatureManager(TAB tab) {
this.tab = tab;
}
@@ -53,6 +58,15 @@ public class FeatureManager {
features.put(featureName, featureHandler);
}
/**
* Unregisters feature making it no longer receive events. This does not run unload method nor cancel
* tasks created by the feature
* @param featureName
*/
public void unregisterFeature(String featureName) {
features.remove(featureName);
}
/**
* Returns whether a feature with said name is registered or not
*
@@ -78,7 +92,11 @@ public class FeatureManager {
* @return list of all loaded features
*/
public Collection<Feature> getAllFeatures(){
return features.values();
try {
return features.values();
} catch (ConcurrentModificationException e) {
return getAllFeatures();
}
}
/**
@@ -133,10 +151,12 @@ public class FeatureManager {
* @param receiver - packet receiver
* @param packet - an instance of custom packet class PacketPlayOutPlayerInfo
* @return altered packet or null if packet should be cancelled
* @throws Exception
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public Object onPacketPlayOutPlayerInfo(TabPlayer receiver, Object packet) throws Exception {
List<PlayerInfoPacketListener> listeners = new ArrayList<PlayerInfoPacketListener>();
public Object onPacketPlayOutPlayerInfo(TabPlayer receiver, Object packet) throws IllegalAccessException, InvocationTargetException {
if (receiver.getVersion().getMinorVersion() < 8) return packet;
List<PlayerInfoPacketListener> listeners = new ArrayList<>();
for (Feature f : getAllFeatures()) {
if (f instanceof PlayerInfoPacketListener) listeners.add((PlayerInfoPacketListener) f);
}
@@ -148,8 +168,8 @@ public class FeatureManager {
tab.getCPUManager().addTime(TabFeature.PACKET_DESERIALIZING, UsageType.PACKET_PLAYER_INFO, System.nanoTime()-time);
for (PlayerInfoPacketListener f : listeners) {
time = System.nanoTime();
((PlayerInfoPacketListener)f).onPacketSend(receiver, info);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PACKET_READING, System.nanoTime()-time);
f.onPacketSend(receiver, info);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PACKET_READING_OUT, System.nanoTime()-time);
}
time = System.nanoTime();
Object pack = info.create(receiver.getVersion());
@@ -179,6 +199,11 @@ public class FeatureManager {
* @param connectedPlayer - player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
if (!connectedPlayer.isOnline()) {
tab.debug("Player " + connectedPlayer.getName() + " was offline during login process.");
return;
}
long millis = System.currentTimeMillis();
tab.addPlayer(connectedPlayer);
for (Feature f : getAllFeatures()) {
if (!(f instanceof JoinEventListener)) continue;
@@ -186,7 +211,8 @@ public class FeatureManager {
((JoinEventListener)f).onJoin(connectedPlayer);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PLAYER_JOIN_EVENT, System.nanoTime()-time);
}
connectedPlayer.markAsLoaded();
((ITabPlayer)connectedPlayer).markAsLoaded();
tab.debug("Player join of " + connectedPlayer.getName() + " processed in " + (System.currentTimeMillis()-millis) + "ms");
}
/**
@@ -196,10 +222,14 @@ public class FeatureManager {
* @param from - name of the previous world/server
* @param to - name of the new world/server
*/
public void onWorldChange(TabPlayer changed, String to) {
if (changed == null || !changed.isLoaded()) return;
public void onWorldChange(UUID playerUUID, String to) {
TabPlayer changed = TAB.getInstance().getPlayer(playerUUID);
if (changed == null || !changed.isLoaded()) {
tab.getCPUManager().runTaskLater(100, "processing delayed world/server switch", TabFeature.OTHER, UsageType.WORLD_SWITCH_EVENT, () -> onWorldChange(playerUUID, to));
return;
}
String from = changed.getWorldName();
changed.setWorldName(to);
((ITabPlayer)changed).setWorldName(to);
for (Feature f : getAllFeatures()) {
if (!(f instanceof WorldChangeListener)) continue;
long time = System.nanoTime();
@@ -228,7 +258,7 @@ public class FeatureManager {
}
/**
* Calls onPacketReceive(...) on all features that implement RawPacketFeature and measures how long it took them to process
* Calls onPacketReceive(...) on all features that implement RawPacketListener and measures how long it took them to process
*
* @param receiver - packet receiver
* @param packet - IN packet coming from player
@@ -237,48 +267,34 @@ public class FeatureManager {
public Object onPacketReceive(TabPlayer receiver, Object packet){
Object newPacket = packet;
for (Feature f : getAllFeatures()) {
if (!(f instanceof RawPacketFeature)) continue;
if (!(f instanceof RawPacketListener)) continue;
long time = System.nanoTime();
try {
if (newPacket != null) newPacket = ((RawPacketFeature)f).onPacketReceive(receiver, newPacket);
} catch (Throwable e) {
if (newPacket != null) newPacket = ((RawPacketListener)f).onPacketReceive(receiver, newPacket);
} catch (IllegalAccessException e) {
tab.getErrorManager().printError("Feature " + f.getFeatureType() + " failed to read packet", e);
}
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PACKET_READING, System.nanoTime()-time);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PACKET_READING_IN, System.nanoTime()-time);
}
return newPacket;
}
/**
* Calls onPacketSend(...) on all features that implement RawPacketFeature and measures how long it took them to process
* Calls onPacketSend(...) on all features that implement RawPacketListener and measures how long it took them to process
*
* @param receiver - packet receiver
* @param packet - OUT packet coming from the server
*/
public void onPacketSend(TabPlayer receiver, Object packet){
for (Feature f : getAllFeatures()) {
if (!(f instanceof RawPacketFeature)) continue;
if (!(f instanceof RawPacketListener)) continue;
long time = System.nanoTime();
try {
((RawPacketFeature)f).onPacketSend(receiver, packet);
} catch (Throwable e) {
((RawPacketListener)f).onPacketSend(receiver, packet);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | SecurityException | InstantiationException e) {
tab.getErrorManager().printError("Feature " + f.getFeatureType() + " failed to read packet", e);
}
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PACKET_READING, System.nanoTime()-time);
}
}
/**
* Calls onRespawn on all featurs that implement RespawnEventListener and measures how long it took them to process
* @param respawned - player who respawned
*/
public void onRespawn(TabPlayer respawned) {
if (respawned == null) return;
for (Feature f : getAllFeatures()) {
if (!(f instanceof RespawnEventListener)) continue;
long time = System.nanoTime();
((RespawnEventListener)f).onRespawn(respawned);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PLAYER_RESPAWN_EVENT, System.nanoTime()-time);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.PACKET_READING_OUT, System.nanoTime()-time);
}
}
@@ -295,7 +311,14 @@ public class FeatureManager {
}
}
public boolean onDisplayObjective(TabPlayer packetReceiver, Object packet) throws Exception {
/**
* Calls onPacketSend on all featurs that implement DisplayObjectivePacketListener and measures how long it took them to process
* @param packetReceiver - player who received the packet
* @param packet - the packet
* @return true if packet should be cancelled, false if not
* @throws IllegalAccessException
*/
public boolean onDisplayObjective(TabPlayer packetReceiver, Object packet) throws IllegalAccessException {
long time = System.nanoTime();
PacketPlayOutScoreboardDisplayObjective display = tab.getPacketBuilder().readDisplayObjective(packet, packetReceiver.getVersion());
tab.getCPUManager().addTime(TabFeature.PACKET_DESERIALIZING, UsageType.PACKET_DISPLAY_OBJECTIVE, System.nanoTime()-time);
@@ -309,7 +332,12 @@ public class FeatureManager {
return false;
}
public void onObjective(TabPlayer packetReceiver, Object packet) throws Exception {
/**
* Calls onObjective on all featurs that implement ObjectivePacketListener and measures how long it took them to process
* @param packetReceiver - player who received the packet
* @throws IllegalAccessException
*/
public void onObjective(TabPlayer packetReceiver, Object packet) throws IllegalAccessException {
long time = System.nanoTime();
PacketPlayOutScoreboardObjective display = tab.getPacketBuilder().readObjective(packet, packetReceiver.getVersion());
tab.getCPUManager().addTime(TabFeature.PACKET_DESERIALIZING, UsageType.PACKET_OBJECTIVE, System.nanoTime()-time);
@@ -321,20 +349,10 @@ public class FeatureManager {
}
}
public boolean onHeaderFooter(TabPlayer packetReceiver, Object packet) throws Exception {
long time = System.nanoTime();
PacketPlayOutPlayerListHeaderFooter display = tab.getPacketBuilder().readHeaderFooter(packet, packetReceiver.getVersion());
tab.getCPUManager().addTime(TabFeature.PACKET_DESERIALIZING, UsageType.PACKET_DISPLAY_OBJECTIVE, System.nanoTime()-time);
for (Feature f : getAllFeatures()) {
if (!(f instanceof HeaderFooterPacketListener)) continue;
time = System.nanoTime();
boolean cancel = ((HeaderFooterPacketListener)f).onPacketSend(packetReceiver, display);
tab.getCPUManager().addTime(f.getFeatureType(), UsageType.ANTI_OVERRIDE, System.nanoTime()-time);
if (cancel) return true;
}
return false;
}
/**
* Returns currently loaded nametag feature or null if disabled
* @return currently loaded nametag feature
*/
public NameTag getNameTagFeature() {
if (isFeatureEnabled("nametag16")) return (NameTag) getFeature("nametag16");
return (NameTag) getFeature("nametagx");
@@ -1,9 +1,11 @@
package me.neznamy.tab.shared;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;
@@ -16,12 +18,13 @@ import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.command.level1.PlayerCommand;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.GroupRefresher;
import me.neznamy.tab.shared.features.NameTag;
import me.neznamy.tab.shared.features.bossbar.BossBarLine;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
import me.neznamy.tab.shared.packets.PacketPlayOutChat;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.shared.packets.UniversalPacketPlayOut;
import me.neznamy.tab.shared.packets.PacketPlayOutChat.ChatMessageType;
/**
* The core class for player
@@ -31,66 +34,37 @@ public abstract class ITabPlayer implements TabPlayer {
protected String name;
protected UUID uniqueId;
protected String world;
private String permissionGroup = "<null>";
private String permissionGroup = GroupRefresher.DEFAULT_GROUP;
private String teamName;
private String teamNameNote;
private String forcedTeamName;
private Map<String, Property> properties = new HashMap<String, Property>();
private Map<String, Property> properties = new HashMap<>();
private ArmorStandManager armorStandManager;
protected ProtocolVersion version = ProtocolVersion.SERVER_VERSION;
protected ProtocolVersion version = TAB.getInstance().getServerVersion();
protected Channel channel;
private boolean bossbarVisible;
private boolean previewingNametag;
private Set<BossBar> activeBossBars = new HashSet<BossBar>();
private boolean collision;
private Set<BossBar> activeBossBars = new HashSet<>();
private Boolean forcedCollision;
private boolean onJoinFinished;
private boolean hiddenNametag;
private Set<UUID> hiddenNametagFor = new HashSet<UUID>();
private boolean onBoat;
private Set<UUID> hiddenNametagFor = new HashSet<>();
private Scoreboard activeScoreboard;
private boolean scoreboardVisible;
private Scoreboard forcedScoreboard;
private String otherPluginScoreboard;
protected Map<String, String> attributes = new HashMap<String, String>();
private boolean teamHandlingPaused;
public void init() {
protected Map<String, String> attributes = new HashMap<>();
protected void init() {
setGroup(((GroupRefresher)TAB.getInstance().getFeatureManager().getFeature("group")).detectPermissionGroup(this), false);
}
public abstract boolean hasPermission(String permission);
public abstract long getPing();
public abstract void sendPacket(Object nmsPacket);
public void sendMessage(String message, boolean translateColors) {
if (message == null || message.length() == 0) return;
IChatBaseComponent component;
if (translateColors) {
component = IChatBaseComponent.fromColoredText(message);
} else {
component = new IChatBaseComponent(message);
}
sendCustomPacket(new PacketPlayOutChat(component));
}
public void sendMessage(IChatBaseComponent message) {
sendCustomPacket(new PacketPlayOutChat(message));
}
public abstract Object getSkin();
private boolean getTeamVisibility(TabPlayer viewer) {
if (TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx") && !onBoat) return false;
if (hiddenNametag || (boolean) TAB.getInstance().getConfiguration().getSecretOption("invisible-nametags", false) || hiddenNametagFor.contains(viewer.getUniqueId())) return false;
return !TAB.getInstance().getFeatureManager().getNameTagFeature().getInvisiblePlayers().contains(getName());
}
public void setProperty(String identifier, String rawValue, String source) {
private void setProperty(String identifier, String rawValue, String source) {
Property p = getProperty(identifier);
if (p == null) {
properties.put(identifier, new Property(this, rawValue, source));
@@ -99,11 +73,23 @@ public abstract class ITabPlayer implements TabPlayer {
p.setSource(source);
}
}
@Override
public void sendMessage(String message, boolean translateColors) {
if (message == null || message.length() == 0) return;
IChatBaseComponent component;
if (translateColors) {
component = IChatBaseComponent.fromColoredText(message);
} else {
component = new IChatBaseComponent(message);
}
sendCustomPacket(new PacketPlayOutChat(component, ChatMessageType.CHAT));
}
/*
* Implementing interface
*/
@Override
public void sendMessage(IChatBaseComponent message) {
sendCustomPacket(new PacketPlayOutChat(message, ChatMessageType.CHAT));
}
@Override
public String getName() {
@@ -114,7 +100,7 @@ public abstract class ITabPlayer implements TabPlayer {
public UUID getUniqueId() {
return uniqueId;
}
@Override
public UUID getTablistUUID() {
return uniqueId;
@@ -122,32 +108,35 @@ public abstract class ITabPlayer implements TabPlayer {
@Override
public void setValueTemporarily(EnumProperty type, String value) {
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
TAB.getInstance().debug("Received API request to set property " + type + " of " + getName() + " temporarily to " + value + " by " + Thread.currentThread().getStackTrace()[2].toString());
Property pr = getProperty(type.toString());
if (pr == null) throw new IllegalStateException("Feature handling this property is not enabled");
if (pr == null) throw new IllegalStateException("Feature handling this property (" + type + ") is not enabled");
pr.setTemporaryValue(value);
if (TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx") && type.toString().contains("tag")) {
setProperty("nametag",getProperty("tagprefix").getCurrentRawValue() + getProperty("customtagname").getCurrentRawValue() + getProperty("tagsuffix").getCurrentRawValue(), null);
setProperty(PropertyUtils.NAMETAG,getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue() + getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue() + getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue(), null);
}
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
forceRefresh();
}
@Override
public void setValuePermanently(EnumProperty type, String value) {
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
((PlayerCommand)TAB.getInstance().command.subcommands.get("player")).savePlayer(null, getName(), type.toString(), value);
TAB.getInstance().debug("Received API request to set property " + type + " of " + getName() + " permanently to " + value + " by " + Thread.currentThread().getStackTrace()[2].toString());
((PlayerCommand)TAB.getInstance().getCommand().getSubcommands().get("player")).savePlayer(null, getName(), type.toString(), value);
if (TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx") && type.toString().contains("tag")) {
setProperty("nametag", getProperty("tagprefix").getCurrentRawValue() + getProperty("customtagname").getCurrentRawValue() + getProperty("tagsuffix").getCurrentRawValue(), null);
setProperty(PropertyUtils.NAMETAG, getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue() + getProperty(PropertyUtils.CUSTOMTAGNAME).getCurrentRawValue() + getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue(), null);
}
Property pr = getProperty(type.toString());
if (pr == null) return;
pr.changeRawValue(value);
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
forceRefresh();
}
@Override
public String getTemporaryValue(EnumProperty type) {
return getProperty(type.toString()).getTemporaryValue();
Property pr = getProperty(type.toString());
return pr == null ? null : pr.getTemporaryValue();
}
@Override
@@ -167,14 +156,16 @@ public abstract class ITabPlayer implements TabPlayer {
@Override
public void hideNametag() {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() == null) return;
hiddenNametag = true;
updateTeamData();
TAB.getInstance().getFeatureManager().getNameTagFeature().updateTeamData(this);
}
@Override
public void showNametag() {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() == null) return;
hiddenNametag = false;
updateTeamData();
TAB.getInstance().getFeatureManager().getNameTagFeature().updateTeamData(this);
}
@Override
@@ -196,8 +187,8 @@ public abstract class ITabPlayer implements TabPlayer {
activeScoreboard.unregister(this);
activeScoreboard = null;
}
forcedScoreboard = (Scoreboard) scoreboard;
((Scoreboard)scoreboard).register(this);
forcedScoreboard = scoreboard;
scoreboard.register(this);
}
@Override
@@ -231,19 +222,21 @@ public abstract class ITabPlayer implements TabPlayer {
return world;
}
@Override
public void setWorldName(String name) {
world = name;
}
@Override
public void sendCustomPacket(UniversalPacketPlayOut packet) {
sendPacket(packet.create(getVersion()));
Object p = packet.create(getVersion());
long time = System.nanoTime();
sendPacket(p);
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
}
@Override
public void sendCustomPacket(UniversalPacketPlayOut packet, TabFeature feature) {
sendPacket(packet.create(getVersion()));
sendCustomPacket(packet);
TAB.getInstance().getCPUManager().packetSent(feature);
}
@@ -262,10 +255,10 @@ public abstract class ITabPlayer implements TabPlayer {
if (armorStandManager == null) throw new IllegalStateException("Unlimited nametag mode is not enabled");
if (previewingNametag) {
armorStandManager.destroy(this);
sendMessage(TAB.getInstance().getConfiguration().translation.getString("preview-off"), true);
sendMessage(TAB.getInstance().getConfiguration().getTranslation().getString("preview-off"), true);
} else {
armorStandManager.spawn(this);
sendMessage(TAB.getInstance().getConfiguration().translation.getString("preview-on"), true);
sendMessage(TAB.getInstance().getConfiguration().getTranslation().getString("preview-on"), true);
}
previewingNametag = !previewingNametag;
}
@@ -285,79 +278,11 @@ public abstract class ITabPlayer implements TabPlayer {
return channel;
}
@Override
public void unregisterTeam() {
if (teamName == null) return;
for (TabPlayer viewer : TAB.getInstance().getPlayers()) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName).setTeamOptions(69), TabFeature.NAMETAGS);
}
}
@Override
public void unregisterTeam(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName).setTeamOptions(69), TabFeature.NAMETAGS);
}
@Override
public void registerTeam() {
Property tagprefix = getProperty("tagprefix");
Property tagsuffix = getProperty("tagsuffix");
for (TabPlayer viewer : TAB.getInstance().getPlayers()) {
String currentPrefix = tagprefix.getFormat(viewer);
String currentSuffix = tagsuffix.getFormat(viewer);
PacketAPI.registerScoreboardTeam(viewer, teamName, currentPrefix, currentSuffix, getTeamVisibility(viewer), collision, Arrays.asList(getName()), null, TabFeature.NAMETAGS);
}
}
@Override
public void registerTeam(TabPlayer viewer) {
Property tagprefix = getProperty("tagprefix");
Property tagsuffix = getProperty("tagsuffix");
String replacedPrefix = tagprefix.getFormat(viewer);
String replacedSuffix = tagsuffix.getFormat(viewer);
PacketAPI.registerScoreboardTeam(viewer, teamName, replacedPrefix, replacedSuffix, getTeamVisibility(viewer), collision, Arrays.asList(getName()), null, TabFeature.NAMETAGS);
}
@Override
public void updateTeam() {
if (teamName == null) return; //player not loaded yet
String newName = TAB.getInstance().getFeatureManager().getNameTagFeature().sorting.getTeamName(this);
if (teamName.equals(newName)) {
updateTeamData();
} else {
unregisterTeam();
teamName = newName;
registerTeam();
}
}
@Override
public void updateTeamData() {
Property tagprefix = getProperty("tagprefix");
Property tagsuffix = getProperty("tagsuffix");
for (TabPlayer viewer : TAB.getInstance().getPlayers()) {
String currentPrefix = tagprefix.getFormat(viewer);
String currentSuffix = tagsuffix.getFormat(viewer);
boolean visible = getTeamVisibility(viewer);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName, currentPrefix, currentSuffix, visible?"always":"never", collision?"always":"never", 69), TabFeature.NAMETAGS);
}
}
private void updateTeamData(TabPlayer viewer) {
Property tagprefix = getProperty("tagprefix");
Property tagsuffix = getProperty("tagsuffix");
boolean visible = getTeamVisibility(viewer);
String currentPrefix = tagprefix.getFormat(viewer);
String currentSuffix = tagsuffix.getFormat(viewer);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName, currentPrefix, currentSuffix, visible?"always":"never", collision?"always":"never", 69), TabFeature.NAMETAGS);
}
@Override
public boolean isLoaded() {
return onJoinFinished;
}
@Override
public void markAsLoaded() {
onJoinFinished = true;
}
@@ -368,8 +293,28 @@ public abstract class ITabPlayer implements TabPlayer {
}
@Override
public void setBossbarVisible(boolean visible) {
public void setBossbarVisible(boolean visible, boolean sendToggleMessage) {
bossbarVisible = visible;
me.neznamy.tab.shared.features.bossbar.BossBar feature = (me.neznamy.tab.shared.features.bossbar.BossBar) TAB.getInstance().getFeatureManager().getFeature("bossbar");
if (feature == null) return;
if (hasBossbarVisible()) {
feature.detectBossBarsAndSend(this);
if (sendToggleMessage) sendMessage(TAB.getInstance().getConfiguration().getTranslation().getString("bossbar-toggle-on"), true);
if (feature.isRememberToggleChoice()) {
feature.getBossbarOffPlayers().remove(getName());
TAB.getInstance().getConfiguration().getPlayerDataFile().set("bossbar-off", feature.getBossbarOffPlayers());
}
} else {
for (BossBar line : getActiveBossBars().toArray(new BossBar[0])) {
removeBossBar(line);
}
getActiveBossBars().clear();
if (sendToggleMessage) sendMessage(TAB.getInstance().getConfiguration().getTranslation().getString("bossbar-toggle-off"), true);
if (feature.isRememberToggleChoice() && !feature.getBossbarOffPlayers().contains(getName())) {
feature.getBossbarOffPlayers().add(getName());
TAB.getInstance().getConfiguration().getPlayerDataFile().set("bossbar-off", feature.getBossbarOffPlayers());
}
}
}
@Override
@@ -390,39 +335,22 @@ public abstract class ITabPlayer implements TabPlayer {
@Override
public void loadPropertyFromConfig(String property, String ifNotSet) {
String playerGroupFromConfig = permissionGroup.replace(".", "@#@");
String worldGroup = TAB.getInstance().getConfiguration().getWorldGroupOf(getWorldName());
String worldGroup = TAB.getInstance().getConfiguration().getWorldGroupOf(TAB.getInstance().getConfiguration().getConfig().getConfigurationSection("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings").keySet(), getWorldName());
String value;
if ((value = TAB.getInstance().getConfiguration().config.getString("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Users." + getName() + "." + property)) != null) {
setProperty(property, value, "Player: " + getName() + ", " + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Users." + getUniqueId().toString() + "." + property)) != null) {
setProperty(property, value, "PlayerUUID: " + getName() + ", " + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("Users." + getName() + "." + property)) != null) {
setProperty(property, value, "Player: " + getName());
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("Users." + getUniqueId().toString() + "." + property)) != null) {
setProperty(property, value, "PlayerUUID: " + getName());
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Groups." + playerGroupFromConfig + "." + property)) != null) {
setProperty(property, value, "Group: " + permissionGroup + ", " + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Groups._OTHER_." + property)) != null) {
setProperty(property, value, "Group: _OTHER_," + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("Groups." + playerGroupFromConfig + "." + property)) != null) {
setProperty(property, value, "Group: " + permissionGroup);
return;
}
if ((value = TAB.getInstance().getConfiguration().config.getString("Groups._OTHER_." + property)) != null) {
setProperty(property, value, "Group: _OTHER_");
return;
Map<String, String> priorities = new LinkedHashMap<>();
priorities.put("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Users." + getName() + "." + property, "Player: " + getName() + ", " + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
priorities.put("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Users." + getUniqueId().toString() + "." + property, "PlayerUUID: " + getName() + ", " + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
priorities.put("Users." + getName() + "." + property, "Player: " + getName());
priorities.put("Users." + getUniqueId().toString() + "." + property, "PlayerUUID: " + getName());
priorities.put("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Groups." + playerGroupFromConfig + "." + property, "Group: " + permissionGroup + ", " + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
priorities.put("per-" + TAB.getInstance().getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Groups._OTHER_." + property, "Group: _OTHER_," + TAB.getInstance().getPlatform().getSeparatorType() + ": " + worldGroup);
priorities.put("Groups." + playerGroupFromConfig + "." + property, "Group: " + permissionGroup);
priorities.put("Groups._OTHER_." + property, "Group: _OTHER_");
for (Entry<String, String> entry : priorities.entrySet()) {
if ((value = TAB.getInstance().getConfiguration().getConfig().getString(entry.getKey())) != null) {
setProperty(property, value, entry.getValue());
return;
}
}
setProperty(property, ifNotSet, "None");
}
@@ -437,17 +365,16 @@ public abstract class ITabPlayer implements TabPlayer {
this.armorStandManager = armorStandManager;
}
@Override
public void setTeamName(String name) {
teamName = name;
}
@Override
public String getTeamName() {
if (forcedTeamName != null) return forcedTeamName;
return teamName;
}
@Override
public void setTeamNameNote(String note) {
teamNameNote = note;
}
@@ -467,40 +394,41 @@ public abstract class ITabPlayer implements TabPlayer {
return forcedCollision;
}
@Override
public void setOnBoat(boolean onBoat) {
this.onBoat = onBoat;
}
@Override
public boolean isOnBoat() {
return onBoat;
}
@Override
public void setScoreboardVisible(boolean visible, boolean sendToggleMessage) {
if (scoreboardVisible == visible) return;
scoreboardVisible = visible;
ScoreboardManager scoreboardManager = (ScoreboardManager) TAB.getInstance().getFeatureManager().getFeature("scoreboard");
if (scoreboardManager == null) throw new IllegalStateException("Scoreboard feature is not enabled");
if (scoreboardManager == null) return;
if (visible) {
scoreboardManager.sendHighestScoreboard(this);
if (sendToggleMessage) {
sendMessage(scoreboardManager.scoreboard_on, true);
sendMessage(scoreboardManager.getScoreboardOn(), true);
}
if (scoreboardManager.remember_toggle_choice) {
scoreboardManager.sb_off_players.remove(getName());
TAB.getInstance().getConfiguration().playerdata.set("scoreboard-off", scoreboardManager.sb_off_players);
if (scoreboardManager.isRememberToggleChoice()) {
if (scoreboardManager.isHiddenByDefault()) {
scoreboardManager.getSbOffPlayers().add(getName());
} else {
scoreboardManager.getSbOffPlayers().remove(getName());
}
synchronized (scoreboardManager.getSbOffPlayers()){
TAB.getInstance().getConfiguration().getPlayerDataFile().set("scoreboard-off", new ArrayList<>(scoreboardManager.getSbOffPlayers()));
}
}
} else {
scoreboardManager.unregisterScoreboard(this, true);
if (sendToggleMessage) {
sendMessage(scoreboardManager.scoreboard_off, true);
sendMessage(scoreboardManager.getScoreboardOff(), true);
}
if (scoreboardManager.remember_toggle_choice) {
scoreboardManager.sb_off_players.add(getName());
TAB.getInstance().getConfiguration().playerdata.set("scoreboard-off", scoreboardManager.sb_off_players);
if (scoreboardManager.isRememberToggleChoice()) {
if (scoreboardManager.isHiddenByDefault()) {
scoreboardManager.getSbOffPlayers().remove(getName());
} else {
scoreboardManager.getSbOffPlayers().add(getName());
}
synchronized (scoreboardManager.getSbOffPlayers()){
TAB.getInstance().getConfiguration().getPlayerDataFile().set("scoreboard-off", new ArrayList<>(scoreboardManager.getSbOffPlayers()));
}
}
}
}
@@ -515,7 +443,6 @@ public abstract class ITabPlayer implements TabPlayer {
return scoreboardVisible;
}
@Override
public void setActiveScoreboard(Scoreboard board) {
activeScoreboard = board;
}
@@ -525,7 +452,6 @@ public abstract class ITabPlayer implements TabPlayer {
return activeScoreboard;
}
@Override
public void setGroup(String permissionGroup, boolean refreshIfChanged) {
if (this.permissionGroup.equals(permissionGroup)) return;
if (permissionGroup != null) {
@@ -544,11 +470,6 @@ public abstract class ITabPlayer implements TabPlayer {
return forcedScoreboard != null;
}
@Override
public boolean isVanished() {
return false;
}
@Override
public void showBossBar(BossBar bossbar) {
BossBarLine line = (BossBarLine) bossbar;
@@ -563,52 +484,87 @@ public abstract class ITabPlayer implements TabPlayer {
activeBossBars.remove(line);
}
@Override
public void hideNametag(UUID viewer) {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() == null) return;
if (hiddenNametagFor.add(viewer)) {
updateTeamData(TAB.getInstance().getPlayer(viewer));
TAB.getInstance().getFeatureManager().getNameTagFeature().updateTeamData(this, TAB.getInstance().getPlayer(viewer));
if (armorStandManager != null) armorStandManager.updateVisibility(true);
}
}
@Override
public void showNametag(UUID viewer) {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() == null) return;
if (hiddenNametagFor.remove(viewer)) {
updateTeamData(TAB.getInstance().getPlayer(viewer));
TAB.getInstance().getFeatureManager().getNameTagFeature().updateTeamData(this, TAB.getInstance().getPlayer(viewer));
if (armorStandManager != null) armorStandManager.updateVisibility(true);
}
}
@Override
public boolean hasHiddenNametag(UUID viewer) {
return hiddenNametagFor.contains(viewer);
}
public void updateCollision() {
if (forcedCollision != null) {
if (collision != forcedCollision) {
collision = forcedCollision;
updateTeamData();
}
} else {
boolean collision = !isDisguised() && TAB.getInstance().getConfiguration().revertedCollision.contains(world) ? !TAB.getInstance().getConfiguration().collisionRule : TAB.getInstance().getConfiguration().collisionRule;
if (this.collision != collision) {
this.collision = collision;
updateTeamData();
}
}
}
@Override
public void setAttribute(String attribute, String value) {
attributes.put(attribute, value);
}
@Override
public void setOtherPluginScoreboard(String objectiveName) {
this.otherPluginScoreboard = objectiveName;
}
@Override
public String getOtherPluginScoreboard() {
return otherPluginScoreboard;
}
@Override
public void sendPacket(Object nmsPacket, TabFeature feature) {
sendPacket(nmsPacket);
TAB.getInstance().getCPUManager().packetSent(feature);
}
@Override
public void forceTeamName(String name) {
if (String.valueOf(forcedTeamName).equals(name)) return;
if (name != null && name.length() > 16) throw new IllegalArgumentException("Team name cannot be more than 16 characters long.");
NameTag nametags = TAB.getInstance().getFeatureManager().getNameTagFeature();
if (nametags == null) return;
nametags.unregisterTeam(this);
forcedTeamName = name;
nametags.registerTeam(this);
if (name != null) teamNameNote = "Set using API";
}
@Override
public String getForcedTeamName() {
return forcedTeamName;
}
@Override
public void pauseTeamHandling() {
if (teamHandlingPaused) return;
NameTag f = TAB.getInstance().getFeatureManager().getNameTagFeature();
if (f != null && !f.isDisabledWorld(getWorldName())) {
f.unregisterTeam(this);
}
teamHandlingPaused = true; //setting to true after, so unregisterTeam method runs
}
@Override
public void resumeTeamHandling() {
if (!teamHandlingPaused) return;
teamHandlingPaused = false; //setting to false before, so registerTeam method runs
NameTag f = TAB.getInstance().getFeatureManager().getNameTagFeature();
if (f != null && !f.isDisabledWorld(getWorldName())) {
f.registerTeam(this);
}
}
@Override
public boolean hasTeamHandlingPaused() {
return teamHandlingPaused;
}
}
@@ -15,6 +15,9 @@ import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
*/
public class PacketAPI {
private PacketAPI() {
}
/**
* Registers scoreboard team with given properties but sends unregister packet first unless disabled to avoid bungeecord kick
* @param to - player to send the packet to
@@ -27,10 +30,10 @@ public class PacketAPI {
* @param color - color field (1.13+)
*/
public static synchronized void registerScoreboardTeam(TabPlayer to, String teamName, String prefix, String suffix, boolean enumNameTagVisibility, boolean enumTeamPush, Collection<String> players, EnumChatFormat color, TabFeature feature) {
if (to.getVersion().getMinorVersion() >= 8 && (boolean) TAB.getInstance().getConfiguration().getSecretOption("unregister-before-register", true) && TAB.getInstance().getPlatform().getSeparatorType().equals("world")) {
to.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName).setTeamOptions(69), feature);
if (to.getVersion().getMinorVersion() >= 8 && TAB.getInstance().getConfiguration().isUnregisterBeforeRegister() && TAB.getInstance().getPlatform().getSeparatorType().equals("world")) {
to.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName), feature);
}
to.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName, prefix, suffix, enumNameTagVisibility?"always":"never", enumTeamPush?"always":"never", players, 69).setColor(color), feature);
to.sendCustomPacket(new PacketPlayOutScoreboardTeam(teamName, prefix, suffix, enumNameTagVisibility?"always":"never", enumTeamPush?"always":"never", players, 0).setColor(color), feature);
}
/**
@@ -42,7 +45,7 @@ public class PacketAPI {
* @param displayType - display type of the value (only supported in Playerlist)
*/
public static synchronized void registerScoreboardObjective(TabPlayer to, String objectiveName, String title, int position, EnumScoreboardHealthDisplay displayType, TabFeature feature) {
if (to.getVersion().getMinorVersion() >= 8 && (boolean) TAB.getInstance().getConfiguration().getSecretOption("unregister-before-register", true) && TAB.getInstance().getPlatform().getSeparatorType().equals("world")) {
if (to.getVersion().getMinorVersion() >= 8 && TAB.getInstance().getConfiguration().isUnregisterBeforeRegister() && TAB.getInstance().getPlatform().getSeparatorType().equals("world")) {
to.sendCustomPacket(new PacketPlayOutScoreboardObjective(objectiveName), feature);
}
to.sendCustomPacket(new PacketPlayOutScoreboardObjective(0, objectiveName, title, displayType), feature);
@@ -0,0 +1,175 @@
package me.neznamy.tab.shared;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.config.ConfigurationFile;
import me.neznamy.tab.shared.features.AlignedSuffix;
import me.neznamy.tab.shared.features.GhostPlayerFix;
import me.neznamy.tab.shared.features.GroupRefresher;
import me.neznamy.tab.shared.features.HeaderFooter;
import me.neznamy.tab.shared.features.Playerlist;
import me.neznamy.tab.shared.features.PluginInfo;
import me.neznamy.tab.shared.features.UpdateChecker;
import me.neznamy.tab.shared.features.bossbar.BossBar;
import me.neznamy.tab.shared.features.layout.Layout;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.placeholders.Placeholder;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholder;
import me.neznamy.tab.shared.placeholders.ServerPlaceholder;
/**
* An interface with methods that are called in universal code, but require platform-specific API calls
*/
public interface Platform {
/**
* Detects permission plugin and returns it's representing object
* @return the interface representing the permission hook
*/
public PermissionPlugin detectPermissionPlugin();
/**
* Loads features from config
* @throws IllegalAccessException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
*/
public void loadFeatures() throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, SecurityException;
/**
* Sends a message into console
* @param message - the message
* @param translateColors - if color codes should be translated
*/
public void sendConsoleMessage(String message, boolean translateColors);
/**
* Creates an instance of me.neznamy.tab.shared.placeholders.Placeholder to handle this unknown placeholder (typically a PAPI placeholder)
* @param identifier - placeholder's identifier
*/
public void registerUnknownPlaceholder(String identifier);
/**
* Returns server's version
* @return server's version
*/
public String getServerVersion();
/**
* Returns the word used to separate config options. It's value is "world" for bukkit and "server" for proxies
* @return "world" on bukkit, "server" on proxies
*/
public String getSeparatorType();
/**
* Returns plugin's data folder
* @return plugin's data folder
*/
public File getDataFolder();
/**
* Calls platform-specific event
* This method is called when plugin is fully enabled
*/
public void callLoadEvent();
/**
* Returns max player count configured in server files
* @return max player count
*/
public int getMaxPlayers();
/**
* Returns name of config file in the jar file on specific platform
* @return name of config file of the platform
*/
public String getConfigName();
/**
* Removes an old config option that is not present anymore
* @param config - configuration file
* @param oldKey - name of removed config option
*/
public default void removeOld(ConfigurationFile config, String oldKey) {
if (config.hasConfigOption(oldKey)) {
config.set(oldKey, null);
TAB.getInstance().print('2', "Removed old " + config.getName() + " option " + oldKey);
}
}
/**
* Renames variable in given configuration file
* @param config - configuration file to rename variable in
* @param oldName - old config option's name
* @param newName - new config option's name
*/
public default void rename(ConfigurationFile config, String oldName, String newName) {
if (config.hasConfigOption(oldName)) {
Object value = config.getObject(oldName);
config.set(oldName, null);
config.set(newName, value);
TAB.getInstance().print('2', "Renamed config option " + oldName + " to " + newName);
}
}
/**
* Replaces all placeholders in given string
* @param string - string to replace
* @param player - player to replaced placeholders for
* @return replaced string
*/
public default String replaceAllPlaceholders(String string, TabPlayer player) {
if (string == null) return null;
String replaced = string;
for (Placeholder p : TAB.getInstance().getPlaceholderManager().getAllPlaceholders()) {
if (replaced.contains(p.getIdentifier())) {
if (p instanceof ServerPlaceholder) {
((ServerPlaceholder)p).update();
}
if (p instanceof PlayerPlaceholder) {
((PlayerPlaceholder)p).update(player);
}
replaced = p.set(replaced, player);
}
}
return replaced;
}
/**
* Converts configuration options same on all platforms
* @param file - file to convert
*/
public default void convertUniversalOptions(ConfigurationFile file) {
if (file.getName().equals("animations.yml")) {
Map<String, Object> values = file.getValues();
if (values.size() == 1 && values.containsKey("animations")) {
file.setValues(file.getConfigurationSection("animations"));
file.save();
TAB.getInstance().print('2', "Converted animations.yml to new format.");
}
}
}
/**
* Loads universal features present on all platforms with the same configuration
*/
public default void loadUniversalFeatures() {
TAB tab = TAB.getInstance();
if (tab.getConfiguration().getConfig().getBoolean("enable-header-footer", true)) tab.getFeatureManager().registerFeature("headerfooter", new HeaderFooter(tab));
if (tab.getConfiguration().isRemoveGhostPlayers()) tab.getFeatureManager().registerFeature("ghostplayerfix", new GhostPlayerFix());
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8 && tab.getConfiguration().getConfig().getBoolean("change-tablist-prefix-suffix", true)) {
Playerlist playerlist = new Playerlist(tab);
tab.getFeatureManager().registerFeature("playerlist", playerlist);
if (tab.getConfiguration().getPremiumConfig() != null && tab.getConfiguration().getPremiumConfig().getBoolean("align-tabsuffix-on-the-right", false)) tab.getFeatureManager().registerFeature("alignedsuffix", new AlignedSuffix(playerlist, tab));
}
tab.getFeatureManager().registerFeature("group", new GroupRefresher(tab));
tab.getFeatureManager().registerFeature("info", new PluginInfo());
new UpdateChecker(tab);
if (tab.getConfiguration().isLayout()) tab.getFeatureManager().registerFeature("layout", new Layout(tab));
if (tab.getConfiguration().getBossbarConfig().getBoolean("bossbar-enabled", false)) tab.getFeatureManager().registerFeature("bossbar", new BossBar(tab));
}
}
@@ -6,6 +6,7 @@ import java.util.List;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.placeholders.Placeholder;
import me.neznamy.tab.shared.placeholders.RelationalPlaceholder;
import me.neznamy.tab.shared.rgb.RGBUtils;
/**
* A string with placeholders
@@ -22,16 +23,16 @@ public class Property {
private String temporaryValue;
//last known output after placeholder replacement
public String lastReplacedValue;
private String lastReplacedValue;
//source of property's raw value
private String source;
//used placeholders in current raw value
public List<String> placeholders;
private List<String> placeholders;
//used relational placeholders in current raw value
public List<String> relPlaceholders;
private List<String> relPlaceholders;
public Property(TabPlayer owner, String rawValue) {
this(owner, rawValue, null);
@@ -40,25 +41,28 @@ public class Property {
public Property(TabPlayer owner, String rawValue, String source) {
this.owner = owner;
this.source = source;
this.rawValue = (rawValue == null ? "" : rawValue);
this.rawValue = RGBUtils.getInstance().applyFormats((rawValue == null ? "" : TAB.getInstance().getPlaceholderManager().color(rawValue)), true);
analyze(this.rawValue);
update();
}
/**
* Finds all placeholders used in the value
* @param value - raw value to be checked
*/
private void analyze(String value) {
placeholders = new ArrayList<String>();
relPlaceholders = new ArrayList<String>();
List<String> placeholders0 = new ArrayList<>();
List<String> relPlaceholders0 = new ArrayList<>();
for (String identifier : TAB.getInstance().getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(value)) {
if (identifier.startsWith("%rel_")) {
relPlaceholders.add(identifier);
relPlaceholders0.add(identifier);
} else {
placeholders.add(identifier);
placeholders0.add(identifier);
}
}
//avoiding rare concurrent modification in #update
placeholders = placeholders0;
relPlaceholders = relPlaceholders0;
}
/**
@@ -66,10 +70,11 @@ public class Property {
* @param temporaryValue - temporary value to be assigned
*/
public void setTemporaryValue(String temporaryValue) {
this.temporaryValue = temporaryValue;
if (temporaryValue != null) {
analyze(temporaryValue);
this.temporaryValue = RGBUtils.getInstance().applyFormats(TAB.getInstance().getPlaceholderManager().color(temporaryValue), true);
analyze(this.temporaryValue);
} else {
this.temporaryValue = null;
analyze(rawValue);
}
update();
@@ -81,7 +86,7 @@ public class Property {
*/
public void changeRawValue(String newValue) {
if (rawValue.equals(newValue)) return;
rawValue = newValue;
rawValue = RGBUtils.getInstance().applyFormats(TAB.getInstance().getPlaceholderManager().color(newValue), true);
if (temporaryValue == null) {
analyze(rawValue);
update();
@@ -142,6 +147,7 @@ public class Property {
* @return if updating changed value or not
*/
public boolean update() {
long time = System.nanoTime();
String string = getCurrentRawValue();
for (String identifier : placeholders) {
Placeholder pl = TAB.getInstance().getPlaceholderManager().getPlaceholder(identifier);
@@ -151,14 +157,16 @@ public class Property {
string = applyRemoveStrings(string);
if (lastReplacedValue == null || !lastReplacedValue.equals(string)) {
lastReplacedValue = string;
TAB.getInstance().getCPUManager().addMethodTime("Property#update", System.nanoTime()-time);
return true;
}
TAB.getInstance().getCPUManager().addMethodTime("Property#update", System.nanoTime()-time);
return false;
}
private String applyRemoveStrings(String text) {
String reformatted = text;
for (String removed : TAB.getInstance().getConfiguration().removeStrings) {
for (String removed : TAB.getInstance().getConfiguration().getRemoveStrings()) {
if (removed.startsWith("CONTAINS:") && reformatted.contains(removed.substring(9))) return "";
if (removed.startsWith("STARTS:") && reformatted.startsWith(removed.substring(7))) return "";
if (removed.startsWith("ENDS:") && reformatted.endsWith(removed.substring(5))) return "";
@@ -181,11 +189,10 @@ public class Property {
* @return format for the viewer
*/
public String getFormat(TabPlayer viewer) {
if (viewer == null) return lastReplacedValue;
String format = lastReplacedValue;
for (String identifier : relPlaceholders) {
RelationalPlaceholder pl = (RelationalPlaceholder) TAB.getInstance().getPlaceholderManager().getPlaceholder(identifier);
if (pl != null) format = format.replace(pl.getIdentifier(), pl.getLastValue(viewer, owner));
if (pl != null) format = format.replace(pl.getIdentifier(), viewer == null ? "" : pl.getLastValue(viewer, owner));
}
return format;
}
@@ -0,0 +1,38 @@
package me.neznamy.tab.shared;
public class PropertyUtils {
public static final String HEADER = "header";
public static final String FOOTER = "footer";
public static final String TABPREFIX = "tabprefix";
public static final String CUSTOMTABNAME = "customtabname";
public static final String TABSUFFIX = "tabsuffix";
public static final String TAGPREFIX = "tagprefix";
public static final String CUSTOMTAGNAME = "customtagname";
public static final String TAGSUFFIX = "tagsuffix";
public static final String ABOVENAME = "abovename";
public static final String NAMETAG = "nametag";
public static final String BELOWNAME = "belowname";
private PropertyUtils() {
}
public static String bossbarTitle(String name) {
return "bossbar-title-" + name;
}
public static String bossbarProgress(String name) {
return "bossbar-progress-" + name;
}
public static String bossbarColor(String name) {
return "bossbar-color-" + name;
}
public static String bossbarStyle(String name) {
return "bossbar-style-" + name;
}
}
@@ -0,0 +1,132 @@
package me.neznamy.tab.shared;
/**
* Enum class representing all possibly used protocol versions
*/
public enum ProtocolVersion {
UNKNOWN (999),
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 int networkId;
//minor version, such as 16
private int minorVersion;
//friendly name displayed in %player-version% placeholder
private String friendlyName;
/**
* Constructs new instance with given network id
* @param networkId - network id of this version
*/
private ProtocolVersion(int networkId){
this.networkId = networkId;
if (toString().equals("UNKNOWN")) {
this.minorVersion = 17;
this.friendlyName = "Unknown";
} else {
this.minorVersion = Integer.parseInt(toString().split("_")[1]);
this.friendlyName = toString().substring(1).replace("_", ".");
}
}
/**
* Returns the vesion'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
*/
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;
}
/**
* 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 (Exception 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;
}
}
@@ -25,16 +25,16 @@ public class TAB {
private static TAB instance;
//version of plugin
public static final String pluginVersion = "2.8.11-pre4";
public static final String PLUGIN_VERSION = "2.9.2-pre2";
//player data
public final Map<UUID, TabPlayer> data = new ConcurrentHashMap<UUID, TabPlayer>();
private final Map<UUID, TabPlayer> data = new ConcurrentHashMap<>();
//the command
public final TabCommand command;
private TabCommand command;
//command used if plugin is disabled due to a broken configuration file
public final DisabledCommand disabledCommand = new DisabledCommand();
private final DisabledCommand disabledCommand = new DisabledCommand();
//platform interface
private Platform platform;
@@ -52,23 +52,26 @@ public class TAB {
private FeatureManager featureManager;
//name of broken configuration file filled on load and used in disabledCommand
public String brokenFile = "-";
private String brokenFile = "-";
//platform-specific packet builder
private PacketBuilder packetBuilder;
private Configs configuration;
public boolean debugMode;
private boolean debugMode;
private boolean disabled;
private PlaceholderManager placeholderManager;
//server version, always using latest on proxies
private ProtocolVersion serverVersion;
public TAB(Platform platform, PacketBuilder packetBuilder) {
public TAB(Platform platform, PacketBuilder packetBuilder, ProtocolVersion serverVersion) {
this.platform = platform;
this.packetBuilder = packetBuilder;
this.command = new TabCommand(this);
this.serverVersion = serverVersion;
}
/**
@@ -115,7 +118,7 @@ public class TAB {
*/
public TabPlayer getPlayerByTablistUUID(UUID tablistId) {
for (TabPlayer p : data.values()) {
if (p.getTablistUUID().toString().equals(tablistId.toString())) return p;
if (p.getTablistUUID().equals(tablistId)) return p;
}
return null;
}
@@ -134,7 +137,7 @@ public class TAB {
* @param message - message to be sent into console
*/
public void debug(String message) {
if (debugMode) platform.sendConsoleMessage("&9[TAB DEBUG] " + message, true);
if (isDebugMode()) platform.sendConsoleMessage("&9[TAB DEBUG] " + message, true);
}
/**
@@ -152,8 +155,9 @@ public class TAB {
placeholderManager = new PlaceholderManager(this);
featureManager.registerFeature("placeholders", placeholderManager);
platform.loadFeatures();
setCommand(new TabCommand(this));
featureManager.load();
getPlayers().forEach(p -> p.markAsLoaded());
getPlayers().forEach(p -> ((ITabPlayer)p).markAsLoaded());
errorManager.printConsoleWarnCount();
print('a', "Enabled in " + (System.currentTimeMillis()-time) + "ms");
platform.callLoadEvent();
@@ -161,7 +165,7 @@ public class TAB {
} catch (YAMLException e) {
print('c', "Did not enable due to a broken configuration file.");
disabled = true;
} catch (Throwable e) {
} catch (Exception e) {
errorManager.criticalError("Failed to enable. Did you just invent a new way to break the plugin by misconfiguring it?", e);
disabled = true;
}
@@ -172,13 +176,14 @@ public class TAB {
*/
public void unload() {
if (disabled) return;
disabled = true;
try {
long time = System.currentTimeMillis();
cpu.cancelAllTasks();
featureManager.unload();
data.clear();
platform.sendConsoleMessage("&a[TAB] Disabled in " + (System.currentTimeMillis()-time) + "ms", true);
} catch (Throwable e) {
} catch (Exception e) {
errorManager.criticalError("Failed to disable", e);
}
}
@@ -228,7 +233,7 @@ public class TAB {
}
public String getPluginVersion() {
return pluginVersion;
return PLUGIN_VERSION;
}
public Configs getConfiguration() {
@@ -242,4 +247,36 @@ public class TAB {
public PlaceholderManager getPlaceholderManager() {
return placeholderManager;
}
public TabCommand getCommand() {
return command;
}
public void setCommand(TabCommand command) {
this.command = command;
}
public boolean isDebugMode() {
return debugMode;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
public String getBrokenFile() {
return brokenFile;
}
public void setBrokenFile(String brokenFile) {
this.brokenFile = brokenFile;
}
public DisabledCommand getDisabledCommand() {
return disabledCommand;
}
public ProtocolVersion getServerVersion() {
return serverVersion;
}
}
@@ -5,26 +5,37 @@ import java.util.List;
import me.neznamy.tab.shared.TAB;
/**
* Command processor when TAB is disabled due to broken configuration file
*/
public class DisabledCommand {
/**
* Performs command and return messages to be sent back
* @param args - command arguments
* @param hasReloadPermission - if player has reload permission or not
* @param hasAdminPermission - if player has admin permission or not
* @return list of messages to send back
*/
public List<String> execute(String[] args, boolean hasReloadPermission, boolean hasAdminPermission) {
List<String> messages = new ArrayList<String>();
List<String> messages = new ArrayList<>();
if (args.length == 1 && args[0].equalsIgnoreCase("reload")) {
if (hasReloadPermission) {
TAB.getInstance().load();
if (TAB.getInstance().isDisabled()) {
messages.add(TAB.getInstance().getConfiguration().reloadFailed.replace("%file%", TAB.getInstance().brokenFile));
messages.add(TAB.getInstance().getConfiguration().getReloadFailedMessage().replace("%file%", TAB.getInstance().getBrokenFile()));
} else {
messages.add(TAB.getInstance().getConfiguration().translation.getString("reloaded"));
messages.add(TAB.getInstance().getConfiguration().getTranslation().getString("reloaded"));
}
} else {
messages.add(TAB.getInstance().getConfiguration().translation.getString("no_permission"));
//cannot take message from file when syntax is broken
messages.add("&cI'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.");
}
} else {
if (hasAdminPermission) {
String command = TAB.getInstance().getPlatform().getSeparatorType().equals("world") ? "/tab" : "/btab";
messages.add("&m ");
messages.add(" &cPlugin is disabled due to a broken configuration file (" + TAB.getInstance().brokenFile + "). Check console for more details.");
messages.add(" &cPlugin is disabled due to a broken configuration file (" + TAB.getInstance().getBrokenFile() + "). Check console for more details.");
messages.add(" &8>> &3&l" + command + " reload");
messages.add(" - &7Reloads plugin and config");
messages.add("&m ");
@@ -6,14 +6,18 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
/**
* Abstract class representing a subcommand of "/tab" command
*/
public abstract class SubCommand {
protected final String[] allProperties = {"tabprefix", "tabsuffix", "tagprefix", "tagsuffix", "customtabname", "abovename", "belowname", "customtagname"};
protected final List<String> extraProperties = Arrays.asList("abovename", "belowname", "customtagname");
//all properties assignable with a command
private static String[] allProperties = {PropertyUtils.TABPREFIX, PropertyUtils.TABSUFFIX, PropertyUtils.TAGPREFIX, PropertyUtils.TAGSUFFIX, PropertyUtils.CUSTOMTABNAME, PropertyUtils.ABOVENAME, PropertyUtils.BELOWNAME, PropertyUtils.CUSTOMTAGNAME};
//properties that require unlimited nametag mode
protected final List<String> extraProperties = Arrays.asList(PropertyUtils.ABOVENAME, PropertyUtils.BELOWNAME, PropertyUtils.CUSTOMTAGNAME);
//name of this subcommand
private String name;
@@ -22,9 +26,14 @@ public abstract class SubCommand {
private String permission;
//subcommands of this command
public Map<String, SubCommand> subcommands = new HashMap<String, SubCommand>();
private Map<String, SubCommand> subcommands = new HashMap<>();
public SubCommand(String name, String permission) {
/**
* Constructs new instance with given parameters
* @param name - command name
* @param permission - permission requirement
*/
protected SubCommand(String name, String permission) {
this.name = name;
this.permission = permission;
}
@@ -34,7 +43,7 @@ public abstract class SubCommand {
* @param subcommand - subcommand to register
*/
public void registerSubCommand(SubCommand subcommand) {
subcommands.put(subcommand.getName(), subcommand);
getSubcommands().put(subcommand.getName(), subcommand);
}
/**
@@ -101,7 +110,7 @@ public abstract class SubCommand {
* @return List of compatible players
*/
public List<String> getPlayers(String nameStart){
List<String> suggestions = new ArrayList<String>();
List<String> suggestions = new ArrayList<>();
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (all.getName().toLowerCase().startsWith(nameStart.toLowerCase())) suggestions.add(all.getName());
}
@@ -122,19 +131,24 @@ public abstract class SubCommand {
argument = arguments[0].toLowerCase();
}
if (arguments.length < 2) {
List<String> suggestions = new ArrayList<String>();
for (String subcommand : subcommands.keySet()) {
List<String> suggestions = new ArrayList<>();
for (String subcommand : getSubcommands().keySet()) {
if (subcommand.startsWith(argument)) suggestions.add(subcommand);
}
return suggestions;
}
SubCommand subcommand = subcommands.get(argument);
SubCommand subcommand = getSubcommands().get(argument);
if (subcommand != null) {
return subcommand.complete(sender, Arrays.copyOfRange(arguments, 1, arguments.length));
}
return new ArrayList<String>();
return new ArrayList<>();
}
/**
* Converts string array into a single string with "" and '' support for spaces
* @param args - array of arguments
* @return argument from array
*/
public String buildArgument(String[] args) {
String value = String.join(" ", args);
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
@@ -143,8 +157,13 @@ public abstract class SubCommand {
return value;
}
/**
* Returns message with given key
* @param key - message key
* @return translation from file
*/
public String getTranslation(String key) {
return TAB.getInstance().getConfiguration().translation.getString(key);
return TAB.getInstance().getConfiguration().getTranslation().getString(key);
}
/**
@@ -153,4 +172,20 @@ public abstract class SubCommand {
* @param args - arguments of the command
*/
public abstract void execute(TabPlayer sender, String[] args);
public static String[] getAllProperties() {
return allProperties;
}
public static void setAllProperties(String[] allProperties) {
SubCommand.allProperties = allProperties;
}
public Map<String, SubCommand> getSubcommands() {
return subcommands;
}
public void setSubcommands(Map<String, SubCommand> subcommands) {
this.subcommands = subcommands;
}
}
@@ -3,8 +3,12 @@ package me.neznamy.tab.shared.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import com.google.common.collect.Sets;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.command.level1.AnnounceCommand;
import me.neznamy.tab.shared.command.level1.BossBarCommand;
@@ -20,15 +24,19 @@ import me.neznamy.tab.shared.command.level1.ReloadCommand;
import me.neznamy.tab.shared.command.level1.ScoreboardCommand;
import me.neznamy.tab.shared.command.level1.SendCommand;
import me.neznamy.tab.shared.command.level1.SetCollisionCommand;
import me.neznamy.tab.shared.command.level1.WidthCommand;
/**
* The core command handler
*/
public class TabCommand extends SubCommand {
//tab instance
private TAB tab;
/**
* Constructs new instance with given parameter and registers all subcommands
* @param tab - tab instance
*/
public TabCommand(TAB tab) {
super("tab", null);
this.tab = tab;
@@ -47,15 +55,19 @@ public class TabCommand extends SubCommand {
registerSubCommand(new SetCollisionCommand());
if (tab.isPremium()) {
registerSubCommand(new ScoreboardCommand());
registerSubCommand(new WidthCommand());
}
Set<String> properties = Sets.newHashSet(PropertyUtils.TABPREFIX, PropertyUtils.TABSUFFIX, PropertyUtils.TAGPREFIX, PropertyUtils.TAGSUFFIX, PropertyUtils.CUSTOMTABNAME, PropertyUtils.ABOVENAME, PropertyUtils.BELOWNAME, PropertyUtils.CUSTOMTAGNAME);
for (Object line : ((DebugCommand)getSubcommands().get("debug")).getExtraLines()) {
properties.add(line.toString());
}
SubCommand.setAllProperties(properties.toArray(new String[0]));
}
@Override
public void execute(TabPlayer sender, String[] args) {
if (args.length > 0) {
String arg0 = args[0];
SubCommand command = subcommands.get(arg0.toLowerCase());
SubCommand command = getSubcommands().get(arg0.toLowerCase());
if (command != null) {
if (command.hasPermission(sender)) {
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
@@ -78,22 +90,23 @@ public class TabCommand extends SubCommand {
if (sender == null) tab.getPlatform().sendConsoleMessage("&3TAB v" + tab.getPluginVersion(), true);
if ((sender == null || sender.hasPermission("tab.admin"))) {
String command = tab.getPlatform().getSeparatorType().equals("world") ? "/tab" : "/btab";
String prefix = " &8>> &3&l";
sendMessage(sender, "&m ");
sendMessage(sender, " &8>> &3&l" + command + " reload");
sendMessage(sender, prefix + command + " reload");
sendMessage(sender, " - &7Reloads plugin and config");
sendMessage(sender, " &8>> &3&l" + command + " &9group&3/&9player &3<name> &9<property> &3<value...>");
sendMessage(sender, prefix + command + " &9group&3/&9player &3<name> &9<property> &3<value...>");
sendMessage(sender, " - &7Do &8/tab group/player &7to show properties");
sendMessage(sender, " &8>> &3&l" + command + " ntpreview");
sendMessage(sender, prefix + command + " ntpreview");
sendMessage(sender, " - &7Shows your nametag for yourself, for testing purposes");
sendMessage(sender, " &8>> &3&l" + command + " announce bar &3<name> &9<seconds>");
sendMessage(sender, prefix + command + " announce bar &3<name> &9<seconds>");
sendMessage(sender, " - &7Temporarily displays bossbar to all players");
sendMessage(sender, " &8>> &3&l" + command + " parse <placeholder> ");
sendMessage(sender, prefix + command + " parse <placeholder> ");
sendMessage(sender, " - &7Test if a placeholder works");
sendMessage(sender, " &8>> &3&l" + command + " debug [player]");
sendMessage(sender, prefix + command + " debug [player]");
sendMessage(sender, " - &7displays debug information about player");
sendMessage(sender, " &8>> &3&l" + command + " cpu");
sendMessage(sender, prefix + command + " cpu");
sendMessage(sender, " - &7shows CPU usage of the plugin");
sendMessage(sender, " &8>> &4&l" + command + " group/player <name> remove");
sendMessage(sender, prefix + command + " group/player <name> remove");
sendMessage(sender, " - &7Clears all data about player/group");
sendMessage(sender, "&m ");
}
@@ -101,7 +114,7 @@ public class TabCommand extends SubCommand {
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
if (!(boolean) tab.getConfiguration().getSecretOption("auto-command-complete", true)) return new ArrayList<String>();
if (!hasPermission(sender, "tab.tabcomplete")) return new ArrayList<>();
return super.complete(sender, arguments);
}
}
@@ -12,10 +12,13 @@ import me.neznamy.tab.shared.command.level2.AnnounceScoreboardCommand;
*/
public class AnnounceCommand extends SubCommand {
/**
* Constructs new instance and registers subcommands
*/
public AnnounceCommand() {
super("announce", null);
subcommands.put("bar", new AnnounceBarCommand());
subcommands.put("scoreboard", new AnnounceScoreboardCommand());
getSubcommands().put("bar", new AnnounceBarCommand());
getSubcommands().put("scoreboard", new AnnounceScoreboardCommand());
}
@Override
@@ -26,7 +29,7 @@ public class AnnounceCommand extends SubCommand {
return;
}
String arg0 = args[0].toLowerCase();
SubCommand command = subcommands.get(arg0);
SubCommand command = getSubcommands().get(arg0);
if (command != null) {
if (command.hasPermission(sender)) {
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
@@ -0,0 +1,37 @@
package me.neznamy.tab.shared.command.level1;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.command.SubCommand;
import me.neznamy.tab.shared.features.bossbar.BossBar;
/**
* Handler for "/tab bossbar" subcommand
*/
public class BossBarCommand extends SubCommand{
/**
* Constructs new instance
*/
public BossBarCommand() {
super("bossbar", null);
}
@Override
public void execute(TabPlayer sender, String[] args) {
BossBar bossbar = (BossBar) TAB.getInstance().getFeatureManager().getFeature("bossbar");
if (bossbar == null) {
sendMessage(sender, "&cBossbar feature is not enabled, therefore toggle command cannot be used.");
return;
}
if (sender == null) {
sendMessage(sender, "&cThis command must be ran from the game");
return;
}
if (!bossbar.isPermToToggle() || sender.hasPermission("tab.togglebar")) {
sender.setBossbarVisible(!sender.hasBossbarVisible(), true);
} else {
sender.sendMessage(getTranslation("no_permission"), true);
}
}
}
@@ -0,0 +1,177 @@
package me.neznamy.tab.shared.command.level1;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.command.SubCommand;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
import me.neznamy.tab.shared.placeholders.Placeholder;
/**
* Handler for "/tab cpu" subcommand
*/
public class CpuCommand extends SubCommand {
private final DecimalFormat decimal3 = new DecimalFormat("#.###");
private static final char LINE_CHAR = (char)9553;
private static final String SEPARATOR = "&8&l" + LINE_CHAR + "&8&m ";
/**
* Constructs new instance
*/
public CpuCommand() {
super("cpu", "tab.cpu");
}
@Override
public void execute(TabPlayer sender, String[] args) {
TAB tab = TAB.getInstance();
Map<String, Float> placeholders = tab.getCPUManager().getPlaceholderUsage();
float placeholdersTotal = 0;
for (Float time : placeholders.values()) placeholdersTotal += time;
Map<String, Float> bridgeplaceholders = tab.getCPUManager().getBridgeUsage();
float bridgeplaceholdersTotal = 0;
for (Float time : bridgeplaceholders.values()) bridgeplaceholdersTotal += time;
Map<Object, Map<UsageType, Float>> features = tab.getCPUManager().getFeatureUsage();
float featuresTotal = 0;
for (Map<UsageType, Float> map : features.values()) {
for (Float time : map.values()) {
featuresTotal += time;
}
}
sendMessage(sender, " ");
sendMessage(sender, "&8&l" + LINE_CHAR + "&8&m &r&8&l[ &bTAB CPU Stats &8&l]&r&8&l&m ");
sendMessage(sender, "&8&l" + LINE_CHAR + " &6CPU stats from the last 10 seconds");
sendMessage(sender, SEPARATOR);
sendMessage(sender, "&8&l" + LINE_CHAR + " &6Top 5 placeholders:");
int printCounter = 0;
for (Entry<String, Float> entry : placeholders.entrySet()) {
if (printCounter++ == 5) break;
String refresh = "";
Placeholder p = TAB.getInstance().getPlaceholderManager().getPlaceholder(entry.getKey());
if (p != null) refresh = " &8(" + p.getRefresh() + ")&7";
sendMessage(sender, String.format("&8&l%s &7%s - %s%%", LINE_CHAR, entry.getKey() + refresh, colorize(decimal3.format(entry.getValue()), 1, 0.3f)));
}
sendMessage(sender, SEPARATOR);
sendMessage(sender, "&8&l" + LINE_CHAR + " &6Some internal separately measured methods:");
for (Entry<String, Float> entry : tab.getCPUManager().getMethodUsage().entrySet()) {
sendMessage(sender, String.format("&8&l%s &7%s: %s%%", LINE_CHAR, entry.getKey(), colorize(decimal3.format(entry.getValue()), 5, 2)));
}
sendMessage(sender, SEPARATOR);
if (tab.getPlatform().getSeparatorType().equals("server")) {
sendMessage(sender, "&8&l" + LINE_CHAR + " &6Placeholder usage on Bukkit servers:");
for (Entry<String, Float> entry : bridgeplaceholders.entrySet()) {
if (entry.getValue() < 0.1) continue;
sendMessage(sender, String.format("&8&l%s &7%s - %s%%", LINE_CHAR, entry.getKey(), colorize(decimal3.format(entry.getValue()), 1, 0.3f)));
}
sendMessage(sender, SEPARATOR);
}
if (sender != null) {
sendToPlayer(sender, features);
} else {
sendToConsole(features);
}
sendMessage(sender, SEPARATOR);
sendMessage(sender, String.format("&8&l%s &7Threads created by the plugin (active/total): &7%s", LINE_CHAR, tab.getCPUManager().getThreadCount()));
if (sender != null) {
sendPacketCountToPlayer(sender);
} else {
sendPacketCountToConsole();
}
sendMessage(sender, String.format("&8&l%s &6&lPlaceholders Total: &a&l%s%%", LINE_CHAR, colorize(decimal3.format(placeholdersTotal), 10, 5)));
if (tab.getPlatform().getSeparatorType().equals("server")) {
sendMessage(sender, String.format("&8&l%s &6&lBukkit bridge placeholders Total: &a&l%s%%", LINE_CHAR, colorize(decimal3.format(bridgeplaceholdersTotal), 10, 5)));
}
sendMessage(sender, String.format("&8&l%s &6&lPlugin internals: &a&l%s%%", LINE_CHAR, colorize(decimal3.format(featuresTotal-placeholdersTotal), 10, 5)));
sendMessage(sender, String.format("&8&l%s &6&lTotal: &e&l%s%%", LINE_CHAR, colorize(decimal3.format(featuresTotal + bridgeplaceholdersTotal), 10, 5)));
sendMessage(sender, "&8&l" + LINE_CHAR + "&8&m &r&8&l[ &bTAB CPU Stats &8&l]&r&8&l&m ");
sendMessage(sender, " ");
}
public void sendToConsole(Map<Object, Map<UsageType, Float>> features) {
TAB.getInstance().getPlatform().sendConsoleMessage("&8&l" + LINE_CHAR + " &6Features:", true);
for (Entry<Object, Map<UsageType, Float>> entry : features.entrySet()) {
float featureTotal = 0;
for (Float f : entry.getValue().values()) {
featureTotal += f;
}
String core = String.format("&8&l%s &7%s (%s%%&7):", LINE_CHAR, entry.getKey(), colorize(decimal3.format(featureTotal), 5, 1));
List<String> messages = new ArrayList<>();
for (Entry<UsageType, Float> type : entry.getValue().entrySet()){
messages.add(String.format("&8&l%s &7%s - %s%%", LINE_CHAR, type.getKey(), colorize(decimal3.format(type.getValue()), 5, 1)));
}
TAB.getInstance().getPlatform().sendConsoleMessage(core, true);
for (String message : messages) {
TAB.getInstance().getPlatform().sendConsoleMessage(message, true);
}
}
}
public void sendToPlayer(TabPlayer sender, Map<Object, Map<UsageType, Float>> features) {
sendMessage(sender, "&8&l" + LINE_CHAR + " &6Features (hover with cursor for more info):");
for (Entry<Object, Map<UsageType, Float>> entry : features.entrySet()) {
float featureTotal = 0;
for (Float f : entry.getValue().values()) {
featureTotal += f;
}
String core = String.format("&8&l%s &7%s (%s%%&7):", LINE_CHAR, entry.getKey(), colorize(decimal3.format(featureTotal), 5, 1));
List<String> messages = new ArrayList<>();
for (Entry<UsageType, Float> type : entry.getValue().entrySet()){
messages.add("&3" + type.getKey().toString() + " - " + colorize(decimal3.format(type.getValue()), 5, 1) + "%");
}
IChatBaseComponent message = new IChatBaseComponent(core.replace('&', '\u00a7'));
message.onHoverShowText(String.join("\n", messages).replace('&', '\u00a7'));
sender.sendMessage(message);
}
}
public void sendPacketCountToConsole() {
Map<Object, AtomicInteger> packets = TAB.getInstance().getCPUManager().getSentPackets();
int count = 0;
List<String> messages = new ArrayList<>();
for (Entry<Object, AtomicInteger> entry : packets.entrySet()) {
count += entry.getValue().get();
messages.add("&8&l" + LINE_CHAR + " &7" + entry.getKey().toString() + " - " + entry.getValue());
}
TAB.getInstance().getPlatform().sendConsoleMessage("&8&l" + LINE_CHAR + " &r&7Packets sent by the plugin: " + count, true);
for (String message : messages) {
TAB.getInstance().getPlatform().sendConsoleMessage(message, true);
}
}
public void sendPacketCountToPlayer(TabPlayer sender) {
Map<Object, AtomicInteger> packets = TAB.getInstance().getCPUManager().getSentPackets();
int count = 0;
List<String> messages = new ArrayList<>();
for (Entry<Object, AtomicInteger> entry : packets.entrySet()) {
count += entry.getValue().get();
messages.add("&3" + entry.getKey().toString() + " - " + entry.getValue());
}
IChatBaseComponent message = new IChatBaseComponent(("&8&l" + LINE_CHAR + " &r&7Packets sent by the plugin (hover for more info): " + count).replace('&', '\u00a7'));
message.onHoverShowText(String.join("\n", messages).replace('&', '\u00a7'));
sender.sendMessage(message);
}
/**
* Returns colored usage from provided usage
* @param usage - usage
* @return colored usage
*/
private String colorize(String usage, float threshold1, float threshold2) {
float percent = Float.parseFloat(usage.replace(",", "."));
if (percent > threshold1) return "&c" + usage;
if (percent > threshold2) return "&e" + usage;
return "&a" + usage;
}
}
@@ -10,24 +10,27 @@ import me.neznamy.tab.shared.command.SubCommand;
*/
public class CpuTestCommand extends SubCommand {
/**
* Constructs new instance
*/
public CpuTestCommand() {
super("cputest", "tab.cputest");
}
@Override
public void execute(TabPlayer sender, String[] args) {
new Thread(new Runnable() {
new Thread(() -> {
@Override
public void run() {
sendMessage(sender, "&9Performing CPU test");
long time = System.currentTimeMillis();
test();
sendMessage(sender, "&9Task took &6" + (System.currentTimeMillis()-time) + "ms &9to process");
}
sendMessage(sender, "&9Performing CPU test");
long time = System.currentTimeMillis();
test();
sendMessage(sender, "&9Task took &6" + (System.currentTimeMillis()-time) + "ms &9to process");
}).start();
}
/**
* Performs the test
*/
public void test() {
String test = UUID.randomUUID().toString();
int i=0;
@@ -0,0 +1,227 @@
package me.neznamy.tab.shared.command.level1;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.Lists;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.command.SubCommand;
import me.neznamy.tab.shared.features.GroupRefresher;
import me.neznamy.tab.shared.features.NameTag;
import me.neznamy.tab.shared.features.Playerlist;
import me.neznamy.tab.shared.features.sorting.types.GroupPermission;
/**
* Handler for "/tab debug" subcommand
*/
public class DebugCommand extends SubCommand {
/**
* Constructs new instance
*/
public DebugCommand() {
super("debug", "tab.debug");
}
@Override
public void execute(TabPlayer sender, String[] args) {
TabPlayer analyzed = null;
if (args.length > 0) {
analyzed = TAB.getInstance().getPlayer(args[0]);
if (analyzed == null) {
sendMessage(sender, getTranslation("player_not_found"));
return;
}
}
if (analyzed == null && sender != null) {
analyzed = TAB.getInstance().getPlayer(sender.getUniqueId());
}
debug(sender, analyzed);
}
/**
* Peforms debug on player and displays output
* @param sender - command sender or null if console
* @param analyzed - player to be analyzed
*/
private void debug(TabPlayer sender, TabPlayer analyzed) {
TAB tab = TAB.getInstance();
String separator = "&7&m>-------------------------------<";
sendMessage(sender, "&3[TAB] &a&lShowing debug information");
sendMessage(sender, separator);
String c2 = tab.isPremium() ? "&b" : "&a";
sendMessage(sender, "&6Server version: " + c2 + tab.getPlatform().getServerVersion());
sendMessage(sender, "&6Plugin version: " + c2 + tab.getPluginVersion() + (tab.isPremium() ? " Premium" : ""));
if (tab.getErrorManager().getErrorLog().exists()) {
sendMessage(sender, "&6" + tab.getErrorManager().getErrorLog().getPath() + " size: &c" + tab.getErrorManager().getErrorLog().length()/1024 + "KB");
}
sendMessage(sender, "&6Permission plugin: " + c2 + tab.getPermissionPlugin().getName());
sendMessage(sender, "&6Permission group choice logic: " + c2 + getGroupChoiceLogic());
sendMessage(sender, "&6Sorting system: " + c2 + getSortingType());
sendMessage(sender, separator);
if (analyzed == null) return;
sendMessage(sender, "&ePlayer: &a" + analyzed.getName());
sendMessage(sender, getGroup(analyzed));
sendMessage(sender, getTeamName(analyzed));
sendMessage(sender, getTeamNameNote(analyzed));
if (tab.getFeatureManager().isFeatureEnabled("playerlist")) {
Playerlist playerlist = (Playerlist) tab.getFeatureManager().getFeature("playerlist");
boolean disabledPlayerlist = playerlist.isDisabledWorld(playerlist.getDisabledWorlds(), analyzed.getWorldName());
showProperty(sender, analyzed, PropertyUtils.TABPREFIX, disabledPlayerlist);
showProperty(sender, analyzed, PropertyUtils.TABSUFFIX, disabledPlayerlist);
showProperty(sender, analyzed, PropertyUtils.CUSTOMTABNAME, disabledPlayerlist);
} else {
sendMessage(sender, "&atabprefix: &cDisabled");
sendMessage(sender, "&atabsuffix: &cDisabled");
sendMessage(sender, "&atabname: &cDisabled");
}
if (tab.getFeatureManager().getNameTagFeature() != null) {
boolean disabledNametags = tab.getFeatureManager().getNameTagFeature().isDisabledWorld(analyzed.getWorldName());
showProperty(sender, analyzed, PropertyUtils.TAGPREFIX, disabledNametags);
showProperty(sender, analyzed, PropertyUtils.TAGSUFFIX, disabledNametags);
for (Object line : getExtraLines()) {
showProperty(sender, analyzed, line.toString(), disabledNametags);
}
} else {
sendMessage(sender, "&atagprefix: &cDisabled");
sendMessage(sender, "&atagsuffix: &cDisabled");
}
sendMessage(sender, separator);
}
/**
* Returns group choice logic
* @return group choice logic
*/
private String getGroupChoiceLogic() {
GroupRefresher group = (GroupRefresher) TAB.getInstance().getFeatureManager().getFeature("group");
if (group.isGroupsByPermissions()) {
return "Permissions";
} else if (group.isUsePrimaryGroup()) {
return "Primary group";
} else {
return "Choose from list";
}
}
/**
* Returns sorting type
* @return sorting type
*/
private String getSortingType() {
NameTag nametag = TAB.getInstance().getFeatureManager().getNameTagFeature();
if (nametag != null) {
if (TAB.getInstance().isPremium()) {
String sortingType = nametag.getSorting().typesToString();
if (sortingType.contains("PLACEHOLDER")) {
sortingType += " - " + nametag.getSorting().getSortingPlaceholder();
}
return sortingType;
} else if (nametag.getSorting().getSorting().get(0) instanceof GroupPermission) {
return "Permissions &c(this option was enabled by user, it is disabled by default!)";
} else {
return "Groups";
}
} else {
return "&cDISABLED";
}
}
/**
* Returns all info about player's group
* @param analyzed - player to check group of
* @return all info about player's group
*/
private String getGroup(TabPlayer analyzed) {
GroupRefresher group = (GroupRefresher) TAB.getInstance().getFeatureManager().getFeature("group");
if (group.isGroupsByPermissions()) {
return "&eHighest permission for group: &a" + analyzed.getGroup();
} else if (group.isUsePrimaryGroup()) {
return "&ePrimary permission group: &a" + analyzed.getGroup();
} else {
try {
return "&eFull permission group list: &a" + Arrays.toString(TAB.getInstance().getPermissionPlugin().getAllGroups(analyzed)) + "\n&eChosen group: &a" + analyzed.getGroup();
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) {
return "&eFull permission group list: &a[]\n&eChosen group: &a" + analyzed.getGroup();
}
}
}
/**
* Returns team name of specified player
* @param analyzed - player to get team name of
* @return team name of specified player
*/
private String getTeamName(TabPlayer analyzed) {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() != null) {
if (TAB.getInstance().getFeatureManager().getNameTagFeature().isDisabledWorld(analyzed.getWorldName())) {
return "&eTeam name: &cSorting is disabled in player's " + TAB.getInstance().getPlatform().getSeparatorType();
} else {
return "&eTeam name: &a" + analyzed.getTeamName();
}
}
return "";
}
/**
* Returns team name note of specified player
* @param analyzed - player to get team name note of
* @return team name note of specified player
*/
private String getTeamNameNote(TabPlayer analyzed) {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() != null &&
!TAB.getInstance().getFeatureManager().getNameTagFeature().isDisabledWorld(analyzed.getWorldName()) &&
analyzed.getTeamNameNote() != null)
return "&eTeam name note: &a" + analyzed.getTeamNameNote();
return "";
}
/**
* Returns list of extra properties if unlimited nametag mode is enabled
* @return list of extra properties
*/
@SuppressWarnings("unchecked")
public List<Object> getExtraLines(){
if (!TAB.getInstance().getFeatureManager().isFeatureEnabled("nametagx")) return new ArrayList<>();
if (TAB.getInstance().isPremium()) {
List<Object> lines = Lists.newArrayList((List<Object>) TAB.getInstance().getConfiguration().getPremiumConfig().getObject("unlimited-nametag-mode-dynamic-lines"));
lines.addAll(TAB.getInstance().getConfiguration().getPremiumConfig().getConfigurationSection("unlimited-nametag-mode-static-lines").keySet());
lines.remove(PropertyUtils.NAMETAG);
lines.add(PropertyUtils.CUSTOMTAGNAME);
return lines;
} else {
return Arrays.asList(PropertyUtils.CUSTOMTAGNAME, PropertyUtils.BELOWNAME, PropertyUtils.ABOVENAME);
}
}
/**
* Shows value and source of player's property
* @param sender - command sender or null if console
* @param analyzed - analyzed player
* @param property - property name
* @param disabled - if feature the property belongs to is disabled or not
*/
private void showProperty(TabPlayer sender, TabPlayer analyzed, String property, boolean disabled) {
if (disabled) {
sendMessage(sender, "&a" + property + ": &cDisabled in player's " + TAB.getInstance().getPlatform().getSeparatorType());
} else {
Property pr = analyzed.getProperty(property);
String rawValue = pr.getCurrentRawValue().replace('\u00a7', '&');
String c = TAB.getInstance().isPremium() ? "&7" : "&9";
String value = String.format(("&a%s: &e\"&r%s&r&e\" &7(%s) " + c + "(Source: %s)").replace('&', '\u00a7'), property, rawValue, rawValue.length(), pr.getSource());
sendRawMessage(sender, value);
}
}
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
return arguments.length == 1 ? getPlayers(arguments[0]) : new ArrayList<>();
}
}
@@ -1,18 +1,18 @@
package me.neznamy.tab.shared.command.level1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.command.SubCommand;
/**
* Handler for "/tab group" subcommand
*/
public class GroupCommand extends SubCommand {
public class GroupCommand extends PropertyCommand {
/**
* Constructs new instance
*/
public GroupCommand() {
super("group", null);
}
@@ -26,7 +26,7 @@ public class GroupCommand extends SubCommand {
String value = buildArgument(Arrays.copyOfRange(args, 2, args.length));
if (type.equals("remove")) {
if (hasPermission(sender, "tab.remove")) {
TAB.getInstance().getConfiguration().config.set("Groups." + group, null);
TAB.getInstance().getConfiguration().getConfig().set("Groups." + group, null);
for (TabPlayer pl : TAB.getInstance().getPlayers()) {
if (pl.getGroup().equals(group) || group.equals("_OTHER_")){
pl.forceRefresh();
@@ -36,7 +36,7 @@ public class GroupCommand extends SubCommand {
}
return;
}
for (String property : allProperties) {
for (String property : getAllProperties()) {
if (type.equals(property)) {
if (hasPermission(sender, "tab.change." + property)) {
saveGroup(sender, group, type, value);
@@ -65,28 +65,18 @@ public class GroupCommand extends SubCommand {
* @param value - new value
*/
private void saveGroup(TabPlayer sender, String group, String type, String value){
TAB.getInstance().getConfiguration().config.set("Groups." + group.replace(".", "@#@") + "." + type, value.length() == 0 ? null : value);
if (value.length() > 0){
sendMessage(sender, getTranslation("value_assigned").replace("%type%", type).replace("%value%", value).replace("%unit%", group).replace("%category%", "group"));
} else {
sendMessage(sender, getTranslation("value_removed").replace("%type%", type).replace("%unit%", group).replace("%category%", "group"));
}
if (String.valueOf(value.length() == 0 ? null : value).equals(String.valueOf(TAB.getInstance().getConfiguration().getConfig().getObject("Groups." + group.replace(".", "@#@") + "." + type)))) return;
TAB.getInstance().getConfiguration().getConfig().set("Groups." + group.replace(".", "@#@") + "." + type, value.length() == 0 ? null : value);
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
for (TabPlayer pl : TAB.getInstance().getPlayers()) {
if (pl.getGroup().equals(group) || group.equals("_OTHER_")){
pl.forceRefresh();
}
}
if (value.length() > 0){
sendMessage(sender, getTranslation("value_assigned").replace("%type%", type).replace("%value%", value).replace("%unit%", group).replace("%category%", "group"));
} else {
sendMessage(sender, getTranslation("value_removed").replace("%type%", type).replace("%unit%", group).replace("%category%", "group"));
}
}
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
List<String> suggestions = new ArrayList<String>();
if (arguments.length == 2) {
for (String property : allProperties) {
if (property.startsWith(arguments[1].toLowerCase())) suggestions.add(property);
}
}
return suggestions;
}
}
@@ -9,6 +9,9 @@ import me.neznamy.tab.shared.command.SubCommand;
*/
public class NTPreviewCommand extends SubCommand{
/**
* Constructs new instance
*/
public NTPreviewCommand() {
super("ntpreview", "tab.ntpreview");
}
@@ -10,6 +10,9 @@ import me.neznamy.tab.shared.packets.IChatBaseComponent;
*/
public class ParseCommand extends SubCommand{
/**
* Constructs new instance
*/
public ParseCommand() {
super("parse", "tab.parse");
}
@@ -18,10 +21,10 @@ public class ParseCommand extends SubCommand{
public void execute(TabPlayer sender, String[] args) {
if (args.length > 0) {
String replaced = String.join(" ", args);
String message = TAB.getInstance().getPlaceholderManager().color("&6Replacing placeholder &e%placeholder%" + (sender == null ? "" : "&6 for player &e" + sender.getName())).replace("%placeholder%", replaced);
String message = ("&6Replacing placeholder &e%placeholder%" + (sender == null ? "" : "&6 for player &e" + sender.getName())).replace('&', '\u00a7').replace("%placeholder%", replaced);
sendRawMessage(sender, message);
replaced = TAB.getInstance().getPlatform().replaceAllPlaceholders(replaced, sender);
IChatBaseComponent colored = IChatBaseComponent.fromColoredText("With colors: " + replaced);
IChatBaseComponent colored = IChatBaseComponent.optimizedComponent("With colors: " + replaced);
if (sender != null) {
sender.sendMessage(colored);
} else {
@@ -1,6 +1,5 @@
package me.neznamy.tab.shared.command.level1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -13,6 +12,9 @@ import me.neznamy.tab.shared.command.SubCommand;
*/
public class PlayerCommand extends SubCommand {
/**
* Constructs new instance
*/
public PlayerCommand() {
super("player", null);
}
@@ -26,7 +28,7 @@ public class PlayerCommand extends SubCommand {
String value = buildArgument(Arrays.copyOfRange(args, 2, args.length));
if (type.equals("remove")) {
if (hasPermission(sender, "tab.remove")) {
TAB.getInstance().getConfiguration().config.set("Users." + player, null);
TAB.getInstance().getConfiguration().getConfig().set("Users." + player, null);
TabPlayer pl = TAB.getInstance().getPlayer(player);
if (pl != null) {
pl.forceRefresh();
@@ -35,7 +37,7 @@ public class PlayerCommand extends SubCommand {
}
return;
}
for (String property : allProperties) {
for (String property : getAllProperties()) {
if (type.equals(property)) {
if (hasPermission(sender, "tab.change." + property)) {
savePlayer(sender, player, type, value);
@@ -64,28 +66,23 @@ public class PlayerCommand extends SubCommand {
* @param value - new value
*/
public void savePlayer(TabPlayer sender, String player, String type, String value){
TabPlayer pl = TAB.getInstance().getPlayer(player);
TAB.getInstance().getConfiguration().config.set("Users." + player + "." + type, value.length() == 0 ? null : value);
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
if (pl != null) {
pl.forceRefresh();
}
if (value.length() > 0){
sendMessage(sender, getTranslation("value_assigned").replace("%type%", type).replace("%value%", value).replace("%unit%", player).replace("%category%", "player"));
} else {
sendMessage(sender, getTranslation("value_removed").replace("%type%", type).replace("%unit%", player).replace("%category%", "player"));
}
if (String.valueOf(value.length() == 0 ? null : value).equals(String.valueOf(TAB.getInstance().getConfiguration().getConfig().getObject("Users." + player + "." + type)))) return;
TAB.getInstance().getConfiguration().getConfig().set("Users." + player + "." + type, value.length() == 0 ? null : value);
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
TabPlayer pl = TAB.getInstance().getPlayer(player);
if (pl != null) {
pl.forceRefresh();
}
}
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
if (arguments.length == 1) return getPlayers(arguments[0]);
List<String> suggestions = new ArrayList<String>();
if (arguments.length == 2) {
for (String property : allProperties) {
if (property.startsWith(arguments[1].toLowerCase())) suggestions.add(property);
}
}
return suggestions;
return super.complete(sender, arguments);
}
}
@@ -1,6 +1,5 @@
package me.neznamy.tab.shared.command.level1;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -13,6 +12,9 @@ import me.neznamy.tab.shared.command.SubCommand;
*/
public class PlayerUUIDCommand extends SubCommand {
/**
* Constructs new instance
*/
public PlayerUUIDCommand() {
super("playeruuid", null);
}
@@ -31,13 +33,13 @@ public class PlayerUUIDCommand extends SubCommand {
String value = buildArgument(Arrays.copyOfRange(args, 2, args.length));
if (type.equals("remove")) {
if (hasPermission(sender, "tab.remove")) {
TAB.getInstance().getConfiguration().config.set("Users." + changed.getUniqueId().toString(), null);
TAB.getInstance().getConfiguration().getConfig().set("Users." + changed.getUniqueId().toString(), null);
changed.forceRefresh();
sendMessage(sender, getTranslation("data_removed").replace("%category%", "player").replace("%value%", changed.getName() + "(" + changed.getUniqueId().toString() + ")"));
}
return;
}
for (String property : allProperties) {
for (String property : getAllProperties()) {
if (type.equals(property)) {
if (hasPermission(sender, "tab.change." + property)) {
savePlayer(sender, changed, type, value);
@@ -66,25 +68,20 @@ public class PlayerUUIDCommand extends SubCommand {
* @param value - new value
*/
public void savePlayer(TabPlayer sender, TabPlayer player, String type, String value){
TAB.getInstance().getConfiguration().config.set("Users." + player.getUniqueId() + "." + type, value.length() == 0 ? null : value);
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
player.forceRefresh();
if (value.length() > 0){
sendMessage(sender, getTranslation("value_assigned").replace("%type%", type).replace("%value%", value).replace("%unit%", player.getName() + "(" + player.getUniqueId().toString() + ")").replace("%category%", "UUID"));
} else {
sendMessage(sender, getTranslation("value_removed").replace("%type%", type).replace("%unit%", player.getName() + "(" + player.getUniqueId().toString() + ")").replace("%category%", "UUID"));
}
if (String.valueOf(value.length() == 0 ? null : value).equals(String.valueOf(TAB.getInstance().getConfiguration().getConfig().getObject("Users." + player.getUniqueId() + "." + type)))) return;
TAB.getInstance().getConfiguration().getConfig().set("Users." + player.getUniqueId() + "." + type, value.length() == 0 ? null : value);
TAB.getInstance().getPlaceholderManager().checkForRegistration(value);
player.forceRefresh();
}
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
if (arguments.length == 1) return getPlayers(arguments[0]);
List<String> suggestions = new ArrayList<String>();
if (arguments.length == 2) {
for (String property : allProperties) {
if (property.startsWith(arguments[1].toLowerCase())) suggestions.add(property);
}
}
return suggestions;
return super.complete(sender, arguments);
}
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.shared.command.level1;
import java.util.ArrayList;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.command.SubCommand;
public abstract class PropertyCommand extends SubCommand {
protected PropertyCommand(String name, String permission) {
super(name, permission);
}
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
List<String> suggestions = new ArrayList<>();
if (arguments.length == 2) {
for (String property : getAllProperties()) {
if (property.startsWith(arguments[1].toLowerCase())) suggestions.add(property);
}
}
return suggestions;
}
}
@@ -9,6 +9,9 @@ import me.neznamy.tab.shared.command.SubCommand;
*/
public class ReloadCommand extends SubCommand {
/**
* Constructs new instance
*/
public ReloadCommand() {
super("reload", "tab.reload");
}
@@ -19,7 +22,7 @@ public class ReloadCommand extends SubCommand {
TAB.getInstance().load();
if (TAB.getInstance().isDisabled()) {
if (sender != null) {
sendMessage(sender, TAB.getInstance().getConfiguration().reloadFailed.replace("%file%", TAB.getInstance().brokenFile));
sendMessage(sender, TAB.getInstance().getConfiguration().getReloadFailedMessage().replace("%file%", TAB.getInstance().getBrokenFile()));
}
} else {
sendMessage(sender, getTranslation("reloaded"));
@@ -0,0 +1,67 @@
package me.neznamy.tab.shared.command.level1;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.command.SubCommand;
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
/**
* Handler for "/tab scoreboard [on/off/toggle] [player] [options]" subcommand
*/
public class ScoreboardCommand extends SubCommand {
/**
* Constructs new instance
*/
public ScoreboardCommand() {
super("scoreboard", null);
}
@Override
public void execute(TabPlayer sender, String[] args) {
ScoreboardManager scoreboard = (ScoreboardManager) TAB.getInstance().getFeatureManager().getFeature("scoreboard");
if (scoreboard == null) {
sendMessage(sender, "&cScoreboard feature is not enabled, therefore toggle command cannot be used.");
return;
}
if (scoreboard.isPermToToggle() && !hasPermission(sender, "tab.togglescoreboard")) {
sendMessage(sender, getTranslation("no_permission"));
return;
}
if (args.length == 0) {
if (sender == null) {
sendMessage(sender, "Toggle command must be ran from the game");
} else {
sender.toggleScoreboard(true);
}
return;
}
TabPlayer p = sender;
if (args.length >= 2 && TAB.getInstance().getPlayer(args[1]) != null) {
if (hasPermission(sender, "tab.togglescoreboard.other")) {
p = TAB.getInstance().getPlayer(args[1]);
} else {
sendMessage(sender, getTranslation("no_permission"));
return;
}
}
if (((ITabPlayer)p).getOtherPluginScoreboard() != null) return; //not overriding other plugins
boolean silent = args.length >= 3 && args[2].equals("-s");
if (args.length >= 1) {
switch(args[0]) {
case "on":
p.setScoreboardVisible(true, !silent);
break;
case "off":
p.setScoreboardVisible(false, !silent);
break;
case "toggle":
p.toggleScoreboard(!silent);
break;
default:
break;
}
}
}
}
@@ -11,16 +11,19 @@ import me.neznamy.tab.shared.command.level2.SendBarCommand;
*/
public class SendCommand extends SubCommand {
/**
* Constructs new instance
*/
public SendCommand() {
super("send", null);
subcommands.put("bar", new SendBarCommand());
getSubcommands().put("bar", new SendBarCommand());
}
@Override
public void execute(TabPlayer sender, String[] args) {
if (args.length > 0) {
String arg0 = args[0].toLowerCase();
SubCommand command = subcommands.get(arg0);
SubCommand command = getSubcommands().get(arg0);
if (command != null) {
if (command.hasPermission(sender)) {
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
@@ -6,6 +6,9 @@ import me.neznamy.tab.shared.command.SubCommand;
public class SetCollisionCommand extends SubCommand {
/**
* Constructs new instance
*/
public SetCollisionCommand() {
super("setcollision", "tab.setcollision");
}
@@ -21,7 +24,7 @@ public class SetCollisionCommand extends SubCommand {
try {
boolean value = Boolean.parseBoolean(args[1]);
target.setCollisionRule(value);
target.updateTeamData();
TAB.getInstance().getFeatureManager().getNameTagFeature().updateTeamData(target);
} catch (Exception e) {
sendMessage(sender, "&c\"" + args[1] + "\" is not a valid true/false value");
}
@@ -15,7 +15,9 @@ import me.neznamy.tab.shared.features.bossbar.BossBarLine;
*/
public class AnnounceBarCommand extends SubCommand{
/**
* Constructs new instance
*/
public AnnounceBarCommand() {
super("bar", "tab.announce.bar");
}
@@ -36,32 +38,32 @@ public class AnnounceBarCommand extends SubCommand{
try {
duration = Integer.parseInt(args[1]);
} catch (Exception e) {
sender.sendMessage(args[1] + " is not a number!", false);
sendMessage(sender, args[1] + " is not a number!");
return;
}
BossBarLine bar = feature.lines.get(barname);
BossBarLine bar = feature.getLines().get(barname);
if (bar == null) {
sender.sendMessage("Bar not found", false);
sendMessage(sender, "Bar not found");
return;
}
new Thread(() -> {
try {
feature.announcements.add(barname);
feature.announceEndTime = System.currentTimeMillis() + duration*1000;
feature.getAnnouncements().add(barname);
feature.setAnnounceEndTime(System.currentTimeMillis() + duration*1000);
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (!all.hasBossbarVisible()) continue;
bar.create(all);
all.getActiveBossBars().add(bar);
}
Thread.sleep(duration*1000);
Thread.sleep(duration*1000L);
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (!all.hasBossbarVisible()) continue;
bar.remove(all);
all.getActiveBossBars().remove(bar);
}
feature.announcements.remove(barname);
} catch (Exception e) {
feature.getAnnouncements().remove(barname);
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
}
}).start();
}
@@ -69,13 +71,13 @@ public class AnnounceBarCommand extends SubCommand{
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
BossBar b = (BossBar) TAB.getInstance().getFeatureManager().getFeature("bossbar");
if (b == null) return new ArrayList<String>();
List<String> suggestions = new ArrayList<String>();
if (b == null) return new ArrayList<>();
List<String> suggestions = new ArrayList<>();
if (arguments.length == 1) {
for (String bar : b.lines.keySet()) {
for (String bar : b.getLines().keySet()) {
if (bar.toLowerCase().startsWith(arguments[0].toLowerCase())) suggestions.add(bar);
}
} else if (arguments.length == 2 && b.lines.get(arguments[0]) != null){
} else if (arguments.length == 2 && b.getLines().get(arguments[0]) != null){
for (String time : Arrays.asList("5", "10", "30", "60", "120")) {
if (time.startsWith(arguments[1])) suggestions.add(time);
}
@@ -17,7 +17,9 @@ import me.neznamy.tab.shared.features.scoreboard.ScoreboardManager;
*/
public class AnnounceScoreboardCommand extends SubCommand{
/**
* Constructs new instance
*/
public AnnounceScoreboardCommand() {
super("scoreboard", "tab.announce.scoreboard");
}
@@ -38,33 +40,43 @@ public class AnnounceScoreboardCommand extends SubCommand{
try {
duration = Integer.parseInt(args[1]);
} catch (Exception e) {
sender.sendMessage(args[1] + " is not a number!", false);
sendMessage(sender, args[1] + " is not a number!");
return;
}
Scoreboard sb = feature.getScoreboards().get(scoreboard);
if (sb == null) {
sender.sendMessage("Scoreboard not found", false);
sendMessage(sender, "Scoreboard not found");
return;
}
announce(feature, sb, duration);
}
/**
* Performs accounce
* @param feature - scoreboard feature
* @param sb - scoreboard to announce
* @param duration - duration in seconds
*/
private void announce(ScoreboardManager feature, Scoreboard sb, int duration) {
new Thread(() -> {
try {
feature.announcement = sb;
Map<TabPlayer, Scoreboard> previous = new HashMap<TabPlayer, Scoreboard>();
feature.setAnnouncement(sb);
Map<TabPlayer, Scoreboard> previous = new HashMap<>();
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (!all.isScoreboardVisible()) continue;
previous.put(all, all.getActiveScoreboard());
if (all.getActiveScoreboard() != null) all.getActiveScoreboard().unregister(all);
sb.register(all);
}
Thread.sleep(duration*1000);
Thread.sleep(duration*1000L);
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (!all.hasBossbarVisible()) continue;
sb.unregister(all);
if (previous.get(all) != null) previous.get(all).register(all);
}
feature.announcement = null;
} catch (Exception e) {
feature.setAnnouncement(null);
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
}
}).start();
}
@@ -72,8 +84,8 @@ public class AnnounceScoreboardCommand extends SubCommand{
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
ScoreboardManager s = (ScoreboardManager) TAB.getInstance().getFeatureManager().getFeature("scoreboard");
if (s == null) return new ArrayList<String>();
List<String> suggestions = new ArrayList<String>();
if (s == null) return new ArrayList<>();
List<String> suggestions = new ArrayList<>();
if (arguments.length == 1) {
for (String bar : s.getScoreboards().keySet()) {
if (bar.toLowerCase().startsWith(arguments[0].toLowerCase())) suggestions.add(bar);
@@ -15,7 +15,9 @@ import me.neznamy.tab.shared.features.bossbar.BossBarLine;
*/
public class SendBarCommand extends SubCommand{
/**
* Constructs new instance
*/
public SendBarCommand() {
super("bar", "tab.send.bar");
}
@@ -44,9 +46,9 @@ public class SendBarCommand extends SubCommand{
sendMessage(sender, args[1] + " is not a number!");
return;
}
BossBarLine bar = feature.lines.get(barname);
BossBarLine bar = feature.getLines().get(barname);
if (bar == null) {
sender.sendMessage("Bar not found", false);
sendMessage(sender, "No bar found with name \"" + bar + "\"");
return;
}
new Thread(() -> {
@@ -55,13 +57,13 @@ public class SendBarCommand extends SubCommand{
bar.create(target);
target.getActiveBossBars().add(bar);
}
Thread.sleep(duration*1000);
Thread.sleep(duration*1000L);
if (target.hasBossbarVisible()) {
bar.remove(target);
target.getActiveBossBars().remove(bar);
}
} catch (Exception e) {
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
}
}).start();
}
@@ -69,15 +71,15 @@ public class SendBarCommand extends SubCommand{
@Override
public List<String> complete(TabPlayer sender, String[] arguments) {
BossBar b = (BossBar) TAB.getInstance().getFeatureManager().getFeature("bossbar");
if (b == null) return new ArrayList<String>();
List<String> suggestions = new ArrayList<String>();
if (b == null) return new ArrayList<>();
List<String> suggestions = new ArrayList<>();
if (arguments.length == 1) {
return getPlayers(arguments[0]);
} else if (arguments.length == 2) {
for (String bar : b.lines.keySet()) {
for (String bar : b.getLines().keySet()) {
if (bar.toLowerCase().startsWith(arguments[1].toLowerCase())) suggestions.add(bar);
}
} else if (arguments.length == 3 && b.lines.get(arguments[1]) != null){
} else if (arguments.length == 3 && b.getLines().get(arguments[1]) != null){
for (String time : Arrays.asList("5", "10", "30", "60", "120")) {
if (time.startsWith(arguments[2])) suggestions.add(time);
}
@@ -0,0 +1,288 @@
package me.neznamy.tab.shared.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.yaml.snakeyaml.error.YAMLException;
import me.neznamy.tab.shared.TAB;
/**
* Core of loading configuration files
*/
public class Configs {
private TAB tab;
//config.yml file
private ConfigurationFile config;
private List<String> removeStrings;
private boolean bukkitPermissions;
//hidden config options
private boolean rgbSupport;
private boolean unregisterBeforeRegister;
private String multiWorldSeparator;
private String essentialsNickPrefix;
private boolean armorStandsAlwaysVisible; //paid private addition
private boolean removeGhostPlayers;
private boolean layout;
private boolean pipelineInjection;
//animations.yml file
private ConfigurationFile animation;
//bossbar.yml file
private ConfigurationFile bossbar;
//translation.yml file
private ConfigurationFile translation;
//default reload message in case plugin did not load translation file due to an error
private String reloadFailed = "&4Failed to reload, file %file% has broken syntax. Check console for more info.";
//premiumconfig.yml
private ConfigurationFile premiumconfig;
//playerdata.yml, used for bossbar & scoreboard toggle saving
private ConfigurationFile playerdata;
/**
* Constructs new instance with given parameter
* @param tab - tab instance
*/
public Configs(TAB tab) {
this.tab = tab;
}
/**
* Loads all configuration files and converts files to latest version
* @throws IOException
* @throws YAMLException
*/
public void loadFiles() throws YAMLException, IOException {
ClassLoader loader = Configs.class.getClassLoader();
loadConfig();
animation = new YamlConfigurationFile(loader.getResourceAsStream("animations.yml"), new File(tab.getPlatform().getDataFolder(), "animations.yml"));
Map<String, Object> values = getAnimationFile().getValues();
if (values.size() == 1 && values.containsKey("animations")) {
getAnimationFile().setValues(getAnimationFile().getConfigurationSection("animations"));
getAnimationFile().save();
TAB.getInstance().print('2', "Converted animations.yml to new format.");
}
bossbar = new YamlConfigurationFile(loader.getResourceAsStream("bossbar.yml"), new File(tab.getPlatform().getDataFolder(), "bossbar.yml"));
translation = new YamlConfigurationFile(loader.getResourceAsStream("translation.yml"), new File(tab.getPlatform().getDataFolder(), "translation.yml"));
reloadFailed = getTranslation().getString("reload-failed", "&4Failed to reload, file %file% has broken syntax. Check console for more info.");
if (tab.isPremium()) premiumconfig = new YamlConfigurationFile(loader.getResourceAsStream("premiumconfig.yml"), new File(tab.getPlatform().getDataFolder(), "premiumconfig.yml"));
}
/**
* Loads config.yml and some of it's values
* @throws IOException
* @throws YAMLException
*/
@SuppressWarnings("unchecked")
public void loadConfig() throws YAMLException, IOException {
config = new YamlConfigurationFile(Configs.class.getClassLoader().getResourceAsStream(tab.getPlatform().getConfigName()), new File(tab.getPlatform().getDataFolder(), "config.yml"), Arrays.asList("# Detailed explanation of all options available at https://github.com/NEZNAMY/TAB/wiki/config.yml", ""));
removeStrings = new ArrayList<>();
for (String s : getConfig().getStringList("placeholders.remove-strings", Arrays.asList("[] ", "< > "))) {
getRemoveStrings().add(s.replace('&', '\u00a7'));
}
tab.setDebugMode(getConfig().getBoolean("debug", false));
rgbSupport = (boolean) getSecretOption("rgb-support", true);
unregisterBeforeRegister = (boolean) getSecretOption("unregister-before-register", true);
multiWorldSeparator = (String) getSecretOption("multi-world-separator", "-");
essentialsNickPrefix = (String) getSecretOption("essentials-nickname-prefix", "");
armorStandsAlwaysVisible = (boolean) getSecretOption("unlimited-nametag-prefix-suffix-mode.always-visible", false);
removeGhostPlayers = (boolean) getSecretOption("remove-ghost-players", false);
layout = (boolean) getSecretOption("layout", false);
pipelineInjection = (boolean) getSecretOption("pipeline-injection", true);
if (tab.getPlatform().getSeparatorType().equals("server")) {
bukkitPermissions = getConfig().getBoolean("use-bukkit-permissions-manager", false);
}
//checking for unnecessary copypaste in config
Set<Object> groups = getConfig().getConfigurationSection("Groups").keySet();
if (groups.size() < 2) return;
Map<Object, Object> sharedProperties = new HashMap<>(getConfig().getConfigurationSection("Groups." + groups.toArray()[0])); //cloning to not delete from original one
for (Object groupSettings : getConfig().getConfigurationSection("Groups").values()) {
if (!(groupSettings instanceof Map)) continue;
Map<String, Object> group = (Map<String, Object>) groupSettings;
for (Entry<Object, Object> sharedProperty : new HashSet<>(sharedProperties.entrySet())) {
String property = sharedProperty.getKey().toString();
if (!group.containsKey(property) || !String.valueOf(group.get(property)).equals(sharedProperty.getValue())) {
sharedProperties.remove(property);
}
}
}
for (Object property : sharedProperties.keySet()) {
tab.print('9', "Hint: All of your groups have the same value of \"&d" + property + "&9\" set. Delete it from all groups and add it only to _OTHER_ for cleaner and smaller config.");
}
}
/**
* Returns value of hidden config option with specified path if it exists, defaultValue otherwise
* @param path - path to value
* @param defaultValue - value to return if option is not present in file
* @return value with specified path or default value if not present
*/
private Object getSecretOption(String path, Object defaultValue) {
if (getConfig() == null) return defaultValue;
Object value = getConfig().getObject(path);
return value == null ? defaultValue : value;
}
/**
* Returns player data with specified key
* @param key - data key
* @return list of players logged in this data key
*/
public List<String> getPlayerData(String key) {
if (playerdata == null) {
File file = new File(tab.getPlatform().getDataFolder(), "playerdata.yml");
try {
if (file.exists() || file.createNewFile()) {
playerdata = new YamlConfigurationFile(null, file);
return playerdata.getStringList(key, new ArrayList<>());
}
} catch (Exception e) {
tab.getErrorManager().criticalError("Failed to load playerdata.yml", e);
}
} else {
return playerdata.getStringList(key, new ArrayList<>());
}
return new ArrayList<>();
}
/**
* Returns name of world group in specified set that may consist of multiple
* worlds separated with "-" or something else defined in config
* @param world - name of world to find group of
* @return name of world group
*/
public String getWorldGroupOf(Set<?> groups, String world) {
if (groups.isEmpty()) return world;
for (Object worldGroup : groups) {
for (String definedWorld : worldGroup.toString().split(multiWorldSeparator)) {
if (definedWorld.endsWith("*")) {
if (world.toLowerCase().startsWith(definedWorld.substring(0, definedWorld.length()-1).toLowerCase())) return worldGroup.toString();
} else {
if (world.equalsIgnoreCase(definedWorld)) return worldGroup.toString();
}
}
}
return world;
}
/**
* Reads all lines in file and returns them as List
* @return list of lines in file
*/
public static List<String> readAllLines(File file) {
List<String> list = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))){
String line;
while ((line = br.readLine()) != null) {
list.add(line);
}
} catch (Exception ex) {
TAB.getInstance().getErrorManager().criticalError("Failed to read file " + file, ex);
}
return list;
}
/**
* Writes defined line of text to file
* @param f - file to write to
* @param line - line to write
*/
public static void write(File file, List<String> lines){
try (BufferedWriter buf = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8))){
for (String line : lines) {
buf.write(line + System.getProperty("line.separator"));
}
} catch (Exception ex) {
TAB.getInstance().getErrorManager().criticalError("Failed to write to file " + file.getName(), ex);
}
}
public List<String> getRemoveStrings() {
return removeStrings;
}
public boolean isUnregisterBeforeRegister() {
return unregisterBeforeRegister;
}
public ConfigurationFile getTranslation() {
return translation;
}
public ConfigurationFile getConfig() {
return config;
}
public boolean isRemoveGhostPlayers() {
return removeGhostPlayers;
}
public ConfigurationFile getPremiumConfig() {
return premiumconfig;
}
public ConfigurationFile getBossbarConfig() {
return bossbar;
}
public boolean isLayout() {
return layout;
}
public ConfigurationFile getAnimationFile() {
return animation;
}
public boolean isRgbSupport() {
return rgbSupport;
}
public boolean isBukkitPermissions() {
return bukkitPermissions;
}
public boolean isPipelineInjection() {
return pipelineInjection;
}
public boolean isArmorStandsAlwaysVisible() {
return armorStandsAlwaysVisible;
}
public String getEssentialsNickPrefix() {
return essentialsNickPrefix;
}
public String getReloadFailedMessage() {
return reloadFailed;
}
public ConfigurationFile getPlayerDataFile() {
return playerdata;
}
}
@@ -1,15 +1,8 @@
package me.neznamy.tab.shared.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
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.HashMap;
@@ -36,12 +29,20 @@ public abstract class ConfigurationFile {
//the file
protected File file;
public ConfigurationFile(InputStream source, File destination, List<String> header) throws IOException {
/**
* Constructs new instance and copies file from source to destination if it does not exist
* @param source - source to copy from if file does not exist
* @param destination - destination to load
* @param header - comments on top of the file
* @throws IllegalStateException - if file does not exist and source is null
* @throws IOException - if I/O file operation fails
*/
protected ConfigurationFile(InputStream source, File destination, List<String> header) throws IOException {
this.header = header;
this.file = destination;
file.getParentFile().mkdirs();
if (file.getParentFile() != null) file.getParentFile().mkdirs();
if (!file.exists()) {
if (source == null) throw new IllegalStateException("file does not exist and source is null");
if (source == null) throw new IllegalStateException("File does not exist and source is null");
Files.copy(source, file.toPath());
}
}
@@ -67,6 +68,10 @@ public abstract class ConfigurationFile {
return values;
}
/**
* Replaces values with provided map
* @param values - values to replace map with
*/
public void setValues(Map<String, Object> values){
this.values = values;
}
@@ -91,7 +96,7 @@ public abstract class ConfigurationFile {
return defaultValue;
}
return value;
} catch (Throwable e) {
} catch (Exception e) {
if (defaultValue != null) set(path, defaultValue);
return defaultValue;
}
@@ -113,8 +118,8 @@ public abstract class ConfigurationFile {
* @return value from map
*/
private Object getIgnoreCase(Map<Object, Object> map, String key) {
for (Object mapkey : map.keySet()) {
if (mapkey.toString().equalsIgnoreCase(key)) return map.get(mapkey);
for (Entry<Object, Object> entry : map.entrySet()) {
if (entry.getKey().toString().equalsIgnoreCase(key)) return entry.getValue();
}
return map.get(key);
}
@@ -138,7 +143,7 @@ public abstract class ConfigurationFile {
public String getString(String path, String defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
return value+"";
return String.valueOf(value);
}
/**
@@ -162,11 +167,11 @@ public abstract class ConfigurationFile {
if (value == null) return defaultValue;
if (!(value instanceof List)) {
dataMismatch(path, ArrayList.class, value.getClass());
return new ArrayList<String>();
return new ArrayList<>();
}
List<String> fixedList = new ArrayList<String>();
List<String> fixedList = new ArrayList<>();
for (Object key : (List<Object>)value) {
fixedList.add(key+"");
fixedList.add(key.toString());
}
return fixedList;
}
@@ -200,7 +205,7 @@ public abstract class ConfigurationFile {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try{
return Integer.parseInt(value+"");
return Integer.parseInt(value.toString());
} catch (Exception e) {
dataMismatch(path, Integer.class, value.getClass());
return defaultValue;
@@ -213,7 +218,14 @@ public abstract class ConfigurationFile {
* @return value from file or null if not present
*/
public Boolean getBoolean(String path) {
return getBoolean(path, null);
Object value = getObject(path, null);
if (value == null) return null;
try {
return Boolean.parseBoolean(value.toString());
} catch (Exception e) {
dataMismatch(path, Boolean.class, value.getClass());
return null;
}
}
/**
@@ -223,11 +235,11 @@ public abstract class ConfigurationFile {
* @param defaultValue - value to be inserted and returned if option is not present
* @return value from config
*/
public Boolean getBoolean(String path, Boolean defaultValue) {
public boolean getBoolean(String path, boolean defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try{
return Boolean.parseBoolean(value+"");
try {
return Boolean.parseBoolean(value.toString());
} catch (Exception e) {
dataMismatch(path, Boolean.class, value.getClass());
return defaultValue;
@@ -245,7 +257,7 @@ public abstract class ConfigurationFile {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try{
return Double.parseDouble(value+"");
return Double.parseDouble(value.toString());
} catch (Exception e) {
dataMismatch(path, Double.class, value.getClass());
return defaultValue;
@@ -300,7 +312,7 @@ public abstract class ConfigurationFile {
if (path.contains(".")) {
String keyWord = getRealKey(map, path.split("\\.")[0]);
Object submap = map.get(keyWord);
if (submap == null || !(submap instanceof Map)) {
if (!(submap instanceof Map)) {
submap = new HashMap<String, Object>();
}
map.put(keyWord.replace("@#@", "."), set((Map<String, Object>) submap, path.substring(keyWord.length()+1, path.length()), value));
@@ -333,7 +345,7 @@ public abstract class ConfigurationFile {
*/
public boolean hasHeader() {
if (header == null) return true;
for (String line : readAllLines(file)) {
for (String line : Configs.readAllLines(file)) {
if (line.contains("#")) return true;
}
return false;
@@ -345,39 +357,17 @@ public abstract class ConfigurationFile {
public void fixHeader() {
if (header == null) return;
try {
List<String> content = new ArrayList<String>(header);
content.addAll(readAllLines(file));
file.delete();
file.createNewFile();
BufferedWriter buf = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8));
for (String line : content) {
buf.write(line + System.getProperty("line.separator"));
List<String> content = new ArrayList<>(header);
content.addAll(Configs.readAllLines(file));
Files.delete(file.toPath());
if (file.createNewFile()) {
Configs.write(file, content);
}
buf.close();
} catch (Exception ex) {
TAB.getInstance().getErrorManager().criticalError("Failed to modify file " + file, ex);
}
}
/**
* Reads all lines in file and returns them as List
* @return list of lines in file
*/
private List<String> readAllLines(File file) {
List<String> list = new ArrayList<String>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
list.add(line);
}
br.close();
} catch (Exception ex) {
TAB.getInstance().getErrorManager().criticalError("Failed to read file " + file, ex);
}
return list;
}
/**
* Finds all used placeholder identifiers in the file including nested placeholders in given keys
* @param simpleKeys - keys to check
@@ -394,22 +384,22 @@ public abstract class ConfigurationFile {
* @return Set of used identifiers
*/
private Set<String> getUsedPlaceholders(Map<String, Object> map, String... simpleKeys){
Set<String> values = new HashSet<String>();
Set<String> placeholders = new HashSet<>();
for (Entry<String, Object> entry : map.entrySet()) {
for (String simpleKey : simpleKeys) {
if (String.valueOf(entry.getKey()).equals(simpleKey)) values.addAll(TAB.getInstance().getPlaceholderManager().detectAll(String.valueOf(entry.getValue())));
if (String.valueOf(entry.getKey()).equals(simpleKey)) placeholders.addAll(TAB.getInstance().getPlaceholderManager().detectAll(String.valueOf(entry.getValue())));
}
if (entry.getValue() instanceof Map) {
values.addAll(getUsedPlaceholders((Map<String, Object>)entry.getValue(), simpleKeys));
placeholders.addAll(getUsedPlaceholders((Map<String, Object>)entry.getValue(), simpleKeys));
}
if (entry.getValue() instanceof List) {
for (Object obj : (List<Object>)entry.getValue()) {
for (String simpleKey : simpleKeys) {
if (String.valueOf(obj).equals(simpleKey)) values.addAll(TAB.getInstance().getPlaceholderManager().detectAll(String.valueOf(entry.getValue())));
if (String.valueOf(obj).equals(simpleKey)) placeholders.addAll(TAB.getInstance().getPlaceholderManager().detectAll(String.valueOf(entry.getValue())));
}
}
}
}
return values;
return placeholders;
}
}
@@ -29,7 +29,7 @@ public class YamlConfigurationFile extends ConfigurationFile {
private Yaml yaml;
/**
* Constructs new instance and tried to load configuration file
* Constructs new instance and tries to load configuration file
* @param source - source to copy file from if it does not exist
* @param destination - destination of the file to be copied file to if needed and loaded
* @throws IllegalStateException - when file does not exist and source is null
@@ -41,7 +41,7 @@ public class YamlConfigurationFile extends ConfigurationFile {
}
/**
* Constructs new instance and tried to load configuration file
* Constructs new instance and tries to load configuration file
* @param source - source to copy file from if it does not exist
* @param destination - destination of the file to be copied file to if needed and loaded
* @param header - comments at the beginning of the file to be pasted when file changes or null if you do not want any
@@ -63,10 +63,10 @@ public class YamlConfigurationFile extends ConfigurationFile {
input.close();
if (!hasHeader()) fixHeader();
} catch (YAMLException e) {
input.close();
if (input != null) input.close();
TAB tab = TAB.getInstance();
tab.getErrorManager().startupWarn("File " + destination + " has broken formatting.");
tab.brokenFile = file.getPath();
tab.getErrorManager().startupWarn("File " + destination + " has broken syntax.");
tab.setBrokenFile(file.getPath());
tab.getPlatform().sendConsoleMessage("&6[TAB] Error message from yaml parser: " + e.getMessage(), true);
List<String> suggestions = YamlAssist.getSuggestions(file);
if (!suggestions.isEmpty()) {
@@ -86,8 +86,8 @@ public class YamlConfigurationFile extends ConfigurationFile {
yaml.dump(values, writer);
writer.close();
if (!hasHeader()) fixHeader();
} catch (Throwable e) {
TAB.getInstance().getErrorManager().criticalError("Failed to save yaml file " + file.getPath(), e);
} catch (IOException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to save yaml file " + file.getPath() + " with content " + values.toString(), e);
}
}
}
@@ -0,0 +1,405 @@
package me.neznamy.tab.shared.cpu;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import me.neznamy.tab.shared.ErrorManager;
/**
* A class which measures CPU usage of all tasks inserted into it and shows usage
*/
public class CPUManager {
//data reset interval in milliseconds
private static final int BUFFER_SIZE_MILLIS = 10000;
//nanoseconds worked in the current 10 seconds
private Map<Object, Map<UsageType, AtomicLong>> featureUsageCurrent = new ConcurrentHashMap<>();
private Map<String, AtomicLong> placeholderUsageCurrent = new ConcurrentHashMap<>();
private Map<String, AtomicLong> bridgePlaceholderUsageCurrent = new ConcurrentHashMap<>();
private Map<String, AtomicLong> methodUsageCurrent = new ConcurrentHashMap<>();
//packets sent in the current 10 seconds
private Map<Object, AtomicInteger> packetsCurrent = new ConcurrentHashMap<>();
//nanoseconds worked in the previous 10 seconds
private Map<Object, Map<UsageType, AtomicLong>> featureUsagePrevious = new HashMap<>();
private Map<String, AtomicLong> placeholderUsagePrevious = new HashMap<>();
private Map<String, AtomicLong> bridgePlaceholderUsagePrevious = new HashMap<>();
private Map<String, AtomicLong> methodUsagePrevious = new HashMap<>();
//packets sent in the previous 10 seconds
private Map<Object, AtomicInteger> packetsPrevious = new ConcurrentHashMap<>();
//thread pool
private ThreadPoolExecutor exe = (ThreadPoolExecutor) Executors.newCachedThreadPool();
//error manager
private ErrorManager errorManager;
/**
* Constructs new instance and starts repeating task that resets values every 10 seconds
* @param errorManager - error manager
*/
public CPUManager(ErrorManager errorManager) {
this.errorManager = errorManager;
exe.submit(() -> {
try {
while (true) {
Thread.sleep(BUFFER_SIZE_MILLIS);
featureUsagePrevious = featureUsageCurrent;
placeholderUsagePrevious = placeholderUsageCurrent;
bridgePlaceholderUsagePrevious = bridgePlaceholderUsageCurrent;
methodUsagePrevious = methodUsageCurrent;
packetsPrevious = packetsCurrent;
featureUsageCurrent = new ConcurrentHashMap<>();
placeholderUsageCurrent = new ConcurrentHashMap<>();
bridgePlaceholderUsageCurrent = new ConcurrentHashMap<>();
methodUsageCurrent = new ConcurrentHashMap<>();
packetsCurrent = new ConcurrentHashMap<>();
}
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
}
});
}
/**
* Returns amount of active and total threads
* @return active and total threads from this thread pool
*/
public String getThreadCount() {
return exe.getActiveCount() + "/" + exe.getPoolSize();
}
/**
* Cancels all tasks, new instance is set to avoid errors when starting tasks on shutdown (such as packet readers)
*/
public void cancelAllTasks() {
//preventing errors when tasks are inserted while shutting down
ExecutorService old = exe;
exe = (ThreadPoolExecutor) Executors.newCachedThreadPool();
old.shutdownNow();
}
/**
* 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
*/
public void runMeasuredTask(String errorDescription, Object feature, UsageType type, Runnable task) {
submit(errorDescription, () -> {
long time = System.nanoTime();
task.run();
addTime(feature, type, System.nanoTime()-time);
});
}
/**
* Runs task in a new thread
* @param errorDescription - description to use if this task throws an error
* @param task - the task
*/
public void runTask(String errorDescription, Runnable task) {
submit(errorDescription, 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
*/
public void startRepeatingMeasuredTask(int intervalMilliseconds, String errorDescription, Object feature, UsageType type, Runnable task) {
if (intervalMilliseconds <= 0) return;
submit(errorDescription, () -> {
long lastLoop = System.currentTimeMillis()-intervalMilliseconds;
while (true) {
try {
long sleep = intervalMilliseconds - (System.currentTimeMillis()-lastLoop);
if (sleep < 0) {
sleep = 0;
}
Thread.sleep(sleep);
lastLoop = System.currentTimeMillis();
long time = System.nanoTime();
task.run();
addTime(feature, type, System.nanoTime()-time);
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
break;
}
}
});
}
/**
* 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
*/
public void runTaskLater(int delayMilliseconds, String errorDescription, Object feature, UsageType type, Runnable task) {
submit(errorDescription, () -> {
try {
Thread.sleep(delayMilliseconds);
long time = System.nanoTime();
task.run();
addTime(feature, type, System.nanoTime()-time);
} catch (InterruptedException pluginDisabled) {
Thread.currentThread().interrupt();
}
});
}
private void submit(String errorDescription, Runnable task) {
exe.submit(() -> {
try {
task.run();
} catch (Exception e) {
errorManager.printError("An error occurred when " + errorDescription, e);
}
});
}
/**
* Returns cpu usage map of placeholders from previous 10 seconds
* @return cpu usage map of placeholders
*/
public Map<String, Float> getPlaceholderUsage(){
return getUsage(placeholderUsagePrevious);
}
/**
* Returns cpu usage map of placeholders on bukkit side from previous 10 seconds. This is only used
* when TAB is on bungeecord with PAPI placeholder support
* @return cpu usage map of placeholders
*/
public Map<String, Float> getBridgeUsage(){
return getUsage(bridgePlaceholderUsagePrevious);
}
/**
* Returns cpu usage map of methods previous 10 seconds
* @return cpu usage map of methods
*/
public Map<String, Float> getMethodUsage(){
return getUsage(methodUsagePrevious);
}
/**
* Returns map of sent packets per feature in previous 10 seconds
* @return map of sent packets per feature
*/
public Map<Object, AtomicInteger> getSentPackets(){
return sortByValue1(packetsPrevious);
}
/**
* Converts nano map to percent and sorts it from highest to lowest usage
* @param map - map to converted
* @return converted and sorted map
*/
private Map<String, Float> getUsage(Map<String, AtomicLong> map){
Map<String, Long> nanoMap = new HashMap<>();
String key;
for (Entry<String, AtomicLong> nanos : map.entrySet()) {
key = nanos.getKey();
if (!nanoMap.containsKey(key)) nanoMap.put(key, 0L);
nanoMap.put(key, nanoMap.get(key)+nanos.getValue().get());
}
Map<String, Float> percentMap = new HashMap<>();
for (Entry<String, Long> entry : nanoMap.entrySet()) {
percentMap.put(entry.getKey(), nanosToPercent(entry.getValue()));
}
return sortByValue(percentMap);
}
/**
* Returns map of CPU usage per feature per type in the previous 10 seconds
* @return map of CPU usage per feature per type
*/
public Map<Object, Map<UsageType, Float>> getFeatureUsage(){
Map<Object, EnumMap<UsageType, Long>> total = new HashMap<>();
for (Entry<Object, Map<UsageType, AtomicLong>> nanos : featureUsagePrevious.entrySet()) {
Object key = nanos.getKey();
if (!total.containsKey(key)) {
total.put(key, new EnumMap<>(UsageType.class));
}
Map<UsageType, Long> usage = total.get(key);
for (Entry<UsageType, AtomicLong> entry : nanos.getValue().entrySet()) {
if (!usage.containsKey(entry.getKey())) {
usage.put(entry.getKey(), 0L);
}
usage.put(entry.getKey(), usage.get(entry.getKey()) + entry.getValue().get());
}
}
Map<Object, Map<UsageType, Float>> sorted = new LinkedHashMap<>();
for (Object key : sortKeys(total)) {
Map<UsageType, Long> local = sortByValue(total.get(key));
Map<UsageType, Float> percent = new LinkedHashMap<>();
for (Entry<UsageType, Long> entry : local.entrySet()) {
percent.put(entry.getKey(), nanosToPercent(entry.getValue()));
}
sorted.put(key, percent);
}
return sorted;
}
/**
* Converts nanoseconds to percentual usage
* @param nanos - nanoseconds worked
* @return usage in % (0-100)
*/
private float nanosToPercent(long nanos) {
float percent = (float) nanos / BUFFER_SIZE_MILLIS / 1000000; //relative usage (0-1)
percent *= 100; //relative into %
return percent;
}
/**
* Sorts map by value from higest to lowest
* @param <K> - map key
* @param <V> - map value
* @param map - map to sort
* @return sorted map
*/
private <K, V extends Comparable<V>> Map<K, V> sortByValue(Map<K, V> map) {
Comparator<K> valueComparator = (k1, k2) -> {
int compare = map.get(k2).compareTo(map.get(k1));
if (compare == 0) return 1;
else return compare;
};
Map<K, V> sortedByValues = new TreeMap<>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
/**
* Sorts map by value from higest to lowest
* @param <K> - map key
* @param <V> - map value
* @param map - map to sort
* @return sorted map
*/
private <K> Map<K, AtomicInteger> sortByValue1(Map<K, AtomicInteger> map) {
Comparator<K> valueComparator = (k1, k2) -> {
int compare = ((Comparable<Integer>)map.get(k2).get()).compareTo(map.get(k1).get());
if (compare == 0) return 1;
else return compare;
};
Map<K, AtomicInteger> sortedByValues = new TreeMap<>(valueComparator);
sortedByValues.putAll(map);
return sortedByValues;
}
/**
* Sorts keys by map nested values from highest to lowest and returns sorted list of keys
* @param <K> - map key
* @param <L> - nested map key
* @param map - map to sort
* @return list of keys sorted from highest usage to lowest
*/
private <K> List<K> sortKeys(Map<K, EnumMap<UsageType, Long>> map){
Map<K, Long> simplified = new LinkedHashMap<>();
for (Entry<K, EnumMap<UsageType, Long>> entry : map.entrySet()) {
simplified.put(entry.getKey(), sumValues(entry.getValue()));
}
simplified = sortByValue(simplified);
List<K> sortedKeys = new ArrayList<>();
for (K key : simplified.keySet()) {
sortedKeys.add(key);
}
return sortedKeys;
}
/**
* Returns summary of all values in a map
* @param map - map to sum up values of
* @return summary of values
*/
private long sumValues(Map<?, Long> map) {
long total = 0;
for (Long value : map.values()) {
total += value;
}
return total;
}
/**
* Adds cpu time to specified feature and usage type
* @param feature - feature to add time to
* @param type - usage to add time to of the feature
* @param nanoseconds - time to add
*/
public void addTime(Object feature, UsageType type, long nanoseconds) {
featureUsageCurrent.computeIfAbsent(feature, f -> new ConcurrentHashMap<>()).computeIfAbsent(type, t -> new AtomicLong()).addAndGet(nanoseconds);
}
/**
* Adds placeholder time to defined placeholder
* @param placeholder - placeholder to add time to
* @param nanoseconds - time to add
*/
public void addPlaceholderTime(String placeholder, long nanoseconds) {
addTime(placeholderUsageCurrent, placeholder, nanoseconds);
}
/**
* Adds placeholder time to defined placeholder
* @param placeholder - placeholder to add time to
* @param nanoseconds - time to add
*/
public void addBridgePlaceholderTime(String placeholder, long nanoseconds) {
addTime(bridgePlaceholderUsageCurrent, placeholder, nanoseconds);
}
/**
* Adds method time to defined method
* @param method - method to add time to
* @param nanoseconds - time to add
*/
public void addMethodTime(String method, long nanoseconds) {
addTime(methodUsageCurrent, method, nanoseconds);
}
/**
* Adds used time to specified key into specified map
* @param map - map to add usage to
* @param key - usage key
* @param time - nanoseconds the task took
*/
private void addTime(Map<String, AtomicLong> map, String key, long time) {
map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(time);
}
/**
* Increments packets sent by 1 of specified feature
* @param feature - feature to increment packet counter of
*/
public void packetSent(Object feature) {
packetsCurrent.computeIfAbsent(feature, f -> new AtomicInteger()).incrementAndGet();
}
}
@@ -24,22 +24,26 @@ public enum TabFeature {
PLACEHOLDER_REFRESHING("Refreshing placeholders"),
SORTING("Sorting"),
PIPELINE_INJECTION("Pipeline injection"),
COMMAND_PROCESSING("Command processing"),
PACKET_DESERIALIZING("Packet deserializing"),
PACKET_SERIALIZING("Packet serializing"),
TABLIST_LAYOUT("Tablist layout"),
PLUGIN_MESSAGE_HANDLING("Plugin message handling"),
PING_SPOOF("Ping spoof"),
OTHER("Other");
//user-friendly name to be used in /tab cpu
private String friendlyName;
private String displayName;
private TabFeature(String friendlyName){
this.friendlyName = friendlyName;
/**
* Constructs new instance with given display name
* @param displayName - name to display in /tab cpu
*/
private TabFeature(String displayName){
this.displayName = displayName;
}
@Override
public String toString() {
return friendlyName;
return displayName;
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.cpu;
/**
* Enum containing all reasons features require CPU time
* This includes events, packets, repeating tasks and other
*/
public enum UsageType {
@@ -11,27 +12,27 @@ public enum UsageType {
WORLD_SWITCH_EVENT("PlayerChangedWorldEvent"),
PLAYER_TOGGLE_SNEAK_EVENT("PlayerToggleSneakEvent"),
PLAYER_MOVE_EVENT("PlayerMoveEvent"),
PLAYER_TELEPORT_EVENT("PlayerTeleportEvent"),
PLAYER_RESPAWN_EVENT("PlayerRespawnEvent"),
COMMAND_PREPROCESS("PlayerCommandPreprocessEvent"),
PLUGIN_MESSAGE_EVENT("PluginMessageEvent"),
PLAYER_CHAT_EVENT("AsyncPlayerChatEvent"),
PLAYER_DEATH_EVENT("PlayerDeathEvent"),
//packets
ANTI_OVERRIDE("Anti override"),
PACKET_READING("Packet reading"),
PACKET_MOUNT("PacketPlayOutMount"),
PACKET_ENTITY_DESTROY("PacketPlayOutEntityDestroy"),
PACKET_READING_IN("Packet reading (in)"),
PACKET_READING_OUT("Packet reading (out)"),
PACKET_ENTITY_MOVE("PacketPlayOutEntity"),
PACKET_NAMED_ENTITY_SPAWN("PacketPlayOutNamedEntitySpawn"),
PACKET_PLAYER_INFO("PacketPlayOutPlayerInfo"),
PACKET_LOGIN("PacketLogin"),
PACKET_DISPLAY_OBJECTIVE("PacketPlayOutScoreboardDisplayObjective"),
PACKET_OBJECTIVE("PacketPlayOutScoreboardObjective"),
PACKET_HEADER_FOOTER("PacketPlayOutPlayerListHeaderFooter"),
PACKET_NAMED_ENTITY_SPAWN("PacketPlayOutNamedEntitySpawn"),
PACKET_ENTITY_DESTROY("PacketPlayOutEntityDestroy"),
//nametags
REFRESHING_COLLISION("Refreshing collision rule"),
REFRESHING_NAMETAG_VISIBILITY("Refreshing nametag visibility"),
REFRESHING_NAMETAG_VISIBILITY_AND_COLLISION("Refreshing nametag visibility and collision"),
REFRESHING_TEAM_NAME("Refreshing team name"),
//bossbar
@@ -40,7 +41,8 @@ public enum UsageType {
//other
REFRESHING("Refreshing"),
REPEATING_TASK("Repeating task"),
v1_8_0_BUG_COMPENSATION("Compensating for 1.8.0 bugs");
TICKING_VEHICLES("Ticking vehicles"),
V1_8_0_BUG_COMPENSATION("Compensating for 1.8.0 bugs");
//user-friendly name to be used in /tab cpu
private String friendlyName;
@@ -0,0 +1,189 @@
package me.neznamy.tab.shared.features;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.WorldChangeListener;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
/**
* Additional code for Playerlist class to secure alignment
*/
public class AlignedSuffix implements QuitEventListener, WorldChangeListener {
private TAB tab;
private int maxWidth;
private TabPlayer maxPlayer;
private Map<Character, Byte> widths = new HashMap<>();
private Playerlist playerlist;
public AlignedSuffix(Playerlist playerlist, TAB tab) {
this.tab = tab;
this.playerlist = playerlist;
loadWidthsFromFile();
Map<Integer, Integer> widthOverrides = tab.getConfiguration().getPremiumConfig().getConfigurationSection("character-width-overrides");
for (Entry<Integer, Integer> entry : widthOverrides.entrySet()) {
widths.put((char)(int)entry.getKey(), (byte)(int)entry.getValue());
}
tab.debug(String.format("Loaded AlignedSuffix feature with parameters widthOverrides=%s", widthOverrides));
}
/**
* Loads all predefined widths from included widths.txt file
*/
private void loadWidthsFromFile() {
try {
InputStream input = getClass().getClassLoader().getResourceAsStream("widths.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(input));
String line;
int characterId = 1;
while ((line = br.readLine()) != null) {
widths.put((char)characterId++, (byte)Float.parseFloat(line));
}
br.close();
} catch (Exception ex) {
tab.getErrorManager().criticalError("Failed to read character widths from file", ex);
}
}
public String formatNameAndUpdateLeader(TabPlayer player, TabPlayer viewer) {
int playerNameWidth = getTextWidth(IChatBaseComponent.fromColoredText(player.getProperty(PropertyUtils.TABPREFIX).getFormat(null) + player.getProperty(PropertyUtils.CUSTOMTABNAME).getFormat(null) + player.getProperty(PropertyUtils.TABSUFFIX).getFormat(null)));
if (player == maxPlayer && playerNameWidth < maxWidth) {
maxWidth = playerNameWidth;
for (TabPlayer all : tab.getPlayers()) {
int localWidth = getPlayerNameWidth(all);
if (localWidth > maxWidth) {
maxWidth = localWidth;
maxPlayer = all;
}
}
updateAllNames(null);
} else if (playerNameWidth > maxWidth) {
maxWidth = playerNameWidth;
maxPlayer = player;
updateAllNames(player);
}
return formatName(player.getProperty(PropertyUtils.TABPREFIX).getFormat(viewer) + player.getProperty(PropertyUtils.CUSTOMTABNAME).getFormat(viewer), player.getProperty(PropertyUtils.TABSUFFIX).getFormat(viewer));
}
public String formatName(String prefixAndName, String suffix) {
int playerNameWidth = getTextWidth(IChatBaseComponent.fromColoredText(prefixAndName + suffix));
String newFormat = prefixAndName + "\u00a7r";
try {
newFormat += buildSpaces(maxWidth + 12 - playerNameWidth);
} catch (IllegalArgumentException e) {
//will investigate later
newFormat += buildSpaces(12);
}
newFormat += tab.getPlaceholderManager().getLastColors(prefixAndName) + suffix;
return newFormat;
}
/**
* Returns text width of characters in given component
* @param component - component to get width of
* @return text width of characters in given component
*/
private int getTextWidth(IChatBaseComponent component) {
int width = 0;
if (component.getText() != null) {
for (Character c : component.getText().toCharArray()) {
if (component.isBold()) {
width += widths.get(c)+2;
} else {
width += widths.get(c)+1;
}
}
}
for (IChatBaseComponent extra : component.getExtra()) {
width += getTextWidth(extra);
}
return width;
}
/**
* Returns width of player's tablist name format
* @param p - player to get width for
* @return width of player's tablist name format
*/
private int getPlayerNameWidth(TabPlayer p) {
String format = p.getProperty(PropertyUtils.TABPREFIX).getFormat(null) + p.getProperty(PropertyUtils.CUSTOMTABNAME).getFormat(null) + p.getProperty(PropertyUtils.TABSUFFIX).getFormat(null);
return getTextWidth(IChatBaseComponent.fromColoredText(format));
}
/**
* Returns a combination of normal and bold spaces to build exactly the requested amount of pixels.
* Must be at least 12 as lower numbers cannot always be built using numbers 4 (normal space + 1 pixel) and 5 (bold space + 1 pixel)
* Returns the result string with normal then bold spaces, such as " &l &r"
* @param pixelWidth - amount of pixels to be built
* @return string consisting of spaces and &l &r
* @throws IllegalArgumentException if pixelWidth is < 12
*/
private String buildSpaces(int pixelWidth) {
if (pixelWidth < 12) throw new IllegalArgumentException("Cannot build space lower than 12 pixels wide");
int pixelsLeft = pixelWidth;
StringBuilder output = new StringBuilder();
while (pixelsLeft % 5 != 0) {
pixelsLeft -= 4;
output.append(" ");
}
output.append("\u00a7l");
while (pixelsLeft > 0) {
pixelsLeft -= 5;
output.append(" ");
}
output.append("\u00a7r");
return output.toString();
}
@Override
public void onQuit(TabPlayer p) {
if (recalculateMaxWidth(p)) {
updateAllNames(p);
}
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
if (recalculateMaxWidth(null)) {
updateAllNames(null);
}
}
private void updateAllNames(TabPlayer exception) {
for (TabPlayer all : tab.getPlayers()) {
if (all == exception) continue;
playerlist.refresh(all, true);
}
}
// returns true if max changed, false if not
private boolean recalculateMaxWidth(TabPlayer ignoredPlayer) {
int oldMaxWidth = maxWidth;
maxWidth = 0;
maxPlayer = null;
for (TabPlayer all : tab.getPlayers()) {
if (all == ignoredPlayer) continue;
int localWidth = getPlayerNameWidth(all);
if (localWidth > maxWidth) {
maxWidth = localWidth;
maxPlayer = all;
}
}
return oldMaxWidth != maxWidth;
}
@Override
public TabFeature getFeatureType() {
return TabFeature.ALIGNED_TABSUFFIX;
}
}
@@ -0,0 +1,193 @@
package me.neznamy.tab.shared.features;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.PacketAPI;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.Refreshable;
import me.neznamy.tab.shared.features.types.event.JoinEventListener;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.WorldChangeListener;
import me.neznamy.tab.shared.features.types.packet.DisplayObjectivePacketListener;
import me.neznamy.tab.shared.features.types.packet.LoginPacketListener;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardScore;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardScore.Action;
/**
* Feature handler for BelowName feature
*/
public class BelowName implements Loadable, JoinEventListener, QuitEventListener, WorldChangeListener, Refreshable, LoginPacketListener, DisplayObjectivePacketListener {
public static final String OBJECTIVE_NAME = "TAB-BelowName";
public static final int DISPLAY_SLOT = 2;
private static final String NUMBER_PROPERTY = "belowname-number";
private static final String TEXT_PROPERTY = "belowname-text";
private TAB tab;
private String rawNumber;
private String rawText;
private List<String> usedPlaceholders;
private List<String> disabledWorlds;
private Set<TabPlayer> playersInDisabledWorlds = new HashSet<>();
public BelowName(TAB tab) {
this.tab = tab;
rawNumber = tab.getConfiguration().getConfig().getString("classic-vanilla-belowname.number", "%health%");
rawText = tab.getConfiguration().getConfig().getString("classic-vanilla-belowname.text", "Health");
disabledWorlds = tab.getConfiguration().getConfig().getStringList("disable-features-in-"+tab.getPlatform().getSeparatorType()+"s.belowname", Arrays.asList("disabled" + tab.getPlatform().getSeparatorType()));
refreshUsedPlaceholders();
tab.debug(String.format("Loaded BelowName feature with parameters number=%s, text=%s, disabledWorlds=%s", rawNumber, rawText, disabledWorlds));
tab.getFeatureManager().registerFeature("belowname-text-refresher", new TextRefresher());
}
@Override
public void load() {
for (TabPlayer loaded : tab.getPlayers()){
loaded.setProperty(NUMBER_PROPERTY, rawNumber);
loaded.setProperty(TEXT_PROPERTY, rawText);
if (isDisabledWorld(disabledWorlds, loaded.getWorldName())) {
playersInDisabledWorlds.add(loaded);
continue;
}
PacketAPI.registerScoreboardObjective(loaded, OBJECTIVE_NAME, loaded.getProperty(TEXT_PROPERTY).updateAndGet(), DISPLAY_SLOT, EnumScoreboardHealthDisplay.INTEGER, TabFeature.BELOWNAME_TEXT);
}
for (TabPlayer viewer : tab.getPlayers()){
for (TabPlayer target : tab.getPlayers()){
viewer.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, target.getName(), getValue(target)), TabFeature.BELOWNAME_NUMBER);
}
}
}
@Override
public void unload() {
for (TabPlayer p : tab.getPlayers()){
if (playersInDisabledWorlds.contains(p)) continue;
p.sendCustomPacket(new PacketPlayOutScoreboardObjective(OBJECTIVE_NAME), TabFeature.BELOWNAME_TEXT);
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
connectedPlayer.setProperty(NUMBER_PROPERTY, rawNumber);
connectedPlayer.setProperty(TEXT_PROPERTY, rawText);
if (isDisabledWorld(disabledWorlds, connectedPlayer.getWorldName())) {
playersInDisabledWorlds.add(connectedPlayer);
return;
}
PacketAPI.registerScoreboardObjective(connectedPlayer, OBJECTIVE_NAME, connectedPlayer.getProperty(TEXT_PROPERTY).get(), DISPLAY_SLOT, EnumScoreboardHealthDisplay.INTEGER, TabFeature.BELOWNAME_TEXT);
int number = getValue(connectedPlayer);
for (TabPlayer all : tab.getPlayers()){
all.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, connectedPlayer.getName(), number), TabFeature.BELOWNAME_NUMBER);
if (all.isLoaded()) connectedPlayer.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, all.getName(), getValue(all)), TabFeature.BELOWNAME_NUMBER);
}
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
boolean disabledBefore = playersInDisabledWorlds.contains(p);
boolean disabledNow = false;
if (isDisabledWorld(disabledWorlds, p.getWorldName())) {
disabledNow = true;
playersInDisabledWorlds.add(p);
} else {
playersInDisabledWorlds.remove(p);
}
if (disabledNow && !disabledBefore) {
p.sendCustomPacket(new PacketPlayOutScoreboardObjective(OBJECTIVE_NAME), TabFeature.BELOWNAME_TEXT);
}
if (!disabledNow && disabledBefore) {
onJoin(p);
}
}
private int getValue(TabPlayer p) {
return tab.getErrorManager().parseInteger(p.getProperty(NUMBER_PROPERTY).updateAndGet(), 0, "belowname number");
}
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (playersInDisabledWorlds.contains(refreshed)) return;
int number = getValue(refreshed);
for (TabPlayer all : tab.getPlayers()) {
all.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, refreshed.getName(), number), TabFeature.BELOWNAME_NUMBER);
}
}
@Override
public List<String> getUsedPlaceholders() {
return usedPlaceholders;
}
@Override
public void refreshUsedPlaceholders() {
usedPlaceholders = tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(rawNumber);
}
@Override
public TabFeature getFeatureType() {
return TabFeature.BELOWNAME_NUMBER;
}
@Override
public void onLoginPacket(TabPlayer packetReceiver) {
if (playersInDisabledWorlds.contains(packetReceiver)) return;
PacketAPI.registerScoreboardObjective(packetReceiver, OBJECTIVE_NAME, packetReceiver.getProperty(TEXT_PROPERTY).get(), DISPLAY_SLOT, EnumScoreboardHealthDisplay.INTEGER, TabFeature.BELOWNAME_TEXT);
for (TabPlayer all : tab.getPlayers()){
if (all.isLoaded()) packetReceiver.sendCustomPacket(new PacketPlayOutScoreboardScore(Action.CHANGE, OBJECTIVE_NAME, all.getName(), getValue(all)), TabFeature.BELOWNAME_NUMBER);
}
}
@Override
public boolean onPacketSend(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
if (playersInDisabledWorlds.contains(receiver)) return false;
if (packet.getSlot() == DISPLAY_SLOT && !packet.getObjectiveName().equals(OBJECTIVE_NAME)) {
tab.getErrorManager().printError("Something just tried to register objective \"" + packet.getObjectiveName() + "\" in position " + packet.getSlot() + " (belowname)", null, false, tab.getErrorManager().getAntiOverrideLog());
return true;
}
return false;
}
public class TextRefresher implements Refreshable {
private List<String> usedPlaceholders;
public TextRefresher(){
refreshUsedPlaceholders();
}
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (playersInDisabledWorlds.contains(refreshed)) return;
refreshed.sendCustomPacket(new PacketPlayOutScoreboardObjective(2, OBJECTIVE_NAME, refreshed.getProperty(TEXT_PROPERTY).updateAndGet(), EnumScoreboardHealthDisplay.INTEGER), TabFeature.BELOWNAME_TEXT);
}
@Override
public List<String> getUsedPlaceholders() {
return usedPlaceholders;
}
@Override
public void refreshUsedPlaceholders() {
usedPlaceholders = tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(rawText);
}
@Override
public TabFeature getFeatureType() {
return TabFeature.BELOWNAME_TEXT;
}
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
playersInDisabledWorlds.remove(disconnectedPlayer);
}
}
@@ -4,7 +4,7 @@ import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.interfaces.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
@@ -14,22 +14,18 @@ import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
*/
public class GhostPlayerFix implements QuitEventListener {
private TAB tab;
public GhostPlayerFix(TAB tab) {
this.tab = tab;
public GhostPlayerFix() {
TAB.getInstance().debug("Loaded GhostPlayerFix feature");
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
tab.getCPUManager().runTaskLater(200, "removing players", getFeatureType(), UsageType.PLAYER_QUIT_EVENT, new Runnable() {
TAB.getInstance().getCPUManager().runTaskLater(500, "removing players", getFeatureType(), UsageType.PLAYER_QUIT_EVENT, () -> {
@Override
public void run() {
for (TabPlayer all : tab.getPlayers()) {
if (all == disconnectedPlayer) continue;
all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, new PlayerInfoData(disconnectedPlayer.getUniqueId())), getFeatureType());
}
if (TAB.getInstance().getPlayer(disconnectedPlayer.getName()) != null) return; //player reconnected meanwhile, not removing then
for (TabPlayer all : TAB.getInstance().getPlayers()) {
if (all == disconnectedPlayer) continue;
all.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, new PlayerInfoData(disconnectedPlayer.getUniqueId())), getFeatureType());
}
});
}
@@ -0,0 +1,207 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.event.JoinEventListener;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.WorldChangeListener;
import me.neznamy.tab.shared.features.types.packet.PlayerInfoPacketListener;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
/**
* Feature handler for global playerlist feature
*/
public class GlobalPlayerlist implements Loadable, JoinEventListener, QuitEventListener, WorldChangeListener, PlayerInfoPacketListener{
private TAB tab;
private List<String> spyServers;
private Map<String, List<String>> sharedServers;
private boolean displayAsSpectators;
private boolean vanishedAsSpectators;
private boolean isolateUnlistedServers;
private List<TabPlayer> vanishedPlayers = new ArrayList<>();
public GlobalPlayerlist(TAB tab) {
this.tab = tab;
}
@Override
public void load() {
spyServers = tab.getConfiguration().getConfig().getStringList("global-playerlist.spy-servers", Arrays.asList("spaserver1"));
sharedServers = tab.getConfiguration().getConfig().getConfigurationSection("global-playerlist.server-groups");
displayAsSpectators = tab.getConfiguration().getConfig().getBoolean("global-playerlist.display-others-as-spectators", false);
setVanishedAsSpectators(tab.getConfiguration().getConfig().getBoolean("global-playerlist.display-vanished-players-as-spectators", true));
isolateUnlistedServers = tab.getConfiguration().getConfig().getBoolean("global-playerlist.isolate-unlisted-servers", false);
for (TabPlayer displayed : tab.getPlayers()) {
for (TabPlayer viewer : tab.getPlayers()) {
if (viewer.getWorldName().equals(displayed.getWorldName())) continue;
if (shouldSee(viewer, displayed)) viewer.sendCustomPacket(getAddPacket(displayed, viewer), getFeatureType());
}
}
tab.debug(String.format("Loaded GlobalPlayerlist feature with parameters spyServers=%s, sharedServers=%s, displayAsSpectators=%s, vanishedAsSpectators=%s, isolateUnlistedServers=%s",
spyServers, sharedServers, displayAsSpectators, isVanishedAsSpectators(), isolateUnlistedServers));
startTask();
}
private void startTask() {
tab.getCPUManager().startRepeatingMeasuredTask(500, "refreshing vanished players", getFeatureType(), UsageType.REPEATING_TASK, () -> {
for (TabPlayer p : tab.getPlayers()) {
if (vanishedPlayers.contains(p) && !p.isVanished()) {
vanishedPlayers.remove(p);
for (TabPlayer viewer : tab.getPlayers()) {
if (viewer == p) continue;
if (shouldSee(viewer, p)) {
viewer.sendCustomPacket(getAddPacket(p, viewer), getFeatureType());
}
}
}
if (!vanishedPlayers.contains(p) && p.isVanished()) {
vanishedPlayers.add(p);
for (TabPlayer all : tab.getPlayers()) {
if (all == p) continue;
if (!shouldSee(all, p)) {
all.sendCustomPacket(getRemovePacket(p), getFeatureType());
}
}
}
}
});
}
public boolean shouldSee(TabPlayer viewer, TabPlayer displayed) {
if (displayed == viewer) return true;
if (displayed.isVanished() && !viewer.hasPermission("tab.seevanished")) return false;
if (spyServers.contains(viewer.getWorldName())) return true;
return getServerGroup(viewer.getWorldName()).equals(getServerGroup(displayed.getWorldName()));
}
private String getServerGroup(String serverName) {
for (Entry<String, List<String>> group : sharedServers.entrySet()) {
if (group.getValue().contains(serverName)) return group.getKey();
}
return isolateUnlistedServers ? "isolated:" + serverName : "DEFAULT";
}
@Override
public void unload() {
for (TabPlayer displayed : tab.getPlayers()) {
PacketPlayOutPlayerInfo displayedRemovePacket = getRemovePacket(displayed);
for (TabPlayer viewer : tab.getPlayers()) {
if (!displayed.getWorldName().equals(viewer.getWorldName())) viewer.sendCustomPacket(displayedRemovePacket, getFeatureType());
}
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
for (TabPlayer all : tab.getPlayers()) {
if (all == connectedPlayer) continue;
if (shouldSee(all, connectedPlayer)) {
all.sendCustomPacket(getAddPacket(connectedPlayer, all), getFeatureType());
}
if (shouldSee(connectedPlayer, all)) {
connectedPlayer.sendCustomPacket(getAddPacket(all, connectedPlayer), getFeatureType());
}
}
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
//delay due to waterfall bug calling server switch when players leave
tab.getCPUManager().runTaskLater(50, "removing players", getFeatureType(), UsageType.PLAYER_QUIT_EVENT, () -> {
PacketPlayOutPlayerInfo remove = getRemovePacket(disconnectedPlayer);
for (TabPlayer all : tab.getPlayers()) {
if (all == disconnectedPlayer) continue;
all.sendCustomPacket(remove, getFeatureType());
}
});
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
PacketPlayOutPlayerInfo removeChanged = getRemovePacket(p);
for (TabPlayer all : tab.getPlayers()) {
if (all == p) continue;
if (shouldSee(all, p)) {
all.sendCustomPacket(getAddPacket(p, all), getFeatureType());
} else {
all.sendCustomPacket(removeChanged, getFeatureType());
}
if (shouldSee(p, all)) {
p.sendCustomPacket(getAddPacket(all, p), getFeatureType());
} else {
p.sendCustomPacket(getRemovePacket(all), getFeatureType());
}
}
}
public PacketPlayOutPlayerInfo getRemovePacket(TabPlayer p) {
PlayerInfoData data = new PlayerInfoData(p.getTablistUUID());
data.setName(p.getName());
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.REMOVE_PLAYER, data);
}
public PacketPlayOutPlayerInfo getAddPacket(TabPlayer p, TabPlayer viewer) {
IChatBaseComponent format = null;
Playerlist playerlist = (Playerlist) tab.getFeatureManager().getFeature("playerlist");
if (playerlist != null) {
format = playerlist.getTabFormat(p, viewer);
}
return new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.ADD_PLAYER, new PlayerInfoData(p.getName(), p.getTablistUUID(), p.getSkin(),
(int)p.getPing(), isVanishedAsSpectators() && p.isVanished() ? EnumGamemode.SPECTATOR : EnumGamemode.CREATIVE, format));
}
@Override
public void onPacketSend(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (info.getAction() == EnumPlayerInfoAction.REMOVE_PLAYER) {
for (PlayerInfoData playerInfoData : info.getEntries()) {
//not preventing NPC removals
if (tab.getPlayerByTablistUUID(playerInfoData.getUniqueId()) != null && (playerInfoData.getName() == null || playerInfoData.getName().length() == 0)) {
//remove packet not coming from tab
//changing to random non-existing player, the easiest way to cancel the removal
playerInfoData.setUniqueId(UUID.randomUUID());
}
}
}
if (!displayAsSpectators) return;
if (info.getAction() == EnumPlayerInfoAction.ADD_PLAYER || info.getAction() == EnumPlayerInfoAction.UPDATE_GAME_MODE) {
for (PlayerInfoData playerInfoData : info.getEntries()) {
TabPlayer packetPlayer = tab.getPlayerByTablistUUID(playerInfoData.getUniqueId());
if (packetPlayer != null && !receiver.getWorldName().equals(packetPlayer.getWorldName())) {
playerInfoData.setGameMode(EnumGamemode.SPECTATOR);
}
}
}
}
@Override
public TabFeature getFeatureType() {
return TabFeature.GLOBAL_PLAYERLIST;
}
public boolean isVanishedAsSpectators() {
return vanishedAsSpectators;
}
public void setVanishedAsSpectators(boolean vanishedAsSpectators) {
this.vanishedAsSpectators = vanishedAsSpectators;
}
}
@@ -1,45 +1,49 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.interfaces.Feature;
import me.neznamy.tab.shared.features.types.Feature;
/**
* Permission group refresher
*/
public class GroupRefresher implements Feature {
public static final String DEFAULT_GROUP = "NONE";
private TAB tab;
public boolean groupsByPermissions;
public boolean usePrimaryGroup;
private boolean groupsByPermissions;
private boolean usePrimaryGroup;
private List<String> primaryGroupFindingList;
public GroupRefresher(TAB tab) {
this.tab = tab;
usePrimaryGroup = tab.getConfiguration().config.getBoolean("use-primary-group", true);
groupsByPermissions = tab.getConfiguration().config.getBoolean("assign-groups-by-permissions", false);
primaryGroupFindingList = tab.getConfiguration().config.getStringList("primary-group-finding-list", Arrays.asList("Owner", "Admin", "Helper", "default"));
tab.getCPUManager().startRepeatingMeasuredTask(1000, "refreshing permission groups", getFeatureType(), UsageType.REPEATING_TASK, new Runnable() {
usePrimaryGroup = tab.getConfiguration().getConfig().getBoolean("use-primary-group", true);
groupsByPermissions = tab.getConfiguration().getConfig().getBoolean("assign-groups-by-permissions", false);
primaryGroupFindingList = new ArrayList<>();
for (Object group : tab.getConfiguration().getConfig().getStringList("primary-group-finding-list", Arrays.asList("Owner", "Admin", "Helper", "default"))){
primaryGroupFindingList.add(group.toString());
}
tab.getCPUManager().startRepeatingMeasuredTask(1000, "refreshing permission groups", getFeatureType(), UsageType.REPEATING_TASK, () -> {
@Override
public void run() {
for (TabPlayer p : tab.getPlayers()) {
p.setGroup(detectPermissionGroup(p), true);
}
for (TabPlayer p : tab.getPlayers()) {
((ITabPlayer) p).setGroup(detectPermissionGroup(p), true);
}
});
}
public String detectPermissionGroup(TabPlayer p) {
if (groupsByPermissions) {
if (isGroupsByPermissions()) {
return getByPermission(p);
}
if (usePrimaryGroup) {
if (isUsePrimaryGroup()) {
return getByPrimary(p);
}
return getFromList(p);
@@ -48,8 +52,8 @@ public class GroupRefresher implements Feature {
public String getByPrimary(TabPlayer p) {
try {
return tab.getPermissionPlugin().getPrimaryGroup(p);
} catch (Throwable e) {
return tab.getErrorManager().printError("<null>", "Failed to get permission group of " + p.getName() + " using " + tab.getPermissionPlugin().getName() + " v" + tab.getPermissionPlugin().getVersion(), e);
} catch (Exception e) {
return tab.getErrorManager().printError(DEFAULT_GROUP, "Failed to get permission group of " + p.getName() + " using " + tab.getPermissionPlugin().getName() + " v" + tab.getPermissionPlugin().getVersion(), e);
}
}
@@ -57,19 +61,19 @@ public class GroupRefresher implements Feature {
try {
String[] playerGroups = tab.getPermissionPlugin().getAllGroups(p);
if (playerGroups != null && playerGroups.length > 0) {
for (Object groupFromList : primaryGroupFindingList) {
for (String groupFromList : primaryGroupFindingList) {
for (String playerGroup : playerGroups) {
if (playerGroup.equalsIgnoreCase(groupFromList + "")) {
if (playerGroup.equalsIgnoreCase(groupFromList)) {
return playerGroup;
}
}
}
return playerGroups[0];
} else {
return "<null>";
return DEFAULT_GROUP;
}
} catch (Throwable e) {
return tab.getErrorManager().printError("<null>", "Failed to get permission groups of " + p.getName() + " using " + tab.getPermissionPlugin().getName() + " v" + tab.getPermissionPlugin().getVersion(), e);
} catch (Exception e) {
return tab.getErrorManager().printError(DEFAULT_GROUP, "Failed to get permission groups of " + p.getName() + " using " + tab.getPermissionPlugin().getName() + " v" + tab.getPermissionPlugin().getVersion(), e);
}
}
@@ -80,11 +84,19 @@ public class GroupRefresher implements Feature {
}
}
tab.getErrorManager().oneTimeConsoleError("Player " + p.getName() + " does not have any group permission while assign-groups-by-permissions is enabled! Did you forget to add his group to primary-group-finding-list?");
return "<null>";
return DEFAULT_GROUP;
}
@Override
public TabFeature getFeatureType() {
return TabFeature.GROUP_REFRESHING;
}
public boolean isGroupsByPermissions() {
return groupsByPermissions;
}
public boolean isUsePrimaryGroup() {
return usePrimaryGroup;
}
}
@@ -0,0 +1,160 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.Refreshable;
import me.neznamy.tab.shared.features.types.event.JoinEventListener;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.WorldChangeListener;
import me.neznamy.tab.shared.packets.IChatBaseComponent;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerListHeaderFooter;
/**
* Feature handler for header and footer
*/
public class HeaderFooter implements Loadable, JoinEventListener, QuitEventListener, WorldChangeListener, Refreshable {
private static final String LINE_SEPARATOR = "\n\u00a7r";
private TAB tab;
private List<String> usedPlaceholders;
private List<String> disabledWorlds;
private Set<TabPlayer> playersInDisabledWorlds = new HashSet<>();
public HeaderFooter(TAB tab) {
this.tab = tab;
disabledWorlds = tab.getConfiguration().getConfig().getStringList("disable-features-in-"+tab.getPlatform().getSeparatorType()+"s.header-footer", Arrays.asList("disabled" + tab.getPlatform().getSeparatorType()));
refreshUsedPlaceholders();
tab.debug(String.format("Loaded HeaderFooter feature with parameters disabledWorlds=%s", disabledWorlds));
}
@Override
public void load() {
for (TabPlayer p : tab.getPlayers()) {
onJoin(p);
}
}
@Override
public void unload() {
for (TabPlayer p : tab.getPlayers()) {
if (playersInDisabledWorlds.contains(p) || p.getVersion().getMinorVersion() < 8) continue;
p.sendCustomPacket(new PacketPlayOutPlayerListHeaderFooter("",""), getFeatureType());
}
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
if (isDisabledWorld(disabledWorlds, connectedPlayer.getWorldName())) {
playersInDisabledWorlds.add(connectedPlayer);
return;
}
refresh(connectedPlayer, true);
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
boolean disabledBefore = playersInDisabledWorlds.contains(p);
boolean disabledNow = false;
if (isDisabledWorld(disabledWorlds, p.getWorldName())) {
disabledNow = true;
playersInDisabledWorlds.add(p);
} else {
playersInDisabledWorlds.remove(p);
}
if (p.getVersion().getMinorVersion() < 8) return;
if (disabledNow) {
if (!disabledBefore) p.sendCustomPacket(new PacketPlayOutPlayerListHeaderFooter("", ""), getFeatureType());
} else {
if (disabledBefore) {
refresh(p, true);
return;
}
boolean refresh = false;
String headerAppend = getValue(p, "headerappend");
if (headerAppend.length() > 0) headerAppend = LINE_SEPARATOR + headerAppend;
String header = getValue(p, PropertyUtils.HEADER) + headerAppend;
if (!p.getProperty(PropertyUtils.HEADER).getOriginalRawValue().equals(header)) {
p.setProperty(PropertyUtils.HEADER, header);
refresh = true;
}
String footerAppend = getValue(p, "footerappend");
if (footerAppend.length() > 0) footerAppend = LINE_SEPARATOR + footerAppend;
String footer = getValue(p, PropertyUtils.FOOTER) + footerAppend;
if (!p.getProperty(PropertyUtils.FOOTER).getOriginalRawValue().equals(footer)) {
p.setProperty(PropertyUtils.FOOTER, footer);
refresh = true;
}
if (refresh) {
p.sendCustomPacket(new PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent.optimizedComponent(p.getProperty(PropertyUtils.HEADER).updateAndGet()),
IChatBaseComponent.optimizedComponent(p.getProperty(PropertyUtils.FOOTER).updateAndGet())), getFeatureType());
}
}
}
@Override
public void refresh(TabPlayer p, boolean force) {
if (force) {
String headerAppend = getValue(p, "headerappend");
if (headerAppend.length() > 0) headerAppend = LINE_SEPARATOR + headerAppend;
p.setProperty(PropertyUtils.HEADER, getValue(p, PropertyUtils.HEADER) + headerAppend);
String footerAppend = getValue(p, "footerappend");
if (footerAppend.length() > 0) footerAppend = LINE_SEPARATOR + footerAppend;
p.setProperty(PropertyUtils.FOOTER, getValue(p, PropertyUtils.FOOTER) + footerAppend);
}
if (playersInDisabledWorlds.contains(p) || p.getVersion().getMinorVersion() < 8) return;
p.sendCustomPacket(new PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent.optimizedComponent(p.getProperty(PropertyUtils.HEADER).updateAndGet()),
IChatBaseComponent.optimizedComponent(p.getProperty(PropertyUtils.FOOTER).updateAndGet())), getFeatureType());
}
@Override
public List<String> getUsedPlaceholders() {
return usedPlaceholders;
}
private String getValue(TabPlayer p, String property) {
String worldGroup = tab.getConfiguration().getWorldGroupOf(tab.getConfiguration().getConfig().getConfigurationSection("per-" + tab.getPlatform().getSeparatorType() + "-settings").keySet(), p.getWorldName());
String[] priorities = {
"per-" + tab.getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Users." + p.getName() + "." + property,
"per-" + tab.getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Users." + p.getUniqueId().toString() + "." + property,
"Users." + p.getName() + "." + property,
"Users." + p.getUniqueId().toString() + "." + property,
"per-" + tab.getPlatform().getSeparatorType() + "-settings." + worldGroup + ".Groups." + p.getGroup() + "." + property,
"per-" + tab.getPlatform().getSeparatorType() + "-settings." + worldGroup + "." + property,
"Groups." + p.getGroup() + "." + property,
property
};
List<String> lines = null;
for (String path : priorities) {
lines = tab.getConfiguration().getConfig().getStringList(path);
if (lines != null) break;
}
if (lines == null) lines = new ArrayList<>();
return String.join(LINE_SEPARATOR, lines);
}
@Override
public void refreshUsedPlaceholders() {
usedPlaceholders = tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(PropertyUtils.HEADER, PropertyUtils.FOOTER);
}
@Override
public TabFeature getFeatureType() {
return TabFeature.HEADER_FOOTER;
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
playersInDisabledWorlds.remove(disconnectedPlayer);
}
}
@@ -0,0 +1,315 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.PacketAPI;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.PropertyUtils;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.cpu.UsageType;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.Refreshable;
import me.neznamy.tab.shared.features.types.event.JoinEventListener;
import me.neznamy.tab.shared.features.types.event.QuitEventListener;
import me.neznamy.tab.shared.features.types.event.WorldChangeListener;
import me.neznamy.tab.shared.features.types.packet.LoginPacketListener;
import me.neznamy.tab.shared.packets.PacketPlayOutScoreboardTeam;
public class NameTag implements Loadable, Refreshable, LoginPacketListener, QuitEventListener, WorldChangeListener, JoinEventListener {
protected TAB tab;
protected Set<String> usedPlaceholders;
protected List<String> disabledWorlds;
private boolean collisionRule;
private List<String> revertedCollision;
private boolean invisibleNametags;
protected Set<String> invisiblePlayers = new HashSet<>();
private Sorting sorting;
protected Map<String, Boolean> collision = new HashMap<>();
protected Set<TabPlayer> playersInDisabledWorlds = new HashSet<>();
public NameTag(TAB tab) {
this.tab = tab;
disabledWorlds = tab.getConfiguration().getConfig().getStringList("disable-features-in-"+tab.getPlatform().getSeparatorType()+"s.nametag", Arrays.asList("disabled" + tab.getPlatform().getSeparatorType()));
collisionRule = tab.getConfiguration().getConfig().getBoolean("enable-collision", true);
revertedCollision = tab.getConfiguration().getConfig().getStringList("revert-collision-rule-in-" + tab.getPlatform().getSeparatorType()+"s", Arrays.asList("reverted" + tab.getPlatform().getSeparatorType()));
invisibleNametags = tab.getConfiguration().getConfig().getBoolean("invisible-nametags", false);
sorting = new Sorting(tab, this);
usedPlaceholders = new HashSet<>(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(PropertyUtils.TAGPREFIX, PropertyUtils.TAGSUFFIX));
for (TabPlayer p : tab.getPlayers()) {
usedPlaceholders.addAll(tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(
p.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue(), p.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue()));
}
tab.debug(String.format("Loaded NameTag feature with parameters collisionRule=%s, revertedCollision=%s, disabledWorlds=%s, invisibleNametags=%s",
collisionRule, revertedCollision, disabledWorlds, invisibleNametags));
}
@Override
public void load(){
for (TabPlayer all : tab.getPlayers()) {
((ITabPlayer) all).setTeamName(getSorting().getTeamName(all));
updateProperties(all);
collision.put(all.getName(), true);
if (all.hasInvisibilityPotion()) invisiblePlayers.add(all.getName());
if (isDisabledWorld(disabledWorlds, all.getWorldName())) {
playersInDisabledWorlds.add(all);
continue;
}
registerTeam(all);
}
startRefreshingTasks();
}
@Override
public void unload() {
for (TabPlayer p : tab.getPlayers()) {
if (!playersInDisabledWorlds.contains(p)) unregisterTeam(p);
}
}
public void startRefreshingTasks() {
//workaround for a 1.8.x client-sided bug
tab.getCPUManager().startRepeatingMeasuredTask(500, "refreshing nametag visibility", TabFeature.NAMETAGS, UsageType.REFRESHING_NAMETAG_VISIBILITY_AND_COLLISION, () -> {
for (TabPlayer p : tab.getPlayers()) {
if (!p.isLoaded() || playersInDisabledWorlds.contains(p)) continue;
//nametag visibility
boolean invisible = p.hasInvisibilityPotion();
if (invisible && !invisiblePlayers.contains(p.getName())) {
invisiblePlayers.add(p.getName());
updateTeamData(p);
}
if (!invisible && invisiblePlayers.contains(p.getName())) {
invisiblePlayers.remove(p.getName());
updateTeamData(p);
}
//cannot control collision rule on <1.9 servers in any way
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) updateCollision(p);
}
});
}
public boolean isDisabledWorld(String world) {
return isDisabledWorld(disabledWorlds, world);
}
public Set<String> getInvisiblePlayers(){
return invisiblePlayers;
}
@Override
public List<String> getUsedPlaceholders() {
return new ArrayList<>(usedPlaceholders);
}
public void unregisterTeam(TabPlayer p) {
if (p.hasTeamHandlingPaused()) return;
if (p.getTeamName() == null) return;
for (TabPlayer viewer : tab.getPlayers()) {
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName()), TabFeature.NAMETAGS);
}
}
public void unregisterTeam(TabPlayer p, TabPlayer viewer) {
if (p.hasTeamHandlingPaused()) return;
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName()), TabFeature.NAMETAGS);
}
public void registerTeam(TabPlayer p) {
if (p.hasTeamHandlingPaused()) return;
Property tagprefix = p.getProperty(PropertyUtils.TAGPREFIX);
Property tagsuffix = p.getProperty(PropertyUtils.TAGSUFFIX);
for (TabPlayer viewer : tab.getPlayers()) {
String currentPrefix = tagprefix.getFormat(viewer);
String currentSuffix = tagsuffix.getFormat(viewer);
PacketAPI.registerScoreboardTeam(viewer, p.getTeamName(), currentPrefix, currentSuffix, getTeamVisibility(p, viewer), getCollision(p), Arrays.asList(p.getName()), null, TabFeature.NAMETAGS);
}
}
public void registerTeam(TabPlayer p, TabPlayer viewer) {
if (p.hasTeamHandlingPaused()) return;
Property tagprefix = p.getProperty(PropertyUtils.TAGPREFIX);
Property tagsuffix = p.getProperty(PropertyUtils.TAGSUFFIX);
String replacedPrefix = tagprefix.getFormat(viewer);
String replacedSuffix = tagsuffix.getFormat(viewer);
PacketAPI.registerScoreboardTeam(viewer, p.getTeamName(), replacedPrefix, replacedSuffix, getTeamVisibility(p, viewer), getCollision(p), Arrays.asList(p.getName()), null, TabFeature.NAMETAGS);
}
public void updateTeam(TabPlayer p) {
if (p.getTeamName() == null) return; //player not loaded yet
String newName = getSorting().getTeamName(p);
if (p.getTeamName().equals(newName)) {
updateTeamData(p);
} else {
unregisterTeam(p);
((ITabPlayer) p).setTeamName(newName);
registerTeam(p);
}
}
public void updateTeamData(TabPlayer p) {
Property tagprefix = p.getProperty(PropertyUtils.TAGPREFIX);
Property tagsuffix = p.getProperty(PropertyUtils.TAGSUFFIX);
for (TabPlayer viewer : tab.getPlayers()) {
String currentPrefix = tagprefix.getFormat(viewer);
String currentSuffix = tagsuffix.getFormat(viewer);
boolean visible = getTeamVisibility(p, viewer);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), currentPrefix, currentSuffix, translate(visible), translate(getCollision(p)), 0), TabFeature.NAMETAGS);
}
}
public void updateTeamData(TabPlayer p, TabPlayer viewer) {
Property tagprefix = p.getProperty(PropertyUtils.TAGPREFIX);
Property tagsuffix = p.getProperty(PropertyUtils.TAGSUFFIX);
boolean visible = getTeamVisibility(p, viewer);
String currentPrefix = tagprefix.getFormat(viewer);
String currentSuffix = tagsuffix.getFormat(viewer);
viewer.sendCustomPacket(new PacketPlayOutScoreboardTeam(p.getTeamName(), currentPrefix, currentSuffix, translate(visible), translate(getCollision(p)), 0), TabFeature.NAMETAGS);
}
private String translate(boolean b) {
return b ? "always" : "never";
}
private void updateCollision(TabPlayer p) {
if (TAB.getInstance().getFeatureManager().getNameTagFeature() == null || !p.isOnline()) return;
if (p.getCollisionRule() != null) {
if (getCollision(p) != p.getCollisionRule()) {
collision.put(p.getName(), p.getCollisionRule());
updateTeamData(p);
}
} else {
boolean newCollision = !p.isDisguised() && revertedCollision.contains(p.getWorldName()) ? !collisionRule : collisionRule;
if (collision.get(p.getName()) == null || getCollision(p) != newCollision) {
collision.put(p.getName(), newCollision);
updateTeamData(p);
}
}
}
protected Boolean getCollision(TabPlayer p) {
if (!p.isOnline()) return false;
if (p.getCollisionRule() != null) return p.getCollisionRule();
if (!collision.containsKey(p.getName())) {
collision.put(p.getName(), revertedCollision.contains(p.getWorldName()) ? !collisionRule : collisionRule);
}
return collision.get(p.getName());
}
@Override
public void onLoginPacket(TabPlayer packetReceiver) {
for (TabPlayer all : tab.getPlayers()) {
if (!all.isLoaded()) continue;
if (!playersInDisabledWorlds.contains(all)) registerTeam(all, packetReceiver);
}
}
@Override
public void refresh(TabPlayer refreshed, boolean force) {
if (playersInDisabledWorlds.contains(refreshed)) return;
boolean refresh;
if (force) {
updateProperties(refreshed);
refresh = true;
} else {
boolean prefix = refreshed.getProperty(PropertyUtils.TAGPREFIX).update();
boolean suffix = refreshed.getProperty(PropertyUtils.TAGSUFFIX).update();
refresh = prefix || suffix;
}
if (refresh) updateTeam(refreshed);
}
@Override
public void onJoin(TabPlayer connectedPlayer) {
((ITabPlayer) connectedPlayer).setTeamName(getSorting().getTeamName(connectedPlayer));
updateProperties(connectedPlayer);
collision.put(connectedPlayer.getName(), true);
for (TabPlayer all : tab.getPlayers()) {
if (!all.isLoaded() || all == connectedPlayer) continue; //avoiding double registration
if (!playersInDisabledWorlds.contains(all)) {
registerTeam(all, connectedPlayer);
}
}
if (isDisabledWorld(disabledWorlds, connectedPlayer.getWorldName())) {
playersInDisabledWorlds.add(connectedPlayer);
return;
}
registerTeam(connectedPlayer);
}
@Override
public void onQuit(TabPlayer disconnectedPlayer) {
if (!playersInDisabledWorlds.contains(disconnectedPlayer)) unregisterTeam(disconnectedPlayer);
invisiblePlayers.remove(disconnectedPlayer.getName());
collision.remove(disconnectedPlayer.getName());
playersInDisabledWorlds.remove(disconnectedPlayer);
for (TabPlayer all : tab.getPlayers()) {
if (all == disconnectedPlayer) continue;
all.showNametag(disconnectedPlayer.getUniqueId()); //clearing memory from API method
}
}
@Override
public void onWorldChange(TabPlayer p, String from, String to) {
boolean disabledBefore = playersInDisabledWorlds.contains(p);
boolean disabledNow = false;
if (isDisabledWorld(disabledWorlds, p.getWorldName())) {
disabledNow = true;
playersInDisabledWorlds.add(p);
} else {
playersInDisabledWorlds.remove(p);
}
updateProperties(p);
if (disabledNow && !disabledBefore) {
unregisterTeam(p);
} else if (!disabledNow && disabledBefore) {
registerTeam(p);
} else {
updateTeam(p);
}
}
@Override
public void refreshUsedPlaceholders() {
usedPlaceholders = new HashSet<>(tab.getConfiguration().getConfig().getUsedPlaceholderIdentifiersRecursive(PropertyUtils.TAGPREFIX, PropertyUtils.TAGSUFFIX));
for (TabPlayer p : tab.getPlayers()) {
usedPlaceholders.addAll(tab.getPlaceholderManager().getUsedPlaceholderIdentifiersRecursive(
p.getProperty(PropertyUtils.TAGPREFIX).getCurrentRawValue(), p.getProperty(PropertyUtils.TAGSUFFIX).getCurrentRawValue()));
}
}
public void updateProperties(TabPlayer p) {
p.loadPropertyFromConfig(PropertyUtils.TAGPREFIX);
p.loadPropertyFromConfig(PropertyUtils.TAGSUFFIX);
}
public boolean getTeamVisibility(TabPlayer p, TabPlayer viewer) {
return !p.hasHiddenNametag() && !p.hasHiddenNametag(viewer.getUniqueId()) &&
!invisibleNametags && !invisiblePlayers.contains(p.getName());
}
@Override
public TabFeature getFeatureType() {
return TabFeature.NAMETAGS;
}
public Sorting getSorting() {
return sorting;
}
public Set<TabPlayer> getPlayersInDisabledWorlds(){
return playersInDisabledWorlds;
}
}
@@ -0,0 +1,63 @@
package me.neznamy.tab.shared.features;
import java.util.ArrayList;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.packet.PlayerInfoPacketListener;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.shared.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
/**
* Sets ping of all players in the packet to configured value to prevent hacked clients from seeing exact ping value of each player
*/
public class PingSpoof implements PlayerInfoPacketListener, Loadable {
//fake ping value
private int value;
/**
* Constructs new instance and loads config options
*/
public PingSpoof() {
value = TAB.getInstance().getConfiguration().getConfig().getInt("ping-spoof.value", 0);
TAB.getInstance().debug(String.format("Loaded PingSpoof feature with parameters value=%s", value));
}
@Override
public void onPacketSend(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
if (info.getAction() != EnumPlayerInfoAction.UPDATE_LATENCY && info.getAction() != EnumPlayerInfoAction.ADD_PLAYER) return;
for (PlayerInfoData playerInfoData : info.getEntries()) {
playerInfoData.setLatency(value);
}
}
@Override
public TabFeature getFeatureType() {
return TabFeature.PING_SPOOF;
}
@Override
public void load() {
updateAll(false);
}
@Override
public void unload() {
updateAll(true);
}
private void updateAll(boolean realPing) {
List<PlayerInfoData> list = new ArrayList<>();
for (TabPlayer p : TAB.getInstance().getPlayers()) {
list.add(new PlayerInfoData(p.getUniqueId(), realPing ? (int) p.getPing() : 0));
}
for (TabPlayer p : TAB.getInstance().getPlayers()) {
p.sendCustomPacket(new PacketPlayOutPlayerInfo(EnumPlayerInfoAction.UPDATE_LATENCY, list), getFeatureType());
}
}
}
@@ -3,13 +3,15 @@ package me.neznamy.tab.shared.features;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.cpu.TabFeature;
import me.neznamy.tab.shared.features.interfaces.JoinEventListener;
import me.neznamy.tab.shared.features.interfaces.Loadable;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.event.JoinEventListener;
/**
* A large source of hate. Packet intercepting to secure proper functionality of some features:
* Tablist names - anti-override (preventing other plugins from setting this value)
* Tablist names - anti-override
* Nametags - anti-override
* TabObjective & Belowname - anti-override
* Scoreboard - disabling tab's scoreboard to prevent conflict
* SpectatorFix - to change gamemode to something else than spectator
* PetFix - to remove owner field from entity data
* Unlimited nametags - replacement for bukkit events with much better accuracy and reliability
@@ -17,12 +19,26 @@ import me.neznamy.tab.shared.features.interfaces.Loadable;
public abstract class PipelineInjector implements JoinEventListener, Loadable {
//name of the pipeline decoder injected in netty
public final String DECODER_NAME = "TAB";
public static final String DECODER_NAME = "TAB";
//tab instance
protected TAB tab;
public PipelineInjector(TAB tab) {
//preventing spam when packet is sent to everyone
private String lastTeamOverrideMessage;
//anti-override rules
protected boolean antiOverrideTeams;
protected boolean antiOverrideObjectives;
/**
* Constructs new instance
* @param tab
*/
protected PipelineInjector(TAB tab) {
this.tab = tab;
antiOverrideTeams = tab.getConfiguration().getConfig().getBoolean("anti-override.scoreboard-teams", true);
antiOverrideObjectives = tab.getConfiguration().getConfig().getBoolean("anti-override.scoreboard-objectives", true);
}
/**
@@ -58,6 +74,11 @@ public abstract class PipelineInjector implements JoinEventListener, Loadable {
}
protected void logTeamOverride(String team, String player) {
tab.getErrorManager().printError("Something just tried to add player " + player + " into team " + team, null, false, tab.getErrorManager().antiOverrideLog);
String message = "Something just tried to add player " + player + " into team " + team;
//not logging the same message for every online player who received the packet
if (lastTeamOverrideMessage == null || !message.equals(lastTeamOverrideMessage)) {
lastTeamOverrideMessage = message;
tab.getErrorManager().printError(message, null, false, tab.getErrorManager().getAntiOverrideLog());
}
}
}
Loaded 100 of 304 files, more files were not shown because too many files have changed in this diff. Show more