Compare commits

...
1160 Commits
Author SHA1 Message Date
NEZNAMY d5c5502abe 4.0.7 2023-10-16 20:45:35 +02:00
NEZNAMY 194e8ffbd9 Code style 2023-10-15 20:39:06 +02:00
NEZNAMY ef39458abd With all the problems on 1.20.2 BungeeCord it turns out that Velocity isn't that bad. 2023-10-15 17:39:21 +02:00
NEZNAMY 6a6907fb0a Process login packet with a delay to avoid problems 2023-10-15 17:33:06 +02:00
NEZNAMY 8bb7c4a4a6 Adapt redis support subfeatures to recent changes 2023-10-15 17:00:55 +02:00
NEZNAMY 91700e7ed0 Fix global playerlist not readding players for 1.20.2 clients on server switch 2023-10-15 17:00:16 +02:00
NEZNAMY 661c2e6fc3 Simplify 2023-10-15 15:37:38 +02:00
NEZNAMY 3d053a6149 Implement backup solutions if header/footer fields fail to initialize 2023-10-15 15:35:25 +02:00
NEZNAMY 34179e4f62 Fix startup on old versions due to wrong enum names 2023-10-15 14:56:40 +02:00
NEZNAMY a23e5d9f50 Remove toComponent method from Platform interface and only keep it in platforms that actually use it 2023-10-15 14:26:09 +02:00
NEZNAMY 1cbcbf70d9 Fix aligned playerlist attempting to send packets to players who just left 2023-10-15 14:08:35 +02:00
NEZNAMY 0a7371aaec Micro optimizations with enum conversions 2023-10-14 13:55:52 +02:00
NEZNAMY da13e2d861 Fix Bridge still injecting placeholders that are not used on proxy anymore 2023-10-13 12:27:12 +02:00
NEZNAMY 5b18fa8c25 Fix PingSpoof spoofing everything instead of just online players (or their respective layout entries) 2023-10-13 11:34:38 +02:00
NEZNAMY 347231850a Fix error 2023-10-13 09:51:40 +02:00
NEZNAMY b1f9bdc26c Resend BossBar on server switch for 1.20.2 players as Login packet now clears it 2023-10-12 20:51:50 +02:00
NEZNAMY 03c9b95041 Listen to login packet instead of server switch delay for 1.20.2 players for better consistency (also fix layout not being resent on server switch on 1.20.2) 2023-10-12 19:20:22 +02:00
NEZNAMY 6e7e1d783b Fix rare placeholder formatting error 2023-10-12 16:33:58 +02:00
NEZNAMY cfb9b0a029 Fix compatibility with some random 1.8.8 overpriced fork (#1105) 2023-10-12 14:32:21 +02:00
NEZNAMY b7ea6e5af6 Fix wrong exception being caught causing the try/catch to not work (#1092) 2023-10-12 09:33:50 +02:00
NEZNAMY 44104df712 Change error message about invalid team/objective actions to a debug message. They don't cause any issues as they are properly caught and debugging is hard, especially if reporter has no interest in cooperating (#1083) 2023-10-11 19:35:57 +02:00
NEZNAMY 5de20d1c58 Add try/catch for BungeeCord sendPacket method to avoid a NPE in BungeeCord code (#1090, #1092) 2023-10-11 16:24:43 +02:00
NEZNAMY d156303740 Remove warn when placeholders use too much CPU. Since they have been moved to another thread, they will no longer prevent the plugin from working. While the warning can be useful for more advanced users, this is a public plugin used by many and most of them don't care about performance at all. This has resulted in having to answer people who would have never contacted me otherwise. 2023-10-10 15:04:00 +02:00
NEZNAMY 6e36eb3554 Add back fabric class for multi-version code for easier backport 2023-10-09 21:49:21 +02:00
NEZNAMY f7f3891817 Avoid attempts to send plugin message to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-08 12:41:12 +02:00
NEZNAMY 1ec28856ff Avoid attempts to send packets to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-06 11:41:32 +02:00
NEZNAMY 9658e0b74d Add debug message showing how long bridge took to respond to player join plugin message 2023-10-06 11:31:09 +02:00
NEZNAMY 86cd8d4151 Make feature loading more readable 2023-10-05 17:39:27 +02:00
NEZNAMY 2418eb206e Improve unknown version handling code & error messages 2023-10-05 13:57:56 +02:00
NEZNAMY 08fa715d26 Fix runtime warns false triggering on proxy when placeholders are not loaded due to bridge not having a chance to respond yet (#1088) 2023-10-04 14:16:08 +02:00
NEZNAMY 52426de283 Remove double method call 2023-10-04 14:04:57 +02:00
NEZNAMY 64b3da14c4 It's called disable condition, not display condition 2023-10-02 20:38:21 +02:00
NEZNAMY bf7341c52c Change way getProfile method is retrieved (#1089) 2023-10-02 19:09:53 +02:00
NEZNAMY e3490564a2 4.0.7-SNAPSHOT 2023-10-02 19:07:05 +02:00
NEZNAMY 6218f0ed16 4.0.6 2023-10-01 16:35:39 +02:00
NEZNAMY 54e8e1ea98 Add a temporary workaround for https://github.com/SpigotMC/BungeeCord/issues/3542 2023-10-01 16:35:33 +02:00
NEZNAMY 4cd50fb3d5 Use sendPacketQueued method on BungeeCord to avoid error on 1.20.2 clients 2023-10-01 16:31:25 +02:00
NEZNAMY 590286a6c2 4.0.5 2023-09-27 21:12:58 +02:00
NEZNAMY 6c96650dcc Add support for new Spigot build that uses mojang mappings for fields and methods 2023-09-27 21:12:22 +02:00
NEZNAMY 7c83a74fd8 Fix plugin trying to send scoreboard packets to players who left already 2023-09-27 19:20:32 +02:00
NEZNAMY 7b3f60cb3b 4.0.5-SNAPSHOT 2023-09-23 16:37:02 +02:00
NEZNAMY bfc845088a Merge pull request #1084 from GsTio86/master
Fix Arclight compatibility
2023-09-23 12:36:32 +02:00
Galen Huang e67d93a592 Update DataWatcher.java 2023-09-23 16:58:19 +08:00
gstio86 b518b980ae Fix Arclight compatibility 2023-09-23 14:35:06 +08:00
NEZNAMY 744bc1403a Remove tps placeholder from config entirely, the negation was wrong as well 2023-09-22 11:01:35 +02:00
NEZNAMY e11113073f 4.0.4 2023-09-22 09:00:37 +02:00
NEZNAMY 3615818812 Add condition to default config to prevent %tps% being displayed on BungeeCord in scoreboard, as the message is too long 2023-09-22 08:59:31 +02:00
NEZNAMY 0cdef12caf Add support for BungeeCord 1.20.2 2023-09-22 08:56:19 +02:00
NEZNAMY dda8c6c053 Fix another error on Bukkit 1.20.2 2023-09-21 22:21:46 +02:00
NEZNAMY 8c3327f553 Fix error on Bukkit 1.20.2 2023-09-21 20:49:29 +02:00
NEZNAMY c037a14e1f Update Bukkit to 1.20.2 2023-09-21 19:45:06 +02:00
NEZNAMY bd661360ee Delete %server_uptime% from default config as it's a PlaceholderAPI placeholder, which is not always guaranteed to work. (#1077) 2023-09-21 18:33:27 +02:00
NEZNAMY 317fabbc87 Update Fabric to 1.20.2 2023-09-21 18:31:49 +02:00
NEZNAMY 87161446c5 Add 1.20.2 to protocol versions 2023-09-21 18:25:22 +02:00
NEZNAMY 7db8b2e84b Send warn if trying to use parse command from the console with "me" player name argument 2023-09-21 15:04:03 +02:00
NEZNAMY 5f3fa2b317 Layout - Fix ROWS direction type not working properly & avoid real players appearing on top when teams are disabled 2023-09-19 22:14:37 +02:00
NEZNAMY 64c15730a6 Add missing correct Placeholder#update implementations (#1075) 2023-09-17 18:48:41 +02:00
NEZNAMY e5ab46b8ad Initialize scoreboard placeholders to avoid them rarely not parsing due to not being set 2023-09-17 15:01:51 +02:00
NEZNAMY 3060d8cc2b Add override for %player_ping% placeholder because 1.20.2 will have a breaking change to prevent github issues reporting server expansion not supporting new version 2023-09-16 10:30:05 +02:00
NEZNAMY f5d6ba462e Remove unnecessary fully qualified class names 2023-09-15 15:21:39 +02:00
NEZNAMY 81fdeece96 Port new Velocity anti-override to Sponge as well 2023-09-15 14:21:20 +02:00
NEZNAMY 8f60439ef3 Fix multiple sorting types with same priority separated with | not actually working 2023-09-14 16:13:44 +02:00
NEZNAMY c5420ead90 Send gamemode update packet on world switch when spectator fix is enabled as the server may resend gamemode on world switch back to spectator (#1069) 2023-09-13 16:55:21 +02:00
NEZNAMY 99d372d03c Fix refresh interval config conversion ignoring random invalid user-made maps and then causing error on load 2023-09-12 14:32:15 +02:00
NEZNAMY 1219a9822f Remove unused dependency 2023-09-10 10:42:37 +02:00
NEZNAMY d40c182af9 Clean up repositories 2023-09-10 10:39:12 +02:00
NEZNAMY 785e986a72 Fix component cache including child components 2023-09-09 21:04:41 +02:00
NEZNAMY e8b80f455b Replace converter methods with arrays for fast access 2023-09-09 11:33:16 +02:00
NEZNAMY 2d545f1bf1 Fix potential blocking of other plugins in new anti-override for Velocity 2023-09-09 08:22:45 +02:00
NEZNAMY d4713dc7f4 Use direct code for LibsDisguises hook instead of reflection and move it into a separate class 2023-09-08 23:54:21 +02:00
NEZNAMY cd96c13670 Remove unused imports 2023-09-08 23:07:13 +02:00
NEZNAMY 808607faf0 Add try/catch for Fabric loading to send warn on unsupported versions instead of throwing an error (#1068) 2023-09-08 21:30:54 +02:00
NEZNAMY bc403f5c23 Fix ConcurrentModificationException on Velocity 2023-09-08 19:04:00 +02:00
NEZNAMY bfc67e1953 Allow the new anti-override to be disabled with standard settings 2023-09-08 08:21:42 +02:00
NEZNAMY a186c16c34 Implement anti-override for tablist display names on Velocity 2023-09-07 21:37:33 +02:00
NEZNAMY f9be9d4c4b Improve check to reduce errors thrown 2023-09-07 18:09:38 +02:00
NEZNAMY 79fc3ce33f Fix BossBars getting resent when output of placeholder used in display condition changed even if condition result didn't change (#1065) 2023-09-06 21:14:35 +02:00
NEZNAMY 055ff8749e Add support for custom fonts in configuration 2023-09-05 21:38:38 +02:00
NEZNAMY d36410e851 Simplify 2023-09-05 21:32:01 +02:00
NEZNAMY 0fed5dca7f Add font field and conversion to component class and quality of life changes for component conversion 2023-09-05 15:30:42 +02:00
NEZNAMY dd34ffc5b2 Create internal enum for scoreboard display type instead of using a boolean 2023-09-04 21:00:17 +02:00
NEZNAMY 2cd5ac85f9 Simplify 2023-09-04 18:20:17 +02:00
NEZNAMY 571f949f50 Remove unnecessary player version check 2023-09-04 17:58:38 +02:00
NEZNAMY 81e32957b2 Fix startup error 2023-09-04 17:57:20 +02:00
NEZNAMY 4639016ffb Preload NMS GameModes instead of calling Enum#valueOf every time 2023-09-04 17:41:26 +02:00
NEZNAMY dabc2b49dc Remove unused fields and methods 2023-09-03 17:52:29 +02:00
NEZNAMY 2dec389c8c Load NMS method by parameter types instead of name 2023-09-01 11:26:05 +02:00
NEZNAMY bfe5b5960e Fix error in per world playerlist on <1.8 2023-08-31 21:04:36 +02:00
NEZNAMY e352967a17 Improve parse command and fix color-related issues on bukkit 2023-08-31 18:48:04 +02:00
NEZNAMY 930917f285 Fill auto-complete suggestions in plugin's PlaceholderAPI expansion 2023-08-30 21:04:49 +02:00
NEZNAMY b9747f7fbe Fix NPE when user deletes default-refresh-interval option from config (1052) 2023-08-28 13:39:39 +02:00
NEZNAMY 5ac9dcec37 Fix startup on Sponge 7 2023-08-28 13:00:11 +02:00
NEZNAMY 8328b43783 Fix NPE when using relational placeholder with multiple proxies 2023-08-25 22:28:23 +02:00
NEZNAMY 131fcfe144 Remove redundant annotation 2023-08-24 17:36:16 +02:00
NEZNAMY 709cb967b5 Fix plugin sometimes trying to format tablist entries for 1.7 players 2023-08-24 12:16:16 +02:00
NEZNAMY 3370b83087 Fix issue in config conversion 2023-08-24 12:06:04 +02:00
NEZNAMY 5c44bd500c Simplify 2023-08-24 10:05:21 +02:00
NEZNAMY ab8a345944 Small style changes 2023-08-24 09:45:23 +02:00
NEZNAMY 337dd56c87 Simplify 2023-08-24 09:38:54 +02:00
NEZNAMY de78ae30db Copy the previous fix to aligned playerlist as well 2023-08-22 14:29:04 +02:00
NEZNAMY 3f64185389 Fix disable condition not being respected in playerlist in one method (#1037) 2023-08-22 14:23:32 +02:00
NEZNAMY 5077cbbc9a Return empty list instead of new list 2023-08-22 14:01:50 +02:00
NEZNAMY 4d522ba5f5 Improve bossbar command to match scoreboard command features 2023-08-22 13:57:45 +02:00
NEZNAMY 4f1180b3f5 Boost performance of tablist getter on BungeeCord 2023-08-17 17:53:22 +02:00
NEZNAMY 577a3f2551 Add support for Mohist 1.18.2 2023-08-13 17:54:42 +02:00
NEZNAMY 8623ab053e Remove some unnecessary local variables 2023-08-12 20:51:25 +02:00
NEZNAMY 6d0f99eaf1 Change WeakHashMap key to use own player object instead of server's to prevent map getting big when another plugin has a memory leak 2023-08-12 19:04:30 +02:00
NEZNAMY f9490d2652 Move field into local variable 2023-08-12 15:08:15 +02:00
NEZNAMY 9d321e4a31 Remove unused parameter and simplify 2023-08-11 22:12:00 +02:00
NEZNAMY b8c2625b80 Only show placeholder retrieve time message if debug is enabled 2023-08-09 20:22:38 +02:00
NEZNAMY 8e42f328dc Register plugin's PlaceholderAPI expansion automatically without having to enable a config option 2023-08-09 20:13:26 +02:00
NEZNAMY 0c6b4efba5 Simplify 2023-08-09 17:02:25 +02:00
NEZNAMY 0821ed4dae Bukkit - Make NMS loading less dependent on the main NMS class 2023-08-09 17:00:20 +02:00
NEZNAMY 8bc38e25f9 Fix empty line detection sometimes not working 2023-08-07 21:51:43 +02:00
NEZNAMY 7a7a4e6da2 Global playerlist - Do not remove & add self on server switch 2023-08-07 14:04:25 +02:00
NEZNAMY f7d703ed1e Placeholders - Decrease return time warn threshold from 1000ms to 50ms 2023-08-07 13:40:35 +02:00
NEZNAMY 443d7fd082 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-08-07 13:38:47 +02:00
NEZNAMY b6114625c1 Remove duplicate check 2023-08-07 12:18:13 +02:00
NEZNAMY 4a3eb78f12 Add back ping spoof feature 2023-08-06 11:32:02 +02:00
NEZNAMY fa117a671a Simplify placeholder registration 2023-08-05 23:04:36 +02:00
NEZNAMY 6448fb975a Fix scoreboard title limit 2023-08-05 14:41:40 +02:00
NEZNAMY 1c685ce0bb Bukkit - Properly play around Bukkit API limits in scoreboard 2023-08-05 14:36:14 +02:00
NEZNAMY 1662fa8d60 Bukkit - Implement scoreboard handler using Bukkit API (still unused) 2023-08-05 13:23:03 +02:00
NEZNAMY 2787b1735b Header/Footer - Fix being sent on server switch even if disable condition is met & fix being sent twice 2023-08-04 22:07:06 +02:00
NEZNAMY e58390e30b Add some missing NotNull / Nullable annotations 2023-08-04 20:43:58 +02:00
NEZNAMY 41a10a61bf Small refactoring 2023-08-03 22:56:05 +02:00
NEZNAMY fdcc7d5e7c Global Playerlist - Add support for * symbol in server-groups 2023-08-01 13:35:06 +02:00
NEZNAMY 50b39e462f Remove unnecessary full manual import 2023-07-28 23:18:49 +02:00
NEZNAMY 4bc781f0da Refresh placeholders in a different thread, so they can no longer kill the plugin 2023-07-28 22:33:33 +02:00
NEZNAMY aca4c00083 Do not refresh placeholders in PlaceholderManager on join, as it can cause error when updating nested placeholder in a feature that's not loaded yet 2023-07-28 17:17:07 +02:00
NEZNAMY 77b820ad4d Do not spawn armor stands of dead players on join (#1005) 2023-07-28 17:02:42 +02:00
NEZNAMY 328f604236 Add pipeline injection for fabric 2023-07-28 17:02:13 +02:00
NEZNAMY 2f77bf3d10 Create a separate class for CPU report 2023-07-28 13:05:04 +02:00
NEZNAMY 23eb55aa06 Add back support for mojang-mapped server jars 2023-07-26 16:19:21 +02:00
NEZNAMY 32b7356bc0 Send console hint if both layout and prevent-spectator-effect are enabled 2023-07-25 18:22:34 +02:00
NEZNAMY 6091641ed9 Add warn if both layout and yellow-number features are enabled 2023-07-25 18:16:01 +02:00
NEZNAMY e368213d18 Add misconfiguration check for scoreboard chaining 2023-07-25 15:17:41 +02:00
NEZNAMY 58f9bb6fca Add unlimited nametag mode to Fabric (with a few missing things) 2023-07-24 22:26:24 +02:00
NEZNAMY 79798199b8 More abstraction of unlimited nametag mode 2023-07-24 20:53:14 +02:00
NEZNAMY c8d20f8479 Move NMS loading of classes 2023-07-24 17:59:17 +02:00
NEZNAMY c0498a60b4 Remove some console messages on startup 2023-07-24 16:46:00 +02:00
NEZNAMY faa01ddfc2 Add back compatibility attempt for nick plugins (#974) 2023-07-23 12:47:16 +02:00
NEZNAMY a2f628e267 Add try/catch for PremiumVanish hook due to its bug (#993) 2023-07-23 11:54:04 +02:00
NEZNAMY 66007b9121 Decrease component cache size to 1000 entries 2023-07-22 01:25:28 +02:00
NEZNAMY 9e0056b603 Improve source code parity between platforms (Sponge 7, Sponge 8 and Fabric) and improvements to Fabric 2023-07-22 01:22:13 +02:00
NEZNAMY b15cc3a6af Improve source code parity between platforms (BungeeCord and Velocity) 2023-07-21 22:33:55 +02:00
NEZNAMY 5393f869e7 Remove option to disable global playerlist latency update. The performance drop is almost zero and updating is actually needed as initial ping value can be invalid ((#990) 2023-07-20 18:05:39 +02:00
NEZNAMY f8088402e8 Make it more obvious that %tps% is a backend-only placeholder (#990) 2023-07-20 17:48:27 +02:00
NEZNAMY 979ff7d2b8 Move %mspt% placeholder registration into backend package 2023-07-19 23:29:07 +02:00
NEZNAMY bdbde58f42 Drop krypton platform as author abandoned it 2023-07-19 21:33:07 +02:00
NEZNAMY 1e26e0bd13 Change backend placeholder registration method 2023-07-19 19:34:36 +02:00
NEZNAMY be3c00d17e Fix colors in error messages 2023-07-16 12:53:46 +02:00
NEZNAMY d542bb67de Add a console warn if anti-override is disabled 2023-07-16 12:09:16 +02:00
NEZNAMY a79cdfaa1c Add a console warn if placeholder used in numeric sorting did not return a valid number 2023-07-16 11:46:07 +02:00
NEZNAMY 42db715421 Fix NPE 2023-07-15 21:16:10 +02:00
NEZNAMY 2228d5a35d Small cleanup 2023-07-15 21:08:18 +02:00
NEZNAMY c434d0eafd Remove component cache from Sponge 7 2023-07-15 21:01:27 +02:00
NEZNAMY 414a3a971f Optimize placeholder high_to_low and low_to_high to only take 3 characters instead of 10 and slightly increase range as well 2023-07-15 18:32:58 +02:00
NEZNAMY b8a22a2561 Fix layout fixed slot updating issue when switching between layouts 2023-07-15 15:06:25 +02:00
NEZNAMY bc1dc2f84f Fix layout skins not working on sponge 8 2023-07-15 14:24:01 +02:00
NEZNAMY 904c4f27aa Add check if a placeholder's refresh interval is configured same as default interval 2023-07-14 16:28:21 +02:00
NEZNAMY b19e5435ef Add check for redundant per-server / per-world overrides for header/footer 2023-07-14 15:45:18 +02:00
NEZNAMY 78fbcd829a Construct player object async 2023-07-14 14:22:56 +02:00
NEZNAMY a255844a92 Create separate methods for logging info/warn and use them 2023-07-14 14:19:22 +02:00
NEZNAMY c7a548c575 Fix placeholders in tablist formatting getting stuck when on bungeecord and joining into disabled server 2023-07-14 11:50:18 +02:00
NEZNAMY 90a4bab114 Fix global playerlist formatting tablist names for players meeting disable condition 2023-07-13 23:47:28 +02:00
NEZNAMY a1633d146a Fix NPE when using relational placeholders on bungeecord 2023-07-12 22:42:21 +02:00
NEZNAMY 87bf4b03df Add misconfiguration checks for bossbar properties 2023-07-12 17:58:17 +02:00
NEZNAMY 7731aea0d6 Create %player_ping% placeholder on bukkit to override the PAPI one and avoid issues like #972 when ping field changes 2023-07-10 21:37:33 +02:00
NEZNAMY e66350111e Make detectPlaceholders method 7x faster when text only consists of 1 placeholder 2023-07-09 22:27:13 +02:00
NEZNAMY 7e5df68626 Round animation change interval instead of decreasing it when invalid value is used 2023-07-09 21:25:10 +02:00
NEZNAMY 8e3e7de4ee Send warn if a placeholder took longer than 1000ms to return value 2023-07-09 18:35:03 +02:00
NEZNAMY 0ee535fbd5 Small code style improvements from PR 2023-07-09 10:45:05 +02:00
NEZNAMY 1b471d266c Merge pull request #970 from TFSMads/master
Added support for different default skins depending on the slot index in the layout feature
2023-07-09 10:38:44 +02:00
NEZNAMY 19a4f6ea1f Merge pull request #971 from misteeka/master
@NotNull marked function actually can return null
2023-07-08 19:10:36 +02:00
misteeka 7cc8d71224 Fix typo 2023-07-08 19:42:30 +03:00
_Mads b843475a0a Remove unnecessary imports and debug message 2023-07-08 15:18:05 +02:00
_Mads 027c0fa771 Added support for different default skins depending on the slot index 2023-07-08 15:09:50 +02:00
NEZNAMY c08e76f110 Fix startup error when refresh intervals are misconfigured (#969) 2023-07-08 12:46:39 +02:00
NEZNAMY b837fe7d15 4.0.4-SNAPSHOT 2023-07-08 12:34:21 +02:00
NEZNAMY d4d1bed408 4.0.3 2023-07-07 22:51:41 +02:00
NEZNAMY 8870295d54 Fix compatibility with bukkit/forge hybrids if they use different mappings 2023-07-07 22:50:32 +02:00
NEZNAMY 79cd6e4ae2 Add null check for messages form bridge (for future updates to avoid NPE / protocol break) 2023-07-07 17:36:44 +02:00
NEZNAMY 8e5ea29c40 Define decimal separator in one more way to make sure it works properly (#967) 2023-07-07 15:25:11 +02:00
NEZNAMY 287b51a0bf Send warn if using debug command on player who is not loaded (#959) 2023-07-06 20:55:16 +02:00
NEZNAMY 28cbf952fa Send warn if both per world playerlist and layout are enabled 2023-07-06 15:12:28 +02:00
NEZNAMY 598c35a3aa Simplify 2023-07-06 12:37:24 +02:00
NEZNAMY b8b67a64ab Simplify nms loading 2023-07-06 12:33:47 +02:00
NEZNAMY 7f6020ef34 Fix loading on catserver 2023-07-06 12:13:12 +02:00
NEZNAMY aeed453e15 Move named field loading to respective classes 2023-07-05 23:41:54 +02:00
NEZNAMY 71dddcca4c Drop support for mojang mapped server jar 2023-07-05 21:49:54 +02:00
NEZNAMY 46cdf79e20 Use all arg constructor for spawn entity packet on 1.17+ (using the wrong packet still spawns the entity) 2023-07-05 21:12:47 +02:00
NEZNAMY d7af96823b Merge pull request #962 from MachineBreaker/patch-1
Fix ViaVersion bug returning -1 version
2023-07-04 23:11:08 +02:00
MachineBreaker 5a228bd6b3 Fix ViaVersion bug returning -1 version
Explanation:

Both TAB and ViaVersion PlayerJoinEvent priority are LOWEST and seems that TAB registers earlier than ViaVersion the listeners, this causes TAB not being able to get the client protocol version due to ViaVersion not being able to register it firstly.

There might be another way to workaround this since i don't know changing the priority would cause any issue with TAB itself.
2023-07-04 16:19:07 -04:00
NEZNAMY fcd95655ce Send warn when trying to use parse command without any placeholders 2023-07-04 11:09:14 +02:00
NEZNAMY 7f5b57f8d2 Merge pull request #956 from Tanguygab/master
Update skins in Layout's FixedSlots
2023-07-03 17:07:01 +02:00
NEZNAMY f596877716 Fix forwarding unlimited nametag disable status to backend 2023-07-02 17:30:58 +02:00
NEZNAMY 1802c8fd90 Fix debug command not showing properties from unlimited nametag as disabled when they are 2023-07-02 17:30:35 +02:00
Tanguygab e05856facc only update name if skin didn't update 2023-07-02 13:51:37 +02:00
Tanguygab 45cfc3abb0 simplified FixedSlot skin updating 2023-07-02 13:40:08 +02:00
Tanguygab 2d39579861 added support for placeholders in layout's fixed-slots' skins 2023-07-02 13:27:37 +02:00
NEZNAMY 431f4bb4d5 Change Velocity policy 2023-07-02 00:39:10 +02:00
NEZNAMY 7bc9d38176 Slightly improve debug messages 2023-07-01 20:15:50 +02:00
NEZNAMY 7536447a8f Update README.md 2023-06-29 20:47:33 +02:00
NEZNAMY 059b796061 Update CONTRIBUTING.md 2023-06-29 20:35:44 +02:00
NEZNAMY a89942484f Layout - Fix duplicate internal names causing issues 2023-06-28 21:03:13 +02:00
NEZNAMY c28be3fd65 Add (another) warn if plugin's CPU usage is high 2023-06-26 21:04:26 +02:00
NEZNAMY 5f1116795c Add Folia support (#919) 2023-06-25 12:11:41 +02:00
NEZNAMY 4f54d2f6b2 4.0.3-SNAPSHOT 2023-06-25 10:59:05 +02:00
NEZNAMY 6b7bedbe2d Fix NPE when placeholder returns null 2023-06-24 14:30:44 +02:00
NEZNAMY 20ff225c31 Add missing set team color 2023-06-23 22:03:32 +02:00
NEZNAMY af2700798e 4.0.2 2023-06-22 13:07:44 +02:00
NEZNAMY f9c280bbdc Add back vault, so people stop asking 2023-06-22 12:50:36 +02:00
NEZNAMY 6ee31c2c3f Fix unlimited nametag property changes on world switch if per-world settings are used 2023-06-22 11:10:21 +02:00
NEZNAMY cbd944f6f2 Remove redundant field initialization 2023-06-19 21:19:48 +02:00
NEZNAMY cc20748f31 Move ping field initialization 2023-06-19 21:16:20 +02:00
NEZNAMY c9545165aa Rename a few classes 2023-06-19 20:38:19 +02:00
NEZNAMY 05c10bf2e7 Move all scoreboard packet fields & methods to PacketScoreboard class 2023-06-19 19:18:53 +02:00
NEZNAMY 67aeae2094 Create scoreboard implementation using paper api (not used yet) 2023-06-19 17:53:20 +02:00
NEZNAMY 1cc85a0647 Add API function for getting player's team name (#939) 2023-06-19 17:29:04 +02:00
NEZNAMY 988c0f8b4c Construct player object sync 2023-06-19 16:34:03 +02:00
NEZNAMY 62306fa5d6 More cleanup using @SneakyThrows annotation 2023-06-19 12:44:06 +02:00
NEZNAMY 641c5a49c3 Remove unused import 2023-06-19 11:12:16 +02:00
NEZNAMY 5637402435 Fix startup on some bukkit versions & improve compatibility error message 2023-06-18 20:54:25 +02:00
NEZNAMY 728d6c9a25 Make compatibility checks slightly more strict and remove package check. 2023-06-17 23:55:20 +02:00
NEZNAMY 22d3ba2877 Remove layout view on quit to avoid permission checks for offline players 2023-06-17 22:26:38 +02:00
NEZNAMY d1e032e010 Rename methods to reflect on recent change 2023-06-17 21:09:58 +02:00
NEZNAMY 80c4316826 Use Player#getPing() instead of NMS for 1.17+ where it was added 2023-06-17 21:01:02 +02:00
NEZNAMY 3addeba256 Bump bridge protocol 2023-06-17 17:43:21 +02:00
NEZNAMY c52db733b7 Fix potential issue with spectator fix not working if joining with spectator gamemode 2023-06-17 17:42:46 +02:00
NEZNAMY 07a492e353 Forward gamemode from bridge for spectator fix to work on online mode bungee (#934) 2023-06-17 17:42:03 +02:00
NEZNAMY 3c1ec478c2 Small code cleanup using lombok 2023-06-17 11:25:42 +02:00
NEZNAMY f3f614c3de Fix spacing 2023-06-16 17:27:50 +02:00
NEZNAMY a6627ac6c6 Merge pull request #933 from Golfing7/master
Fix loading on velocity
2023-06-16 17:26:14 +02:00
Golfing8 8d1040518f Fix loading on velocity 2023-06-16 11:11:20 -04:00
NEZNAMY ca5d17e6ec Merge pull request #930 from NEZNAMY/v4
Merge v4
2023-06-16 09:08:55 +02:00
NEZNAMY b9fab42510 Update supported version range in error message 2023-06-15 17:31:18 +02:00
NEZNAMY 22f1ef43c4 Add missing return to fix error (#927) 2023-06-15 09:44:14 +02:00
NEZNAMY ffbd4bf26b Move bungee component conversion to shared 2023-06-14 17:59:13 +02:00
NEZNAMY 4d6f93e975 Build adventure components with ProtocolVersion for manually defined legacy colors to actually work 2023-06-14 12:23:51 +02:00
NEZNAMY cb45cff77f 4.0.2-SNAPSHOT 2023-06-14 12:22:29 +02:00
NEZNAMY 4718040345 4.0.1 2023-06-13 00:11:20 +02:00
NEZNAMY afffefcce1 1.20.1 2023-06-13 00:11:10 +02:00
NEZNAMY 4580f47563 Fix bungee kick on 1.7 (#921) 2023-06-12 22:41:07 +02:00
NEZNAMY e51f4bb3a1 Remove unused methods 2023-06-12 13:34:30 +02:00
NEZNAMY e8c8d1dee7 Fix TabComplete on Sponge 7 2023-06-11 19:21:54 +02:00
NEZNAMY 3559afaef7 Fix TabComplete on Fabric 2023-06-11 18:26:39 +02:00
NEZNAMY fc29420eb3 Fix TabComplete on Sponge 8+ 2023-06-11 17:09:42 +02:00
NEZNAMY 023cd3fb75 4.0.0 2023-06-08 14:16:19 +02:00
NEZNAMY 2781d9f232 Fix errors when platform doesn't support unlimited nametags, but it's enabled in config 2023-06-08 14:15:34 +02:00
NEZNAMY d31e5de449 Fix NPE 2023-06-08 13:59:36 +02:00
NEZNAMY 245fcdf917 Update to 1.20 2023-06-08 13:50:47 +02:00
NEZNAMY e3ef3ffa68 Refactor scoreboard command and change /tab announce scoreboard to /tab scoreboard announce 2023-06-07 14:04:36 +02:00
NEZNAMY 95c3608c3f Fix typo 2023-06-07 14:03:34 +02:00
NEZNAMY 7c2236dc96 Not ready yet 2023-06-07 14:03:23 +02:00
NEZNAMY b83ef0e352 API - Add functions for force-updating placeholder values 2023-06-06 11:49:49 +02:00
NEZNAMY 2865871d04 Add check for ViaVersion bug returning -1 version 2023-06-06 11:30:42 +02:00
NEZNAMY 7f7b76a11e Fabric - Remove multi version class 2023-05-28 20:30:20 +02:00
NEZNAMY 52569e4c65 Create method in event listener class for replacing player instead of manually creating it in every class 2023-05-28 20:18:06 +02:00
NEZNAMY 0a0ae6c83b Fabric - Replace player object on respawn because it changes 2023-05-28 20:13:40 +02:00
NEZNAMY 6c8a41388e Unlimited NameTags - Fix disable-condition not working properly when set for the main feature 2023-05-28 16:43:52 +02:00
NEZNAMY fe94e33d85 Use PremiumVanish API code directly instead of reflection 2023-05-28 15:40:33 +02:00
NEZNAMY ca1be457cf Remove error message that was never actually sent 2023-05-25 21:20:22 +02:00
NEZNAMY 550b6350ea Change server version info into a method instead of field and provide more info 2023-05-25 13:38:42 +02:00
NEZNAMY 76470c880d Create player object on join async (broke it in a recode) 2023-05-25 12:23:48 +02:00
NEZNAMY a315bd88b3 Fix MySQL 2023-05-24 21:25:05 +02:00
NEZNAMY bb62930525 Simplify permission plugin data structure 2023-05-24 19:05:43 +02:00
NEZNAMY b7490f0002 Rename a few methods and fields 2023-05-24 13:21:56 +02:00
NEZNAMY 42f3851339 API - Small improvements/fixes and more documentation 2023-05-24 12:37:20 +02:00
NEZNAMY 34f39ac8d5 Fix compilation 2023-05-23 13:02:34 +02:00
NEZNAMY 8c16330119 API - Remove getApiVersion method, since everyone should be up-to-date and users are unlikely to check for it anyway 2023-05-23 12:56:33 +02:00
NEZNAMY 8323c21c3e Move ProtocolVersion enum out of API 2023-05-23 12:49:56 +02:00
NEZNAMY 2a3ca932a4 API - Add functions for unregistering placeholders 2023-05-23 12:41:48 +02:00
NEZNAMY e339928caa Conditions - Fix startsWith and endsWith types (how could they even have worked before?) 2023-05-23 12:40:22 +02:00
NEZNAMY 3eeb025f23 Add API for Layout and move team names to a new sorting class 2023-05-22 21:03:28 +02:00
NEZNAMY f19c1f1baa Another way of breaking the plugin was found by a customer 2023-05-20 18:56:17 +02:00
NEZNAMY 003e6a6adb Forward placeholder output replacements to bridge 2023-05-18 12:01:19 +02:00
NEZNAMY 95d435d61c Create field for viewer instead of passing as parameter 2023-05-17 14:25:19 +02:00
NEZNAMY bbc4e1d767 Unlimited nametags - Compensate for new 1.19.4 bug calling sneak event on vehicle leave with value "true", but never calling with "false" (#894) 2023-05-17 14:01:41 +02:00
NEZNAMY ae46adf8b9 Layout - Make layouts per-player, allowing hiding vanished players and opening new possibilities for the future 2023-05-17 13:31:02 +02:00
NEZNAMY 461ef988d3 Make Platform class an interface and make each implementation override sendConsoleMessage 2023-05-16 20:33:57 +02:00
NEZNAMY 307d21cc85 Remove nickname field, since plugin doesn't track name changes anymore 2023-05-16 19:01:51 +02:00
NEZNAMY e2beac6570 A couple of fixes for disable-conditions 2023-05-16 18:48:02 +02:00
NEZNAMY 75e455f078 Replace NonNull annotation with NotNull for internal methods 2023-05-16 12:52:04 +02:00
NEZNAMY 19c832d370 Rename a few classes 2023-05-16 11:34:02 +02:00
NEZNAMY 9cd4afe1d4 Rework disabled worlds/servers into disable conditions 2023-05-16 11:32:37 +02:00
NEZNAMY 3b00f07f78 Unlimited NameTags - Update visibility on vehicle enter/leave instantly (also maybe fix #894?) 2023-05-16 11:25:15 +02:00
NEZNAMY 0b5008c087 Remove unused fields 2023-05-14 21:59:44 +02:00
NEZNAMY 76f210db03 Print console warn if some placeholder's usage exceeds 50% 2023-05-14 17:43:29 +02:00
NEZNAMY 85c8d9f1b9 Bukkit - Add ViaRewind to softdepend to avoid console warn 2023-05-14 17:12:02 +02:00
NEZNAMY a795fc7f77 BossBar - Send console warn if progress could not be evaluated to a number between 0 and 100 2023-05-14 17:10:39 +02:00
NEZNAMY 7742ab8220 Add native support for multi-character symbols for aligned tabsuffix 2023-05-13 13:18:12 +02:00
NEZNAMY 3e235a8f18 Construct packet class with null title instead of empty string for unregister 2023-05-11 23:56:50 +02:00
NEZNAMY 30f9888f6a Split layout loading into multiple methods for better readability 2023-05-11 19:14:58 +02:00
NEZNAMY d1f8886e2b Layout - Add misconfiguration checks 2023-05-11 16:56:01 +02:00
NEZNAMY 3c037f9aa6 Create and use enums for team visibility and collision 2023-05-11 15:16:45 +02:00
NEZNAMY c70346c57c Fix NPE in injection when player is not loaded yet (#891) 2023-05-09 22:16:36 +02:00
NEZNAMY bd40dd85af Global PlayerList - Properly display players on other servers as spectators if enabled (#890) 2023-05-09 16:42:21 +02:00
NEZNAMY eacd929b0f Remove redundant fully qualified name of imported classes 2023-05-04 01:04:31 +02:00
NEZNAMY 0ff5f17b7d Create and use more constants instead of literal strings 2023-05-04 00:57:46 +02:00
NEZNAMY 57fa01151e Move command implementation for platforms into separate classes instead of being a nested class inside main class 2023-05-04 00:29:51 +02:00
NEZNAMY e7bc1fc74b Move sendPlayerListHeaderFooter method from TabPlayer to TabList 2023-04-29 21:32:40 +02:00
NEZNAMY 897f35ab53 Remove BulkUpdateTabList class and make all implementations update entries one by one 2023-04-29 16:32:51 +02:00
NEZNAMY 7f8b738a69 Add more NotNull / NonNull / Nullable annotations to methods 2023-04-29 15:10:30 +02:00
NEZNAMY 079302fb05 Small code cleanup 2023-04-28 23:31:31 +02:00
NEZNAMY 630fcc9e50 Remove underscore from class names 2023-04-27 16:08:20 +02:00
NEZNAMY c1601ca487 Remove nick compatibility hook, as it should no longer be needed 2023-04-27 14:03:47 +02:00
NEZNAMY d035b9cb80 Fix NPE 2023-04-27 13:20:49 +02:00
NEZNAMY 8db5e4d8ba Add NotNull / NonNull / Nullable annotations to fields and methods 2023-04-26 15:33:06 +02:00
NEZNAMY 4d51bb39d7 Print warn if both global playerlist and layout are enabled 2023-04-25 23:19:16 +02:00
NEZNAMY 7eb0231e08 Move floodgate hook into a separate class 2023-04-25 22:57:30 +02:00
NEZNAMY 6412461576 Simplify 2023-04-25 19:46:07 +02:00
NEZNAMY 5732eb366b Remove unused fields and methods 2023-04-25 19:36:03 +02:00
NEZNAMY ed3df8bb68 Recode redisbungee support and fix many bugs 2023-04-25 19:07:13 +02:00
NEZNAMY 787f531203 Remove unused fields 2023-04-25 19:00:57 +02:00
NEZNAMY cf5d888901 Remove unused parameter 2023-04-23 22:08:59 +02:00
NEZNAMY 1969e5f755 Global playerlist - Remove redundant entry removal on server switch (proxy removes it) 2023-04-23 15:57:43 +02:00
NEZNAMY 8d12ee0958 Remove unused import 2023-04-23 15:57:10 +02:00
NEZNAMY c3bbc89c5b Add final modifier where possible 2023-04-23 15:56:57 +02:00
NEZNAMY 625c7cf3c2 Fix issue with legacy color codes 2023-04-23 11:58:35 +02:00
NEZNAMY 14f0321992 Reduce static usage 2023-04-22 22:43:25 +02:00
NEZNAMY e5294c596a Change way of detection for presence of other plugins 2023-04-22 21:04:57 +02:00
NEZNAMY c83290a99a Fix error in RedisBungee 2023-04-22 20:56:48 +02:00
NEZNAMY 8c66310af0 Add RedisBungee support to Velocity as well 2023-04-22 19:17:05 +02:00
NEZNAMY 5ae91bf89e Update config comment (global playerlist will no longer duplicate players if this issue occurs) 2023-04-22 13:44:13 +02:00
NEZNAMY d5dc1b7bdb Simplify 2023-04-22 13:39:12 +02:00
NEZNAMY 864eee3349 Spectator Fix - Resend on server switch 2023-04-22 13:32:43 +02:00
NEZNAMY 88932e3a5e Move bungee component conversion out of shared to avoid class not found error 2023-04-20 21:59:04 +02:00
NEZNAMY 4ebc60ff00 Print original error instead of the current one 2023-04-20 21:43:05 +02:00
NEZNAMY b0cb63b05d Remove weird code in error printing 2023-04-20 21:19:08 +02:00
NEZNAMY 1db40ee2d5 Fix layout loading 2023-04-19 21:20:50 +02:00
NEZNAMY fd3f0ebd21 Move classes into packages 2023-04-19 11:46:22 +02:00
NEZNAMY ce73069345 Remove hover/click event from chat components 2023-04-18 13:54:06 +02:00
NEZNAMY 088f2748e3 Print amount of startup warns into the console 2023-04-17 20:39:35 +02:00
NEZNAMY d9a8c69f43 Refactor ViaVersion hook and drop ProtocolSupport hook as the plugin is abandoned 2023-04-17 19:23:54 +02:00
NEZNAMY 16ff503b6a Add back task queue while plugin is loading 2023-04-17 18:20:54 +02:00
NEZNAMY 68c78b9c73 Make field public 2023-04-17 18:04:17 +02:00
NEZNAMY 0c9d78a644 Fix method name 2023-04-15 18:59:54 +02:00
NEZNAMY 99d22642e2 Scoreboard - Fix placeholders not being updated in title on scoreboard switch 2023-04-15 15:22:02 +02:00
NEZNAMY dc762eacf8 Fill all fields in plugin.yml 2023-04-13 20:47:01 +02:00
NEZNAMY 5a44f30140 Reorder permission constants 2023-04-13 20:37:53 +02:00
NEZNAMY 82e86b0f8a Fix NPE on reload 2023-04-13 18:02:21 +02:00
NEZNAMY 9c4eb4359c Document empty methods 2023-04-12 21:53:32 +02:00
NEZNAMY 827c380463 Move more reflection related methods to ReflectionUtils 2023-04-12 21:50:01 +02:00
NEZNAMY 7c4fc324c8 Simplify 2023-04-12 21:18:29 +02:00
NEZNAMY 46215f3304 Fix indentation 2023-04-12 21:15:16 +02:00
NEZNAMY 1accc21365 Avoid possible NPE 2023-04-12 21:11:29 +02:00
NEZNAMY b74c2f62f7 Replace star import with explicit imports 2023-04-12 21:08:38 +02:00
NEZNAMY 9d14c0c923 Resend header/footer on server switch because velocity clears it 2023-04-12 16:17:12 +02:00
NEZNAMY 650fda5781 Reorder class loading to make it cleaner 2023-04-11 16:43:40 +02:00
NEZNAMY 286ccf5836 Reorder class loading to make it cleaner 2023-04-10 19:08:54 +02:00
NEZNAMY dfa310666c Move reflection related methods to ReflectionUtils 2023-04-10 19:02:35 +02:00
NEZNAMY 06595241ab Remove plugins from softdepend which are no longer being hooked into 2023-04-10 18:38:57 +02:00
NEZNAMY 0f80dd1cfd Simplify 2023-04-10 18:33:02 +02:00
NEZNAMY 7bc5be264c Define dependencies directly instead of using versions file 2023-04-10 16:23:44 +02:00
NEZNAMY e31784fb1e Include initial task delay to prevent errors while plugin is still loading 2023-04-10 13:15:14 +02:00
NEZNAMY ae156f424b Move layout to config.yml 2023-04-10 13:11:41 +02:00
NEZNAMY cfe0b3883e Remove deleted placeholder from default configuration 2023-04-10 12:49:54 +02:00
NEZNAMY fc6beaa0a5 Add removing of removed config option to converter 2023-04-10 12:45:11 +02:00
NEZNAMY 5f2140b147 Make placeholder output replacements no longer require splitting config section with each "." used in placeholder 2023-04-10 12:43:28 +02:00
NEZNAMY c497bdff39 Swap condition registration order for proper detection of type 2023-04-10 12:22:16 +02:00
NEZNAMY 7c84b6432e Simplify condition classes (or maybe just make them more complicated?) 2023-04-10 01:21:14 +02:00
NEZNAMY 3f787c3b0b Create separate Platform class for consistency 2023-04-10 00:00:32 +02:00
NEZNAMY 2046a39e99 Simplify 2023-04-09 23:42:32 +02:00
NEZNAMY 2ccf89e396 Further simplify Fabric code changes for older server versions 2023-04-08 22:45:43 +02:00
NEZNAMY efaebc3cb0 Simplify code changes for older versions on Fabric 2023-04-08 17:03:56 +02:00
NEZNAMY d7c952cc36 Fix placeholders from bridge not updating if player is not loaded yet when response is sent 2023-04-08 15:19:56 +02:00
NEZNAMY 1d3ea37129 Prevent bridge form re-registering placeholders that got deleted from config 2023-04-08 15:06:58 +02:00
NEZNAMY 1e9c47398d Add bridge connection status to debug command 2023-04-08 14:43:05 +02:00
NEZNAMY 5a1f48f1a5 Apparently it cannot be null 2023-04-08 14:25:03 +02:00
NEZNAMY ed65554d30 Remove duplication in player/playeruuid/group commands 2023-04-07 22:36:35 +02:00
NEZNAMY 0f19970401 Clean up ChatModifier serialization 2023-04-07 22:04:57 +02:00
NEZNAMY b419499710 Remove unnecessary fully qualified name 2023-04-07 21:58:38 +02:00
NEZNAMY 14a5f30b74 Split method for processing plugin messages into multiple 2023-04-07 21:48:38 +02:00
NEZNAMY 302cdf200f Small refactor of classes implemented by platforms 2023-04-07 21:32:15 +02:00
NEZNAMY bdde257cac Remove unused fields 2023-04-07 13:01:25 +02:00
NEZNAMY 73af013bca Change usage of methods which didn't exist before on fabric 2023-04-06 18:26:26 +02:00
Callum Seabrook e421427007 Disabled access widener validation task for Fabric 2023-04-06 16:17:51 +01:00
NEZNAMY 169a180d68 Remove unused imports 2023-04-06 16:28:28 +02:00
Callum Seabrook 16a19bf578 Switched to access wideners for player info entries on Fabric 2023-04-06 14:29:43 +01:00
Callum Seabrook 5b39204a8a Cleaned up Krypton code 2023-04-06 13:54:54 +01:00
Callum Seabrook b2bb125248 Switched Krypton tab list to API and removed server dependency 2023-04-06 13:35:08 +01:00
NEZNAMY 980354104b Remove fabric constant that is only used once 2023-04-06 00:18:15 +02:00
NEZNAMY b27571dcaa Catch UnsupportedOperationException 2023-04-05 22:27:44 +02:00
NEZNAMY b215bf9000 Fix dumb mistake 2023-04-05 21:10:04 +02:00
NEZNAMY ab75b13b3f Add option to choose target minecraft version on Fabric all the way down to 1.17 2023-04-05 20:42:51 +02:00
Callum Seabrook 31cd2aeb08 Made getFeature generic and removed casts 2023-04-05 19:18:39 +01:00
Callum Seabrook 8b6b33b314 Fixed compile 2023-04-05 18:40:10 +01:00
Callum Seabrook 1b66257ece Removed chat session from ProxyTabPlayer 2023-04-05 18:30:52 +01:00
Callum Seabrook bed9fcf212 Show new blank scoreboard to joining players on Krypton 2023-04-05 18:22:01 +01:00
Callum Seabrook 692f309750 Switched to API instead of packets for Krypton scoreboard 2023-04-05 16:30:19 +01:00
Callum Seabrook 2c9c9b502a Renamed Main to KryptonTAB for consistency 2023-04-05 15:48:50 +01:00
Callum Seabrook 3da8fd2a77 Fixed failed startup without Spark present on Krypton 2023-04-05 15:45:56 +01:00
Callum Seabrook ba6826f3d1 Added variable to change target Minecraft version for Fabric 2023-04-05 12:19:59 +01:00
Callum Seabrook 68b3629437 Added permissions API support for Fabric 2023-04-05 12:08:32 +01:00
Callum Seabrook 8bc5962d1c Use reflection for changing player info entries 2023-04-05 11:55:25 +01:00
NEZNAMY b8aa1982e4 Improve ViaVersion check on Fabric 2023-04-05 00:57:35 +02:00
NEZNAMY e0a427732e Update README.md 2023-04-04 23:25:38 +02:00
NEZNAMY 8d1f60f5fa Fix github actions 2023-04-04 23:16:15 +02:00
NEZNAMY 5cd7fdf385 Small improvements and fixes to fabric 2023-04-04 23:14:51 +02:00
Callum Seabrook e54d342ac5 Made relocations have a consistent pattern 2023-04-04 21:30:52 +01:00
Callum Seabrook 9019261c2a Fabric support 2023-04-04 21:27:55 +01:00
Callum Seabrook b1a5092053 Added Adventure-based boss bar handler for platforms using Adventure 2023-04-04 20:11:27 +01:00
NEZNAMY ef8bdbb40f Spectator fix - Add delay on join to avoid being too early 2023-04-04 21:08:19 +02:00
Callum Seabrook 0d67826b3d Removed message component cache on Bukkit 2023-04-04 19:34:24 +01:00
NEZNAMY 6c99e28d51 Improve code style in Scoreboard class 2023-04-04 17:21:38 +02:00
NEZNAMY 13931aafa5 Rethrow exceptions as IllegalStateException instead of RuntimeException 2023-04-04 17:01:17 +02:00
NEZNAMY 089e7ce8b5 Move class out of package 2023-04-04 15:52:01 +02:00
NEZNAMY 54287c8a29 Fix color getter 2023-04-04 15:41:18 +02:00
NEZNAMY 5ebc5dee21 Make gamemode change detection a periodic task instead of listening to packets 2023-04-04 15:29:34 +02:00
NEZNAMY bedbdbcca9 Remove redundant interface class 2023-04-04 14:44:37 +02:00
NEZNAMY a1c35adf4c Convert to adventure components directly instead of deserializing string 2023-04-04 14:34:56 +02:00
NEZNAMY 81b6a9be8b Remove redundant component deserialization attempts 2023-04-04 13:50:21 +02:00
Callum Seabrook 88eee58329 Moved chat system out of API 2023-04-02 22:49:15 +01:00
Callum Seabrook 2f02a5bda4 Removed unlimited nametags from Krypton 2023-04-02 21:11:05 +01:00
Callum Seabrook ba716df634 Removed unlimited nametags from Sponge 8 2023-04-02 20:12:30 +01:00
NEZNAMY 988292b82a XD 2023-04-02 16:30:06 +02:00
Callum Seabrook d52cfdd6e6 Start of Fabric 2023-04-02 13:55:38 +01:00
Callum Seabrook b13a49539f Updated to Gradle 8 2023-04-02 13:47:37 +01:00
NEZNAMY 32a39a361c Improve marking vanish placeholder as used for features listening to vanish status change 2023-04-02 13:46:20 +02:00
NEZNAMY 0e2e9b9ae6 Fix playerlist formatting not updating on unvanish 2023-04-02 13:41:14 +02:00
NEZNAMY 331faefde2 Fix compilation 2023-04-02 12:57:46 +02:00
NEZNAMY 1603ebe9fc Remove unused parameter 2023-04-02 12:43:15 +02:00
NEZNAMY bad4359469 Remove usage of separated initializers 2023-04-02 12:17:16 +02:00
NEZNAMY a860b463ed Merge ifs 2023-04-02 12:13:01 +02:00
NEZNAMY b3b0a96bb8 Remove commented out code that will never come back 2023-04-02 12:11:20 +02:00
NEZNAMY c211724d13 RedisBungee - Don't send yellow number / belowname if feature is disabled (#861) 2023-04-02 12:02:01 +02:00
NEZNAMY 0fb9a832ca Fix raw generic use 2023-04-02 00:34:42 +02:00
NEZNAMY 7ce1a97c93 Remove player from map on quit 2023-04-02 00:10:33 +02:00
NEZNAMY 1d461c1adf Make Scoreboard class generic to avoid massive casting 2023-04-02 00:02:31 +02:00
NEZNAMY 5ec09c37bc Remove things from API which shouldn't be there 2023-04-01 22:10:11 +02:00
NEZNAMY 95a20766c3 Simplify redisbungee belowname/yellow number forwarding 2023-04-01 17:41:25 +02:00
NEZNAMY 87feb1c2bf Improve misconfiguration checks and management 2023-04-01 17:33:34 +02:00
NEZNAMY 8c732ac7e9 Use defined constant instead of literal string 2023-04-01 13:01:26 +02:00
NEZNAMY 977c1f4d74 Remove unused field 2023-04-01 12:46:04 +02:00
NEZNAMY 65ac22aadb Perform scoreboard objective & team re-registration on server switch instead of listening to Login packet, which is assumed to be sent always 2023-04-01 12:37:02 +02:00
NEZNAMY 8e7a14bdb8 PlayerList - Format players after they un-vanish 2023-04-01 12:07:19 +02:00
NEZNAMY 4693a297b6 Remove unused field 2023-04-01 01:41:03 +02:00
NEZNAMY af69dcd9b5 Add this branch to github actions 2023-04-01 01:20:56 +02:00
NEZNAMY 71110fc722 Remove redundant conversion from older versions 2023-04-01 01:16:50 +02:00
NEZNAMY c2af5632a2 Remove hidden ghost player fix feature 2023-04-01 01:13:21 +02:00
NEZNAMY 399e813ca4 Remove placeholder refresh intervals categories to reduce confusion, split them in code based on identifier 2023-04-01 01:10:05 +02:00
NEZNAMY daac7c6171 Rename config conversion methods 2023-04-01 00:52:30 +02:00
NEZNAMY 940352ef2c Replace null checks with @NonNull 2023-04-01 00:47:21 +02:00
NEZNAMY c70b909c0f Remove disabled worlds/servers from bossbar and scoreboard, since conditions can achieve the same 2023-04-01 00:43:36 +02:00
NEZNAMY dcc2eb9217 Remove vault hook, vault placeholders and %afk% placeholder 2023-04-01 00:34:12 +02:00
NEZNAMY 5425b3b6ea Finish previous commit and remove pet fix feature 2023-04-01 00:17:48 +02:00
NEZNAMY a90ea3e3fa Remove ping spoof feature 2023-04-01 00:03:21 +02:00
NEZNAMY 6d52b7b615 4.0.0-SNAPSHOT 2023-04-01 00:01:43 +02:00
NEZNAMY 51635a7322 3.3.2 2023-03-31 20:44:09 +02:00
NEZNAMY 481b33ba55 Print error message if plugin message could not be sent 2023-03-31 15:30:30 +02:00
NEZNAMY 22c785fc99 Add error message when plugin message could not be processed 2023-03-31 15:21:53 +02:00
NEZNAMY 42d4159b69 Global PlayerList - Register placeholders in constructor to avoid it being registered as PAPI placeholder 2023-03-31 14:41:21 +02:00
NEZNAMY 6d95a2f06d Fix error on concurrent access 2023-03-31 13:40:50 +02:00
NEZNAMY 7f0b1d8a2e Fix rare error on /btab reload 2023-03-31 13:40:37 +02:00
NEZNAMY a3fecd0fea Unregister tab expansion on unload to fix compatibility with latest PAPI release 2023-03-30 21:17:09 +02:00
NEZNAMY f3d6a54a2a Merge remote-tracking branch 'origin/master' 2023-03-29 17:24:21 +02:00
NEZNAMY 1d434f2557 Add more documentation 2023-03-29 17:24:11 +02:00
NEZNAMY b3500086e4 Merge pull request #856 from zelvalea/master
optimizations in CPUManager
2023-03-28 23:23:48 +02:00
Amoeba bf9202af38 added time constants 2023-03-28 20:31:29 +03:00
Amoeba bd2c683568 fixed happens before and cpu usage statistics 2023-03-28 20:09:51 +03:00
Amoeba c03feaab23 fixed nanosToPercent 2023-03-28 19:44:11 +03:00
Amoeba 9cb9ccbd2a optimize 2023-03-28 19:21:48 +03:00
Callum Seabrook 4606ebd588 Use single thread for tasks and remove ThreadManager from API 2023-03-28 12:32:13 +01:00
Callum Seabrook 679be83c5a Merge pull request #854 from zelvalea/master 2023-03-28 11:51:03 +01:00
NEZNAMY afe868bb95 Fix group update through bridge after recoding group updating 2023-03-28 01:21:29 +02:00
ZelvaLea c9ce3aa23e reduced number of threads in the pool, fixed races 2023-03-28 01:15:04 +03:00
NEZNAMY 11a49c4874 Remove unnecessary full import 2023-03-27 21:21:27 +02:00
NEZNAMY 65d2a0fba9 Remove unused field 2023-03-27 21:19:15 +02:00
NEZNAMY c1219195c7 Remove workarounds for fixing compilation, which are no longer needed 2023-03-26 17:06:57 +02:00
NEZNAMY fca0dfc91b More constant extraction to plugin.yml & bungee.yml 2023-03-26 17:02:32 +02:00
NEZNAMY 7f1cc3063c Move text cutting to 16/32 characters to avoid duplication 2023-03-26 16:53:47 +02:00
NEZNAMY e444c25287 Convert krypton to java 2023-03-26 16:36:51 +02:00
NEZNAMY 476354e407 Change thrown exception 2023-03-25 23:25:07 +01:00
NEZNAMY 9f84d3f22f Remove cpu measurement of chosen methods 2023-03-25 23:22:28 +01:00
NEZNAMY 4608ff360c Simplify 2023-03-25 23:12:48 +01:00
NEZNAMY 68b12e199d Placeholders - Measure per-placeholder cpu usage on join as well 2023-03-25 20:54:53 +01:00
NEZNAMY 4b1151f769 Use defined value instead of duplicating it in build settings 2023-03-25 20:25:27 +01:00
NEZNAMY 6687214f2a Sponge - Add support all the way down to Sponge 5 2023-03-25 19:22:07 +01:00
NEZNAMY a57757258e Sponge - Use API for scoreboards instead of packets (also small bug fix) 2023-03-25 18:41:56 +01:00
NEZNAMY 3a6060c45d Fix display name change on 1.7 2023-03-25 14:29:27 +01:00
NEZNAMY ee375c1b60 Unify tablist management between proxies 2023-03-25 14:22:40 +01:00
NEZNAMY c9d870cca0 Use placeholder refreshing engine for refreshing player groups instead of creating another thread 2023-03-23 14:52:19 +01:00
NEZNAMY 3ad8015cc1 Placeholder manager - Initialize task with the lowest refresh interval immediately instead of re-initializing 2023-03-23 14:19:42 +01:00
NEZNAMY 33609c322f Remove duplicated Field#setAccessible 2023-03-22 00:59:02 +01:00
NEZNAMY 431b89c487 Create constant for "textures" String in skin data 2023-03-22 00:33:16 +01:00
NEZNAMY 35a146992c Remove redundant checks and simplify 2023-03-22 00:27:46 +01:00
NEZNAMY dedee114c4 BungeeCord - Split boss bar handler into 2 classes 2023-03-21 20:07:11 +01:00
NEZNAMY 9d42af0388 Bukkit - Split boss bar handler into 3 classes 2023-03-21 18:39:08 +01:00
NEZNAMY 8f5d81bc33 Remove redundant annotation 2023-03-21 18:38:41 +01:00
NEZNAMY b17f5563dd Remove unused method 2023-03-21 14:09:57 +01:00
NEZNAMY 0ab8bb363a Unlimited NameTags - Force marker tag for 1.8.x and remove config option 2023-03-20 22:33:56 +01:00
NEZNAMY ac5fca86bd Split TabFeature into multiple interfaces based on used methods 2023-03-20 19:36:40 +01:00
NEZNAMY 28c2c2e193 Fix armor stand metadata on spawn 2023-03-20 16:25:37 +01:00
NEZNAMY 904c0b6a17 Fix anti-override for tablist name formatting (and possibly ping spoof & spectator fix) 2023-03-20 16:21:23 +01:00
NEZNAMY f3c31bc92c Fix error 2023-03-20 12:42:42 +01:00
NEZNAMY 6016b84889 BungeeCord - Fix bossbar color & style update 2023-03-19 22:24:13 +01:00
NEZNAMY cfb5cb3a9f Create object for calling bossbar methods 2023-03-19 22:07:45 +01:00
NEZNAMY 1746f6b1d0 Bukkit - Send all tablist packets grouped, ViaVersion splits them properly for 1.7 players 2023-03-19 21:21:33 +01:00
NEZNAMY 9c3979a0d9 More tablist fixes on 1.7 2023-03-19 21:04:52 +01:00
NEZNAMY 810676c69f Bukkit - Fix NPE on <1.8 servers 2023-03-19 16:44:55 +01:00
NEZNAMY d0892a245a BungeeCord - Fix NPE kick on 1.7 2023-03-19 16:40:53 +01:00
NEZNAMY f539b1b93b BungeeCord - Split TabList class into 3 based on version & make 1.7 actually work (potential future use?) 2023-03-19 14:50:02 +01:00
NEZNAMY adfa64a29f Scoreboard - Fix unregistering teams of lines that aren't displayed due to placeholder output being empty 2023-03-19 14:07:44 +01:00
NEZNAMY d4f86ee79d Remove redundant declaration 2023-03-19 13:43:06 +01:00
NEZNAMY 6ebe31b737 Add @NonNull / @Nullable annotations to TeamManager (#843) 2023-03-19 13:38:58 +01:00
NEZNAMY dcbd5e62d4 Fix packet un-cancel breaking PetFix (#842) 2023-03-19 13:26:24 +01:00
NEZNAMY a7e6e49363 Fix #845 (1.7 requires non-null title in scoreboard unregister action) 2023-03-19 12:52:56 +01:00
NEZNAMY d115257ff7 Move DisplaySlot class inside Scoreboard 2023-03-18 23:53:49 +01:00
NEZNAMY d578be276c Remove duplicated caches 2023-03-18 22:35:16 +01:00
NEZNAMY e338f57441 Completely get rid of PacketBuilder class 2023-03-18 22:25:05 +01:00
Callum Seabrook c87321b5b0 Rewrote packet injection for Krypton 2023-03-18 19:37:34 +00:00
NEZNAMY 84bb9b449b Add methods for manipulating entities instead of using sendPacket 2023-03-18 19:22:29 +01:00
NEZNAMY 9d613fc24d Rework tablist packet management to make it more API-style 2023-03-18 17:44:43 +01:00
NEZNAMY 3f2ae3131e Add back a default animation, which is still used in default scoreboard 2023-03-18 00:14:56 +01:00
NEZNAMY 0e9f53bd58 Fix wrong action on objective unregister 2023-03-17 18:20:47 +01:00
NEZNAMY 3981815b63 Create Scoreboard interface for calling scoreboard-related methods 2023-03-17 15:04:55 +01:00
NEZNAMY 3ef99e4e7c Better abstraction of pipeline injection 2023-03-16 15:16:24 +01:00
NEZNAMY 76767a0958 Use API-style managing of scoreboard teams 2023-03-16 13:53:50 +01:00
NEZNAMY 1683943d58 Use API-style managing of scoreboard objectives 2023-03-16 01:07:21 +01:00
NEZNAMY cc0dbe8623 Move sendPluginMessage method to ProxyTabPlayer 2023-03-16 00:03:13 +01:00
NEZNAMY b056251e07 Remove calculating of packets sent by the plugin 2023-03-15 23:50:23 +01:00
NEZNAMY 3b352c66e2 Change way how scoreboard score and display objective packets are sent 2023-03-15 23:31:30 +01:00
NEZNAMY b4f0ea73c1 Supplier with Exception is Callable 2023-03-15 21:43:27 +01:00
NEZNAMY 1c683788b5 BungeeCord - Create cleaner methods for player info action conversion 2023-03-15 21:29:58 +01:00
NEZNAMY 250597df2d Sponge 8 - Fix class cast exception 2023-03-15 21:07:20 +01:00
NEZNAMY 0d2d1d5aaa Sponge 8 - Use direct code instead of reflection for entity teleport packet 2023-03-15 21:00:42 +01:00
NEZNAMY 061375d1e7 3.3.2-SNAPSHOT 2023-03-15 21:00:14 +01:00
NEZNAMY 5b05614305 Remove unused field 2023-03-15 20:13:29 +01:00
NEZNAMY 69db2e0e53 Use API style code for bossbars since packets were not used anymore 2023-03-15 20:03:10 +01:00
NEZNAMY 00ef364324 Use APIs for header/footer instead of packets where possible (rip bukkit) 2023-03-15 16:13:31 +01:00
NEZNAMY de68312b6b Use APIs for chat messages instead of packets 2023-03-15 14:36:25 +01:00
NEZNAMY ddac08bcbc Fix bungee compilation (did md5 break something?) 2023-03-15 14:31:59 +01:00
NEZNAMY 517e285bb3 Fix bossbar error on <1.9 from recode 2023-03-15 14:29:13 +01:00
NEZNAMY 81a08a6b51 3.3.1 2023-03-14 19:18:01 +01:00
NEZNAMY 4dfef1d032 1.19.4 support 2023-03-14 19:15:15 +01:00
NEZNAMY 5d38e28cae Extract more constants and add config option 2023-03-14 18:35:25 +01:00
NEZNAMY 66a12d10c4 Layout - Resend on server switch on Velocity 2023-03-14 17:27:27 +01:00
NEZNAMY 1a4027c8d5 Remove unused fields and methods 2023-03-14 16:15:55 +01:00
NEZNAMY 2a0243c51a RedisSupport - Add missing packet source 2023-03-14 15:24:13 +01:00
NEZNAMY 4851d09e70 More abstraction of unlimited nametag mode & packets it uses 2023-03-14 15:20:49 +01:00
NEZNAMY 3cdf3accfd Remove redundant checks 2023-03-14 10:46:27 +01:00
NEZNAMY fd1f094216 Layout - Small cleanup in code 2023-03-14 00:22:02 +01:00
NEZNAMY 25ece1ecb8 Move ViaVersion hook (and remove safety checks, hopefully it works correctly now) 2023-03-13 23:31:10 +01:00
NEZNAMY 802e65c280 Change constructor fields to getters in features 2023-03-13 23:04:22 +01:00
NEZNAMY 882688f056 Layout - Cache remove packet 2023-03-13 21:52:29 +01:00
NEZNAMY 97d60d0ab3 Bungee - Remove unused field (and simplify) 2023-03-13 21:33:48 +01:00
NEZNAMY 9ef99daed1 Revert feature loading as references were not null when they were supposed to be 2023-03-13 21:31:48 +01:00
NEZNAMY 71c85d9d39 NameTags - Speed up unloading 2023-03-13 19:36:05 +01:00
NEZNAMY b981ab46be Add debug messages about feature unloading times 2023-03-13 19:35:45 +01:00
NEZNAMY 016633e494 BelowName - Speed up reloads by caching values for players (tested with 100 bots, reloads were 2x faster) 2023-03-13 19:22:00 +01:00
NEZNAMY 1db862ab56 NameTags - Speed up reloading (tested with 100 bots, reload was 3x faster) 2023-03-13 19:15:45 +01:00
NEZNAMY 3563786310 PlayerList - Drastically speed up reloads (up to 10+ times with high player counts) 2023-03-13 19:01:26 +01:00
NEZNAMY ac8851dd6a Cache EnumSets for info packet for better performance 2023-03-13 18:22:16 +01:00
NEZNAMY 53ab2d9793 Commands - Do not load widths for widthcheck command unless command is used 2023-03-13 18:03:55 +01:00
NEZNAMY 2a58c86ffc Move code from initializer to load 2023-03-13 16:35:30 +01:00
NEZNAMY fa46d527ae Spectator Fix - Don't send useless packets on load/unload 2023-03-13 15:17:13 +01:00
NEZNAMY 667bd38305 Velocity - Fix Velocity being Velocity exception 2023-03-13 14:51:52 +01:00
NEZNAMY faba439466 Global PlayerList - Send all affected entries in a single packet, reducing amount of packets sent and improving performance 2023-03-13 14:47:29 +01:00
NEZNAMY c301157758 Global PlayerList - No longer delay task on quit as I was not able to reproduce the bug; Update placeholders periodically as they didn't update properly on vanish change anyway 2023-03-13 14:14:06 +01:00
NEZNAMY 8d09d425a4 Global PlayerList - No longer prevent other sources from removing players, better organize adding on server switch as well 2023-03-13 13:26:12 +01:00
NEZNAMY 11f03d5a10 Simplify 2023-03-13 12:58:19 +01:00
NEZNAMY 83625202a1 Merge pull request #841 from NEZNAMY/better-pipeline-injection
Better pipeline injection
2023-03-12 14:39:19 +01:00
Callum Seabrook eb4b715a88 Move pipeline injection to its own package 2023-03-12 13:35:47 +00:00
Callum Seabrook 89dce941cf Add RequiredArgsConstructor to NettyPipelineInjector 2023-03-12 13:31:58 +00:00
Callum Seabrook 6371b67e9e Removed player Channel and moved Netty injection to NettyPipelineInjector 2023-03-12 13:23:16 +00:00
NEZNAMY 966e27418a Bukkit - Attempt to fix error on ArrayList#remove 2023-03-11 15:35:31 +01:00
NEZNAMY 4adf7d1734 Bukkit - Fix NoSuchElementException that doesn't make sense 2023-03-11 15:25:06 +01:00
NEZNAMY 1a4c4a6fe8 Add more debug messages about feature loading times 2023-03-11 15:14:12 +01:00
NEZNAMY 7ff36592ae Do not re-initialize ErrorManager 2023-03-11 14:27:23 +01:00
NEZNAMY 1b513412d2 Remove redundant import 2023-03-10 13:20:14 +01:00
NEZNAMY f2bb43fbcd Make tabExpansion variable not null and add default empty implementation 2023-03-10 13:16:22 +01:00
NEZNAMY df5d07d885 Include forgotten code from previous commit 2023-03-10 13:13:21 +01:00
NEZNAMY 0ad2f33c4c Further clean up feature loading and mutual hooks 2023-03-10 12:45:28 +01:00
NEZNAMY dee48fccb3 Partially de-spaghetti feature loading and mutual hooking 2023-03-09 00:13:15 +01:00
NEZNAMY 9ca372ab13 Velocity is XD 2023-03-07 23:07:26 +01:00
NEZNAMY 58a849a902 Update README.md 2023-03-06 18:15:04 +01:00
NEZNAMY 895072ec35 Update README.md 2023-03-06 18:07:14 +01:00
NEZNAMY 2b2c4d2c39 Update README.md 2023-03-06 12:03:45 +01:00
NEZNAMY d534b6e2e8 Layout - Make fixed slots take priority over player groups if a slot is used in both 2023-03-05 15:31:14 +01:00
NEZNAMY 783e9d9931 Fix wrong values in widths.txt 2023-03-02 21:06:15 +01:00
NEZNAMY a109d9b55b Enable widthcheck command and fix error in it 2023-03-02 20:54:28 +01:00
NEZNAMY 5ce59ce44f Fix compilation 2023-03-02 20:33:37 +01:00
NEZNAMY 1e9195a9bc Velocity - Add try/catch for calling new methods on old versions 2023-03-01 18:49:41 +01:00
NEZNAMY 3099d16468 Create SECURITY.md 2023-03-01 12:23:39 +01:00
NEZNAMY c9c6af346c Add new issue templates 2023-03-01 12:18:23 +01:00
NEZNAMY af80a43628 Catch Throwable in pipeline instead of Exception to reduce chance of plugin causing player disconnect in future unsupported versions 2023-02-28 23:33:31 +01:00
NEZNAMY dc3c98ec91 Bukkit - Change how ViaVersion is detected (to avoid wrong detection due to a plugin order load bug in 1.17.1) 2023-02-25 18:41:25 +01:00
NEZNAMY dffa2c83f6 Typo 2023-02-24 23:45:40 +01:00
NEZNAMY dc66f253a7 Config converter - Remove placeholder that got deleted 2023-02-24 23:42:35 +01:00
NEZNAMY 17498cbfc2 BungeeCord - Fix bytebuf deserialization from previous commits 2023-02-24 23:15:01 +01:00
NEZNAMY 96e4d30168 Include 1.5 in supported version range 2023-02-24 16:48:56 +01:00
NEZNAMY c9d1934bb9 Use lombok for empty private constructor 2023-02-24 16:33:39 +01:00
NEZNAMY 8bec083194 Remove redundant duplicated checks 2023-02-24 16:30:34 +01:00
NEZNAMY abf047be23 Fix global playerlist class being instantiated twice 2023-02-24 16:22:02 +01:00
NEZNAMY 4a95399d56 3.3.1-SNAPSHOT 2023-02-24 16:19:01 +01:00
NEZNAMY 885bf5154e NameTags - Fix updating nametag visibility of players in disabled world/server 2023-02-24 15:43:58 +01:00
NEZNAMY 3e146fc496 Simplify 2023-02-24 15:41:30 +01:00
NEZNAMY 95710bcaf5 Sorting - Fix team name change registering teams for players in disabled worlds/servers 2023-02-24 15:37:40 +01:00
NEZNAMY edd620f17d BelowName - Fix text refreshing packets being sent to players in disabled worlds/servers 2023-02-24 15:25:09 +01:00
NEZNAMY 9c466d8b1e Update comment 2023-02-24 14:57:56 +01:00
NEZNAMY b385ab70fb Simplify 2023-02-24 14:52:22 +01:00
NEZNAMY 140224840b Remove double registration of sorting feature 2023-02-23 21:24:10 +01:00
NEZNAMY 2078cd5a5f Downgrade sponge API dependencies to confirm support 2023-02-21 18:46:43 +01:00
NEZNAMY 27bae7fdbd 3.3.0 2023-02-21 17:03:39 +01:00
NEZNAMY 174b4214de Bukkit - Fix error from previous commits 2023-02-21 13:52:21 +01:00
NEZNAMY 4ac524840c Remove non-existing branch from build.yml 2023-02-20 20:42:48 +01:00
NEZNAMY dd4ce4e2c3 Remove redundant library (gson) 2023-02-20 17:34:38 +01:00
NEZNAMY 2a03d69628 Add @SuppressWarnings annotation to a method 2023-02-20 17:33:44 +01:00
NEZNAMY c28252e6ce RGB - Fix redundant color codes in conversion to legacy, causing issues with limits (#827) 2023-02-20 17:02:20 +01:00
NEZNAMY da1768d833 Further code cleanup 2023-02-20 16:43:49 +01:00
NEZNAMY b58c3e6b47 Fix typos 2023-02-20 12:11:58 +01:00
NEZNAMY c7575bdc8f Remove redundant throws and empty method 2023-02-20 12:00:01 +01:00
NEZNAMY e275d22061 Bukkit - Fix startup on <1.8 2023-02-20 00:35:50 +01:00
NEZNAMY b5600ef22a Fix merge conflict 2023-02-20 00:29:05 +01:00
NEZNAMY b8e14c9256 Merge branch 'master' of https://github.com/NEZNAMY/TAB
 Conflicts:
	krypton/src/main/kotlin/me/neznamy/tab/platforms/krypton/KryptonPlaceholderRegistry.kt
2023-02-19 23:58:11 +01:00
NEZNAMY 1af5b9a308 Bukkit - Further split NMS loading and packet creation 2023-02-19 23:58:01 +01:00
Callum Seabrook 2dd719c3a5 Added Spark placeholder support back to Krypton 2023-02-19 21:08:04 +00:00
NEZNAMY 45d94694cd Bukkit - Split NMS fields and initialization into multiple classes 2023-02-19 21:15:10 +01:00
NEZNAMY 3ce8c6a192 Code style - Extract duplicated code 2023-02-19 17:41:06 +01:00
NEZNAMY 7deb86f105 Fix typo 2023-02-19 17:28:25 +01:00
NEZNAMY d9b4f6a43f Bukkit - Move data watcher serializer initialization 2023-02-19 17:15:58 +01:00
NEZNAMY 54a7fb8721 Code style - Extract duplicated code for permission plugin detection 2023-02-19 16:08:02 +01:00
NEZNAMY d1dcb2f437 Merge remote-tracking branch 'origin/master' 2023-02-18 17:35:03 +01:00
NEZNAMY a92bab617a Unlimited nametags - Fix error caused by wrong method being called 2023-02-18 17:34:52 +01:00
Callum Seabrook 57e34bc595 Updated to latest Krypton and added TAB prefix to console 2023-02-15 23:58:25 +00:00
Callum Seabrook 953baa273c Build Krypton targeting Java 8 2023-02-15 20:23:39 +00:00
NEZNAMY c403cb3187 Fix startup after recode 2023-02-15 21:03:19 +01:00
NEZNAMY ffd6dc5bb9 Disable krypton compilation to fix Sponge 7 startup (due to a Sponge 7 bug) 2023-02-15 20:53:06 +01:00
NEZNAMY 0f6504a3dc Remove unused local variable 2023-02-15 20:28:55 +01:00
NEZNAMY f376fea598 Formatting 2023-02-15 20:26:50 +01:00
NEZNAMY 4896cdff91 Further extend lombok usage 2023-02-15 19:17:29 +01:00
NEZNAMY b4c87431bc Scoreboard - Fix placeholders rarely not refreshing in title 2023-02-15 19:16:29 +01:00
NEZNAMY 1cffbab97d Remove plugin message text from /tab cpu due to being impossible to read 2023-02-15 15:14:30 +01:00
NEZNAMY 9b0711075a Code style - Use constants in more places 2023-02-15 15:06:15 +01:00
NEZNAMY 17a8e902eb Velocity - Use data folder given by the platform instead of manually defining it 2023-02-15 15:01:16 +01:00
Callum Seabrook 4bdb5ba245 Use more constants for plugin info 2023-02-15 12:16:48 +00:00
NEZNAMY fd7748d280 Merge remote-tracking branch 'origin/master' 2023-02-15 12:51:36 +01:00
NEZNAMY 317b0292ff Fix error on sponge (and redundancy on bukkit) 2023-02-15 12:51:19 +01:00
Callum Seabrook b87be649bc Made ARMOR_STAND_ID constant and removed packet builder singleton for Sponge 2023-02-15 11:51:09 +00:00
Callum Seabrook e2dda54011 Added sneaking support for unlimited nametags Sponge 2023-02-15 11:43:05 +00:00
NEZNAMY d17518c39b Sponge7 - Fix broken scoreboards 2023-02-14 23:19:33 +01:00
NEZNAMY 48541dd0bd Placeholders - Fix NPE 2023-02-14 22:29:28 +01:00
NEZNAMY b83b023fee Sponge8 - Add ViaVersion hook 2023-02-14 22:29:13 +01:00
NEZNAMY 60e0c78033 Add Sponge7 module 2023-02-14 22:28:32 +01:00
NEZNAMY a395f7f01f Unlimited NameTags - Fix entity type detection from previous commits 2023-02-14 18:46:17 +01:00
NEZNAMY 09c36ef69e Relocate yamlassist as well 2023-02-14 18:28:45 +01:00
NEZNAMY 9d5fa685a0 Rename folder as well 2023-02-14 18:25:08 +01:00
NEZNAMY 6794f593fa Rename sponge module to sponge8 2023-02-14 17:39:41 +01:00
NEZNAMY 5e9965276f Code style - Change how packet builder is initialized in Platform class 2023-02-14 16:54:58 +01:00
NEZNAMY 1473038955 Code style - Fix redundant imports, throws, extract constants, grammar 2023-02-14 16:39:21 +01:00
Callum Seabrook 2041ca57f3 Finished Sponge support 2023-02-14 14:38:09 +00:00
NEZNAMY 098782d820 3.3.0-SNAPSHOT 2023-02-14 12:53:10 +01:00
NEZNAMY 87e9a6defd 3.2.5 2023-02-13 16:40:36 +01:00
NEZNAMY 3d896ff828 More abstraction of unlimited nametag mode (also a small fix from previous commit) 2023-02-13 14:25:38 +01:00
NEZNAMY bf93240888 Unlimited NameTags - Make armor stands visible for spectators (#785) 2023-02-13 13:57:49 +01:00
NEZNAMY 0b0e30c3c9 Disable sponge module until compilation is fixed 2023-02-13 12:54:05 +01:00
NEZNAMY 84d3816bd1 Fix NPE 2023-02-12 12:51:37 +01:00
NEZNAMY 04ff606947 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-02-12 12:29:06 +01:00
NEZNAMY 1cb8e6e238 Almost finish Sponge 8 2023-02-12 12:29:01 +01:00
NEZNAMY 4a2226170d Merge pull request #819 from Tanguygab/master
added support for *server or *world (just like server* and world*)
2023-02-12 12:27:03 +01:00
Tanguygab 1469124cfc added support for *server or *world (just like server* and world*) 2023-02-12 11:41:11 +01:00
NEZNAMY 0826095e73 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-02-11 15:34:05 +01:00
NEZNAMY 1bd698adde Code style - Create custom cache object to avoid code duplication 2023-02-11 14:33:31 +00:00
Callum Seabrook 1058d269ff Start of rewrite of Sponge for v8 over v7 2023-02-11 14:13:05 +00:00
NEZNAMY bacd87b78e Code style - Create custom cache object to avoid code duplication 2023-02-11 13:08:20 +01:00
Callum Seabrook 5befbe8780 Updated Sponge platform 2023-02-10 23:11:38 +00:00
Callum Seabrook e159b2adae Finished Sponge implementation (mostly) 2023-02-10 23:08:21 +00:00
Callum Seabrook 52f7418853 The start of Sponge 2023-02-10 23:08:21 +00:00
Callum Seabrook 76061f7ccd Set up Sponge skeleton project 2023-02-10 23:08:21 +00:00
NEZNAMY 7d39e21eae Fix mistake after recode 2023-02-11 00:07:10 +01:00
NEZNAMY fee3723474 Code style - More abstraction of features and feature loading 2023-02-10 23:06:52 +01:00
NEZNAMY 2f0e4c6888 Merge remote-tracking branch 'origin/master' 2023-02-10 12:58:49 +01:00
NEZNAMY b04cb6d3c5 Config converter - Reverse old config detection (#762) 2023-02-10 12:58:07 +01:00
Callum Seabrook a9228a698a Fixed NPE being thrown when player info data name is null for Krypton 2023-02-09 16:18:13 +00:00
NEZNAMY 27102495d8 Code style - Use lombok in more locations 2023-02-08 00:49:48 +01:00
NEZNAMY d1bf96ab33 Code style - Use lombok for getters/setters 2023-02-07 23:14:02 +01:00
NEZNAMY 3360dbb468 Update README.md 2023-02-07 20:21:14 +01:00
NEZNAMY d224e7584e Create CONTRIBUTING.md 2023-02-07 20:20:31 +01:00
Callum Seabrook 6373540076 Updated Krypton platform and removed Indra 2023-02-07 16:56:58 +00:00
NEZNAMY 5f78aca997 Hide proxy-only methods into proxy player object 2023-02-07 15:33:24 +01:00
NEZNAMY 145d944d33 Bukkit - Remove unused (and incorrectly implemented) code 2023-02-07 15:26:01 +01:00
NEZNAMY ac91940e3b Velocity - Send packet using wanted player uuid instead of random if entry does not exist (apparently it goes through even if not registered) 2023-01-31 21:36:26 +01:00
NEZNAMY ef8452be73 Code style - Simplify BungeeCord info packet read/write based on client version 2023-01-31 18:23:24 +01:00
NEZNAMY 47e0810697 Placeholders - Remove %essentialsnick% placeholder due to being pretty redundant (keeping empty implementation so users don't get it suddenly broken) 2023-01-31 17:11:45 +01:00
NEZNAMY 1f30f225c7 Code style - Use reflection for PremiumVanish API instead of direct code with dependencies 2023-01-31 16:17:33 +01:00
NEZNAMY fcbfd60ddb Code style - Call method directly when command sender is console 2023-01-31 15:55:28 +01:00
NEZNAMY 46f9033713 Code style - Add final modifier to variable 2023-01-31 15:54:48 +01:00
NEZNAMY 7e65018a0e Velocity - Switch to non-deprecated method 2023-01-31 15:51:28 +01:00
NEZNAMY 9ef3485968 Code style - Create a method for convenience to avoid duplicated code 2023-01-27 15:52:38 +01:00
NEZNAMY 257f8f1145 Commands - Remove properly length from debug output 2023-01-27 15:24:26 +01:00
NEZNAMY 8d65cbb0e5 3.2.5-SNAPSHOT 2023-01-26 10:35:08 +01:00
NEZNAMY 20d221e3ca Merge pull request #806 from NotAlexNoyle/proposal
Propose adopting open source license
2023-01-25 12:17:56 +01:00
NEZNAMY 94072acf10 3.2.4 2023-01-25 10:39:44 +01:00
NEZNAMY 0422cc2597 Update issue templates 2023-01-24 14:00:13 +01:00
NEZNAMY 58f61e645b 3.2.4-SNAPSHOT 2023-01-24 10:45:11 +01:00
NEZNAMY 5f416bd9e5 Unlimited NameTags - Avoid Bukkit API NPE (#800, #804) 2023-01-24 10:44:35 +01:00
NotAlexNoyle 64528c913c Propose adopting open source license 2023-01-23 22:33:21 -05:00
NEZNAMY 357e7c6a49 Revert "Remove redundant try/catch"
This reverts commit 1777d8bf01.
2023-01-23 14:39:54 +01:00
NEZNAMY 81dd202053 3.2.3 2023-01-21 14:58:34 +01:00
NEZNAMY 189797aa73 Bukkit - Use the other constructor for player info packet which is cleaner (also #797) 2023-01-21 00:18:19 +01:00
NEZNAMY 1ae2ef7cad Fix compilation 2023-01-21 00:17:02 +01:00
NEZNAMY de14e14d33 Revert "Placeholders - Add timeout for placeholders to return value, causing TAB to freeze"
This reverts commit 37caed3123.
2023-01-19 22:30:09 +01:00
NEZNAMY 19892c14c1 Revert "Placeholders - Increase placeholder retrieve timeout to 500ms (#792)"
This reverts commit 6d85875fde.
2023-01-19 22:29:59 +01:00
NEZNAMY 2e00c4c002 Revert "NameTags - Fix error that doesn't make sense (#794)"
This reverts commit 12f5dd52b4.
2023-01-19 22:29:52 +01:00
NEZNAMY 97aa367629 Revert "Fix thread pool not being single threaded (maybe #781?)"
This reverts commit 87cc9a030b.
2023-01-19 22:28:22 +01:00
NEZNAMY 87cc9a030b Fix thread pool not being single threaded (maybe #781?) 2023-01-19 22:01:11 +01:00
NEZNAMY 8ba83d1cb1 BossBar - Fix errors when progress is below 0 or above 100 2023-01-19 16:59:05 +01:00
NEZNAMY 9f0e60bd47 Per world PlayerList - Remove redundant task schedule to main thread 2023-01-19 16:58:45 +01:00
NEZNAMY 93b2a25b2e Merge pull request #795 from Chris6ix/update-actions
Update Actions
2023-01-17 22:01:37 +01:00
Chris 4242d7a839 Remove Test Build Trigger 2023-01-17 21:33:33 +01:00
Chris 0ddb99bd46 Trigger Test Build 2023-01-17 21:29:34 +01:00
Chris 969672e3ad Update Actions 2023-01-17 21:26:40 +01:00
NEZNAMY 2423fbb150 Update README.md 2023-01-17 20:28:47 +01:00
NEZNAMY a99928dbf5 Placeholders - Colorize output of relational placeholders as well 2023-01-17 20:25:06 +01:00
NEZNAMY 12f5dd52b4 NameTags - Fix error that doesn't make sense (#794) 2023-01-17 15:38:20 +01:00
NEZNAMY 2bd105446e Bukkit - Properly send RGB messages into console (only for Paper+, Spigot doesn't support RGB at all) 2023-01-17 14:11:51 +01:00
NEZNAMY 6d85875fde Placeholders - Increase placeholder retrieve timeout to 500ms (#792) 2023-01-16 18:49:23 +01:00
NEZNAMY 42810aa490 3.2.3-SNAPSHOT 2023-01-16 18:47:42 +01:00
NEZNAMY bd31521bd3 3.2.2 2023-01-15 12:02:05 +01:00
NEZNAMY ec0deb8378 Yellow Number - Disable the feature for Bedrock players 2023-01-12 21:57:56 +01:00
NEZNAMY ae66b6e9a1 RGB - Fix MiniMessage->Bukkit conversion being after Bukkit->TAB, making it not translate anymore 2023-01-12 14:55:41 +01:00
NEZNAMY 3b86becebd Header/Footer - Do not add &r after \n anymore 2023-01-12 14:53:38 +01:00
NEZNAMY 4a8d86825b Configuration - Small changes to default configuration files 2023-01-10 18:56:34 +01:00
NEZNAMY 54a3bd81b4 Global PlayerList - Increase player removal prevention delay to 2000ms from 500ms 2023-01-10 16:49:16 +01:00
NEZNAMY b6768cca99 CPU manager - Add try/catch for task scheduling (#781) 2023-01-10 16:48:28 +01:00
NEZNAMY 60c414deed Bukkit - Remove redundant methods in nms loader 2023-01-05 16:48:16 +01:00
NEZNAMY 009283a83f Update README.md 2023-01-05 16:04:38 +01:00
Callum Seabrook 4a430b97df Merge pull request #779 from JT122406/master
Updates
2023-01-04 22:50:08 +00:00
NEZNAMY 722333da2e Fix RejectedExecutionException (#769) 2023-01-04 19:15:56 +01:00
NEZNAMY 1215af5262 Bukkit - Add 1.5.0 support 2023-01-04 18:28:24 +01:00
NEZNAMY 254088c37b Bukkit - Repackage nms loaders (also fix startup on Thermos) 2023-01-04 18:14:39 +01:00
J.T. McQuiggandCallum Seabrook cb2ce4acca Update gradle/libs.versions.toml
Co-authored-by: Callum Seabrook <callum.seabrook@prevarinite.com>
2023-01-04 11:20:55 -05:00
J.T.McQuigg 872adca8fd Updates 2023-01-03 23:09:35 -05:00
J.T.McQuigg f37cc0bdd8 Gradle 7.6 2023-01-03 23:02:04 -05:00
NEZNAMY 4063578ac3 Code style - Remove duplicate .toLowerCase() 2023-01-02 20:45:23 +01:00
NEZNAMY 2528de7eeb Code style - Use built-in String#trim function 2023-01-02 20:25:11 +01:00
NEZNAMY 37caed3123 Placeholders - Add timeout for placeholders to return value, causing TAB to freeze 2022-12-23 14:49:31 +01:00
NEZNAMY 27fe9dfb12 Commands - Make player command update player instantly if uuid is used instead of player name 2022-12-21 22:00:58 +01:00
NEZNAMY 0169cbd4d2 Bukkit - Fix wrong method taken sometimes, causing player kick with unlimited nametag mode 2022-12-20 15:08:18 +01:00
NEZNAMY ed760aec08 Aligned PlayerList - Fix rare error that makes no sense 2022-12-20 15:04:48 +01:00
NEZNAMY 858cbcefeb Code style - Add toString override 2022-12-20 15:04:21 +01:00
NEZNAMY 8a87d581a9 Scoreboard - Do not send packets to players who cannot see that specific scoreboard 2022-12-18 17:16:26 +01:00
NEZNAMY 6d642f4e51 NameTags - Fix players getting unregistered with wrong team name 2022-12-17 19:12:00 +01:00
NEZNAMY aca63640bc Attempt to fix rare NPE 2022-12-17 19:08:32 +01:00
NEZNAMY 1845a35315 3.2.2-SNAPSHOT 2022-12-16 22:25:39 +01:00
NEZNAMY cab5f89a95 Remove redundant check 2022-12-16 22:03:21 +01:00
NEZNAMY 1777d8bf01 Remove redundant try/catch 2022-12-16 21:14:47 +01:00
NEZNAMY 91b0bfaa9d Configuration - Simplify file copying, since default files already are encoded with UTF-8 encoding 2022-12-16 15:04:52 +01:00
NEZNAMY f1cf994d10 3.2.1 2022-12-16 11:27:48 +01:00
NEZNAMY 69957be933 Pet name fix - Fix excessive CPU usage caused by heavy operation and not caching the result 2022-12-15 00:22:23 +01:00
NEZNAMY b78eb5efb6 Bukkit - Remove forgotten debug stack trace printing 2022-12-15 00:21:28 +01:00
NEZNAMY 3577ad1964 Bukkit - Fix NPE in packet creation on <1.13 (#763) 2022-12-14 18:02:04 +01:00
NEZNAMY 4510491177 3.2.0 2022-12-12 09:46:21 +01:00
NEZNAMY 01b3a12377 1.19.3 support 2022-12-12 09:46:12 +01:00
NEZNAMY 64ae9863fb Bukkit - Drop old event API system 2022-12-10 12:27:51 +01:00
NEZNAMY ecd315a407 Sorting - Fix error from previous commit (.toLowerCase made string longer) 2022-12-08 11:20:58 +01:00
NEZNAMY 678eed288e Conditions - Add OR type for anonymous conditions using "|" symbol 2022-12-06 10:32:41 +01:00
NEZNAMY 9510455cb1 Conditions - Fix anonymous conditions not updating from previous commit 2022-12-06 10:31:29 +01:00
NEZNAMY e925ce23d0 Conditions - Fix nested conditions not working if they are defined below the condition using them 2022-12-06 09:51:16 +01:00
NEZNAMY c68cad6a1e Global PlayerList - Fix feature breaking nick/skin changes (minecraft mechanic changed in 1.19) (#752) 2022-12-06 09:50:00 +01:00
NEZNAMY 01d6eda4fb Configuration - Use luckperms placeholders instead of vault in default configuration (and make them return empty value if LP is not found) 2022-12-04 14:19:21 +01:00
NEZNAMY 73e7119bbc Configuration - add hidden server-name option for %server% value (useful with MySQL) 2022-12-04 11:49:59 +01:00
NEZNAMY cea781382a Sorting - Fix wrong team name being unregistered (#727) 2022-12-03 21:18:38 +01:00
NEZNAMY cf25b84e86 Commands - Fix debug command showing sorting is disabled even if layout is enabled for sorting 2022-12-03 21:16:29 +01:00
NEZNAMY 86e1d22390 Sorting - Only use 1 character for sorting by groups, permissions and predefined values; Improve debug output of sorting results 2022-12-03 12:16:38 +01:00
NEZNAMY 1625be111f Global PlayerList - Make server names in server groups and spy-servers not case-sensitive 2022-12-02 23:18:40 +01:00
NEZNAMY 00cda3b2aa Placeholders - Load all used placeholders on join in placeholder manager to make cpu usage appear under that "feature" instead of a feature actually using the placeholders 2022-12-01 22:46:40 +01:00
NEZNAMY 85a00a4436 Code style - Extract minimum placeholder refresh interval into an editable constant 2022-12-01 22:36:33 +01:00
NEZNAMY 37aec1a497 Global PlayerList - Add %playerlist-group_<group>% placeholder for online count in each group 2022-12-01 22:32:43 +01:00
NEZNAMY ef637616ee Placeholders - Improve error message 2022-11-27 17:13:24 +01:00
NEZNAMY af3b5067fb BossBar - Ignore players in disabled worlds/servers in announce command (#748) 2022-11-25 11:23:48 +01:00
NEZNAMY 53e5b15f96 Tab Expansion - Fix error in nested placeholder parsing 2022-11-24 11:01:55 +01:00
NEZNAMY 6a3afae607 Implement empty method 2022-11-16 12:24:03 +01:00
NEZNAMY 2e05c7d9d1 Another attempt for #727 2022-11-15 19:15:49 +01:00
NEZNAMY fb698a04b7 Configuration - Merge default config.yml files into one 2022-11-12 19:11:36 +01:00
NEZNAMY 52bdf30ddb Merge remote-tracking branch 'origin/master' 2022-11-09 22:34:22 +01:00
NEZNAMY a92f07aa40 Tab Expansion - Resend values to bridge on server switch (#740) 2022-11-09 22:34:09 +01:00
NEZNAMY 003ff7f99d Merge pull request #739 from CoachYT1/patch-1
bump org.yaml:snakeyaml to 1.33
2022-11-09 21:59:08 +01:00
NEZNAMY 47b9806ae7 Tab Expansion - Parse nested placeholders in replace function 2022-11-07 22:26:17 +01:00
CoachYT d2a7c65e08 bump org.yaml:snakeyaml to 1.33
snakeyaml 1.30 is affected by 5 vulnerabilities and should be upgraded to the latest version
2022-11-07 17:53:24 +01:00
NEZNAMY e232336a5f Add check for modifying non-existing teams (#727) 2022-11-06 22:42:23 +01:00
NEZNAMY 8816c75212 BungeeCord - Fix crash on 1.7 caused by unregistering non-existing objectives (login packet cleared them) (#727) 2022-11-04 13:31:34 +01:00
NEZNAMY f9d54751ba Fix rare ConcurrentModificationException 2022-11-04 12:53:00 +01:00
NEZNAMY 43d41ee761 BungeeCord - Improve unsupported BungeeCord build message 2022-11-01 17:55:43 +01:00
NEZNAMY 4512ba8d5e NameTags - Print a console warn if both unlimited nametags and invisible nametags are enabled 2022-10-30 17:56:00 +01:00
NEZNAMY 8b55bca564 Remove redundant variable 2022-10-30 13:40:32 +01:00
NEZNAMY d8d54970d3 Code style - Clean up vanish check code 2022-10-30 13:29:45 +01:00
NEZNAMY 15ea4482f9 Bukkit - Drop direct Essentials hook for vanishing as it supports metadata 2022-10-30 13:27:24 +01:00
NEZNAMY 87f9f38eac Update issue templates 2022-10-29 18:35:11 +02:00
NEZNAMY dba559f041 Layout - Fix startup error caused by invalid number as slot 2022-10-29 12:17:04 +02:00
NEZNAMY c610b6f323 PlayerList - Fix name applying on server switch when pipeline injection is disabled 2022-10-26 17:36:08 +02:00
NEZNAMY a5fb900e19 API - Add back TabPlayer#getTeamName() 2022-10-24 14:05:39 +02:00
NEZNAMY 09d6e455f5 Layout - Implement a fix for compatibility with plugins which add empty player name into their 2022-10-21 19:55:59 +02:00
NEZNAMY e68097b0f4 Layout - Fix startup error if slots is a single number instead of interval 2022-10-16 15:32:26 +02:00
NEZNAMY 0744fed953 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-10-16 14:40:42 +02:00
NEZNAMY de0adf4067 MySQL - Attempt to fix startup error (#717) 2022-10-16 13:12:54 +02:00
NEZNAMY 76bf2b3efc Update links in README 2022-10-16 13:11:56 +02:00
NEZNAMY c089badeb4 Configuration - Remove owner and admin settings from default configuration 2022-10-15 19:15:38 +02:00
NEZNAMY b3b99d366a Placeholders - Fix relational placeholders updating causing normal placeholders to not refresh 2022-10-14 19:28:05 +02:00
NEZNAMY 4a36486f65 Bukkit - Fix startup on 1.8 2022-10-08 12:41:05 +02:00
NEZNAMY 75126d00d9 BossBar - Fix countdown placeholder not activating on usage in PlaceholderAPI 2022-10-06 16:43:06 +02:00
NEZNAMY 17ebf95d5b BossBar - Fix announcement bars disappearing on refresh 2022-10-06 16:42:32 +02:00
NEZNAMY c764420cfe Code Style - Simplify field value assignment 2022-10-06 15:33:50 +02:00
Callum Seabrook 28f05fc2af Changed publishing artifact IDs to begin with "tab-" 2022-10-06 12:02:28 +01:00
Callum Seabrook a420892887 Fixed publishing ID 2022-10-06 11:54:06 +01:00
NEZNAMY d910dc5913 Header / Footer - Prioritize world over server in config.yml as well 2022-10-06 11:11:00 +02:00
NEZNAMY c9c1cbc349 Layout - Fix anti-override for playerlist names being disabled even when no layout is visible 2022-09-26 11:02:14 +02:00
NEZNAMY fd4704f75e 3.1.5 2022-09-20 15:46:28 +02:00
NEZNAMY 5400ca637f Global PlayerList - Do not fill display name for 1.7 players 2022-09-19 18:23:16 +02:00
NEZNAMY 79d2610117 Global PlayerList - Fix server switch when pipeline injection is disabled 2022-09-17 18:38:06 +02:00
NEZNAMY 6a51f4cb9d BungeeCord - Fix 1.7 players not being processed fully on server switch (reapplying scoreboards) 2022-09-13 18:33:52 +02:00
NEZNAMY c190776665 Scoreboard - Fix show command enabling scoreboard for players who disabled it using toggle command 2022-09-13 18:32:54 +02:00
NEZNAMY 3765a1232e Merge pull request #697 from JT122406/master
Updates and Velocity Deprecation Fix
2022-08-27 19:48:00 +02:00
J.T. McQuigg 28866eba21 More Updates and Build Fix 2022-08-27 13:07:28 -04:00
J.T. McQuigg 08adee3dc3 Recomment this out for PR 2022-08-27 11:57:34 -04:00
J.T. McQuigg 8a1adaf2cd More Updates 2022-08-27 11:57:08 -04:00
J.T. McQuigg fdb9accaa6 Updates and don't use Deprecation in Velocity module 2022-08-27 11:42:15 -04:00
NEZNAMY 4f7656748b Merge pull request #695 from Tanguygab/master
added StartWith `|-` and EndsWith `-|` condition types
2022-08-25 22:25:48 +02:00
Tanguygab 03e4495ac7 added |- and -| conditions 2022-08-25 15:15:21 +02:00
NEZNAMY 01af5cb9e1 Placeholders - Fix nested placeholders not being applied when updating from nested?!! (#694) 2022-08-24 16:52:01 +02:00
NEZNAMY 8d49f32df2 Code style - Change the way API instance is set 2022-08-22 20:30:16 +02:00
NEZNAMY 8a955b3bc8 Javadoc - Further improve documentation 2022-08-22 20:27:20 +02:00
NEZNAMY ead40cb413 Merge pull request #691 from Tanguygab/master
Commands - Added target and -s arguments for /tab nametag preview/toggle (#690)
2022-08-22 16:22:32 +02:00
Tanguygab 5e07f78d4f updated messages.yml 2022-08-22 16:20:43 +02:00
Tanguygab c7d98795bc Revert "* for all players in /tab scoreboard show"
This reverts commit e4f33b58f7.
2022-08-22 16:13:46 +02:00
Tanguygab 9f637e3d6a added target and -s arguments for /tab nametag preview/toggle 2022-08-22 16:09:23 +02:00
Tanguygab 2d5f074c78 Merge remote-tracking branch 'upstream/master' 2022-08-22 15:35:23 +02:00
NEZNAMY 3be6ba484d API - Fix show_item hover event on 1.16+ 2022-08-18 22:36:43 +02:00
NEZNAMY d9d74e7a32 Placeholders - Fix NPE in %essentialsnick% event listener due to Essentials bug (#684) 2022-08-18 12:46:54 +02:00
NEZNAMY 887306a5ed Bukkit - Include 1.19.2 in supported version range 2022-08-16 01:25:15 +02:00
NEZNAMY 4b2542eb4f Global Playerlist - Add config option to disable (and enable) profile key filling (#680) 2022-08-15 13:28:56 +02:00
NEZNAMY 66bd3baa3e Layout - Add config option to show vanished players in layout 2022-08-14 15:41:19 +02:00
NEZNAMY bf32004f8f Layout - Do not format original entries in any way (can cause spaces) 2022-08-13 17:50:28 +02:00
NEZNAMY bfb2df9c95 Commands - Show storage type in debug output 2022-08-13 17:44:46 +02:00
Callum Seabrook ddeb05e408 Merge pull request #662 from JT122406/Updates
Update Gradle Wrapper to 7.5
2022-08-12 16:58:38 +01:00
Callum Seabrook 9312d61258 Fix some more BS 2022-08-12 16:49:30 +01:00
Callum Seabrook d44909bc42 Fixed some BS 2022-08-12 16:41:26 +01:00
Callum Seabrook 1d77df335c Switched to Indra for publishing and improved code reuse in build scripts 2022-08-12 16:29:12 +01:00
NEZNAMY a268dc25f4 BungeeCord - Use direct code for proxy module again 2022-08-12 15:46:14 +02:00
NEZNAMY 8c5696faa0 Bukkit - Register universal placeholders after bukkit ones to fix reference issue with nested placeholders 2022-08-12 15:05:40 +02:00
NEZNAMY 94f3ccfb7d Javadoc - Further improve documentation 2022-08-12 15:04:49 +02:00
NEZNAMY 576329214d Add 1.19.2 to known versions 2022-08-11 17:57:49 +02:00
NEZNAMY 5c13c2e225 Velocity - Remove join processing delay since the required delay is already included elsewhere 2022-08-11 17:49:42 +02:00
NEZNAMY 1070f85617 Placeholders - Retry updateValue if player is not loaded yet (might fix #564) 2022-08-11 17:48:26 +02:00
NEZNAMY 7c9dc87fcb NameTags - Make canSeeFriendlyInvisibles flag configurable 2022-08-09 14:12:14 +02:00
NEZNAMY 363e58bf91 API - Expose another method 2022-08-04 17:06:30 +02:00
NEZNAMY daca3f5b71 3.1.4 2022-08-04 15:43:53 +02:00
NEZNAMY a7d2ec084b Use profile key in player info packet (#673) 2022-08-03 12:57:48 +02:00
NEZNAMY 167f8cd0ff BungeeCord - Remove Waterfall tablist rewrite hook since the option doesn't work as I expected 2022-08-03 11:14:10 +02:00
NEZNAMY 170307f45f Catch everything 2022-07-29 15:49:59 +02:00
NEZNAMY 2f36377780 Revert "Placeholder output replacements - Apply again after applying nested placeholders"
This reverts commit 2706cd5881.
2022-07-29 12:48:13 +02:00
NEZNAMY 48c565d85d 3.1.3 2022-07-28 17:59:15 +02:00
NEZNAMY 86b3cc4044 Sorting - Fix startup error caused by misconfiguration (#664) 2022-07-28 11:40:42 +02:00
NEZNAMY 6a2d5091b8 Bukkit - Add 1.19.1 support 2022-07-28 11:30:34 +02:00
J.T. McQuigg dc09e77190 Update Gradle Wrapper to 7.5 2022-07-26 17:16:56 -04:00
NEZNAMY 4fc139c0c6 Remove synchronized lock from sendCustomPacket method 2022-07-26 21:57:36 +02:00
NEZNAMY e5d800f469 API - Expose a few more methods 2022-07-26 21:56:16 +02:00
NEZNAMY 4879ac28b5 PingSpoof - Add Layout compatibility 2022-07-25 15:34:33 +02:00
NEZNAMY bd0badab48 Feature management - Don't deserialize objective packets if no feature is listening to them 2022-07-24 21:20:55 +02:00
NEZNAMY 5575a26d3b Placeholders - Use event instead of refresh task for %essentialsnick% placeholder 2022-07-24 20:27:43 +02:00
NEZNAMY d8cbe96949 BossBar - Fix issues with hidden-by-default enabled 2022-07-24 17:55:04 +02:00
NEZNAMY cfb506a7ff BossBar - Make Wither's size (nearly) zero on 1.8 (#654) 2022-07-24 13:28:18 +02:00
Tanguygab d5b0ff7f40 Merge remote-tracking branch 'upstream/master' 2022-07-24 13:11:11 +02:00
NEZNAMY 31d259210c Bukkit - Add support for mojang mapped jars 2022-07-21 12:56:23 +02:00
NEZNAMY 39112b8d41 Tab expansion - Add %tab_nametag_visibility% and rename %tab_ntpreview% to %tab_nametag_preview% 2022-07-20 00:14:38 +02:00
NEZNAMY 4c92286ebb Thread management - Increase max thread pool size to 20 2022-07-19 14:02:44 +02:00
NEZNAMY a317aeba85 Plugin message handling - Fix ClassCastException when a placeholder is externally registered as a server placeholder 2022-07-19 13:30:32 +02:00
NEZNAMY dcaf17294e Aligned PlayerList - Fix commit ebd67dd - Do not save config if no changes were made 2022-07-19 12:00:16 +02:00
NEZNAMY 0bc734689a Unlimited Nametags - Fix per-world formatting not applying on world switch 2022-07-19 00:22:11 +02:00
NEZNAMY d0c2c00cc9 BungeeCord - Respect Waterfall's tablist rewrite config option (#652) 2022-07-18 19:25:07 +02:00
NEZNAMY fd02328af9 Aligned PlayerList - Update widths.txt for 1.19 2022-07-18 14:55:05 +02:00
NEZNAMY 3b7de31324 Aligned PlayerList - Add a subcommand (disabled by default) for mass-verifying character widths 2022-07-18 14:52:44 +02:00
NEZNAMY ebd67dd025 Aligned PlayerList - Remove custom widths from config that match default value 2022-07-17 21:30:56 +02:00
NEZNAMY 4557a39ae2 RGB - Fix &r getting removed 2022-07-17 21:29:59 +02:00
NEZNAMY ce1eb299d3 Conditions - Fix conditions being 1 loop behind nested placeholders 2022-07-16 22:58:37 +02:00
Callum Seabrook 20ab3f0a72 Disabled JavaDoc generation for all but API and added property to enable for API 2022-07-16 20:11:58 +01:00
NEZNAMY d61f90c25c Code style - use api module instead of shared where possible 2022-07-16 20:56:59 +02:00
NEZNAMY 90474e7592 Layout - Fix startup error when a layout is missing groups section 2022-07-16 19:15:18 +02:00
NEZNAMY a2e28003bc Code style - Move TabConstants class to api module 2022-07-16 19:13:31 +02:00
NEZNAMY 6e409c3dcd Thread management - Use scheduled executor for repeating and delayed tasks 2022-07-16 18:39:13 +02:00
NEZNAMY 85a699c8de Commands - Only show announcement bars in auto-complete for bossbar announcement command 2022-07-16 18:33:57 +02:00
NEZNAMY a788d1fb23 Commands - Add missing permission check when toggling scoreboard using player argument 2022-07-16 14:14:35 +02:00
NEZNAMY ab5b72375c Code style - Remove redundant suppression and cast 2022-07-13 16:42:36 +02:00
NEZNAMY 3a023c51b5 Catch NoClassDefFoundError which is rarely thrown on reloads and leads to memory leaks 2022-07-13 16:24:17 +02:00
NEZNAMY 0ad91bcafe Sorting - Fix space in sorting types causing it to not work 2022-07-13 15:25:30 +02:00
NEZNAMY f41ea3bcab Unlimited Nametags - Improve heights to match vanilla & remove config option for space between lines, since original value was found 2022-07-13 14:57:07 +02:00
NEZNAMY 327dc4b416 Unlimited Nametags - Fix sneaking not being detected immediately on reload 2022-07-13 14:55:39 +02:00
NEZNAMY 27e7779456 Unlimited Nametags - Do not spawn for <1.8 players 2022-07-13 14:02:33 +02:00
NEZNAMY a7b2f5bb22 BungeeCord - Fix NPE on <1.8 clients 2022-07-13 13:32:53 +02:00
NEZNAMY 19fcb7c946 Layout - Do not send latency update packets to <1.8 clients 2022-07-13 13:32:23 +02:00
NEZNAMY 2706cd5881 Placeholder output replacements - Apply again after applying nested placeholders 2022-07-13 12:17:39 +02:00
NEZNAMY 4e2abd9c5b Bukkit - Fix startup on 1.7.x and 1.8.0 2022-07-13 12:04:21 +02:00
NEZNAMY 51ba2fa80a Placeholder output replacements - Fix exact match detection when input is not colored (new placeholderapi version) 2022-07-11 00:28:14 +02:00
NEZNAMY fd60cd0a21 Change the way the module is disabled 2022-07-06 22:00:52 +02:00
NEZNAMY 5025372c05 Revert "Code style - Remove unused method" 2022-07-06 21:15:54 +02:00
NEZNAMY 4806bd075f Configuration - Fix NPE when disabled world/server is null 2022-07-05 19:09:56 +02:00
NEZNAMY 370d17757a Add poor and unreliable MiniMessage support 2022-07-04 18:06:19 +02:00
NEZNAMY a648d7e75f Scoreboard - Fix NPE when using show command from console without player argument (#639) 2022-07-04 14:05:44 +02:00
NEZNAMY 518e1c5310 AlignedPlayerList - Fix rare NPE caused by multi-thread access 2022-07-04 13:46:29 +02:00
NEZNAMY 11539d8290 Commands - Allow removing per-world/server values 2022-07-04 13:44:08 +02:00
Tanguygab e4f33b58f7 * for all players in /tab scoreboard show 2022-07-04 13:34:04 +02:00
NEZNAMY 073caa9515 Compilation - Bump Authlib version 2022-07-04 13:33:00 +02:00
NEZNAMY 2b06b11916 Scoreboard - Fix same lines of text not appearing with use-numbers true 2022-06-24 21:16:45 +02:00
NEZNAMY 032b0f8a41 Configuration - Fix NPE when copying bukkit config to bungeecord 2022-06-19 14:05:59 +02:00
NEZNAMY 9391363f9b Unlimited Nametags - Disable on boat with chest as well (#625) 2022-06-19 12:36:35 +02:00
NEZNAMY 467fb2e9c7 Bukkit - Use reflection for LibsDisguises hook 2022-06-18 16:12:53 +02:00
NEZNAMY 257893b8de Downgrade dependency to fix compilation 2022-06-18 16:12:09 +02:00
NEZNAMY e919839de5 Merge pull request #623 from JT122406/Updates
Repo Bumps
2022-06-18 14:12:41 +02:00
J.T. McQuigg 77d372cbee Use new RedisBungee 2022-06-18 07:42:38 -04:00
J.T. McQuigg 03e9facaf7 Repo Bumps 2022-06-18 07:35:45 -04:00
NEZNAMY f3e459d911 Revert "BungeeCord - Retry plugin message processing if player isn't loaded for any reason (#564)"
This reverts commit dffac5deb8.
2022-06-17 14:15:39 +02:00
NEZNAMY 36feff0e04 Sorting - Fix off-by-1 possibly causing sorting to not work correctly for players without sorting group/permission 2022-06-16 18:53:42 +02:00
NEZNAMY 0320140a1b BungeeCord - Fix vanish/invisible status not resetting on server switch 2022-06-12 14:16:20 +02:00
NEZNAMY 4fb1b1c9f9 3.1.2 2022-06-11 18:01:17 +02:00
NEZNAMY 37f3896a41 Update to 1.19 2022-06-11 18:01:08 +02:00
NEZNAMY e1c152d95d Add missing change in previous commit 2022-06-11 18:00:08 +02:00
NEZNAMY dffac5deb8 BungeeCord - Retry plugin message processing if player isn't loaded for any reason (#564) 2022-06-11 17:57:18 +02:00
NEZNAMY 8aa3bbfad3 Scoreboard - Fix RGB codes not translating into legacy correctly in long lines 2022-06-09 23:10:44 +02:00
NEZNAMY 5bb4006e55 Scoreboard - Fix additional empty lines when use-numbers is enabled 2022-06-08 00:46:32 +02:00
NEZNAMY e06a406682 Clean up repositories 2022-06-08 00:45:21 +02:00
NEZNAMY a19719be4b Configuration - Remove deleted placeholder from default config 2022-06-03 23:47:41 +02:00
NEZNAMY 35856a9f3d Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-06-02 15:50:46 +02:00
NEZNAMY cf1d5d5ee2 Code style - Remove unused method 2022-06-01 20:23:16 +02:00
NEZNAMY f69a845e51 Bukkit - Implement chat message sending on <1.7 2022-05-31 12:21:07 +02:00
NEZNAMY db67e93bb8 Disable compilation of a module 2022-05-31 11:51:21 +02:00
NEZNAMY 313f089222 Aligned Suffix - Fix memory leak 2022-05-20 01:10:00 +02:00
NEZNAMY 458c08ded7 Tab expansion - Slightly optimize placeholder retrieving 2022-05-18 01:39:20 +02:00
NEZNAMY fe6cc7417f Placeholders - Fix refresh interval forwarding to bridge (#597) (2nd attempt) 2022-05-17 19:00:23 +02:00
NEZNAMY 181c94a808 Code style - Extract strings into constants 2022-05-13 00:42:30 +02:00
NEZNAMY 9ff6f02d8d Code style - Remove unused code and other minor changes 2022-05-12 22:49:41 +02:00
NEZNAMY 3cf74516d9 Bukkit - Remove unused data watcher registries 2022-05-12 22:25:18 +02:00
NEZNAMY 31f5502c16 BungeeCord - Delete TAB's bungeecord events (to make users switch to universal ones) 2022-05-12 22:04:37 +02:00
NEZNAMY 671d60bd72 API - Throw user-friendly exception if player is not loaded yet (#585) 2022-05-10 18:32:09 +02:00
NEZNAMY 294e866501 Placeholders - Fix refresh interval forwarding to bridge (#597) 2022-05-10 18:21:13 +02:00
NEZNAMY 96789100e3 Bukkit - Wrap incoming components instead of deserializing and then serializing them 2022-05-10 13:22:27 +02:00
NEZNAMY ea934fc7fb GroupManager - Add null check for player group from permission plugin 2022-05-08 12:48:06 +02:00
NEZNAMY d529b7b340 Sorting - Allow placeholders with ":" inside to work properly in PLACEHOLDER sorting type 2022-05-08 12:47:40 +02:00
NEZNAMY 092ca7f9f8 Aligned Suffix - Assume players can see themselves when vanished 2022-05-03 18:03:29 +02:00
NEZNAMY 79f4bdb869 Unlimited Nametags - Do not delay respawn by 1 tick anymore to fix OOM 2022-05-01 13:07:42 +02:00
NEZNAMY b0d2d0f7dc NickCompatibility - Clean up code 2022-04-30 20:01:40 +02:00
NEZNAMY cdf58c1dfe Teams - set canSeeFriendlyInvisibles to true 2022-04-30 18:21:46 +02:00
NEZNAMY 4680de6022 Feature management - Add try/catch for each feature in join processing (#560) 2022-04-30 17:22:03 +02:00
NEZNAMY e8f61181b6 Error logging - Log (critical) errors into console even when error file is full 2022-04-30 17:20:24 +02:00
NEZNAMY 75e9e007d0 Fix disabled worlds not loading correctly (from #6f07cf4) 2022-04-28 17:27:23 +02:00
NEZNAMY 3574ef7c0b Belowname - Do not update score when entering disabled world 2022-04-28 12:20:54 +02:00
NEZNAMY 86158036d2 Commands - Make permission group assigning more obvious in debug output 2022-04-28 11:55:21 +02:00
NEZNAMY e73beb47a3 API - Fix scoreboard Line#setText not working 2022-04-26 23:23:42 +02:00
NEZNAMY 0db5f1dd73 Scoreboard - Fix NPE when a line is null 2022-04-23 14:18:30 +02:00
NEZNAMY 6dcdc2aa58 PetFix - Fix attack packet cancellation 2022-04-20 10:46:08 +02:00
NEZNAMY 64601137e8 Scoreboard - Show empty lines when defined as empty, only hide when placeholder output is empty 2022-04-15 00:07:44 +02:00
NEZNAMY af7697b670 BungeeCord - Fixed NPE from moving to reflection 2022-04-14 23:24:45 +02:00
NEZNAMY 9e4f46088b Commands - Do not force refresh players who are not loaded yet (#560) 2022-04-12 11:21:06 +02:00
NEZNAMY 91e71a9b9c Layout - Fixed NPE when player had no layout visible 2022-04-10 23:32:13 +02:00
NEZNAMY 1198ffb847 BungeeCord - Use reflection for proxy module access due to the repository no longer being available 2022-04-10 23:31:51 +02:00
NEZNAMY 72116ecd0c Javadoc - Further improve documentation 2022-04-10 21:08:19 +02:00
NEZNAMY 04ebb7123d Code style - Move NMS object declaration and initialization 2022-04-10 20:28:29 +02:00
NEZNAMY 43096beafe Bukkit - Use ConsoleCommandSender for colored messages (#555) 2022-04-09 18:27:37 +02:00
NEZNAMY 2a08c62456 Tab expansion - Fix server placeholders not loading on join 2022-04-07 15:44:58 +02:00
NEZNAMY b62d74c3fa Code style - Remove unnecessary reflection 2022-04-06 20:38:03 +02:00
NEZNAMY 652fb5a6ad Bukkit - Switch from Paper to Purpur API 2022-04-06 19:11:19 +02:00
NEZNAMY d1f25cc19f Bukkit - Disable checking for plugins with compatibility issue 2022-04-06 18:25:12 +02:00
NEZNAMY dc6a9d9761 Fix layout startup 2022-04-06 13:44:16 +02:00
NEZNAMY 838d725715 Code style - Removed redundant static modifiers 2022-04-06 13:31:31 +02:00
NEZNAMY c45acb39ca Code style - Clean up variable initialization 2022-04-06 13:10:11 +02:00
NEZNAMY 957edf2a52 Javadoc - Fixed compilation warn 2022-04-06 12:44:54 +02:00
NEZNAMY d969c6ed0e Code style - Change way of checking if server is proxy 2022-04-06 12:41:20 +02:00
NEZNAMY dd6fca961c Code style - Clean up configuration loading 2022-04-06 12:34:39 +02:00
NEZNAMY e88a6df456 Config - remove hidden unregister-before-register option and remove the function entirely. Hopefully with improved checks this should not be necessary anymore. It caused client errors, as well as #549 2022-04-06 11:35:03 +02:00
NEZNAMY 3a4530c999 RGB - Finish #552 - Only replace clean gradients in property initialization 2022-04-05 23:24:37 +02:00
NEZNAMY b623dba40a Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-04-05 23:20:13 +02:00
NEZNAMY e2fd945109 Merge pull request #552 from lucypoulton/feat/kyori-rgb-format
Add support for Kyori MiniMessage RGB format
2022-04-05 23:19:52 +02:00
NEZNAMY e6dc3cda31 Unlimited nametags - Manually destroy armor stands on world switch 2022-04-05 20:21:36 +02:00
Lucy Poulton 18d8ff127a add support for kyori minimessage rgb format 2022-04-05 14:25:22 +01:00
NEZNAMY 4a9526451f Code style - Remove redundant "triggerMode" flag from Placeholder class due being redundant. Setting refresh to -1 has the same effect. 2022-04-04 19:03:51 +02:00
NEZNAMY 669d89c870 Code style - Move tab expansion code to interface 2022-04-04 18:15:06 +02:00
NEZNAMY 39161f2af5 Nametags - Reduced CPU usage of anti-override 2022-04-04 16:02:32 +02:00
NEZNAMY be8c33e3fc Velocity - Delay join event processing to avoid unformatted tablist names 2022-04-03 14:08:19 +02:00
NEZNAMY 7c1a27f9df Javadoc - Further improve documentation 2022-04-02 19:07:18 +02:00
NEZNAMY 342cc93dbf Code style - Clean up code 2022-04-02 13:17:15 +02:00
NEZNAMY 6f07cf439b Code style - Clean up loading of disabled servers/worlds 2022-04-01 23:40:04 +02:00
NEZNAMY 1d4d00b69b Code style - Clean up code with usage of @Inject 2022-04-01 23:21:30 +02:00
NEZNAMY 54cb3e4d54 3.1.1 2022-04-01 10:02:26 +02:00
NEZNAMY b0fc2caef9 RedisBungee - Detect player name change from other proxies 2022-03-31 21:54:51 +02:00
NEZNAMY 1221509d24 MySQL - Do not lowercase _DEFAULT_ group name, which caused it to not load (#546) (for real this time) 2022-03-31 20:52:12 +02:00
NEZNAMY 17f5f60504 MySQL - Do not lowercase _DEFAULT_ group name, which caused it to not load (#546) 2022-03-31 17:02:12 +02:00
NEZNAMY a8b0b488c6 Layout - Implement latency updating for player slots (#545) 2022-03-31 15:45:14 +02:00
NEZNAMY 6d922d5b70 Unlimited nametags - Fix NPE when player moves too quickly after joining 2022-03-31 14:50:54 +02:00
NEZNAMY 1ab62a33e3 Placeholders - Improve refresh task interval adaptation (#543) 2022-03-31 10:01:10 +02:00
NEZNAMY f9540a1e78 Tab Expansion - Fix placeholders not loading on player join 2022-03-29 11:17:04 +02:00
NEZNAMY a720bf2c8b Unlimited nametags - Do not allow preview command when armor stands are disabled for player 2022-03-28 21:25:42 +02:00
NEZNAMY c143eab6fb Global playerlist - Fix player possibly being removed from playerlist after reconnecting rapidly (#539) 2022-03-28 20:57:47 +02:00
NEZNAMY 1afb20af8b Code style - Avoid reassigning parameters 2022-03-28 12:57:31 +02:00
NEZNAMY c6f27c839d Code style - Document empty method body 2022-03-28 12:49:37 +02:00
NEZNAMY 742f1708c5 Code style - Remove unused import 2022-03-28 12:47:19 +02:00
NEZNAMY ac9601a48b Unlimited nametags - Fix nametag toggle command not hiding vanilla name on players with disabled armor stands via API 2022-03-28 12:13:23 +02:00
NEZNAMY 4cd109a5fc Javadoc - Update style and fix alignment after replacing tabs with spaces 2022-03-26 23:13:19 +01:00
NEZNAMY 078554a8b0 RedisBungee - Fix /btab reload removing players from tablist when global playerlist is disabled 2022-03-25 14:46:59 +01:00
NEZNAMY 464e2f4f64 RedisBungee - Fix duplicated team names through proxies 2022-03-25 14:46:22 +01:00
NEZNAMY b84f55aa18 RedisBungee - Fix errors and issues when unlimited nametag mode is enabled 2022-03-25 14:45:55 +01:00
NEZNAMY be2f780bfa Drop LuckPerms event hook and go back to periodic task. Too many issues related to events. 2022-03-25 10:27:36 +01:00
NEZNAMY 262d8493a6 fix NPE 2022-03-25 09:33:32 +01:00
NEZNAMY 24e12e871b finish #534 2022-03-24 22:33:18 +01:00
NEZNAMY a2ee75339c 3.1.1-pre2 2022-03-24 22:20:25 +01:00
NEZNAMY 9c28c4d63c add auto complete for group names in group command 2022-03-22 22:12:55 +01:00
NEZNAMY 7d57da1424 fix disableArmorStands method not destroying armor stands 2022-03-22 13:06:28 +01:00
NEZNAMY b77b20ba4d expose placeholder output getters in the API 2022-03-22 11:54:30 +01:00
NEZNAMY 57ae58f093 call super.onQuit as it should 2022-03-21 20:26:56 +01:00
NEZNAMY b29c878688 Merge remote-tracking branch 'origin/master' 2022-03-21 08:51:52 +01:00
NEZNAMY 5a5a6c76c1 another banger that took a week to fix 2022-03-21 08:51:32 +01:00
NEZNAMY f6a53f537c fix NPE 2022-03-21 08:46:02 +01:00
NEZNAMY d86ed37208 allow negative values in replacement intervals using ~ 2022-03-20 17:55:17 +01:00
NEZNAMY 92b945f28d Merge pull request #534 from itsmemac/master
fix nametag preview help text
2022-03-20 15:03:07 +01:00
Mac cb164f0055 fix nametag preview help text
it seems new update `/tab ntpreview` has been replaced with `/tab nametag preview`
Help text was not updated
2022-03-20 19:31:38 +05:30
NEZNAMY acfe37c1b5 fix NPE 2022-03-20 12:50:30 +01:00
NEZNAMY 44ded26df4 change method call order in which groups are refreshed 2022-03-19 16:13:39 +01:00
NEZNAMY 6dbd92a010 remove players from map on quit 2022-03-19 16:12:06 +01:00
NEZNAMY f729ca0ad2 fix NPE 2022-03-19 16:11:45 +01:00
NEZNAMY 065490c077 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2022-03-18 17:12:20 +01:00
NEZNAMY da6425dd93 Merge pull request #530 from CoachYT1/master
Update dependencies & GitHub actions
2022-03-15 08:25:12 +01:00
CoachYT 250dcbc676 Update build.yml 2022-03-14 12:18:41 +01:00
CoachYT de4ca4a88f Update libs.versions.toml 2022-03-14 12:15:58 +01:00
NEZNAMY 723a0c890e fix NPE 2022-03-13 22:36:37 +01:00
NEZNAMY 7085e7df20 do not forward group if assigning by permissions 2022-03-13 22:36:25 +01:00
NEZNAMY 09496d60f3 add argument check 2022-03-13 13:19:05 +01:00
NEZNAMY bf6bc26a5c add check into API for all the users who shade API into their projects 2022-03-13 13:16:20 +01:00
NEZNAMY a7217eae15 fix placeholders used only in tab expansion not refreshing 2022-03-13 13:12:37 +01:00
NEZNAMY 2a532ef9b7 update README.md 2022-03-13 13:11:37 +01:00
NEZNAMY c304ded924 avoid thread spam from LP events 2022-03-11 15:06:13 +01:00
NEZNAMY 4d8c47a505 #517 - make group names not case-sensitive when connected to MySQL 2022-03-09 10:22:51 +01:00
NEZNAMY d3e0ccbf34 forward nametag preview on server switch 2022-03-09 09:08:49 +01:00
NEZNAMY 3c82b46148 fix updating of invalid score on server switch when pipeline injection is disabled 2022-03-08 23:09:29 +01:00
NEZNAMY 07d7f2cc17 catch StackOverflowError as well 2022-03-08 21:50:10 +01:00
NEZNAMY 54d4f09ab1 fix StackOverflowError when using placeholder identifier inside its own replacements (will act the same as %value%) 2022-03-08 21:45:11 +01:00
NEZNAMY e8c698b5e8 #517 - fix mysql header/footer download/upload 2022-03-07 22:14:49 +01:00
NEZNAMY b096906ffd 3.1.1-pre1 2022-03-07 18:59:40 +01:00
NEZNAMY 5a9cec12fa WeakHashMap isn't so weak after all (fixed memory leak) 2022-03-07 18:59:22 +01:00
NEZNAMY a2ed71751f fix NPE 2022-03-07 18:58:10 +01:00
NEZNAMY 5a3a2df920 simplify 2022-03-07 15:50:24 +01:00
NEZNAMY 9d07e669ea merge ifs 2022-03-07 15:50:12 +01:00
NEZNAMY aa18f1cc35 remove unnecessary full path 2022-03-07 14:03:04 +01:00
NEZNAMY 668d78ea64 remove redundant constructor 2022-03-07 13:59:38 +01:00
NEZNAMY 6f62581d6e print OOM into file instead of throwing into console 2022-03-07 12:53:12 +01:00
NEZNAMY 06686ada5c add synchronized lock to printError method 2022-03-07 12:21:47 +01:00
NEZNAMY 4380482606 remove direct reference to player channel on bukkit 2022-03-07 12:21:24 +01:00
NEZNAMY d5112b3d32 fix group refreshing introduced in previous commit which in the end didn't even fix the issue it was supposed to 2022-03-06 20:26:49 +01:00
NEZNAMY ef67506640 fix error caused by concurrent access 2022-03-06 12:01:50 +01:00
NEZNAMY c2ea4d2793 #516 - skin signature can be null 2022-03-05 16:29:16 +01:00
NEZNAMY f0d8d479bd 3.1.0 2022-03-04 22:46:16 +01:00
NEZNAMY 2d46d70c92 Fix super rare permission group detecting issue when using TAB on bungeecord with assigning by permissions with using backend permissions with many online players. Jesus. 2022-03-04 22:38:43 +01:00
NEZNAMY 4320bb7cb4 try to fix rare error that doesn't make sense 2022-03-04 22:36:53 +01:00
NEZNAMY 398e8ed5ff remove redundant space 2022-03-04 12:31:43 +01:00
NEZNAMY 4e8b804337 prefer per-world settings over per-server 2022-03-04 10:39:45 +01:00
NEZNAMY de7c664e0d remove redundant check 2022-03-02 14:17:05 +01:00
NEZNAMY c7600a2e3d fix issues for players with unknown version 2022-03-02 13:34:29 +01:00
NEZNAMY 8b34e82aab simplify 2022-03-02 12:49:26 +01:00
NEZNAMY c96efe8802 use Logger for console messages 2022-03-02 12:46:33 +01:00
NEZNAMY 0a1bfd3a2e 3.1.0-pre3 2022-03-02 10:57:23 +01:00
NEZNAMY 755cf9ce43 1.18.2 2022-03-02 10:56:46 +01:00
NEZNAMY c6aca74396 fix NPE when assigning groups by permissions on bungeecord 2022-03-01 22:37:37 +01:00
NEZNAMY b5b7f3c16c add try/catch for PremiumVanish hook 2022-02-28 23:26:53 +01:00
NEZNAMY 293162b484 show name of file with syntax error correctly in error message 2022-02-28 15:27:17 +01:00
NEZNAMY bd543b7b9d fix rare NPE (#504) 2022-02-25 20:49:40 +01:00
NEZNAMY 994fe9f482 fix conversion error when bossbar names are numbers (#506) 2022-02-25 20:48:28 +01:00
NEZNAMY f657bdb14c update README.md 2022-02-22 23:42:16 +01:00
NEZNAMY a4238e1191 compensate for 1.8.0 client sided bug (#501) 2022-02-22 08:05:45 +01:00
NEZNAMY daf08423b4 3.1.0-pre2 2022-02-21 19:01:40 +01:00
NEZNAMY 91367f3d3a fix wrong text displayed 2022-02-21 16:33:21 +01:00
NEZNAMY 9dbd52b638 replace tabs with 4 spaces 2022-02-21 16:15:52 +01:00
NEZNAMY 22c6be2770 fix names not updating when new max player joins 2022-02-21 16:05:51 +01:00
NEZNAMY fb261c9f90 cleanup 2022-02-21 12:43:23 +01:00
NEZNAMY 638e35bf90 more abstraction 2022-02-21 12:40:15 +01:00
NEZNAMY 25d13862ae initialize server version & data folder constants instead of having per-platform getters 2022-02-21 12:30:02 +01:00
NEZNAMY 1c8f483138 split content of plugin message handler classes into other classes 2022-02-21 11:57:43 +01:00
NEZNAMY 92ad215873 more code abstraction 2022-02-21 11:06:02 +01:00
NEZNAMY 7ed3cfbb7c fix vanish status not refreshing correctly on bungeecord 2022-02-20 23:42:43 +01:00
NEZNAMY 53e75a27be avoid bytebuf instance check entirely if deserialization is disabled 2022-02-20 23:08:15 +01:00
NEZNAMY 1b67819c3c only forward relevant properties to expansion and fix values not updating 2022-02-20 15:16:12 +01:00
NEZNAMY 07f8020f79 fix syntax error in default file 2022-02-20 14:53:31 +01:00
NEZNAMY b12d0680d8 massively optimize some API methods 2022-02-20 14:50:05 +01:00
NEZNAMY 64247d913e implement "/tab nametag toggle" command hiding nametags of all players for command sender 2022-02-19 21:29:22 +01:00
NEZNAMY 3575cdae85 move code & respawn armor stands on world switch if preview is enabled 2022-02-19 14:27:04 +01:00
NEZNAMY f71a542247 add check for directory existence 2022-02-19 13:56:59 +01:00
NEZNAMY 46cc59138b skip action if list is empty 2022-02-19 13:31:00 +01:00
NEZNAMY 46bfa5112e for some reason this check is needed as well 2022-02-19 13:29:32 +01:00
NEZNAMY fcdff51783 use pre-allocated enum values array (again) 2022-02-19 13:29:18 +01:00
NEZNAMY a4be4ae419 fix check not working properly 2022-02-19 13:28:05 +01:00
NEZNAMY 7083352b9a cache enum values to reduce memory allocations 2022-02-18 23:37:27 +01:00
NEZNAMY 956a0f46c0 name the second thread pool as well 2022-02-18 23:26:26 +01:00
NEZNAMY aea1864627 iterate over arrays to reduce allocations 2022-02-18 23:01:29 +01:00
NEZNAMY 803b87111a make packet deserializing on BungeeCord use 30x less CPU 2022-02-18 22:40:36 +01:00
NEZNAMY f64a641b63 fix NPE caused by a dumb mistake 2022-02-18 12:49:28 +01:00
NEZNAMY 7f9bb354d7 remove wrong null checks 2022-02-18 12:48:08 +01:00
NEZNAMY 4b7b6284f8 move code to avoid duplication 2022-02-18 12:38:55 +01:00
NEZNAMY bbccd26415 remove %maxplayers% placeholder 2022-02-18 12:35:31 +01:00
NEZNAMY bab7c737b3 add more precondition checks 2022-02-18 12:31:06 +01:00
NEZNAMY 3169787b41 fix NPE on /btab parse 2022-02-18 11:13:34 +01:00
NEZNAMY f29bbc6657 move config option loading 2022-02-17 23:04:21 +01:00
NEZNAMY f53cd5ae11 fix armor stands heights not updating 2022-02-17 22:39:59 +01:00
NEZNAMY 38d36b5ab5 remove unused softdependency 2022-02-17 21:33:54 +01:00
NEZNAMY 8f8f596abc drop direct UltraPermissions hook (still supported via Vault) 2022-02-17 16:28:43 +01:00
NEZNAMY 2afc8abdb2 rename fields from "method" to "action" 2022-02-17 16:19:57 +01:00
NEZNAMY 38dd948306 cleanup 2022-02-17 11:37:28 +01:00
NEZNAMY e393cf0a10 make code more universal 2022-02-17 11:35:36 +01:00
NEZNAMY 4f451017e3 make PacketBuilder not abstract and add default implementation to delete a redundant class 2022-02-17 11:17:27 +01:00
NEZNAMY 9cb31af48a class cleanup using consumer 2022-02-17 10:56:45 +01:00
NEZNAMY 2c17041b27 universal registration of %online_<server>% on proxies 2022-02-17 10:34:55 +01:00
NEZNAMY 72c75a443e more abstraction of RedisBungee feature 2022-02-16 20:04:06 +01:00
NEZNAMY 1ba6384647 update README.md 2022-02-16 18:57:40 +01:00
NEZNAMY e2edf9a37c delete unused file 2022-02-16 17:35:23 +01:00
NEZNAMY cb0bfd095d 3.1.0-pre1 2022-02-15 13:05:53 +01:00
NEZNAMY ec03dd4755 fix long line creating empty line above it 2022-02-15 13:05:21 +01:00
NEZNAMY de44f5ae42 update issue templates 2022-02-14 21:34:22 +01:00
NEZNAMY 5ee6796172 implement #347 and probably fix original issue in #475 2022-02-14 21:13:38 +01:00
NEZNAMY d034fca513 add MySQL data conversion commands 2022-02-14 15:58:28 +01:00
NEZNAMY f65c3fb29a remove unused code 2022-02-14 00:02:38 +01:00
NEZNAMY 7192bc9d69 resend objectives/teams on server switch even when pipeline injection is disabled 2022-02-14 00:02:06 +01:00
NEZNAMY d7636a05f4 drastically reduce amount of permission checks by the feature 2022-02-13 23:05:47 +01:00
NEZNAMY bd93301634 send missing packets on that thing through bridge 2022-02-13 21:58:32 +01:00
NEZNAMY b0558ba0a2 just one \t 2022-02-13 21:57:53 +01:00
NEZNAMY 251934bb4b fix NPE on group info when mysql is enabled 2022-02-13 17:01:13 +01:00
NEZNAMY f2c9ef5d30 implement tab expansion on BungeeCord through bridge 2022-02-13 16:43:17 +01:00
NEZNAMY 15a072bbaa fix weird NPE 2022-02-13 13:52:07 +01:00
NEZNAMY 8f1c303862 implement /tab groups and /tab group <name> subcommands 2022-02-12 14:07:23 +01:00
NEZNAMY c79d2e3e6e fix BossBar placeholders not updating when changing value while it was hidden 2022-02-11 19:41:59 +01:00
NEZNAMY f45d8d860b default condition type to AND instead of OR 2022-02-11 19:40:54 +01:00
NEZNAMY 88f31184a0 add back \t for stack traces 2022-02-11 17:54:11 +01:00
NEZNAMY d5368baab4 add final modifier 2022-02-11 17:22:14 +01:00
NEZNAMY 10f645226c fix bridge placeholder refreshing from replacements 2022-02-11 17:12:54 +01:00
NEZNAMY 322c572cd6 add new issue template for wiki changes 2022-02-11 14:19:24 +01:00
NEZNAMY fdfeb178b5 update error.yml 2022-02-10 21:04:37 +01:00
NEZNAMY 1c63cdba91 update error.yml 2022-02-10 21:03:31 +01:00
NEZNAMY 289d0db8e3 create error template 2022-02-10 21:02:26 +01:00
NEZNAMY 9adacca77f upgrade bug report template 2022-02-10 20:59:12 +01:00
NEZNAMY 7dc6813274 avoid NegativeArraySizeException when copying collection by avoiding that operation 2022-02-10 17:53:30 +01:00
NEZNAMY 77bf1cfbb9 #494 2022-02-10 15:44:07 +01:00
NEZNAMY 8f28350579 initial unlimited nametags on bungee support 2022-02-10 15:33:35 +01:00
NEZNAMY 2d06d0c1d3 fix compatibility issue with other plugins 2022-02-06 13:21:35 +01:00
NEZNAMY 7e8301afcd goodbye 3 days of my life 2022-02-05 13:59:12 +01:00
NEZNAMY 2600e8f856 send empty player slots in a single packet 2022-02-03 22:58:35 +01:00
NEZNAMY 914748e14e add toString override to Skin 2022-02-03 22:32:54 +01:00
NEZNAMY 21a7b633e7 simplify 2022-02-01 19:15:57 +01:00
NEZNAMY a77d4352e2 convert classes into anonymous ones 2022-02-01 18:54:01 +01:00
NEZNAMY a7a21e0e51 fix another config conversion bug 2022-02-01 14:49:53 +01:00
NEZNAMY a0a14cd285 fix NPE 2022-02-01 14:18:03 +01:00
NEZNAMY 44b7bedd53 fix bossbar never showing when joining disabled world/server 2022-02-01 14:16:04 +01:00
NEZNAMY 409a0808c1 fix NumberFormatException with Placeholder sorting type 2022-02-01 14:08:22 +01:00
NEZNAMY 4214d02fad send join plugin message on server switch as well 2022-02-01 12:52:02 +01:00
NEZNAMY bc1abb1b58 more javadocs and moved some variables around 2022-01-31 21:37:10 +01:00
NEZNAMY 2f2f53ba4d #488 2022-01-31 20:26:19 +01:00
NEZNAMY cbeb4c151d remove errorDescription parameter from submitted tasks 2022-01-30 19:01:22 +01:00
NEZNAMY 1d5ca70790 remove bridge placeholder usage from cpu command since it's no longer measured 2022-01-30 18:38:03 +01:00
NEZNAMY 1f619ff4d3 allow enabling pet fix feature on proxy and forward status to bridge 2022-01-30 00:43:55 +01:00
NEZNAMY f5acdebe60 bridge recode to drastically reduce packet flow & allow relational placeholder support 2022-01-30 00:34:00 +01:00
NEZNAMY bd59922204 more docs, general cleanup 2022-01-29 15:49:55 +01:00
NEZNAMY c4d9482c79 remove unused code 2022-01-28 21:12:31 +01:00
NEZNAMY 8faef8f017 class cleanup 2022-01-28 20:56:02 +01:00
NEZNAMY 284161db77 create and use Skin class instead of forwarding Objects 2022-01-28 20:54:33 +01:00
NEZNAMY 78db2b66ef placeholder cleanup, unification and performance improvements 2022-01-28 17:18:52 +01:00
NEZNAMY 0d71643ecb make ping value of layout slots configurable 2022-01-28 16:25:39 +01:00
489 changed files with 30061 additions and 26260 deletions

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
* text=auto
*.sh text eol=lf
gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
-28
View File
@@ -1,28 +0,0 @@
---
name: Bug report
about: Report a bug
title: ''
labels: ''
assignees: ''
---
**Server version**
**Plugin version** ("latest" is not a version)
**Describe the bug**
**How to reproduce**
**Expected behavior**
**Actual behavior**
**Additional info** (errors, screenshots, ...)
+63
View File
@@ -0,0 +1,63 @@
name: Bug Report
description: Plugin does not work as described.
labels: ["Type: Bug"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Describe the bug
description: How the plugin behaves
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: How the plugin is supposed to behave instead with your configuration.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: true
- type: textarea
attributes:
label: Additional info
description: Anything else you want to provide that will help in resolving the issue faster, such as screenshots or videos.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have read the wiki to make sure it's not an issue with configuration
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
+57
View File
@@ -0,0 +1,57 @@
name: Error
description: Error message with a stack trace in console or error file.
labels: ["Type: Error"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Stack trace
description: The error message and stack trace you received in console or errors.log file.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included a paste of the error
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
+56
View File
@@ -0,0 +1,56 @@
name: Memory leak
description: Memory leak caused by TAB.
labels: ["Type: Memory leak"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Heap dump
description: Link to uploaded memory dump which I can look into.
validations:
required: true
- type: textarea
attributes:
label: Uncleared object(s)
description: Objects you believe should have been removed from the memory but are not
placeholder: |
There is only 1 player online, but heap dump has 100 instances of TabPlayer
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included download link of my heap dump
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have explained what I believe is wrong with the heap dump
required: true
@@ -0,0 +1,55 @@
name: Performance issue
description: Performance issue in TAB or in server caused by TAB
labels: ["Type: Performance"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Evidence
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included sufficient evidence of the issue
required: true
- label: I ticked all of the boxes without actually reading them
required: false
+29
View File
@@ -0,0 +1,29 @@
name: Wiki change
description: Change to text on a wiki page or adding a new section.
labels: ["Type: Wiki change"]
body:
- type: input
attributes:
label: Page link
description: Link to the page that should receive the change.
placeholder: https://github.com/NEZNAMY/TAB/wiki/Installation
validations:
required: true
- type: textarea
attributes:
label: Current text (if applicable)
description: Current version of the text (if modifying), ignore in case of new section.
placeholder: |
Installation on Bukkit/Spigot/Paper is as simple as it can be. Just put the plugin into plugins folder and restart the server. You can also run `/plugman load tab` to load the plugin while server is running.
validations:
required: false
- type: textarea
attributes:
label: New text
description: What the text should be changed to / how should new section look
placeholder: |
Put the plugin into plugins folder and restart the server. In case you don't want to restart your server, loading with plugins like PlugMan is supported.
validations:
required: true
+9 -8
View File
@@ -3,7 +3,7 @@ on:
push:
branches:
- master
- v3
- v4
pull_request:
types: [opened, synchronize, reopened]
jobs:
@@ -11,21 +11,22 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 11
uses: actions/setup-java@v1
- name: Set up JDK 17
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
with:
java-version: 11
distribution: 'temurin'
java-version: 17
- name: Cache SonarCloud packages
uses: actions/cache@v1
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar
- name: Cache Gradle
uses: actions/cache@v2
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
path: |
~/.gradle/caches
@@ -38,7 +39,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
run: ./gradlew clean build
- name: Upload a Build Artifact
uses: actions/upload-artifact@v2.3.1
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Jar
path: jar/build/libs/TAB-**.jar
-1
View File
@@ -33,7 +33,6 @@ hs_err_pid*
.idea/
# other
.gitattributes
*.iml
run/
+9
View File
@@ -0,0 +1,9 @@
If this plugin has helped you in any way, and you would like to return something back
to make the plugin even better, there is a lot of ways to contribute:
* Open a **pull request** containing a new feature or a bug fix, which you believe many users will benefit from.
* Make detailed high-quality **bug reports**. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report (typically providing correct steps to reproduce that actually work).
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
+1 -28
View File
@@ -1,4 +1,4 @@
Apache License
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
@@ -172,30 +172,3 @@ Apache License
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+14 -32
View File
@@ -1,41 +1,23 @@
# About
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
similar plugins in terms of features, performance, compatibility and support.
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# Distribution
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
version anymore. Only one version of the plugin exists now, which contains all implemented features
and is available to everyone for free. However, official support on discord is only provided to
those who have purchased the plugin.
# Compiling
Compilation requires JDK 8 and up.
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Links
The precompiled JAR can be downloaded for free from
[github releases](https://github.com/NEZNAMY/TAB/releases)
and [MC-Market](https://www.mc-market.org/resources/20631/).
If you want to show your appreciation for my work, you can purchase the plugin on
[MC-Market](https://www.mc-market.org/resources/14009/),
[Polymart](https://polymart.org/resource/484) or just [send a donation](https://paypal.me/neznamy1).
Support is provided to buyers on [discord](https://discord.gg/EaSvdk6).
A detailed explanation of all the plugin's functions can be found on the
[wiki](https://github.com/NEZNAMY/TAB/wiki).
# Documentation
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
of all features, as well as information regarding compatibility or limitations of each feature.
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for **bug reports**
or other issues which are not directly a bug (performance issues, errors, etc.).
Support is provided on discord and feature requests are currently not accepted due to high demand and
being busy with bug reports, which take priority over new features.
We are not looking to add any new major features anymore.
We would rather spend time improving existing features instead.
When opening a bug report, make sure you provide all necessary information to reproduce the bug,
otherwise we will not able to fix it. Saying "it only occurs randomly" or "I don't know how to
reproduce it" has no value and cannot be converted into actual code.
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
+2
View File
@@ -0,0 +1,2 @@
# Security Policy
If you found a security issue, send me a private message anywhere.
+5 -7
View File
@@ -1,9 +1,7 @@
dependencies {
compileOnlyApi(libs.netty)
api(libs.jsonSimple) {
exclude("junit", "junit")
}
compileOnlyApi(libs.snakeyaml)
api(libs.yamlAssist)
compileOnlyApi(libs.gson)
compileOnlyApi("org.jetbrains:annotations:24.0.1")
}
tasks.javadoc {
enabled = project.hasProperty("enable-javadoc")
}
@@ -1,82 +0,0 @@
package me.neznamy.tab.api;
public interface ArmorStand {
/**
* Returns true if offset is static, false if not
* @return true if static, false if dynamic
*/
boolean hasStaticOffset();
/**
* Changes offset of the armor stand
* @param offset - new offset
*/
void setOffset(double offset);
/**
* Returns offset of the armor stand
* @return offset from base
*/
double getOffset();
/**
* Returns property for armor stand's name
* @return property for armor stand's name
*/
Property getProperty();
/**
* Teleports armor stand to owner's current location for everyone in range
*/
void teleport();
/**
* Teleports armor stand to owner's current location for specified player
* @param viewer - player to send packet to
*/
void teleport(TabPlayer viewer);
/**
* Changes sneaking flag and sends packets to everyone in range
* @param sneaking
* new sneaking status
*/
void sneak(boolean sneaking);
/**
* DeSpawns armor stand for everyone
*/
void destroy();
/**
* DeSpawns armor stand for specified player
* @param viewer - player to send packet to
*/
void destroy(TabPlayer viewer);
/**
* Updates armor stand's name if needed
*/
void refresh();
/**
* Updates visibility if needed
* @param force - if refresh should be forced
*/
void updateVisibility(boolean force);
/**
* Returns entity ID of this armor stand
* @return entity ID of this armor stand
*/
int getEntityId();
/**
* Spawns armor stand for specified player
* @param viewer - player to spawn for
*/
void spawn(TabPlayer viewer);
void respawn(TabPlayer viewer);
}
@@ -1,170 +0,0 @@
package me.neznamy.tab.api;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A helper class for easy management of armor stands of a player
*/
public class ArmorStandManager {
//map of registered armor stands
private final Map<String, ArmorStand> armorStands = new LinkedHashMap<>();
//players in entity tracking range
private final List<TabPlayer> nearbyPlayers = new ArrayList<>();
//array to iterate over to avoid concurrent modification and slightly boost performance & memory
private ArmorStand[] armorStandArray = new ArmorStand[0];
private TabPlayer[] nearbyPlayerArray = new TabPlayer[0];
/**
* Adds armor stand into list
* @param name - key name of the armor stand
* @param as - armor stand to add
*/
public void addArmorStand(String name, ArmorStand as) {
armorStands.put(name, as);
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
for (TabPlayer p : nearbyPlayerArray) as.spawn(p);
}
/**
* Returns armor stand with given name or null if not present
* @param name - name of armor stand registered with
* @return armor stand with given name
*/
public ArmorStand getArmorStand(String name) {
return armorStands.get(name);
}
/**
* Removes armor stand from list. It is not automatically destroyed for players
* @param name - name of armor stand to remove
*/
public void removeArmorStand(String name) {
armorStands.get(name).destroy();
armorStands.remove(name);
armorStandArray = armorStands.values().toArray(new ArmorStand[0]);
}
/**
* Spawns all armor stands for specified viewer and adds them into nearby players
* @param viewer - player to spawn armor stands for
*/
public void spawn(TabPlayer viewer) {
nearbyPlayers.add(viewer);
nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
if (viewer.getVersion().getMinorVersion() < 8) return;
for (ArmorStand a : armorStandArray) a.spawn(viewer);
}
/**
* Sets sneak value of armor stands to specified value
* @param sneaking - new sneaking status
*/
public void sneak(boolean sneaking) {
for (ArmorStand a : armorStandArray) a.sneak(sneaking);
}
/**
* Teleports armor stands to player's current location for all nearby players
*/
public void teleport() {
for (ArmorStand a : armorStandArray) a.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) {
for (ArmorStand a : armorStandArray) a.teleport(viewer);
}
/**
* Refreshes name of all armor stands for all nearby players
*/
public void refresh() {
for (ArmorStand a : armorStandArray) a.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) {
for (ArmorStand a : armorStandArray) a.updateVisibility(force);
}
/**
* Removes specified player from list of nearby players
* @param viewer - player to remove
*/
public void unregisterPlayer(TabPlayer viewer) {
if (nearbyPlayers.remove(viewer)) nearbyPlayerArray = nearbyPlayers.toArray(new TabPlayer[0]);
}
/**
* Sends destroy packet of all armor stands to everyone and clears nearby players list
*/
public void destroy() {
for (ArmorStand a : armorStandArray) a.destroy();
nearbyPlayers.clear();
nearbyPlayerArray = new TabPlayer[0];
}
/**
* 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) {
for (ArmorStand a : armorStandArray) a.destroy(viewer);
unregisterPlayer(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) {
for (ArmorStand a : armorStandArray) {
if (a.getEntityId() == entityId) {
return true;
}
}
return false;
}
/**
* Returns array of all registered armor stands
* @return armor stand array
*/
public ArmorStand[] getArmorStands(){
return armorStandArray;
}
/**
* Returns array of nearby players
* @return array of nearby players
*/
public TabPlayer[] getNearbyPlayers(){
return nearbyPlayerArray;
}
public boolean isNearby(TabPlayer viewer) {
return nearbyPlayers.contains(viewer);
}
public void respawn() {
for (ArmorStand a : armorStandArray) {
for (TabPlayer viewer : nearbyPlayerArray) {
a.respawn(viewer);
}
}
}
}
@@ -1,35 +0,0 @@
package me.neznamy.tab.api;
public interface FeatureManager {
/**
* Registers a feature, which will start receiving events
*
* @param name - name of feature
* @param feature - the handler
*/
void registerFeature(String name, TabFeature feature);
/**
* Unregisters feature making it no longer receive events. This does not run unload method nor cancel
* tasks created by the feature
* @param name - feature name defined in registerFeature
*/
void unregisterFeature(String name);
/**
* Returns whether a feature with said name is registered or not
*
* @param name - name of feature defined in registerFeature method
* @return true if feature exists, false if not
*/
boolean isFeatureEnabled(String name);
/**
* Returns feature handler by its name
*
* @param name - name of feature defined in registerFeature method
* @return the feature or null if feature does not exist
*/
TabFeature getFeature(String name);
}
@@ -1,16 +0,0 @@
package me.neznamy.tab.api;
public interface HeaderFooterManager {
void setHeader(TabPlayer player, String header);
void setFooter(TabPlayer player, String footer);
void setHeaderAndFooter(TabPlayer player, String header, String footer);
void resetHeader(TabPlayer player);
void resetFooter(TabPlayer player);
void resetHeaderAndFooter(TabPlayer player);
}
@@ -1,60 +0,0 @@
package me.neznamy.tab.api;
public interface Property {
/**
* Changes raw value to new one
* @param rawValue - new value to be used
*/
void changeRawValue(String rawValue);
/**
* Returns original raw value ignoring API calls
* @return original raw value
*/
String getOriginalRawValue();
/**
* Temporarily overrides current raw value with an API call
* @param temporaryValue - temporary value to be assigned
*/
void setTemporaryValue(String temporaryValue);
/**
* Returns current temporary value
* @return temporary value or null if not set
*/
String getTemporaryValue();
/**
* Returns temporary value (via API) if present, raw value otherwise
* @return current raw value
*/
String getCurrentRawValue();
/**
* Returns last known value
* @return last known value
*/
String get();
/**
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns whether value changed or not
* @return if updating changed value or not
*/
boolean update();
/**
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns it.
* Equal to calling update() and then get().
* @return updated value
*/
String updateAndGet();
/**
* Returns value for defined viewer by applying relational placeholders to last known value
* @param viewer - the viewer
* @return format for the viewer
*/
String getFormat(TabPlayer viewer);
}
@@ -1,10 +0,0 @@
package me.neznamy.tab.api;
public interface PropertyConfiguration {
void setProperty(String name, String property, String server, String world, String value);
String[] getProperty(String name, String property, String server, String world);
void remove(String name);
}
@@ -1,152 +0,0 @@
package me.neznamy.tab.api;
/**
* Enum class representing all possibly used protocol versions
*/
public enum ProtocolVersion {
UNKNOWN ("Unknown"),
PROXY ("Proxy"),
V1_18_1 (757),
V1_18 (757),
V1_17_1 (756),
V1_17 (755),
V1_16_5 (754),
V1_16_4 (754),
V1_16_3 (753),
V1_16_2 (751),
V1_16_1 (736),
V1_16 (735),
V1_15_2 (578),
V1_15_1 (575),
V1_15 (573),
V1_14_4 (498),
V1_14_3 (490),
V1_14_2 (485),
V1_14_1 (480),
V1_14 (477),
V1_13_2 (404),
V1_13_1 (401),
V1_13 (393),
V1_12_2 (340),
V1_12_1 (338),
V1_12 (335),
V1_11_2 (316),
V1_11_1 (316),
V1_11 (315),
V1_10_2 (210),
V1_10_1 (210),
V1_10 (210),
V1_9_4 (110),
V1_9_3 (110),
V1_9_2 (109),
V1_9_1 (108),
V1_9 (107),
V1_8 (47),
V1_7_10 (5),
V1_7_9 (5),
V1_7_8 (5),
V1_7_7 (5),
V1_7_6 (5),
V1_7_5 (4),
V1_7_4 (4),
V1_7_2 (4),
V1_6_4 (78),
V1_6_2 (74),
V1_6_1 (73),
V1_5_2 (61),
V1_5_1 (60),
V1_5 (60),
V1_4_7 (51),
V1_4_6 (51);
//version's network id found at https://wiki.vg/Protocol_version_numbers
private final int networkId;
//minor version, such as 16
private int minorVersion;
//friendly name displayed in %player-version% placeholder
private final String friendlyName;
/**
* Constructs new instance with given network id
* @param networkId - network id of this version
*/
ProtocolVersion(int networkId){
this.networkId = networkId;
this.minorVersion = Integer.parseInt(toString().split("_")[1]);
this.friendlyName = toString().substring(1).replace("_", ".");
}
/**
* Constructs new instance with given friendly name
* @param friendlyName - friendly name to display
*/
ProtocolVersion(String friendlyName){
this.networkId = 999;
this.minorVersion = 18;
this.friendlyName = friendlyName;
}
/**
* Returns the version's network id
* @return version's network id
*/
public int getNetworkId() {
return networkId;
}
/**
* Returns user-friendly name of the version (such as 1.16.2 instead of V1_16_2)
* @return user-friendly name of the version
*/
public String getFriendlyName() {
return friendlyName;
}
/**
* Returns minor version of this release, such as 8 for 1.8.x or 13 for 1.13.x
* @return version's minor version
*/
public int getMinorVersion() {
return minorVersion;
}
/**
* Overrides minor version of the version instance. This is used internally to
* set minor version of UNKNOWN value to value from package to fix compatibility
* with server forks that set bukkit version field value to "Unknown".
* @param minorVersion
* minor version to change value to
*/
public void setMinorVersion(int minorVersion) {
this.minorVersion = minorVersion;
}
/**
* Returns enum constant of entered version or UNKNOWN if unknown version
* @param friendlyName - friendly name of the version
* @return version or UNKNOWN if version is unknown
*/
public static ProtocolVersion fromFriendlyName(String friendlyName) {
if (friendlyName.startsWith("1.8")) return V1_8;
try {
return valueOf("V" + friendlyName.replace(".", "_"));
} catch (IllegalArgumentException e) {
return UNKNOWN;
}
}
/**
* Returns version from given network id
* @param networkId - network id of protocol version
* @return version from given network id
*/
public static ProtocolVersion fromNetworkId(int networkId) {
for (ProtocolVersion v : values()) {
if (networkId == v.getNetworkId()) return v;
}
return UNKNOWN;
}
}
+114 -94
View File
@@ -2,114 +2,134 @@ package me.neznamy.tab.api;
import java.util.UUID;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.api.event.EventBus;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.task.ThreadManager;
import me.neznamy.tab.api.team.TeamManager;
import me.neznamy.tab.api.tablist.SortingManager;
import me.neznamy.tab.api.tablist.HeaderFooterManager;
import me.neznamy.tab.api.tablist.TabListFormatManager;
import me.neznamy.tab.api.nametag.NameTagManager;
import me.neznamy.tab.api.tablist.layout.LayoutManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The primary API class to get instances of other API classes
*/
public abstract class TabAPI {
private static TabAPI instance;
public static TabAPI getInstance() {
return instance;
}
public static void setInstance(TabAPI instance) {
TabAPI.instance = instance;
}
/** Instance of the API */
@Setter private static TabAPI instance;
/**
* Returns player object from given UUID
* @return player object from given UUID
* @param id - Player UUID
*/
public abstract TabPlayer getPlayer(UUID id);
/**
* Returns API instance. If instance was not set by the plugin, throws
* {@code IllegalStateException}. This is usually caused by shading the API
* into own project, which is not allowed. Another option is calling the method
* before plugin was able to load.
*
* @return API instance
* @throws IllegalStateException
* If instance is {@code null}
*/
public static @NotNull TabAPI getInstance() {
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
"plugin loads after TAB, add it as a depend or soft depend of your plugin.\n" +
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
"to not include them, such as scope provided for maven compilation.");
return instance;
}
/**
* Returns player object from given name
* @return player object from given name
* @param name - Player name
*/
public abstract TabPlayer getPlayer(String name);
/**
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
* @return collection of online players
*/
public abstract TabPlayer[] getOnlinePlayers();
/**
* Returns player object from given UUID
*
* @param id
* Player UUID
* @return player object from given UUID
*/
public abstract @Nullable TabPlayer getPlayer(@NonNull UUID id);
/**
* Return BossBar manager instance if the feature is enabled. Returns null otherwise.
* @return BossBar manager
*/
public abstract BossBarManager getBossBarManager();
/**
* Returns scoreboard manager instance if the feature is enabled. Returns null otherwise.
* @return scoreboard manager
*/
public abstract ScoreboardManager getScoreboardManager();
/**
* Returns team manager instance if the feature is enabled, false otherwise
* @return team manager
*/
public abstract TeamManager getTeamManager();
public abstract HeaderFooterManager getHeaderFooterManager();
/**
* Returns PlaceholderManager instance
* @return PlaceholderManager instance
*/
public abstract PlaceholderManager getPlaceholderManager();
/**
* Returns player object from given name
*
* @param name
* Player name
* @return player object from given name
*/
public abstract @Nullable TabPlayer getPlayer(@NonNull String name);
/**
* Returns feature manager instance
* @return feature manager instance
*/
public abstract FeatureManager getFeatureManager();
public abstract TablistFormatManager getTablistFormatManager();
/**
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
*
* @return collection of online players
*/
public abstract @NotNull TabPlayer[] getOnlinePlayers();
/**
* Gets the event bus for registering listeners for TAB events.
*
* @return the event bus
*/
public abstract EventBus getEventBus();
/**
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return BossBar manager
*/
public abstract @Nullable BossBarManager getBossBarManager();
/**
* Returns server version. On proxy installation returns PROXY.
* @return server version
*/
public abstract ProtocolVersion getServerVersion();
/**
* Prints message into console
* @param message - message to print
* @param translateColors - true if colors should be translated, false if not
*/
public abstract void sendConsoleMessage(String message, boolean translateColors);
public abstract ThreadManager getThreadManager();
public abstract ConfigurationFile getPlayerCache();
public abstract ConfigurationFile getConfig();
public abstract PropertyConfiguration getGroups();
public abstract PropertyConfiguration getUsers();
public abstract void debug(String message);
public abstract void logError(String message, Throwable t);
/**
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return scoreboard manager
*/
public abstract @Nullable ScoreboardManager getScoreboardManager();
/**
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return team manager
*/
public abstract @Nullable NameTagManager getNameTagManager();
/**
* Returns header/footer manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return Header/footer manager
*/
public abstract @Nullable HeaderFooterManager getHeaderFooterManager();
/**
* Returns PlaceholderManager instance
*
* @return PlaceholderManager instance
*/
public abstract @NotNull PlaceholderManager getPlaceholderManager();
/**
* Returns TabList name format manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return TabList name format manager
*/
public abstract @Nullable TabListFormatManager getTabListFormatManager();
/**
* Returns Layout manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return Layout manager
*/
public abstract @Nullable LayoutManager getLayoutManager();
/**
* Returns Sorting manager instance if at least one feature capable of sorting players is enabled.
* If not, returns {@code null}.
*
* @return Sorting manager
*/
public abstract @Nullable SortingManager getSortingManager();
/**
* Gets the event bus for registering listeners for TAB events.
*
* @return the event bus
*/
public abstract @Nullable EventBus getEventBus();
}
@@ -1,261 +0,0 @@
package me.neznamy.tab.api;
import java.util.*;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
public abstract class TabFeature {
private final String featureName;
private final String refreshDisplayName;
protected final String[] disabledServers;
private final boolean serverWhitelistMode;
protected final String[] disabledWorlds;
private final boolean worldWhitelistMode;
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
private final List<String> methodOverrides = new ArrayList<>();
protected TabFeature(String featureName, String refreshDisplayName) {
this(featureName, refreshDisplayName, null, null);
}
protected TabFeature(String featureName, String refreshDisplayName, List<String> disabledServers, List<String> disabledWorlds) {
this.featureName = featureName;
this.refreshDisplayName = refreshDisplayName;
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
} else {
this.disabledServers = new String[0];
serverWhitelistMode = false;
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
} else {
this.disabledWorlds = new String[0];
worldWhitelistMode = false;
}
try {
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onCommand");
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onJoin");
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onQuit");
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onWorldChange");
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onServerChange");
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onDisplayObjective");
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onLoginPacket");
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onObjective");
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPlayerInfo");
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketReceive");
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketSend");
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("refresh");
} catch (NoSuchMethodException e) {
//this will never happen
}
}
/**
* Loads all players and sends packets
*/
public void load() {
//empty by default
}
/**
* Unloads all players and sends clear packets
*/
public void unload() {
//empty by default
}
/**
* Processes command from player
* @param sender - command sender
* @param message - command line
* @return true if event should be cancelled, false if not
*/
public boolean onCommand(TabPlayer sender, String message) {
return false;
}
/**
* Processes join event
* @param connectedPlayer - player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
//empty by default
}
/**
* Processes quit event
* @param disconnectedPlayer - player who disconnected
*/
public void onQuit(TabPlayer disconnectedPlayer) {
}
/**
* Processes world switch
* @param changed - player who switched world
* @param from - world player changed from
* @param to - world player changed to
*/
public void onWorldChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes server switch
* @param changed - player who switched server
* @param from - server player changed from
* @param to - server player changed to
*/
public void onServerChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes the packet send and returns true if packet should be cancelled
* @param receiver - player receiving packet
* @param packet - received packet
*/
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
//empty by default
}
/**
* Processes login packet, only available on BungeeCord
* @param packetReceiver - player receiving client reset packet
*/
public void onLoginPacket(TabPlayer packetReceiver) {
//empty by default
}
/**
* Processes the packet send
* @param receiver - player receiving packet
* @param packet - received packet
*/
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
//empty by default
}
/**
* Processes the packet send and possibly modifies it
* @param receiver - player receiving packet
* @param info - received packet
*/
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
//empty by default
}
/**
* Processes raw packet sent by client
* @param sender - packet sender
* @param packet - packet received
* @return true if false should be cancelled, false if not
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
return false;
}
/**
* Processes raw packet sent to client
* @param receiver - packet receiver
* @param packet - the packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
//empty by default
}
/**
* Performs refresh of specified player
* @param refreshed - player to refresh
* @param force - if refresh should be forced despite refresh seemingly not needed
*/
public void refresh(TabPlayer refreshed, boolean force) {
//empty by default
}
/**
* Registers this feature as one using specified placeholders
* @param placeholders - placeholders to add as used in this feature
*/
public void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* Returns true if world or server is disabled, false if not
* @param world - world to check
* @param server - server to check
* @return true if feature should be disabled, false if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns name of the feature displayed in /tab cpu.
* @return name of the feature displayed in /tab cpu
*/
public String getFeatureName() {
return featureName;
}
public boolean overridesMethod(String method) {
return methodOverrides.contains(method);
}
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
public String getRefreshDisplayName() {
return refreshDisplayName;
}
}
@@ -1,263 +1,69 @@
package me.neznamy.tab.api;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.netty.channel.Channel;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.TabPacket;
import java.util.UUID;
/**
* An interface representing a player
*/
public interface TabPlayer {
/**
* Refreshes all visuals on the player
*/
void forceRefresh();
/**
* Returns player's name
*
* @return Player's name
*/
@NotNull String getName();
/**
* Returns player's name
* @return Player's name
*/
String getName();
/**
* Returns player's UUID
*
* @return Player's UUID
*/
@NotNull UUID getUniqueId();
/**
* Returns player's UUID
* @return Player's UUID
*/
UUID getUniqueId();
/**
* Returns platform-specific entity
*
* @return an instance of bukkit/bungee player depending on platform
*/
@NotNull Object getPlayer();
/**
* Returns player's uuid used in TabList. This may only be different from real uuid if
* TAB is installed on velocity with some specific velocity setup
* @return player's uuid in TabList
*/
UUID getTablistUUID();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
*
* @return {@code true} if player is fully loaded, {@code false} otherwise
*/
boolean isLoaded();
/**
* Returns player's protocol version
* @return player's protocol version
*/
ProtocolVersion getVersion();
/**
* Returns player's primary permission group. If group has been changed using
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
* detected by standard group assign logic
*
* @return player's primary permission group
*/
@NotNull String getGroup();
/**
* Returns platform-specific entity
* @return an instance of bukkit/bungee player depending on platform
*/
Object getPlayer();
/**
* Temporarily overrides player's group and applies all changes coming from new group.
* This includes all properties and sorting, if used.
* Set to {@code null} to reset back.
*
* @param group
* New group to use
* @see #hasTemporaryGroup()
*/
void setTemporaryGroup(@Nullable String group);
/**
* Returns player's current world name (on BungeeCord this requires bridge installed)
* @return name of world where player is currently in, null on BungeeCord if bridge is not installed
*/
String getWorld();
/**
* Returns player's current server name on BungeeCord (on bukkit this returns null)
* @return name of server where player is currently in, null on bukkit
*/
String getServer();
/**
* Performs platform-specific API call to check for permission and returns the result
* @param permission - the permission to check for
* @return - true if player has permission, false if not
*/
boolean hasPermission(String permission);
/**
* Sends the player a custom universal packet
* @param packet - packet to send
*/
void sendCustomPacket(TabPacket packet);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - packet to send
* @param feature - feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, TabFeature feature);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - packet to send
* @param feature - feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, String feature);
/**
* Sends the player a platform-specific packet
* @param packet - an instance of packet depending on platform
*/
void sendPacket(Object packet);
/**
* Sends the player a platform-specific packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - an instance of packet depending on platform
* @param feature - feature to increment sent packet counter of
*/
void sendPacket(Object packet, TabFeature feature);
/**
* Sends the player a platform-specific packet and adds that packet into counter that
* is displayed in /tab cpu
* @param packet - an instance of packet depending on platform
* @param feature - feature to increment sent packet counter of
*/
void sendPacket(Object packet, String feature);
/**
* Returns player's property by name
* @param name - name of property
* @return the property or null if not found
*/
Property getProperty(String name);
/**
* Sends a message to the player
* @param message - message to be sent
* @param translateColors - whether colors should be translated or not
*/
void sendMessage(String message, boolean translateColors);
/**
* Sends specified component as a chat message
* @param message - message to send
*/
void sendMessage(IChatBaseComponent message);
/**
* Returns player's primary permission group
* @return player's group depending on configuration
*/
String getGroup();
/**
* Toggles armor stands preview mode for the player
*/
void toggleNametagPreview();
/**
* Returns whether player is previewing NameTag or not
* @return true if previewing, false if not
*/
boolean isPreviewingNametag();
/**
* Returns player's channel or null if server is 1.7 or older
* @return player's channel
*/
Channel getChannel();
/**
* Returns player's ping calculated by server
* @return player's ping
*/
int getPing();
/**
* Returns player's platform-specific skin data
* @return player's skin
*/
Object getSkin();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
* @return true if player is fully loaded, false otherwise
*/
boolean isLoaded();
/**
* Sets property with specified name to new value. If property did not exist before, it is
* created and true is returned. If it existed, it is overridden and true is returned. False is returned otherwise.
* @param feature - feature using this property to get placeholders registered
* @param identifier - property name
* @param rawValue - new raw value
* @return true if value changed / did not exist, false if value did not change
*/
boolean setProperty(TabFeature feature, String identifier, String rawValue);
/**
* Loads property from config using standard property loading algorithm
* @param property - property name to load
* @return true if value did not exist or changed, false otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property);
/**
* Loads property from config using standard property loading algorithm. If the property is
* not set in config, sets it to ifNotSet value
* @param property - property name to load
* @param ifNotSet - value to use if property is not defined in config
* @return true if value did not exist or changed, false otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
/**
* Returns name of player's scoreboard team or null if NameTag feature is disabled
* @return name of player's team
*/
String getTeamName();
/**
* Returns user-friendly explanation of team name
* @return explanation behind team name
*/
String getTeamNameNote();
/**
* Returns true if player is disguised using LibsDisguises
* @return true if player is disguised, false if not
*/
boolean isDisguised();
/**
* Returns true if player has invisibility potion, false if not. For bukkit, API is used, for BungeeCord
* bukkit bridge is used
* @return true if player has invisibility potion, false if not
*/
boolean hasInvisibilityPotion();
/**
* Currently only PV hook on BungeeCord for global PlayerList function. Looking to expand this
* in the future
* @return true if vanished with PV on BungeeCord
*/
boolean isVanished();
/**
* Returns true if player is online according to server
* @return true if online, false if not
*/
boolean isOnline();
/**
* Returns player's armor stand manager if unlimited GameMode mode is enabled, null if disabled
* @return player's armor stand manager
*/
ArmorStandManager getArmorStandManager();
/**
* Sets player's armor stands manager to new instance
* @param armorStandManager - new instance
*/
void setArmorStandManager(ArmorStandManager armorStandManager);
/**
* Returns GameMode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
* @return GameMode of the player
*/
int getGamemode();
boolean isBedrockPlayer();
void setTemporaryGroup(String group);
boolean hasTemporaryGroup();
void resetTemporaryGroup();
/**
* Returns {@code true} if a temporary group was applied to the player using {@link #setTemporaryGroup(String)}.
* If no group was set, returns {@code false}
*
* @return {@code true} if group is set, {@code false} if not
* @see #setTemporaryGroup(String)
*/
boolean hasTemporaryGroup();
}
@@ -1,29 +0,0 @@
package me.neznamy.tab.api;
public interface TablistFormatManager {
void setPrefix(TabPlayer player, String prefix);
void setName(TabPlayer player, String customName);
void setSuffix(TabPlayer player, String suffix);
void resetPrefix(TabPlayer player);
void resetName(TabPlayer player);
void resetSuffix(TabPlayer player);
String getCustomPrefix(TabPlayer player);
String getCustomName(TabPlayer player);
String getCustomSuffix(TabPlayer player);
String getOriginalPrefix(TabPlayer player);
String getOriginalName(TabPlayer player);
String getOriginalSuffix(TabPlayer player);
}
@@ -6,11 +6,11 @@ package me.neznamy.tab.api.bossbar;
*/
public enum BarColor {
PINK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
WHITE
PINK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
WHITE
}
@@ -3,34 +3,12 @@ package me.neznamy.tab.api.bossbar;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
public enum BarStyle {
PROGRESS("SOLID"),
NOTCHED_6("SEGMENTED_6"),
NOTCHED_10("SEGMENTED_10"),
NOTCHED_12("SEGMENTED_12"),
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
private final String bukkitName;
/**
* Initializes enum constant with given bukkit name
*
* @param bukkitName
* Name of color to be returned in {@code getBukkitName()}
*/
BarStyle(String bukkitName){
this.bukkitName = bukkitName;
}
/**
* Returns name of this style in Bukkit API
* @return name of this style in Bukkit API
*/
public String getBukkitName() {
return bukkitName;
}
PROGRESS,
NOTCHED_6,
NOTCHED_10,
NOTCHED_12,
NOTCHED_20
}
@@ -3,7 +3,9 @@ package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.UUID;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* An interface representing a BossBar line.
@@ -19,167 +21,197 @@ import me.neznamy.tab.api.TabPlayer;
*/
public interface BossBar {
/**
* Returns name of this BossBar. If it was defined in config, returns
* the name specified in config. If it was made using the API, returns
* randomly generated ID given to this BossBar when creating.
* @return name of BossBar
*/
String getName();
/**
* Returns name of this BossBar. If it was defined in config, returns
* the name specified in config. If it was made using the API, returns
* randomly generated ID given to this BossBar when creating.
*
* @return name of BossBar
*/
@NotNull String getName();
/**
* Returns randomly generated uuid of this BossBar used internally to match
* the BossBar in client with packets or with Bukkit API BossBar instances.
* @return uuid of this BossBar
*/
UUID getUniqueId();
/**
* Returns randomly generated uuid of this BossBar used internally to match
* the BossBar in client with packets or with Bukkit API BossBar instances.
*
* @return uuid of this BossBar
*/
@NotNull UUID getUniqueId();
/**
* Changes BossBar title to specified string, supporting placeholders.
* <p>
* If title contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified title is equal to the current one, nothing happens.
*
* @param title
* New title to display in this BossBar
*/
void setTitle(String title);
/**
* Changes BossBar title to specified string, supporting placeholders.
* <p>
* If title contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified title is equal to the current one, nothing happens.
*
* @param title
* New title to display in this BossBar
*/
void setTitle(@NonNull String title);
/**
* Changes BossBar progress to specified string, supporting placeholders.
* The string must be a string version of a float value, or a placeholder that
* outputs a float value with range 0-100.
* <p>
* If progress contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified progress is equal to the current one, nothing happens.
*
* @param progress
* New progress to use in this BossBar
*/
void setProgress(String progress);
/**
* Changes BossBar progress to specified string, supporting placeholders.
* The string must be a string version of a float value, or a placeholder that
* outputs a float value with range 0-100.
* <p>
* If progress contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified progress is equal to the current one, nothing happens.
*
* @param progress
* New progress to use in this BossBar
*/
void setProgress(@NonNull String progress);
/**
* Changes BossBar progress to specified value ranging from 0 to 100.
* <p>
* If specified progress is equal to the current one, nothing happens.
* @param progress
* New progress to use in this BossBar
*/
void setProgress(float progress);
/**
* Changes BossBar progress to specified value ranging from 0 to 100.
* <p>
* If specified progress is equal to the current one, nothing happens.
*
* @param progress
* New progress to use in this BossBar
*/
void setProgress(float progress);
/**
* Changes BossBar color to specified string, supporting placeholders.
* The string must be a string version of one of the supported values,
* or a placeholder that outputs one of them.
* <p>
* If color contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
*/
void setColor(String color);
/**
* Changes BossBar color to specified string, supporting placeholders.
* The string must be a string version of one of the supported values,
* or a placeholder that outputs one of them.
* <p>
* If color contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
*/
void setColor(@NonNull String color);
/**
* Changes BossBar color to specified enum constant.
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
*/
void setColor(BarColor color);
/**
* Changes BossBar color to specified enum constant.
* <p>
* If specified color is equal to the current one, nothing happens.
*
* @param color
* New color to use in this BossBar
*/
void setColor(@NonNull BarColor color);
/**
* Changes BossBar style to specified string, supporting placeholders.
* The string must be a string version of one of the supported values,
* or a placeholder that outputs one of them.
* <p>
* If style contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
*/
void setStyle(String style);
/**
* Changes BossBar style to specified string, supporting placeholders.
* The string must be a string version of one of the supported values,
* or a placeholder that outputs one of them.
* <p>
* If style contains new placeholders not used before, they are registered using
* standard registration logic and refreshed periodically. No need to call
* this method to try to keep placeholder values up to date.
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
*/
void setStyle(@NonNull String style);
/**
* Changes BossBar style to specified enum constant.
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
*/
void setStyle(BarStyle style);
/**
* Changes BossBar style to specified enum constant.
* <p>
* If specified style is equal to the current one, nothing happens.
*
* @param style
* New style to use in this BossBar
*/
void setStyle(@NonNull BarStyle style);
/**
* Returns current title of the BossBar in raw format. If it contains placeholders,
* their raw identifiers are used in the result.
* @return title of the BossBar
*/
String getTitle();
/**
* Returns current title of the BossBar in raw format. If it contains placeholders,
* their raw identifiers are used in the result.
*
* @return title of the BossBar
*/
@NotNull String getTitle();
/**
* Returns progress of the BossBar as a string, which is either entered string
* containing placeholders or entered number converted to string
* @return entered progress as a string
*/
String getProgress();
/**
* Returns progress of the BossBar as a string, which is either entered string
* containing placeholders or entered number converted to string
*
* @return entered progress as a string
*/
@NotNull String getProgress();
/**
* Returns color of the BossBar as a string, which is either entered string
* containing placeholders or entered enum value converted to string
* @return entered color as a string
*/
String getColor();
/**
* Returns color of the BossBar as a string, which is either entered string
* containing placeholders or entered enum value converted to string
*
* @return entered color as a string
*/
@NotNull String getColor();
/**
* Returns style of the BossBar as a string, which is either entered string
* containing placeholders or entered enum value converted to string
* @return entered style as a string
*/
String getStyle();
/**
* Returns style of the BossBar as a string, which is either entered string
* containing placeholders or entered enum value converted to string
*
* @return entered style as a string
*/
@NotNull String getStyle();
/**
* Registers this BossBar to specified player.
* <p>
* If the player already sees this BossBar, nothing happens.
*
* @param player
* Player to register this BossBar to
*/
void addPlayer(TabPlayer player);
/**
* Registers this BossBar to specified player.
* <p>
* If the player already sees this BossBar, nothing happens.
*
* @param player
* Player to register this BossBar to
*/
void addPlayer(@NonNull TabPlayer player);
/**
* Unregisters this BossBar from specified player.
* <p>
* If the player does not see this BossBar, nothing happens.
*
* @param player
* Player to unregister this BossBar from
*/
void removePlayer(TabPlayer player);
/**
* Unregisters this BossBar from specified player.
* <p>
* If the player does not see this BossBar, nothing happens.
*
* @param player
* Player to unregister this BossBar from
*/
void removePlayer(@NonNull TabPlayer player);
/**
* Returns list of players who can see this BossBar.
* <p>
* The returned list can only be used to read. Writing
* will not work properly. For adding/removing players see {@link #addPlayer(TabPlayer)}
* and {@link #removePlayer(TabPlayer)}.
* @return List of players seeing this BossBar
*/
List<TabPlayer> getPlayers();
/**
* Returns list of players who can see this BossBar.
* <p>
* The returned list can only be used to read. Writing
* will not work properly. For adding/removing players see {@link #addPlayer(TabPlayer)}
* and {@link #removePlayer(TabPlayer)}.
*
* @return List of players seeing this BossBar
*/
@NotNull List<TabPlayer> getPlayers();
/**
* Returns {@code true} if this bossbar is visible to specified player based on
* display condition or adding via {@link #addPlayer(TabPlayer)}, {@code false} if not.
* <p>
* This may return {@code true} even if player cannot physically see the bossbar due to
* having it disabled via toggle command or {@link BossBarManager#toggleBossBar(TabPlayer, boolean)}
* (or {@link BossBarManager#setBossBarVisible(TabPlayer, boolean, boolean)}).
*
* @param player
* Player to check
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
*/
boolean containsPlayer(TabPlayer player);
/**
* Returns {@code true} if this boss bar is configured as announcement-only bar,
* {@code false} if not.
*
* @return {@code true} if bar is announcement only, {@code false} if not
*/
boolean isAnnouncementBar();
}
@@ -2,166 +2,155 @@ package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An interface allowing work with BossBars such as creating, sending
* and toggling.
* <p>
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getBossBarManager()}.
* Instance can be obtained using {@link TabAPI#getBossBarManager()}.
* This requires the BossBar feature to be enabled in config, otherwise the method will
* return null.
* return {@code null}.
*/
public interface BossBarManager {
/**
* Creates BossBar with specified parameters and registers it to BossBar manager,
* allowing to retrieve it later.
*
* @param title
* Title to display, supports placeholders
*
* @param progress
* Progress to use. Must be between 0 and 100
*
* @param color
* Color to use
*
* @param style
* Style to use
* @return The newly created BossBar
*/
BossBar createBossBar(String title, float progress, BarColor color, BarStyle style);
/**
* Creates BossBar with specified parameters and registers it to BossBar manager,
* allowing to retrieve it later.
*
* @param title
* Title to display, supports placeholders
* @param progress
* Progress to use. Must be between 0 and 100
* @param color
* Color to use
* @param style
* Style to use
* @return The newly created BossBar
*/
@NotNull BossBar createBossBar(@NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
/**
* Creates BossBar with specified parameters and registers it to BossBar manager,
* allowing to retrieve it later.
*
* @param title
* Title to display, supports placeholders
*
* @param progress
* Progress to use. Must be a string version of a number, or
* a placeholder that returns a number
*
* @param color
* Color to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
*
* @param style
* Style to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
* @return The newly created BossBar
*/
BossBar createBossBar(String title, String progress, String color, String style);
/**
* Creates BossBar with specified parameters and registers it to BossBar manager,
* allowing to retrieve it later.
*
* @param title
* Title to display, supports placeholders
* @param progress
* Progress to use. Must be a string version of a number, or
* a placeholder that returns a number
* @param color
* Color to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
* @param style
* Style to use. Must be a string version of one of enum constants, or
* a placeholder that returns one of them
* @return The newly created BossBar
*/
@NotNull BossBar createBossBar(@NonNull String title, @NonNull String progress, @NonNull String color, @NonNull String style);
/**
* Returns registered BossBar with given name. For BossBars from config it is the name
* they were created with, for API BossBars their randomly generated name, which can be
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
* Returns null if no such BossBar exists.
*
* @param name
* Name of registered BossBar
* @return BossBar with specified name
*/
BossBar getBossBar(String name);
/**
* Returns registered BossBar with given name. For BossBars from config it is the name
* they were created with, for API BossBars their randomly generated name, which can be
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
* Returns {@code null} if no such BossBar exists.
*
* @param name
* Name of registered BossBar
* @return BossBar with specified name
*/
@Nullable BossBar getBossBar(@NonNull String name);
/**
* Returns registered BossBar with specified UUID. This UUID is randomly generated when
* BossBar is created and used in packets / to match which the one from Bukkit API.
* Returns null if no such BossBar exists.
*
* @param id
* UUID to return BossBar by
* @return BossBar with specified uuid
*/
BossBar getBossBar(UUID id);
/**
* Returns a map of all registered BossBars. This includes both BossBars from config
* and those registered via API.
* <p>
* Map key is BossBar's name available in {@link me.neznamy.tab.api.bossbar.BossBar#getName()},
* map value is the BossBar interface.
*
* @return all registered BossBars
*/
@NotNull Map<String, BossBar> getRegisteredBossBars();
/**
* Returns a map of all registered BossBars. This includes both BossBars from config
* and those registered via API.
* <p>
* Map key is BossBar's name available in {@link me.neznamy.tab.api.bossbar.BossBar#getName()},
* map value is the BossBar interface.
* @return all registered BossBars
*/
Map<String, BossBar> getRegisteredBossBars();
/**
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
*
* @param player
* Player to toggle BossBar for
* @param sendToggleMessage
* {@code true} if toggle message should be sent, {@code false} if not
* @see #hasBossBarVisible(TabPlayer)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
void toggleBossBar(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
*
* @param player
* Player to toggle BossBar for
*
* @param sendToggleMessage
* {@code true} if toggle message should be sent, {@code false} if not
* @see #hasBossBarVisible(TabPlayer)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
void toggleBossBar(TabPlayer player, boolean sendToggleMessage);
/**
* Returns true if player can see BossBars, false if toggled via command or API.
*
* @param player
* Player to check toggle status of
* @return {@code true} if visible, {@code false} if disabled (toggled)
* @see #toggleBossBar(TabPlayer, boolean)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
boolean hasBossBarVisible(@NonNull TabPlayer player);
/**
* Returns true if player can see BossBars, false if toggled via command or API.
*
* @param player
* Player to check toggle status of
* @return {@code true} if visible, {@code false} if disabled (toggled)
* @see #toggleBossBar(TabPlayer, boolean)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
boolean hasBossBarVisible(TabPlayer player);
/**
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
* If visibility changed, toggle message is sent if {@code sendToggleMessage} is {@code true}.
*
* @param player
* Player to set BossBar visibility of
* @param visible
* {@code true} if BossBar should be visible, {@code false} if not
* @param sendToggleMessage
* {@code true} if toggle message should be sent if value changed, {@code false} if not
* @see #toggleBossBar(TabPlayer, boolean)
* @see #hasBossBarVisible(TabPlayer)
*/
void setBossBarVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
* If visibility changed, toggle message is sent if {@code sendToggleMessage} is {@code true}.
*
* @param player
* Player to set BossBar visibility of
* @param visible
* {@code true} if BossBar should be visible, {@code false} if not
* @param sendToggleMessage
* {@code true} if toggle message should be sent if value changed, {@code false} if not
* @see #toggleBossBar(TabPlayer, boolean)
* @see #hasBossBarVisible(TabPlayer)
*/
void setBossBarVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
*
* @param player
* Player to show BossBar to
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #announceBossBar(String, int)
* @see #getAnnouncedBossBars()
*/
void sendBossBarTemporarily(@NonNull TabPlayer player, @NonNull String bossBar, int duration);
/**
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
* @param player
* Player to show BossBar to
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #announceBossBar(String, int)
* @see #getAnnouncedBossBars()
*/
void sendBossBarTemporarily(TabPlayer player, String bossBar, int duration);
/**
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
*
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #getAnnouncedBossBars()
*/
void announceBossBar(@NonNull String bossBar, int duration);
/**
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
* @param bossBar
* Name of registered BossBar to show
* @param duration
* In milliseconds for how long should BossBar be displayed
* @throws IllegalArgumentException
* if no BossBar with specified name exists
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #getAnnouncedBossBars()
*/
void announceBossBar(String bossBar, int duration);
/**
* Returns list of BossBars which are currently being announced.
* @return list of currently active BossBar announcements
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #announceBossBar(String, int)
*/
List<BossBar> getAnnouncedBossBars();
/**
* Returns list of BossBars which are currently being announced.
*
* @return list of currently active BossBar announcements
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #announceBossBar(String, int)
*/
@NotNull List<BossBar> getAnnouncedBossBars();
}
@@ -1,59 +0,0 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
/**
* Class for click event action in chat component
*/
public class ChatClickable {
/** Click action */
private final EnumClickAction action;
/** Click value */
private final String value;
/**
* Constructs new instance with given action and value.
* @param action
* click event action
* @param value
* click event value
* @throws IllegalArgumentException
* if {@code action} is null or {@code value} is null
*/
public ChatClickable(EnumClickAction action, String value) {
Preconditions.checkNotNull(action, "hover action cannot be null");
Preconditions.checkNotNull(value, "hover value cannot be null");
this.action = action;
this.value = value;
}
/**
* Returns click action defined in constructor
* @return click action
*/
public EnumClickAction getAction() {
return action;
}
/**
* Returns click action defined in constructor
* @return click action
*/
public String getValue() {
return value;
}
/**
* Enum for all possible click actions
*/
public enum EnumClickAction {
OPEN_URL,
RUN_COMMAND,
CHANGE_PAGE, //since 1.8
SUGGEST_COMMAND,
COPY_TO_CLIPBOARD //since 1.15
}
}
@@ -1,38 +0,0 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import com.google.gson.JsonObject;
public class ChatComponentEntity extends IChatBaseComponent {
private final String type;
private final UUID id;
private final String name;
public ChatComponentEntity(String type, UUID id, String name) {
this.type = type;
this.id = id;
this.name = name;
}
@Override
public String toRawText() {
return toString();
}
@Override
public String toString() {
return String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
}
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("type", type);
json.addProperty("id", id.toString());
JsonObject json2 = new JsonObject();
json2.addProperty("text", name);
json.add("name", json2);
return json;
}
}
@@ -1,57 +0,0 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
/**
* Class for hover event action in chat component
*/
public class ChatHoverable {
/** Hover action */
private final EnumHoverAction action;
/** Hover value */
private final IChatBaseComponent value;
/**
* Constructs new instance with given action and value.
* @param action
* hover event action
* @param value
* hover event value
* @throws IllegalArgumentException
* if {@code action} is null or {@code value} is null
*/
public ChatHoverable(EnumHoverAction action, IChatBaseComponent value) {
Preconditions.checkNotNull(action, "hover action cannot be null");
Preconditions.checkNotNull(value, "hover value cannot be null");
this.action = action;
this.value = value;
}
/**
* Returns hover action defined in constructor
* @return hover action
*/
public EnumHoverAction getAction() {
return action;
}
/**
* Returns hover value defined in constructor
* @return hover value
*/
public IChatBaseComponent getValue() {
return value;
}
/**
* Enum for all possible hover actions
*/
public enum EnumHoverAction {
SHOW_TEXT,
SHOW_ITEM,
SHOW_ENTITY
}
}
@@ -1,289 +0,0 @@
package me.neznamy.tab.api.chat;
import java.util.UUID;
import org.json.simple.JSONObject;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
public class ChatModifier {
private TextColor color;
private Boolean bold;
private Boolean italic;
private Boolean underlined;
private Boolean strikethrough;
private Boolean obfuscated;
private ChatClickable clickEvent;
private ChatHoverable hoverEvent;
private String font;
private ProtocolVersion targetVersion;
public ChatModifier() {
}
public ChatModifier(ChatModifier modifier) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
this.bold = modifier.bold;
this.italic = modifier.italic;
this.underlined = modifier.underlined;
this.strikethrough = modifier.strikethrough;
this.obfuscated = modifier.obfuscated;
this.clickEvent = modifier.clickEvent == null ? null : new ChatClickable(modifier.clickEvent.getAction(), modifier.clickEvent.getValue());
this.hoverEvent = modifier.hoverEvent == null ? null : new ChatHoverable(modifier.hoverEvent.getAction(), modifier.hoverEvent.getValue());
this.font = modifier.font;
this.targetVersion = modifier.targetVersion;
}
public TextColor getColor() {
return color;
}
public void setColor(TextColor color) {
this.color = color;
}
public Boolean getBold() {
return bold;
}
public void setBold(Boolean bold) {
this.bold = bold;
}
public Boolean getItalic() {
return italic;
}
public void setItalic(Boolean italic) {
this.italic = italic;
}
public Boolean getUnderlined() {
return underlined;
}
public void setUnderlined(Boolean underlined) {
this.underlined = underlined;
}
public Boolean getStrikethrough() {
return strikethrough;
}
public void setStrikethrough(Boolean strikethrough) {
this.strikethrough = strikethrough;
}
public Boolean getObfuscated() {
return obfuscated;
}
public void setObfuscated(Boolean obfuscated) {
this.obfuscated = obfuscated;
}
/**
* Returns true if bold is defined and set to true, false otherwise
* @return true if bold is defined and set to true, false otherwise
*/
public boolean isBold(){
return Boolean.TRUE.equals(bold);
}
/**
* Returns true if italic is defined and set to true, false otherwise
* @return true if italic is defined and set to true, false otherwise
*/
public boolean isItalic(){
return Boolean.TRUE.equals(italic);
}
/**
* Returns true if underlined is defined and set to true, false otherwise
* @return true if underlined is defined and set to true, false otherwise
*/
public boolean isUnderlined(){
return Boolean.TRUE.equals(underlined);
}
/**
* Returns true if strikethrough is defined and set to true, false otherwise
* @return true if strikethrough is defined and set to true, false otherwise
*/
public boolean isStrikethrough(){
return Boolean.TRUE.equals(strikethrough);
}
/**
* Returns true if obfuscation is defined and set to true, false otherwise
* @return true if obfuscation is defined and set to true, false otherwise
*/
public boolean isObfuscated(){
return Boolean.TRUE.equals(obfuscated);
}
public ChatClickable getClickEvent() {
return clickEvent;
}
public void setClickEvent(ChatClickable clickEvent) {
this.clickEvent = clickEvent;
}
public ChatHoverable getHoverEvent() {
return hoverEvent;
}
public void setHoverEvent(ChatHoverable hoverEvent) {
this.hoverEvent = hoverEvent;
}
/**
* Sets click action to OPEN_URL and url to given value
* @param url - url to open
*/
public void onClickOpenUrl(String url) {
clickEvent = new ChatClickable(EnumClickAction.OPEN_URL, url);
}
/**
* Sets click action to RUN_COMMAND and command to given value
* @param command - command to perform, might be without / to send a chat message
*/
public void onClickRunCommand(String command) {
clickEvent = new ChatClickable(EnumClickAction.RUN_COMMAND, command);
}
/**
* Sets click action to SUGGEST_COMMAND and command to given value
* @param command - command to suggest
*/
public void onClickSuggestCommand(String command) {
clickEvent = new ChatClickable(EnumClickAction.SUGGEST_COMMAND, command);
}
/**
* Sets click action to CHANGE_PAGE and page id to given value
* @param newPage - id of new page
*/
public void onClickChangePage(int newPage) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() < 8) throw new UnsupportedOperationException("change_page click action is not supported on <1.8");
clickEvent = new ChatClickable(EnumClickAction.CHANGE_PAGE, String.valueOf(newPage));
}
/**
* Sets click action to COPY_TO_CLIPBOARD and text to provided value
* @param text - text to copy to clipboard on click
*/
public void onClickCopyToClipBoard(String text) {
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
}
public void onClick(EnumClickAction action, String value) {
clickEvent = new ChatClickable(action, value);
}
/**
* Sets hover action to SHOW_TEXT and text to given value
* @param text - text to show
*/
public void onHoverShowText(IChatBaseComponent text) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_TEXT, text);
}
/**
* Sets hover action to SHOW_ITEM and item to given value
* @param serializedItem - item to show
*/
public void onHoverShowItem(String serializedItem) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ITEM, new IChatBaseComponent(serializedItem));
}
/**
* Sets hover action to SHOW_ENTITY and entity data to given values
* @param type - entity type
* @param id - entity uuid
* @param name - entity custom name
*/
public void onHoverShowEntity(String type, UUID id, String name) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new IChatBaseComponent(String.format("{type:%s,id:%s,name:%s}", type, id, name)));
} else {
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
}
}
public void onHover(EnumHoverAction action, IChatBaseComponent value) {
hoverEvent = new ChatHoverable(action, value);
}
public String getFont() {
return font;
}
public void setFont(String font) {
this.font = font;
}
@SuppressWarnings("unchecked")
public JSONObject serialize() {
JSONObject json = new JSONObject();
if (color != null) json.put("color", targetVersion.getMinorVersion() >= 16 ? color.toString() : color.getLegacyColor().toString().toLowerCase());
if (bold != null) json.put("bold", bold);
if (italic != null) json.put("italic", italic);
if (underlined != null) json.put("underlined", underlined);
if (strikethrough != null) json.put("strikethrough", strikethrough);
if (obfuscated != null) json.put("obfuscated", obfuscated);
if (clickEvent != null) {
JSONObject click = new JSONObject();
click.put("action", clickEvent.getAction().toString().toLowerCase());
click.put("value", clickEvent.getValue());
json.put("clickEvent", click);
}
if (hoverEvent != null) {
JSONObject hover = new JSONObject();
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hover.put("contents", hoverEvent.getValue());
} else {
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
}
json.put("hoverEvent", hover);
}
if (font != null) json.put("font", font);
return json;
}
public String getMagicCodes() {
StringBuilder builder = new StringBuilder();
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
return builder.toString();
}
public void setTargetVersion(ProtocolVersion targetVersion) {
this.targetVersion = targetVersion;
}
public ProtocolVersion getTargetVersion() {
return targetVersion;
}
}
@@ -1,245 +0,0 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
/**
* An enum containing all possible legacy color codes and magic codes. Also contains handy color-related methods.
*/
public enum EnumChatFormat {
BLACK('0', "000000"),
DARK_BLUE('1', "0000AA"),
DARK_GREEN('2', "00AA00"),
DARK_AQUA('3', "00AAAA"),
DARK_RED('4', "AA0000"),
DARK_PURPLE('5', "AA00AA"),
GOLD('6', "FFAA00"),
GRAY('7', "AAAAAA"),
DARK_GRAY('8', "555555"),
BLUE('9', "5555FF"),
GREEN('a', "55FF55"),
AQUA('b', "55FFFF"),
RED('c', "FF5555"),
LIGHT_PURPLE('d', "FF55FF"),
YELLOW('e', "FFFF55"),
WHITE('f', "FFFFFF"),
OBFUSCATED('k'),
BOLD('l'),
STRIKETHROUGH('m'),
UNDERLINE('n'),
ITALIC('o'),
RESET('r');
/** Creating a constant to avoid memory allocations on each request */
private static final EnumChatFormat[] VALUES = values();
/** The symbol minecraft uses to colorize text */
public static final char COLOR_CHAR = 0x00a7;
/** The color symbol in form of a string */
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
/** Character representing the color or magic code */
private final char character;
/** Red value of this constant, 0 for magic codes */
private final short red;
/** Green value of this constant, 0 for magic codes */
private final short green;
/** Blue value of this constant, 0 for magic codes */
private final short blue;
/** Color as 6-digit hex code, null for magic codes */
private final String hexCode;
/** Color symbol followed by constant's character */
private final String chatFormat;
/**
* Constructs new color instance with given character and hex code
* @param character
* character which the color goes by
* @param hexCode
* 6-digit hex code of the color
*/
EnumChatFormat(char character, String hexCode) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
this.hexCode = hexCode;
int hexColor = Integer.parseInt(hexCode, 16);
red = (short) ((hexColor >> 16) & 0xFF);
green = (short) ((hexColor >> 8) & 0xFF);
blue = (short) (hexColor & 0xFF);
}
/**
* Constructs new magic code instance with given character
* @param character
* character representing the magic code
*/
EnumChatFormat(char character) {
this.character = character;
this.chatFormat = String.valueOf(COLOR_CHAR) + character;
red = 0;
green = 0;
blue = 0;
hexCode = null;
}
/**
* Returns red value of this color code
* @return red value
*/
public short getRed() {
return red;
}
/**
* Returns green value of this color code
* @return green value
*/
public short getGreen() {
return green;
}
/**
* Returns blue value of this color code
* @return blue value
*/
public short getBlue() {
return blue;
}
/**
* Returns enum value based on provided character or null if character is not valid
* @param c
* color code character (0-9, a-f, k-o, r)
* @return instance from the character or null if character is not valid
*/
public static EnumChatFormat getByChar(char c) {
for (EnumChatFormat format : VALUES) {
if (format.character == c) return format;
}
return null;
}
/**
* Returns enum value of last colors used in given string.
* If it's null, empty or does not contain color codes, WHITE is returned.
* @param string
* string to check last colors of
* @return last used color code in given string or WHITE if nothing is found
*/
public static EnumChatFormat lastColorsOf(String string) {
if (string == null || string.length() == 0) return EnumChatFormat.WHITE;
String legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
String last = getLastColors(legacyText);
if (last.length() > 0) {
char c = last.toCharArray()[1];
for (EnumChatFormat e : VALUES) {
if (e.character == c) return e;
}
}
return EnumChatFormat.WHITE;
}
/**
* Returns color char followed by color's character
* @return color char followed by color's character
*/
public String getFormat() {
return chatFormat;
}
/**
* Returns character representing this color
* @return character representing this color
*/
public char getCharacter() {
return character;
}
/**
* Returns hex code of this format, null if this is a magic code
* @return hex code of this format
*/
public String getHexCode() {
return hexCode;
}
/**
* Returns enum value with exact red, green and blue values or null if no match was found
* @param red
* exact red value
* @param green
* exact green value
* @param blue
* exact blue value
* @return enum value or null if no such combination exists
*/
public static EnumChatFormat fromRGBExact(int red, int green, int blue){
for (EnumChatFormat format : VALUES) {
if (format.red == red && format.green == green && format.blue == blue) return format;
}
return null;
}
/**
* Color translation method taken from bukkit, which converts '&' symbol into
* the actual color character if followed by a valid color character.
* @param textToTranslate
* text to replace color symbol in
* @return colorized string from provided text
*/
public static String color(String textToTranslate){
if (textToTranslate == null) return null;
if (!textToTranslate.contains("&")) return textToTranslate;
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; i++) {
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx".indexOf(b[(i + 1)]) > -1)){
b[i] = COLOR_CHAR;
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
}
}
return new String(b);
}
/**
* Turns back the color symbol into '&' symbol in provided text.
* @param text
* text to revert colors in
* @return reverted text
*/
public static String decolor(String text) {
if (text == null) return null;
if (!text.contains(COLOR_STRING)) return text;
return text.replace(COLOR_CHAR, '&');
}
/**
* Code taken from bukkit, which returns last color codes used in provided text.
* @param input
* text to get last colors from
* @return last colors used in provided text or empty string if nothing was found
*/
public static String getLastColors(String input) {
StringBuilder result = new StringBuilder();
int length = input.length();
for (int index = length - 1; index > -1; index--){
char section = input.charAt(index);
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)){
char c = input.charAt(index + 1);
if ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".contains(String.valueOf(c))) {
result.insert(0, COLOR_CHAR);
result.insert(1, c);
if ("0123456789AaBbCcDdEeFfRr".contains(String.valueOf(c))) {
break;
}
}
}
}
return result.toString();
}
}
@@ -1,476 +0,0 @@
package me.neznamy.tab.api.chat;
import java.util.*;
import me.neznamy.tab.api.util.Preconditions;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import org.json.simple.parser.ParseException;
/**
* A class representing the n.m.s.IChatBaseComponent class to make work with it much easier
*/
@SuppressWarnings("unchecked")
public class IChatBaseComponent {
/**
* Component cache maps to avoid large memory allocations as well as
* higher CPU usage when using animations which send the same text on repeat.
*/
private static final Map<String, IChatBaseComponent> componentCache = new HashMap<>();
private static final Map<IChatBaseComponent, String> serializeCacheModern = new HashMap<>();
private static final Map<IChatBaseComponent, String> serializeCacheLegacy = new HashMap<>();
/** Text of the component */
private String text;
/** Chat modifier containing color, magic codes, hover and click event */
private ChatModifier modifier = new ChatModifier();
/** Extra components used in "extra" field */
private List<IChatBaseComponent> extra;
/**
* Constructs a new empty component
*/
public IChatBaseComponent() {
}
/**
* Constructs a new component which is a clone of provided component
* @param component
* component to clone
*/
public IChatBaseComponent(IChatBaseComponent component) {
this.text = component.text;
this.modifier = new ChatModifier(component.modifier);
for (IChatBaseComponent child : component.getExtra()) {
addExtra(new IChatBaseComponent(child));
}
}
/**
* Constructs new instance with given text
* @param text
* text to display
*/
public IChatBaseComponent(String text) {
this.text = text;
}
/**
* Returns list of extra components. If no extra components are defined, returns empty list.
* @return list of extra components
*/
public List<IChatBaseComponent> getExtra(){
if (extra == null) return Collections.emptyList();
return extra;
}
/**
* Sets full list of extra components to given list. Does not allow empty list.
* @param components
* components to use as extra
* @return self
* @throws IllegalArgumentException
* if {@code components} is an empty list
*/
public IChatBaseComponent setExtra(List<IChatBaseComponent> components){
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
this.extra = components;
return this;
}
/**
* Appends provided component as extra component
* @param child
* component to append
*/
public void addExtra(IChatBaseComponent child) {
if (extra == null) extra = new ArrayList<>();
extra.add(child);
}
/**
* Returns text of this component
* @return text of this component
*/
public String getText() {
return text;
}
/**
* Returns chat modifier of this component
* @return chat modifier of this component
*/
public ChatModifier getModifier() {
return modifier;
}
/**
* Sets modifier to provided value
* @param modifier
* modifier to set value to
* @throws IllegalArgumentException
* if {@code modifier} is null
*/
public void setModifier(ChatModifier modifier) {
Preconditions.checkNotNull(modifier, "modifier");
this.modifier = modifier;
}
/**
* Changes text of this component
* @param text
* text to show
*/
public void setText(String text) {
this.text = text;
}
/**
* Deserializes string and returns created component. If provided string is null, returns null.
* @param json
* serialized string
* @return Deserialized component or null if input is null
*/
public static IChatBaseComponent deserialize(String json) {
if (json == null) return null;
if (json.startsWith("\"") && json.endsWith("\"") && json.length() > 1) {
//simple component with only text used, minecraft serializer outputs the text in quotes instead of full json
return new IChatBaseComponent(json.substring(1, json.length()-1));
}
JSONObject jsonObject;
try {
jsonObject = (JSONObject) new JSONParser().parse(json);
} catch (ParseException e) {
TabAPI.getInstance().logError("Failed to deserialize json component " + json, e);
return null;
}
IChatBaseComponent component;
if (jsonObject.containsKey("type")) {
return new ChatComponentEntity((String) jsonObject.get("type"), UUID.fromString((String) jsonObject.get("id")), IChatBaseComponent.deserialize(jsonObject.get("name").toString()).toFlatText());
}
component = new IChatBaseComponent();
component.setText((String) jsonObject.get("text"));
component.modifier.setBold(getBoolean(jsonObject, "bold"));
component.modifier.setItalic(getBoolean(jsonObject, "italic"));
component.modifier.setUnderlined(getBoolean(jsonObject, "underlined"));
component.modifier.setStrikethrough(getBoolean(jsonObject, "strikethrough"));
component.modifier.setObfuscated(getBoolean(jsonObject, "obfuscated"));
component.modifier.setColor(TextColor.fromString(((String) jsonObject.get("color"))));
if (jsonObject.containsKey("clickEvent")) {
JSONObject clickEvent = (JSONObject) jsonObject.get("clickEvent");
String action = (String) clickEvent.get("action");
String value = clickEvent.get("value").toString();
component.modifier.onClick(EnumClickAction.valueOf(action.toUpperCase()), value);
}
if (jsonObject.containsKey("hoverEvent")) {
JSONObject hoverEvent = (JSONObject) jsonObject.get("hoverEvent");
String action = (String) hoverEvent.get("action");
String value = (String) hoverEvent.get("value");
component.modifier.onHover(EnumHoverAction.valueOf(action.toUpperCase()), deserialize(value));
}
if (jsonObject.containsKey("extra")) {
List<Object> list = (List<Object>) jsonObject.get("extra");
for (Object extra : list) {
String string = extra.toString();
//reverting .toString() removing "" for simple text
if (!string.startsWith("{")) string = "\"" + string + "\"";
component.addExtra(deserialize(string));
}
}
return component;
}
/**
* Returns boolean value of requested key from map
* @param jsonObject
* map to get value from
* @param key
* name of key
* @return value from json object or null if not present
*/
private static Boolean getBoolean(JSONObject jsonObject, String key) {
String value = String.valueOf(jsonObject.getOrDefault(key, null));
return "null".equals(value) ? null : Boolean.parseBoolean(value);
}
/**
* Converts the component to a string representing the serialized component.
* This method is only used internally by json library since it's missing
* protocol version field used by the method.
* @return serialized component in string form
* @see #toString(ProtocolVersion)
*/
@Override
public String toString() {
JSONObject json = new JSONObject();
if (text != null) json.put("text", text);
if (modifier.getTargetVersion() == null) modifier.setTargetVersion(TabAPI.getInstance().getServerVersion()); //packet.toString() was called as a part of a debug message
json.putAll(modifier.serialize());
if (extra != null) json.put("extra", extra);
return json.toString();
}
/**
* Serializes this component with colors based on client version.
* If client version is <1.16, HEX colors will be converted to legacy colors.
* @param clientVersion
* client version to adapt component for
* @return serialized string
*/
public String toString(ProtocolVersion clientVersion) {
return toString(clientVersion, false);
}
/**
* Serializes this component with colors based on client version.
* If client version is <1.16, HEX colors will be converted to legacy colors.
* @param clientVersion
* client version to adapt component for
* @param sendTranslatableIfEmpty
* whether empty translatable should be sent if text is empty or not
* @return serialized string
*/
public String toString(ProtocolVersion clientVersion, boolean sendTranslatableIfEmpty) {
if (extra == null) {
if (text == null) return null;
if (text.length() == 0) {
if (sendTranslatableIfEmpty) {
return "{\"translate\":\"\"}";
} else {
return "{\"text\":\"\"}";
}
}
}
modifier.setTargetVersion(clientVersion);
for (IChatBaseComponent child : getExtra()) {
child.modifier.setTargetVersion(clientVersion);
}
String string;
if (clientVersion.getMinorVersion() >= 16) {
if (serializeCacheModern.containsKey(this)) return serializeCacheModern.get(this);
string = toString();
if (serializeCacheModern.size() > 10000) serializeCacheModern.clear();
serializeCacheModern.put(this, string);
} else {
if (serializeCacheLegacy.containsKey(this)) return serializeCacheLegacy.get(this);
string = toString();
if (serializeCacheLegacy.size() > 10000) serializeCacheLegacy.clear();
serializeCacheLegacy.put(this, string);
}
return string;
}
/**
* Returns organized component from colored text
* @param originalText
* text to convert
* @return organized component from colored text
*/
public static IChatBaseComponent fromColoredText(String originalText){
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), false);
List<IChatBaseComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
IChatBaseComponent component = new IChatBaseComponent();
for (int i = 0; i < text.length(); i++){
char c = text.charAt(i);
if (c == EnumChatFormat.COLOR_CHAR){
i++;
if (i >= text.length()) {
break;
}
c = text.charAt(i);
if ((c >= 'A') && (c <= 'Z')) {
c = (char)(c + ' ');
}
EnumChatFormat format = EnumChatFormat.getByChar(c);
if (format != null){
if (builder.length() > 0) {
component.setText(builder.toString());
components.add(component);
component = new IChatBaseComponent(component);
component.text = null;
builder = new StringBuilder();
}
switch (format){
case BOLD:
component.modifier.setBold(true);
break;
case ITALIC:
component.modifier.setItalic(true);
break;
case UNDERLINE:
component.modifier.setUnderlined(true);
break;
case STRIKETHROUGH:
component.modifier.setStrikethrough(true);
break;
case OBFUSCATED:
component.modifier.setObfuscated(true);
break;
case RESET:
component = new IChatBaseComponent();
component.modifier.setColor(new TextColor(EnumChatFormat.WHITE));
break;
default:
component = new IChatBaseComponent();
component.modifier.setColor(new TextColor(format));
break;
}
}
} else if (c == '#' && text.length() > i+6){
String hex = text.substring(i+1, i+7);
if (RGBUtils.getInstance().isHexCode(hex)) {
TextColor color;
if (containsLegacyCode(text, i)) {
color = new TextColor(hex, EnumChatFormat.getByChar(text.charAt(i+8)));
i += 8;
} else {
color = new TextColor(hex);
i += 6;
}
if (builder.length() > 0){
component.setText(builder.toString());
components.add(component);
builder = new StringBuilder();
}
component = new IChatBaseComponent();
component.modifier.setColor(color);
} else {
builder.append(c);
}
} else {
builder.append(c);
}
}
component.setText(builder.toString());
components.add(component);
return new IChatBaseComponent("").setExtra(components);
}
/**
* Returns true if text contains legacy color request at defined RGB index start
* @param text
* text to check
* @param i
* current index start
* @return true if legacy color is defined, false if not
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
}
/**
* Converts this component into a simple text with legacy colors (the closest match if color is set to RGB)
* @return The simple text format
*/
public String toLegacyText() {
StringBuilder builder = new StringBuilder();
append(builder, "");
return builder.toString();
}
/**
* Appends text to string builder, might also add color and magic codes if they are different
* from previous component in chain.
* @param builder
* builder to append text to
* @param previousFormatting
* colors and magic codes in previous component
* @return wew formatting, might be identical to previous one
*/
private String append(StringBuilder builder, String previousFormatting) {
String formatting = previousFormatting;
if (text != null) {
formatting = getFormatting();
if (!formatting.equals(previousFormatting)) {
builder.append(formatting);
}
builder.append(text);
}
for (IChatBaseComponent component : getExtra()) {
formatting = component.append(builder, formatting);
}
return formatting;
}
/**
* Returns colors and magic codes of this component
* @return used colors and magic codes
*/
private String getFormatting() {
StringBuilder builder = new StringBuilder();
if (modifier.getColor() != null) {
if (modifier.getColor().getLegacyColor() == EnumChatFormat.WHITE) {
//preventing unwanted &r -> &f conversion and stopping the <1.13 client bug fix from working
builder.append(EnumChatFormat.RESET.getFormat());
} else {
builder.append(modifier.getColor().getLegacyColor().getFormat());
}
}
builder.append(modifier.getMagicCodes());
return builder.toString();
}
/**
* Returns raw text without colors, only works correctly when component is organized
* @return raw text in this component and all child components
*/
public String toRawText() {
StringBuilder builder = new StringBuilder();
if (text != null) builder.append(text);
for (IChatBaseComponent child : getExtra()) {
if (child.text != null) builder.append(child.text);
}
return builder.toString();
}
/**
* Converts the component into flat text with used colors (including rgb) and magic codes
* @return converted text
*/
public String toFlatText() {
StringBuilder builder = new StringBuilder();
if (modifier.getColor() != null) builder.append("#").append(modifier.getColor().getHexCode());
builder.append(modifier.getMagicCodes());
if (text != null) builder.append(text);
for (IChatBaseComponent child : getExtra()) {
builder.append(child.toFlatText());
}
return builder.toString();
}
/**
* Returns the most optimized component based on text. Returns null if text is null,
* organized component if RGB colors are used or simple component with only text field
* containing the whole text when no RGB colors are used
* @param text
* text to create component from
* @return The most performance-optimized component based on text
*/
public static IChatBaseComponent optimizedComponent(String text){
if (text == null) return null;
if (componentCache.containsKey(text)) return componentCache.get(text);
IChatBaseComponent component;
if (text.contains("#") || text.contains("&x") || text.contains(EnumChatFormat.COLOR_CHAR + "x")){
//contains RGB colors
component = IChatBaseComponent.fromColoredText(text);
} else {
//no RGB
component = new IChatBaseComponent(text);
}
if (componentCache.size() > 10000) componentCache.clear();
componentCache.put(text, component);
return component;
}
}
@@ -1,209 +0,0 @@
package me.neznamy.tab.api.chat;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing a component color, which can be either RGB or legacy code.
*/
public class TextColor {
/**
* RGB values as a single number of 3 8-bit numbers (0-255).
* It is only initialized if colors are actually used to avoid
* unnecessary memory allocations with string operations.
*/
private int rgb = -1;
/** Closest legacy color to this color object. */
private EnumChatFormat legacyColor;
/** HEX code of this color as a 6-digit hexadecimal string without "#" prefix. */
private String hexCode;
/**
* Boolean value whether the legacy color was forced with constructor or should be
* automatically assigned as closest color.
* This value is used in gradients when converting text for legacy players. */
private boolean legacyColorForced;
/**
* Constructs new instance as a clone of the provided color.
* @param color
* color to create a clone of
* @throws IllegalArgumentException
* if color is {@code null}
*/
public TextColor(TextColor color) {
Preconditions.checkNotNull(color, "color cannot be null");
rgb = color.rgb;
legacyColor = color.legacyColor;
hexCode = color.hexCode;
legacyColorForced = color.legacyColorForced;
}
/**
* Constructs new instance from provided 6-digit hex code string
* @param hexCode
* a 6-digit combination of hex numbers as a string
* @throws IllegalArgumentException
* if hexCode is {@code null}
*/
public TextColor(String hexCode) {
Preconditions.checkNotNull(hexCode, "hex code cannot be null");
this.hexCode = hexCode;
}
/**
* Constructs new instance from provided 6-digit hex code and forced legacy color
* @param hexCode
* 6-digit combination of hex numbers as a string
* @param legacyColor
* color to use for legacy clients instead of using the closest legacy color
* @throws IllegalArgumentException
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
*/
public TextColor(String hexCode, EnumChatFormat legacyColor) {
Preconditions.checkNotNull(hexCode, "hex code cannot be null");
Preconditions.checkNotNull(legacyColor, "legacy color cannot be null");
this.hexCode = hexCode;
this.legacyColorForced = true;
this.legacyColor = legacyColor;
}
/**
* Constructs new instance from provided legacy color
* @param legacyColor
* legacy color to construct the instance from
* @throws IllegalArgumentException
* if {@code legacyColor} is {@code null}
*/
public TextColor(EnumChatFormat legacyColor) {
Preconditions.checkNotNull(legacyColor, "legacy color cannot be null");
this.rgb = (legacyColor.getRed() << 16) + (legacyColor.getGreen() << 8) + legacyColor.getBlue();
this.hexCode = legacyColor.getHexCode();
}
/**
* Constructs new instance with red, green and blue values
* @param red
* red value
* @param green
* green value
* @param blue
* blue value
* @throws IllegalArgumentException
* if {@code red}, {@code green} or {@code blue} is out of range ({@code 0-255})
*/
public TextColor(int red, int green, int blue) {
Preconditions.checkRange(red, 0, 255, "red color");
Preconditions.checkRange(green, 0, 255, "green color");
Preconditions.checkRange(blue, 0, 255, "blue color");
this.rgb = (red << 16) + (green << 8) + blue;
}
/**
* Loads the closest legacy color based currently provided values
*/
private EnumChatFormat loadClosestColor() {
double minMaxDist = 9999;
double maxDist;
EnumChatFormat closestColor = EnumChatFormat.WHITE;
for (EnumChatFormat color : EnumChatFormat.values()) {
int rDiff = Math.abs(color.getRed() - getRed());
int gDiff = Math.abs(color.getGreen() - getGreen());
int bDiff = Math.abs(color.getBlue() - getBlue());
maxDist = rDiff;
if (gDiff > maxDist) maxDist = gDiff;
if (bDiff > maxDist) maxDist = bDiff;
if (maxDist < minMaxDist) {
minMaxDist = maxDist;
closestColor = color;
}
}
return closestColor;
}
/**
* Returns {@code red} value
* @return red value
*/
public int getRed() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
return (rgb >> 16) & 0xFF;
}
/**
* Returns {@code green} value
* @return green value
*/
public int getGreen() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
return (rgb >> 8) & 0xFF;
}
/**
* Returns {@code blue} value
* @return blue value
*/
public int getBlue() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
return rgb & 0xFF;
}
/**
* Returns the closest legacy color of this color object.
* If the color was defined in constructor, it's returned.
* Otherwise, the closest color is calculated the then returned.
* @return closest legacy color
*/
public EnumChatFormat getLegacyColor() {
if (legacyColor == null) legacyColor = loadClosestColor();
return legacyColor;
}
/**
* Returns the rgb combination as a 6-digit hex code string
* @return the rgb combination as a 6-digit hex code string
*/
public String getHexCode() {
if (hexCode == null) hexCode = String.format("%06X", rgb);
return hexCode;
}
/**
* Converts the color into a valid color value used in color field in chat component.
* That is either 6-digit hex code prefixed with '#', or lowercase legacy color.
* @return the color serialized for use in chat component
*/
public String toString() {
EnumChatFormat legacyEquivalent = EnumChatFormat.fromRGBExact(getRed(), getGreen(), getBlue());
if (legacyEquivalent != null) {
//not sending old colors as RGB to 1.16 clients if not needed as <1.16 servers will fail to apply color
return legacyEquivalent.toString().toLowerCase();
}
return "#" + getHexCode();
}
/**
* Returns true if legacy color was forced with a constructor, false if not
* @return true if forced, false if not
*/
public boolean isLegacyColorForced() {
return legacyColorForced;
}
/**
* Reads the string as it appears in chat component and turns it into the color object.
* If the entered string is null, returns null.
* If it's prefixed with '#', it's considered as a hex code.
* Otherwise, it is considered being a lowercase legacy color.
* @param string
* string from color field in chat component
* @return An instance from specified string or null if string is null
*/
public static TextColor fromString(String string) {
if (string == null) return null;
if (string.startsWith("#")) return new TextColor(string.substring(1));
return new TextColor(EnumChatFormat.valueOf(string.toUpperCase()));
}
}
@@ -1,183 +0,0 @@
package me.neznamy.tab.api.chat.rgb;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
import me.neznamy.tab.api.chat.rgb.format.BukkitFormat;
import me.neznamy.tab.api.chat.rgb.format.CMIFormat;
import me.neznamy.tab.api.chat.rgb.format.HtmlFormat;
import me.neznamy.tab.api.chat.rgb.format.RGBFormatter;
import me.neznamy.tab.api.chat.rgb.format.UnnamedFormat1;
import me.neznamy.tab.api.chat.rgb.gradient.CMIGradient;
import me.neznamy.tab.api.chat.rgb.gradient.CommonGradient;
import me.neznamy.tab.api.chat.rgb.gradient.GradientPattern;
import me.neznamy.tab.api.chat.rgb.gradient.KyoriGradient;
/**
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
*/
public class RGBUtils {
/** Instance of the class */
private static final RGBUtils instance = new RGBUtils();
/** Registered RGB formatters */
private final RGBFormatter[] formats;
/** Registered gradient patterns */
private final GradientPattern[] gradients;
/** TAB's RGB pattern, used to convert text to bukkit format for boss bar */
private final Pattern tabPattern = Pattern.compile("#[0-9a-fA-F]{6}");
/** RGB pattern for legacy codes */
private final Pattern tabPatternLegacy = Pattern.compile("#[0-9a-fA-F]{6}\\|.");
/**
* Constructs new instance and loads all RGB patterns and gradients
*/
public RGBUtils() {
formats = new RGBFormatter[] {
new BukkitFormat(),
new CMIFormat(),
new UnnamedFormat1(),
new HtmlFormat()
};
gradients = new GradientPattern[] {
//{#RRGGBB>}text{#RRGGBB<}
new CMIGradient(),
//<#RRGGBB>Text</#RRGGBB>
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>[^<]*</#[0-9a-fA-F]{6}>"),
Pattern.compile("<#[0-9a-fA-F]{6}\\|.>[^<]*</#[0-9a-fA-F]{6}>"),
"<#", 9, 2, 9, 7),
//<$#RRGGBB>Text<$#RRGGBB>
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>[^<]*<\\$#[0-9a-fA-F]{6}>"),
Pattern.compile("<\\$#[0-9a-fA-F]{6}\\|.>[^<]*<\\$#[0-9a-fA-F]{6}>"),
"<$", 10, 3, 10, 7),
new KyoriGradient()
};
}
/**
* Returns instance of this class
* @return instance
*/
public static RGBUtils getInstance() {
return instance;
}
/**
* Applies all RGB formats and gradients to text and returns it.
* This method is called on every property initialization to convert formats
* once instead of on every refresh, if possible. Because of that, parameter
* {@code ignorePlaceholders} exists to not break raw placeholder identifiers
* @param text
* original text
* @param ignorePlaceholders
* whether placeholders should be ignored or not
* @return text where everything is converted to #RRGGBB
*/
public String applyFormats(String text, boolean ignorePlaceholders) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, ignorePlaceholders);
}
for (RGBFormatter formatter : formats) {
replaced = formatter.reformat(replaced);
}
return replaced;
}
/**
* Converts TAB's RGB format (#RRGGBB) into bukkit one (&x&r&r&g&g&b&b)
* for modern clients (1.16+), for legacy clients it will use the closest color.
* @param text
* text to convert
* @param rgbClient
* whether client accepts RGB or not
* @return converted text
*/
public String convertToBukkitFormat(String text, boolean rgbClient) {
if (text == null) return null;
if (!text.contains("#")) return text; //no rgb codes
if (rgbClient) {
//converting random formats to TAB one
String replaced = applyFormats(text, false);
for (Pattern p : new Pattern[]{tabPatternLegacy, tabPattern}) {
Matcher m = p.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = "&x&" + hexCode.charAt(1) + "&" + hexCode.charAt(2) + "&" + hexCode.charAt(3) + "&" + hexCode.charAt(4) + "&" + hexCode.charAt(5) + "&" + hexCode.charAt(6);
replaced = replaced.replace(hexCode, EnumChatFormat.color(fixed));
}
}
return replaced;
} else {
return convertRGBtoLegacy(text);
}
}
/**
* Converts all hex codes in given string to legacy codes
* @param text
* text to convert
* @return translated text
*/
public String convertRGBtoLegacy(String text) {
if (text == null) return null;
if (!text.contains("#")) return EnumChatFormat.color(text);
String applied = applyFormats(text, false);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < applied.length(); i++){
char c = applied.charAt(i);
if (c == '#' && applied.length() > i+6) {
String hexCode = applied.substring(i+1, i+7);
if (isHexCode(hexCode)) {
if (containsLegacyCode(applied, i)) {
sb.append(new TextColor(hexCode, EnumChatFormat.getByChar(applied.charAt(i+8))).getLegacyColor().getFormat());
i += 8;
} else {
sb.append(new TextColor(hexCode).getLegacyColor().getFormat());
i += 6;
}
} else {
sb.append(c);
}
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Returns true if entered string is a valid 6-digit combination of
* hexadecimal numbers, false if not
* @param string
* string to check
* @return {@code true} if valid, {@code false} if not
*/
public boolean isHexCode(String string) {
if (string.length() != 6) return false;
for (int i=0; i<6; i++) {
char c = string.charAt(i);
if (c < 48 || (c > 57 && c < 65) || (c > 70 && c < 97) || c > 102) return false;
}
return true;
}
/**
* Returns true if text contains legacy color request at defined RGB index start
* @param text
* text to check
* @param i
* current index start
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
}
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.api.chat.EnumChatFormat;
/**
* Formatter for &x&R&R&G&G&B&B
*/
public class BukkitFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("[" + EnumChatFormat.COLOR_CHAR + "&]x[" + EnumChatFormat.COLOR_CHAR + "&\\p{XDigit}]{12}");
@Override
public String reformat(String text) {
if (!text.contains("&") && !text.contains(EnumChatFormat.COLOR_CHAR + "x")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = new String(new char[] {'#', hexCode.charAt(3), hexCode.charAt(5), hexCode.charAt(7), hexCode.charAt(9), hexCode.charAt(11), hexCode.charAt(13)});
replaced = replaced.replace(hexCode, fixed);
}
return replaced;
}
}
@@ -1,25 +0,0 @@
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for {#RRGGBB}
*/
public class CMIFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("\\{#[0-9a-fA-F]{6}}");
@Override
public String reformat(String text) {
if (!text.contains("{#")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -1,25 +0,0 @@
package me.neznamy.tab.api.chat.rgb.format;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for #<RRGGBB>
*/
public class HtmlFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("#<[0-9a-fA-F]{6}>");
@Override
public String reformat(String text) {
if (!text.contains("#<")) return text;
Matcher m = pattern.matcher(text);
String replaced = text;
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -1,14 +0,0 @@
package me.neznamy.tab.api.chat.rgb.format;
/**
* Abstract class for different RGB patterns
*/
public interface RGBFormatter {
/**
* Reformats RGB codes in provided text into #RRGGBB format
* @param text - text to format
* @return reformatted text
*/
String reformat(String text);
}
@@ -1,12 +0,0 @@
package me.neznamy.tab.api.chat.rgb.format;
/**
* Formatter for &#RRGGBB
*/
public class UnnamedFormat1 implements RGBFormatter {
@Override
public String reformat(String text) {
return text.contains("&#") ? text.replace("&#", "#") : text;
}
}
@@ -1,30 +0,0 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CMIGradient extends CommonGradient {
//pattern for {#RRGGBB<>}
private final Pattern shortcutPattern = Pattern.compile("\\{#[0-9a-fA-F]{6}<>}");
public CMIGradient() {
super(Pattern.compile("\\{#[0-9a-fA-F]{6}>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
Pattern.compile("\\{#[0-9a-fA-F]{6}\\|.>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
"{#", 9, 2, 10, 8);
}
@Override
public String applyPattern(String text, boolean ignorePlaceholders) {
String replaced = text;
if (replaced.contains("<>}")) {
Matcher m = shortcutPattern.matcher(replaced);
while (m.find()) {
String format = m.group();
String code = format.substring(2, 8);
replaced = replaced.replace(format, "{#" + code + "<}{#" + code + ">}");
}
}
return super.applyPattern(replaced, ignorePlaceholders);
}
}
@@ -1,57 +0,0 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
public class CommonGradient implements GradientPattern {
private final Pattern pattern;
private final Pattern legacyPattern;
private final String containCheck;
private final int legacyCharPosition;
private final int startColorStart;
private final int messageStart;
private final int endColorStartSub;
public CommonGradient(Pattern pattern, Pattern legacyPattern, String containCheck, int legacyCharPosition,
int startColorStart, int messageStart, int endColorStartSub) {
this.pattern = pattern;
this.legacyPattern = legacyPattern;
this.containCheck = containCheck;
this.legacyCharPosition = legacyCharPosition;
this.startColorStart = startColorStart;
this.messageStart = messageStart;
this.endColorStartSub = endColorStartSub;
}
@Override
public String applyPattern(String text, boolean ignorePlaceholders) {
if (!text.contains(containCheck)) return text;
String replaced = text;
Matcher m = legacyPattern.matcher(replaced);
while (m.find()) {
String format = m.group();
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(legacyCharPosition));
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6), legacyColor);
String message = format.substring(messageStart+2, format.length()-10);
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
}
m = pattern.matcher(replaced);
while (m.find()) {
String format = m.group();
if (ignorePlaceholders && format.contains("%")) continue;
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6));
String message = format.substring(messageStart, format.length()-10);
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
}
return replaced;
}
}
@@ -1,51 +0,0 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
/**
* Abstract class for applying different gradient patterns
*/
public interface GradientPattern {
/**
* Applies gradients in provided text and returns text using only #RRGGBB
* @param text - text to be reformatted
* @return reformatted text
*/
String applyPattern(String text, boolean ignorePlaceholders);
/**
* Returns gradient text based on start color, text and end color
* @param start - start color
* @param text - text to be reformatted
* @param end - end color
* @return reformatted text
*/
default String asGradient(TextColor start, String text, TextColor end) {
//lazy support for magic codes in gradients
String magicCodes = EnumChatFormat.getLastColors(text);
String deColorized = text.substring(magicCodes.length());
StringBuilder sb = new StringBuilder();
int length = deColorized.length();
if (length == 1) {
sb.append("#");
sb.append(new TextColor(start.getRed(), start.getGreen(), start.getBlue()).getHexCode());
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
sb.append(magicCodes);
sb.append(deColorized);
return sb.toString();
}
for (int i=0; i<length; i++) {
int red = (int) (start.getRed() + (float)(end.getRed() - start.getRed())/(length-1)*i);
int green = (int) (start.getGreen() + (float)(end.getGreen() - start.getGreen())/(length-1)*i);
int blue = (int) (start.getBlue() + (float)(end.getBlue() - start.getBlue())/(length-1)*i);
sb.append("#");
sb.append(new TextColor(red, green, blue).getHexCode());
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
sb.append(magicCodes);
sb.append(deColorized.charAt(i));
}
return sb.toString();
}
}
@@ -1,45 +0,0 @@
package me.neznamy.tab.api.chat.rgb.gradient;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.TextColor;
/**
* Gradient applier for <gradient:#RRGGBB:#RRGGBB>Text</gradient> and <gradient:#RRGGBB|L:#RRGGBB>Text</gradient>
*/
public class KyoriGradient implements GradientPattern {
private final Pattern pattern = Pattern.compile("<gradient:#[0-9a-fA-F]{6}:#[0-9a-fA-F]{6}>[^<]*</gradient>");
private final Pattern patternLegacy = Pattern.compile("<gradient:#[0-9a-fA-F]{6}\\|.:#[0-9a-fA-F]{6}>[^<]*</gradient>");
@Override
public String applyPattern(String text, boolean ignorePlaceholders) {
if (!text.contains("<grad")) return text;
String replaced = text;
Matcher m = patternLegacy.matcher(replaced);
while (m.find()) {
String format = m.group();
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(18));
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
TextColor start = new TextColor(format.substring(11, 17), legacyColor);
String message = format.substring(28, format.length()-11);
TextColor end = new TextColor(format.substring(21, 27));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
}
m = pattern.matcher(replaced);
while (m.find()) {
String format = m.group();
if (ignorePlaceholders && format.contains("%")) continue;
TextColor start = new TextColor(format.substring(11, 17));
String message = format.substring(26, format.length()-11);
TextColor end = new TextColor(format.substring(19, 25));
String applied = asGradient(start, message, end);
replaced = replaced.replace(format, applied);
}
return replaced;
}
}
@@ -1,427 +0,0 @@
package me.neznamy.tab.api.config;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.api.TabAPI;
/**
* Abstract class for configuration file
*/
@SuppressWarnings("unchecked")
public abstract class ConfigurationFile {
/** Comments on top of the file */
protected List<String> header;
/** Configuration file content */
protected Map<String, Object> values;
/** File to use */
protected final File file;
/**
* Constructs new instance and attempts to load specified configuration file.
* If file does not exist, default file is copied from {@code source}.
*
* @param source
* Source to copy file from if it does not exist
* @param destination
* File destination to use
* @throws IllegalArgumentException
* if {@code destination} is null
* @throws IllegalStateException
* if file does not exist and source is null
* @throws IOException
* if I/O operation with the file unexpectedly fails
*/
protected ConfigurationFile(InputStream source, File destination) throws IOException {
if (destination == null) throw new IllegalArgumentException("Destination cannot be null");
this.file = destination;
if (file.getParentFile() != null) Files.createDirectories(file.getParentFile().toPath());
if (!file.exists() && source == null) throw new IllegalStateException("File does not exist and source is null");
if (file.createNewFile()) {
// Files.copy(source, file.toPath());
//avoiding MalformedInputException thrown on fabric due to file not having UTF8 encoding by default
//also automatically using the encoding so users don't have to worry about it anymore
try (BufferedReader reader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))){
String line;
while ((line = reader.readLine()) != null) {
writer.write(line + "\n");
}
}
}
detectHeader();
}
/**
* Saves values from map to the file
*/
public abstract void save();
/**
* Returns simple name of the file
* @return simple name of the file
*/
public String getName() {
return file.getName();
}
/**
* Returns the root value map
* @return the root value map
*/
public Map<String, Object> getValues(){
return values;
}
public void setValues(Map<String, Object> values) {
this.values = values;
}
/**
* Gets config option with specified path. If the option is not present and
* {@code defaultValue} is not {@code null}, value is inserted, {@link #save()}
* called and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file
*/
public Object getObject(String path, Object defaultValue) {
Object value = values;
for (String section : path.contains(".") ? path.split("\\.") : new String[] {path}) {
if (!(value instanceof Map)) {
if (defaultValue != null) set(path, defaultValue);
return defaultValue;
}
value = getIgnoreCase((Map<Object, Object>) value, section);
}
if (value == null && defaultValue != null) {
TabAPI.getInstance().debug("Inserting missing config option \"" + path + "\" with value \"" + defaultValue + "\" into " + file.getName());
set(path, defaultValue);
return defaultValue;
}
return value;
}
/**
* Returns config option with specified path. If option is not present,
* {@code null} is returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from configuration file or null if not present
*/
public Object getObject(String path) {
return getObject(path, null);
}
public Object getObject(String[] path) {
Object value = values;
for (String section : path) {
if (!(value instanceof Map)) {
return null;
}
value = getIgnoreCase((Map<Object, Object>) value, section);
}
return value;
}
/**
* Returns value from map without case sensitivity of the key. Returns
* {@code null} if no such key is found.
*
* @param map
* map to get value from
* @param key
* case-insensitive key name
* @return map value from case-insensitive key
*/
private Object getIgnoreCase(Map<Object, Object> map, String key) {
for (Entry<Object, Object> entry : map.entrySet()) {
if (entry.getKey().toString().equalsIgnoreCase(key)) return entry.getValue();
}
return map.get(key);
}
/**
* Returns config option with specified path as {@code String}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public String getString(String path) {
return getString(path, null);
}
/**
* Returns config option with specified path as {@code String}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code String}
*/
public String getString(String path, String defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
return String.valueOf(value);
}
/**
* Returns config option with specified path as {@code List<String>}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public List<String> getStringList(String path) {
return getStringList(path, null);
}
/**
* Returns config option with specified path as {@code List<String>}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path of the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code List<String>}
*/
public List<String> getStringList(String path, List<String> defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
if (!(value instanceof List)) {
return new ArrayList<>();
}
List<String> fixedList = new ArrayList<>();
for (Object key : (List<Object>)value) {
fixedList.add(key.toString());
}
return fixedList;
}
/**
* Returns config option with specified path as {@code Integer}. Returns {@code null} if
* option is not present.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from file or null if not present
*/
public Integer getInt(String path) {
return getInt(path, null);
}
/**
* Returns config option with specified path as {@code Integer}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Integer}
*/
public Integer getInt(String path, Integer defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try{
return Integer.parseInt(value.toString());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Returns config option with specified path as {@code Boolean}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Boolean}
*/
public boolean getBoolean(String path, boolean defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
return Boolean.parseBoolean(value.toString());
}
/**
* Returns config option with specified path as {@code Double}. If the option is not present
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
* and {@code defaultValue} returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param defaultValue
* Value to be inserted and returned if option is not present
* @return value from configuration file as {@code Double}
*/
public Double getDouble(String path, double defaultValue) {
Object value = getObject(path, defaultValue);
if (value == null) return defaultValue;
try {
return Double.parseDouble(value.toString());
} catch (NumberFormatException e) {
return defaultValue;
}
}
/**
* Returns config option with specified path as {@code Map<K, V>}. If the option
* is not present or value is not a {@code Map}, new empty {@code Map} is returned.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return value from configuration file as {@code Map<K, V>}
*/
public <K, V> Map<K, V> getConfigurationSection(String path) {
if (path == null || path.length() == 0) return (Map<K, V>) values;
Object value = getObject(path, null);
if (value instanceof Map) {
return (Map<K, V>) value;
} else {
return new LinkedHashMap<>();
}
}
/**
* Returns {@code true} if the file has option with specified path, {@code false}
* if not.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @return {@code true} if present, {@code false} if not
*/
public boolean hasConfigOption(String path) {
return getObject(path) != null;
}
/**
* Sets value to the specified path and saves the file to disk by calling {@link #save()}.
*
* @param path
* Path to the option with sections separated with "{@code .}"
* @param value
* Value to save
*/
public void set(String path, Object value) {
set(values, path, value);
save();
}
/**
* Sets value to the specified map with specified path and value. This is an internal method
* that correctly creates maps to separate sections using "{@code .}".
*
* @param map
* Map to insert value to
* @param path
* Path to the option with sections separated with "{@code .}"
* @param value
* Value to save
* @return the first argument to allow chaining
*/
private Map<String, Object> set(Map<String, Object> map, String path, Object value) {
if (path.contains(".")) {
String keyWord = getRealKey(map, path.split("\\.")[0]);
Object subMap = map.get(keyWord);
if (!(subMap instanceof Map)) {
subMap = new LinkedHashMap<>();
}
map.put(keyWord, set((Map<String, Object>) subMap, path.substring(keyWord.length()+1), value));
} else {
if (value == null) {
map.remove(getRealKey(map, path));
} else {
map.put(path, value);
}
}
return map;
}
/**
* Returns the real key name without case sensitivity from map. If not found, inserted key
* is returned.
*
* @param map
* Map to check keys of
* @param key
* Key to find
* @return The real key name
*/
private String getRealKey(Map<?, ?> map, String key) {
for (Object mapKey : map.keySet()) {
if (mapKey.toString().equalsIgnoreCase(key)) return mapKey.toString();
}
return key;
}
/**
* Detects header of a file (first lines of file starting with #).
*
* @throws IOException
* if I/O operation fails
*/
private void detectHeader() throws IOException {
header = new ArrayList<>();
for (String line : Files.readAllLines(file.toPath())) {
if (line.startsWith("#")) {
header.add(line);
} else {
break;
}
}
}
/**
* Inserts header back into file. This is required after calling {@link #save()}, because
* it destroys the header.
*
* @throws IOException
* if I/O operation fails
*/
public void fixHeader() throws IOException {
if (header == null) return;
List<String> content = new ArrayList<>(header);
content.addAll(Files.readAllLines(file.toPath()));
Files.delete(file.toPath());
if (file.createNewFile()) {
Files.write(file.toPath(), content);
}
}
public File getFile() {
return file;
}
}
@@ -1,87 +0,0 @@
package me.neznamy.tab.api.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.yamlassist.YamlAssist;
/**
* YAML implementation of ConfigurationFile
*/
public class YamlConfigurationFile extends ConfigurationFile {
/** SnakeYAML instance */
private static final Yaml yaml;
static {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yaml = new Yaml(options);
}
/**
* Constructs new instance and attempts to load specified configuration file.
* If file does not exist, default file is copied from {@code source}.
*
* @param source
* Source to copy file from if it does not exist
* @param destination
* File destination to use
* @throws IllegalArgumentException
* if {@code destination} is null
* @throws IllegalStateException
* if file does not exist and source is null
* @throws YAMLException
* if file has invalid YAML syntax
* @throws IOException
* if I/O operation with the file unexpectedly fails
*/
public YamlConfigurationFile(InputStream source, File destination) throws YAMLException, IOException {
super(source, destination);
FileInputStream input = null;
try {
input = new FileInputStream(file);
values = yaml.load(input);
if (values == null) values = new LinkedHashMap<>();
input.close();
} catch (YAMLException e) {
if (input != null) input.close();
TabAPI tab = TabAPI.getInstance();
tab.sendConsoleMessage("&c[TAB] File " + destination + " has broken syntax.", true);
tab.sendConsoleMessage("&6[TAB] Error message from yaml parser: " + e.getMessage(), true);
List<String> suggestions = YamlAssist.getSuggestions(file);
if (!suggestions.isEmpty()) {
tab.sendConsoleMessage("&d[TAB] Suggestions to fix yaml syntax:", true);
for (String suggestion : suggestions) {
tab.sendConsoleMessage("&d[TAB] - " + suggestion, true);
}
}
throw e;
}
}
@Override
public void save() {
try {
Writer writer = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8);
yaml.dump(values, writer);
writer.close();
fixHeader();
} catch (IOException e) {
TabAPI.getInstance().sendConsoleMessage("&c[TAB] Failed to save yaml file " + file.getPath() + " with content " + values.toString(), true);
}
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.api.event;
import lombok.NonNull;
/**
* The event bus. This is used to register event listeners/handlers for
* handling events that may be fired by TAB.
@@ -10,7 +12,7 @@ public interface EventBus {
* Scans the given listener for any method that is annotated with
* {@link Subscribe} and registers it as a listener to this event bus.
*
* All methods in this class that are annotated with {@link Subscribe}
* <p>All methods in this class that are annotated with {@link Subscribe}
* must meet the following criteria:
* <ul>
* <li>It must be <strong>public</strong>.</li>
@@ -24,7 +26,7 @@ public interface EventBus {
* @param listener the listener to register
* @throws RuntimeException if a method does not meet the criteria
*/
void register(Object listener);
void register(@NonNull Object listener);
/**
* Registers the given handler to this event bus.
@@ -33,7 +35,7 @@ public interface EventBus {
* @param handler the handler to register
* @param <E> the type of the event
*/
<E extends TabEvent> void register(Class<E> type, EventHandler<E> handler);
<E extends TabEvent> void register(@NonNull Class<E> type, @NonNull EventHandler<E> handler);
/**
* Scans the given listener for any method that is annotated with
@@ -41,7 +43,7 @@ public interface EventBus {
*
* @param listener the listener to unregister
*/
void unregister(Object listener);
void unregister(@NonNull Object listener);
/**
* Unregisters the given handler from this event bus.
@@ -49,5 +51,5 @@ public interface EventBus {
* @param handler the handler to unregister
* @param <E> the type of the event
*/
<E extends TabEvent> void unregister(EventHandler<E> handler);
<E extends TabEvent> void unregister(@NonNull EventHandler<E> handler);
}
@@ -3,7 +3,7 @@ package me.neznamy.tab.api.event;
/**
* An event that may be fired by TAB when something occurs.
*
* This is a marker interface. All events <strong>must</strong> implement this
* <p>This is a marker interface. All events <strong>must</strong> implement this
* interface.
*/
public interface TabEvent {
@@ -2,6 +2,7 @@ package me.neznamy.tab.api.event.player;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.TabEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when the player has been fully loaded by TAB.
@@ -11,14 +12,15 @@ public interface PlayerLoadEvent extends TabEvent {
/**
* Gets the player that was loaded.
*
* @return the player that was loaded
* @return the player that was loaded
*/
TabPlayer getPlayer();
@NotNull TabPlayer getPlayer();
/**
* Returns true if player was loaded because they joined,
* false if the player was loaded because tab reload command was executed
* @return true if player joined, false if plugin was reloaded
* Returns {@code true} if player was loaded because they joined,
* {@code false} if the player was loaded because tab reload command was executed
*
* @return {@code true} if player joined, {@code false} if plugin was reloaded
*/
boolean isJoin();
}
@@ -1,11 +1,48 @@
package me.neznamy.tab.api.event.plugin;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.TabEvent;
import me.neznamy.tab.api.placeholder.Placeholder;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Called when an unknown placeholder is about to be registered.
* It defaults to a PlaceholderAPI implementation, but can be overridden.
*/
public interface PlaceholderRegisterEvent extends TabEvent {
String getIdentifier();
/**
* Returns placeholder identifier starting and ending with {@code %}
*
* @return placeholder identifier starting and ending with {@code %}
*/
@NotNull String getIdentifier();
void setPlaceholder(Placeholder placeholder);
/**
* Sets placeholder to specified server placeholder implementation.
*
* @param supplier
* Placeholder replacer
*/
void setServerPlaceholder(@NonNull Supplier<Object> supplier);
/**
* Sets placeholder to specified player placeholder implementation.
*
* @param function
* Placeholder replacer
*/
void setPlayerPlaceholder(@NonNull Function<TabPlayer, Object> function);
/**
* Sets placeholder to specified relational placeholder implementation.
*
* @param function
* Placeholder replacer
*/
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
}
@@ -0,0 +1,183 @@
package me.neznamy.tab.api.nametag;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* Interface for manipulating player name tags.
* <p>
* Instance can be obtained using {@link TabAPI#getNameTagManager()}.
* This requires the Team feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
public interface NameTagManager {
/**
* Makes player's NameTag globally invisible
*
* @param player
* player to hide name tag of
* @see #showNameTag(TabPlayer)
* @see #hasHiddenNameTag(TabPlayer)
*/
void hideNameTag(@NonNull TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
*
* @param player
* player to hide name tag of
* @param viewer
* player to hide name tag for
*/
void hideNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Makes player's NameTag visible again
*
* @param player
* player to show name tag of
* @see #hideNameTag(TabPlayer)
* @see #hasHiddenNameTag(TabPlayer)
*/
void showNameTag(@NonNull TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
*
* @param player
* player to show name tag of
* @param viewer
* player to show NameTag back for
*/
void showNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
*
* @param player
* player to check name tag visibility status of
* @return Whether player has hidden NameTag or not
* @see #hideNameTag(TabPlayer)
* @see #showNameTag(TabPlayer)
*/
boolean hasHiddenNameTag(@NonNull TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
*
* @param player
* player to check visibility status of
* @param viewer
* player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
*
* @param player
* player to pause team handling of
*/
void pauseTeamHandling(@NonNull TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*
* @param player
* player to resume team handling of
*/
void resumeTeamHandling(@NonNull TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
* it was resumed already using resumeTeamHandling
*
* @param player
* player to check handling status of
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
*
* @param player
* player to set collision rule of
* @param collision
* forced collision rule
*/
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
*
* @param player
* player to get forced collision of
* @return forced value or null if not forced
*/
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
/**
* Changes player's prefix to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
/**
* Changes player's suffix to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Toggles name tag visibility view on all players for specified player.
* On first call, name tags of all players will become invisible for specified
* player. On second call, they will become visible again.
*
* @param player
* player to toggle name tag visibility view for
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Returns {@code true} if player has hidden name tags by either calling
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
* {@code false} if not.
*
* @param player
* player to check
* @return {@code true} if hidden, {@code false} if not
*/
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
}
@@ -0,0 +1,128 @@
package me.neznamy.tab.api.nametag;
import java.util.List;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface for manipulating player name tags.
* <p>
* Instance can be obtained using {@link TabAPI#getNameTagManager()} and then casting it.
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
* return {@code null} or not be an instance of this class.
*/
public interface UnlimitedNameTagManager extends NameTagManager {
/**
* Disables armor stands of specified player and displays vanilla name tags instead.
*
* @param player
* Player to disable armor stands of
* @see #enableArmorStands(TabPlayer)
* @see #hasDisabledArmorStands(TabPlayer)
*/
void disableArmorStands(@NonNull TabPlayer player);
/**
* Enabled back armor stands of player, who was disabled previously using
* {@link #disableArmorStands(TabPlayer)}.
*
* @param player
* Player to enable back
* @see #disableArmorStands(TabPlayer)
* @see #hasDisabledArmorStands(TabPlayer)
*/
void enableArmorStands(@NonNull TabPlayer player);
/**
* Returns {@code true} if player has armor stands disabled using
* {@link #disableArmorStands(TabPlayer)}, {@code false} otherwise.
*
* @param player
* Player to check
* @return {@code true} if disabled, {@code false} if not.
* @see #disableArmorStands(TabPlayer)
* @see #enableArmorStands(TabPlayer)
*/
boolean hasDisabledArmorStands(@NonNull TabPlayer player);
/**
* Changes name to specified value. Set to {@code null} to
* reset back to original value.
*
* @param player
* Player to change name of
* @param customName
* New name, or {@code null} to reset to original value
*/
void setName(@NonNull TabPlayer player, @Nullable String customName);
/**
* Changes line to specified value. Set to {@code null} to reset back to original value.
*
* @param player
* Player to change line of
* @param line
* Name of defined line
* @param value
* Value to use, or {@code null} to reset back to original value
* @see #getDefinedLines()
*/
void setLine(@NonNull TabPlayer player, @NonNull String line, @Nullable String value);
/**
* Returns custom name value set using {@link #setName(TabPlayer, String)}.
*
* @param player
* Player to check
* @return Custom name set previously or {@code null} if not set
* @see #setName(TabPlayer, String)
*/
@Nullable String getCustomName(@NonNull TabPlayer player);
/**
* Returns custom line value set using {@link #setLine(TabPlayer, String, String)}.
*
* @param player
* Player to check
* @param line
* Defined line
* @return Custom name set previously or {@code null} if not set
* @see #setName(TabPlayer, String)
* @see #getDefinedLines()
*/
@Nullable String getCustomLineValue(@NonNull TabPlayer player, @NonNull String line);
/**
* Returns original name value configured in files.
*
* @param player
* Player to check
* @return Original configured name
* @see #setName(TabPlayer, String)
*/
@NotNull String getOriginalName(@NonNull TabPlayer player);
/**
* Returns original line value configured in files.
*
* @param player
* Player to check
* @param line
* Line name
* @return Original configured line
* @see #setLine(TabPlayer, String, String)
*/
@NotNull String getOriginalLineValue(@NonNull TabPlayer player, @NonNull String line);
/**
* Returns list of all defined lines in config, both static and dynamic lines.
*
* @return List of all defined lines in config
*/
@NotNull List<String> getDefinedLines();
}
@@ -1,16 +1,23 @@
package me.neznamy.tab.api.placeholder;
import org.jetbrains.annotations.NotNull;
/**
* An interface representing some kind of placeholder
*/
public interface Placeholder {
int getRefresh();
/**
* Returns refresh interval of this placeholder
*
* @return refresh interval of this placeholder
*/
int getRefresh();
String getIdentifier();
boolean isTriggerMode();
void enableTriggerMode();
void enableTriggerMode(Runnable onActivation, Runnable onDisable);
void unload();
/**
* Returns placeholder's identifier
*
* @return placeholder's identifier
*/
@NotNull String getIdentifier();
}
@@ -1,46 +1,92 @@
package me.neznamy.tab.api.placeholder;
import java.util.List;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
/**
* An interface allowing placeholder registration. Instance can be
* obtained using {@link TabAPI#getPlaceholderManager()}
*/
public interface PlaceholderManager {
/**
* Registers a server placeholder (placeholder with same output for all players)
*/
ServerPlaceholder registerServerPlaceholder(String identifier, int refresh, Supplier<Object> supplier);
/**
* Registers a player placeholder (placeholder with player-specific output)
*/
PlayerPlaceholder registerPlayerPlaceholder(String identifier, int refresh, Function<TabPlayer, Object> function);
/**
* Registers a server placeholder (placeholder with same output for all players)
*
* @param identifier
* Placeholder identifier
* @param refresh
* Refresh interval
* @param supplier
* Supplier for placeholder output
* @return Registered placeholder for further use
* @throws IllegalArgumentException
* If {@code identifier} does not start and end with {@code %} or
* {@code refresh} is not divisible by 50
*/
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
/**
* Registers a relational placeholder (different output for each player pair)
*/
RelationalPlaceholder registerRelationalPlaceholder(String identifier, int refresh, BiFunction<TabPlayer, TabPlayer, Object> function);
/**
* Detects placeholders in text using %% pattern and returns list of all detected identifiers
* @param text - text to detect placeholders in
* @return list of detected identifiers
*/
List<String> detectPlaceholders(String text);
/**
* Registers a player placeholder (placeholder different output per player)
*
* @param identifier
* Placeholder identifier
* @param refresh
* Refresh interval
* @param function
* Function for placeholder output
* @return Registered placeholder for further use
* @throws IllegalArgumentException
* If {@code identifier} does not start and end with {@code %} or
* {@code refresh} is not divisible by 50
*/
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, Object> function);
/**
* Adds placeholder to list of used placeholders and assigns this feature as using it,
* which will then receive refresh() if values changes
* @param identifier - placeholder identifier
* @param feature - feature using the placeholder
*/
void addUsedPlaceholder(String identifier, TabFeature feature);
String findReplacement(String placeholder, String output);
/**
* Registers a relational placeholder (placeholder with output different for each player duo)
*
* @param identifier
* Placeholder identifier
* @param refresh
* Refresh interval
* @param function
* Function for placeholder output
* @return Registered placeholder for further use
* @throws IllegalArgumentException
* If {@code identifier} does not start and end with {@code %},
* does not start with {@code %rel_} or
* {@code refresh} is not divisible by 50
*/
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
List<String> getUsedPlaceholders();
/**
* Returns placeholder from specified identifier. If it does not exist, it is registered
* as a PlaceholderAPI placeholder and returned.
*
* @param identifier
* Placeholder identifier
* @return Placeholder with specified identifier
*/
@NotNull Placeholder getPlaceholder(@NonNull String identifier);
/**
* Unregisters placeholder and makes plugin no longer refresh it.
*
* @param placeholder
* Placeholder to unregister
*/
void unregisterPlaceholder(@NonNull Placeholder placeholder);
/**
* Unregisters placeholder and makes plugin no longer refresh it.
*
* @param identifier
* identifier of placeholder to unregister
*/
void unregisterPlaceholder(@NonNull String identifier);
}
@@ -1,10 +1,30 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for player placeholders (placeholders returning different value for each player)
*/
public interface PlayerPlaceholder extends Placeholder {
void updateValue(TabPlayer player, Object value);
Object request(TabPlayer p);
/**
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
* so this is (only) useful for force an update when value is supposed to change, and you have
* a high refresh interval set.
*
* @param player
* Player to update value for
* @param value
* New value
*/
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
/**
* Force-updates placeholder value using previously provided update function.
*
* @param player
* Player to update value for
*/
void update(@NonNull TabPlayer player);
}
@@ -1,10 +1,34 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* An interface for relational placeholders (returning different value for each player duo)
*/
public interface RelationalPlaceholder extends Placeholder {
void updateValue(TabPlayer viewer, TabPlayer target, Object value);
Object request(TabPlayer viewer, TabPlayer target);
/**
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
* so this is (only) useful for force an update when value is supposed to change, and you have
* a high refresh interval set.
*
* @param viewer
* Placeholder viewer
* @param target
* Placeholder target
* @param value
* New value
*/
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
/**
* Force-updates placeholder value using previously provided update function.
*
* @param viewer
* Placeholder viewer
* @param target
* Placeholder target
*/
void update(@NonNull TabPlayer viewer, @NonNull TabPlayer target);
}
@@ -1,8 +1,24 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
/**
* An interface for server placeholders (returning same value for all players)
*/
public interface ServerPlaceholder extends Placeholder {
void updateValue(Object value);
Object request();
/**
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
* so this is (only) useful for force an update when value is supposed to change, and you have
* a high refresh interval set.
*
* @param value
* New value
*/
void updateValue(@NonNull Object value);
/**
* Force-updates placeholder value using previously provided update function.
*/
void update();
}
@@ -1,240 +0,0 @@
package me.neznamy.tab.api.protocol;
import java.util.HashMap;
import java.util.Map;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.BiFunctionWithException;
/**
* An interface representing a platform-specific packet builder
*/
public abstract class PacketBuilder {
/** Function map turning custom packet class into platform-specific packets */
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
/**
* Constructs new instance and fills {@link #buildMap} with abstract build methods
* of packets present on all platforms.
*/
protected PacketBuilder() {
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
}
/**
* Converts custom packet into platform-specific packet by calling a function from
* {@link #buildMap}.
*
* @param packet
* Packet to build
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if reflection fails
*/
public Object build(TabPacket packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
}
/**
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Cuts given string to specified character length (or length-1 if last character is a color character)
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
* RGB codes are converted into legacy, since cutting is only needed for <1.13.
* If {@code string} is {@code null}, empty string is returned.
* @param string
* String to cut
* @param length
* Length to cut to
* @return string cut to {@code length} characters
*/
public String cutTo(String string, int length) {
if (string == null) return "";
String legacyText = string;
if (string.contains("#")) {
//converting RGB to legacy colors
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
}
if (legacyText.length() <= length) return legacyText;
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
} else {
return legacyText.substring(0, length);
}
}
/**
* If {@code clientVersion} is >= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is < 1.12, the text is cut to {@code length} characters if
* needed and returned.
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return cutTo(text, length);
}
}
/**
* Converts platform-specific instance of player info packet into
* {@link PacketPlayOutPlayerInfo} object.
*
* @param packet
* platform-specific info packet
* @param clientVersion
* Version of client receiving the packet
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException;
/**
* Converts platform-specific instance of objective packet into
* {@link PacketPlayOutScoreboardObjective} object.
*
* @param packet
* platform-specific objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException;
/**
* Converts platform-specific instance of display objective packet into
* {@link PacketPlayOutScoreboardDisplayObjective} object.
*
* @param packet
* platform-specific display objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public abstract PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException;
}
@@ -1,280 +0,0 @@
package me.neznamy.tab.api.protocol;
import java.util.UUID;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private final Action operation;
/** BossBar title */
private String name;
/** BossBar progress (0-1)*/
private float pct;
/** BossBar color */
private BarColor color;
/** BossBar style */
private BarStyle overlay;
/** Darken screen flag */
private boolean darkenScreen;
/** Play music flag */
private boolean playMusic;
/** Create fog flag */
private boolean createWorldFog;
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param pct
* BossBar progress
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, String name, float pct, BarColor color, BarStyle overlay) {
this.operation = Action.ADD;
this.id = id;
this.name = name;
this.pct = pct;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.REMOVE action
*
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(UUID id) {
this.operation = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PCT action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(UUID id, float pct) {
this.operation = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_NAME action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(UUID id, String name) {
this.operation = Action.UPDATE_NAME;
this.id = id;
this.name = name;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_STYLE action
*
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(UUID id, BarColor color, BarStyle overlay) {
this.operation = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.UPDATE_PROPERTIES action
*
* @param darkenScreen
* Darken screen flag
* @param playMusic
* Play music flag
* @param createWorldFog
* Create fog flag
*/
public PacketPlayOutBoss(UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.operation = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
this.playMusic = playMusic;
this.createWorldFog = createWorldFog;
}
/**
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
* <p>
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
* to the final value.
*
* @return the bitmask
*/
public byte getFlags(){
byte value = 0;
if (darkenScreen) value += 1;
if (playMusic) value += 2;
if (createWorldFog) value += 4;
return value;
}
@Override
public String toString() {
return String.format("PacketPlayOutBoss{id=%s,operation=%s,name=%s,pct=%s,color=%s,overlay=%s,darkenScreen=%s,playMusic=%s,createWorldFog=%s}",
id, operation, name, pct, color, overlay, darkenScreen, playMusic, createWorldFog);
}
/**
* Returns {@link #color}
* @return color
*/
public BarColor getColor() {
return color;
}
/**
* Returns {@link #overlay}
* @return style
*/
public BarStyle getOverlay() {
return overlay;
}
/**
* Returns {@link #name}
* @return name
*/
public String getName() {
return name;
}
/**
* Returns {@link #id}
* @return id
*/
public UUID getId() {
return id;
}
/**
* Returns {@link #pct}
* @return progress
*/
public float getPct() {
return pct;
}
/**
* Returns {@link #operation}
* @return packet action
*/
public Action getOperation() {
return operation;
}
/**
* Returns {@link #darkenScreen}
* @return darkenScreen
*/
public boolean isDarkenScreen() {
return darkenScreen;
}
/**
* Returns {@link #createWorldFog}
* @return createWorldFog
*/
public boolean isCreateWorldFog() {
return createWorldFog;
}
/**
* Returns {@link #playMusic}
* @return playMusic
*/
public boolean isPlayMusic() {
return playMusic;
}
/**
* Sets {@link #darkenScreen} to specified value
*
* @param darkenScreen
* Darken screen flag
*/
public void setDarkenScreen(boolean darkenScreen) {
this.darkenScreen = darkenScreen;
}
/**
* Sets {@link #createWorldFog} to specified value
*
* @param createWorldFog
* Create fog flag
*/
public void setCreateWorldFog(boolean createWorldFog) {
this.createWorldFog = createWorldFog;
}
/**
* Sets {@link #playMusic} to specified value
*
* @param playMusic
* Play music flag
*/
public void setPlayMusic(boolean playMusic) {
this.playMusic = playMusic;
}
/**
* An enum representing all valid boss packet actions.
* Calling ordinal() will return action's network ID.
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}
@@ -1,60 +0,0 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
private final IChatBaseComponent message;
/** Message position */
private final ChatMessageType type;
/**
* Constructs new instance with given parameters
*
* @param message
* Chat message to be sent
* @param type
* Message position
*/
public PacketPlayOutChat(IChatBaseComponent message, ChatMessageType type) {
this.message = message;
this.type = type;
}
@Override
public String toString() {
return String.format("PacketPlayOutChat{message=%s,type=%s}", message, type);
}
/**
* Returns {@link #message}
* @return message
*/
public IChatBaseComponent getMessage() {
return message;
}
/**
* Returns {@link #type}
* @return type
*/
public ChatMessageType getType() {
return type;
}
/**
* An enum representing positions of a chat message
* Calling ordinal() will return type's network ID.
*/
public enum ChatMessageType {
CHAT,
SYSTEM,
GAME_INFO
}
}
@@ -1,305 +0,0 @@
package me.neznamy.tab.api.protocol;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet action */
private final EnumPlayerInfoAction action;
/** List of affected entries */
private final List<PlayerInfoData> entries;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
this.action = action;
this.entries = Arrays.asList(entries);
}
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, List<PlayerInfoData> entries) {
this.action = action;
this.entries = entries;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerInfo{action=%s,entries=%s}", action, entries);
}
/**
* Returns {@link #action}
* @return packet action
*/
public EnumPlayerInfoAction getAction() {
return action;
}
/**
* Returns {@link #entries}
* @return affected entries
*/
public List<PlayerInfoData> getEntries() {
return entries;
}
/**
* A subclass representing player list entry
*/
public static class PlayerInfoData {
/** Latency */
private int latency;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
private IChatBaseComponent displayName;
/** Real name of affected player */
private String name;
/** Player UUID */
private UUID uniqueId;
/** platform-specific skin object */
private Object skin;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.ADD_PLAYER action
*
* @param name
* Player's name
* @param uniqueId
* Player's uuid
* @param skin
* Player's platform-specific skin object
* @param latency
* Player's ping
* @param gameMode
* Player's GameMode
* @param displayName
* Player's display name
*/
public PlayerInfoData(String name, UUID uniqueId, Object skin, int latency, EnumGamemode gameMode, IChatBaseComponent displayName) {
this.name = name;
this.uniqueId = uniqueId;
this.skin = skin;
this.latency = latency;
this.gameMode = gameMode;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_GAME_MODE action
*
* @param uniqueId
* Player's uuid
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(UUID uniqueId, EnumGamemode gameMode) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_LATENCY action
*
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
*/
public PlayerInfoData(UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction}.UPDATE_DISPLAY_NAME action
*
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
*/
public PlayerInfoData(UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction}.REMOVE_PLAYER action
*
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(UUID uniqueId) {
this.uniqueId = uniqueId;
}
@Override
public String toString() {
return String.format("PlayerInfoData{latency=%s,gameMode=%s,displayName=%s,name=%s,uniqueId=%s,skin=%s}",
latency, gameMode, displayName, name, uniqueId, skin);
}
/**
* Returns {@link #latency}
* @return latency
*/
public int getLatency() {
return latency;
}
/**
* Sets {@link #latency} to specified value
* @param latency
* Latency to use
*/
public void setLatency(int latency) {
this.latency = latency;
}
/**
* Returns {@link #displayName}
* @return displayName
*/
public IChatBaseComponent getDisplayName() {
return displayName;
}
/**
* Sets {@link #displayName} to specified value
* @param displayName
* Display name to use
*/
public void setDisplayName(IChatBaseComponent displayName) {
this.displayName = displayName;
}
/**
* Returns {@link #uniqueId}
* @return uniqueId
*/
public UUID getUniqueId() {
return uniqueId;
}
/**
* Sets {@link #uniqueId} to specified value
* @param uniqueId
* UUID to use
*/
public void setUniqueId(UUID uniqueId) {
this.uniqueId = uniqueId;
}
/**
* Returns {@link #name}
* @return name
*/
public String getName() {
return name;
}
/**
* Sets {@link #name} to specified value
* @param name
* name to use
*/
public void setName(String name) {
this.name = name;
}
/**
* Returns {@link #gameMode}
* @return gameMode
*/
public EnumGamemode getGameMode() {
return gameMode;
}
/**
* Sets {@link #gameMode} to specified value
* @param gameMode
* GameMode to use
*/
public void setGameMode(EnumGamemode gameMode) {
this.gameMode = gameMode;
}
/**
* Returns {@link #skin}
* @return skin
*/
public Object getSkin() {
return skin;
}
/**
* Sets {@link #skin} to specified value
* @param skin
* Skin to use
*/
public void setSkin(Object skin) {
this.skin = skin;
}
}
/**
* En enum representing packet action
* Calling ordinal() will return action's network ID.
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
UPDATE_GAME_MODE,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
}
/**
* An enum representing GameMode
*/
public enum EnumGamemode {
NOT_SET,
SURVIVAL,
CREATIVE,
ADVENTURE,
SPECTATOR
}
}
@@ -1,63 +0,0 @@
package me.neznamy.tab.api.protocol;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
private final IChatBaseComponent header;
/** TabList footer */
private final IChatBaseComponent footer;
/**
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(String header, String footer) {
this.header = IChatBaseComponent.optimizedComponent(header);
this.footer = IChatBaseComponent.optimizedComponent(footer);
}
/**
* Constructs new instance with given parameters.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(IChatBaseComponent header, IChatBaseComponent footer) {
this.header = header;
this.footer = footer;
}
@Override
public String toString() {
return String.format("PacketPlayOutPlayerListHeaderFooter{header=%s,footer=%s}", header, footer);
}
/**
* Returns {@link #header}
* @return header
*/
public IChatBaseComponent getHeader() {
return header;
}
/**
* Returns {@link #footer}
* @return footer
*/
public IChatBaseComponent getFooter() {
return footer;
}
}
@@ -1,55 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
* Display slot.
* 0 = PlayerList,
* 1 = SideBar,
* 2 = BelowName.
*/
private final int slot;
/** Up to 16 characters long objective name */
private final String objectiveName;
/**
* Constructs new instance with given parameters
* @param slot
* Display slot
* @param objectiveName
* Internal name of the objective
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardDisplayObjective(int slot, String objectiveName) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
this.slot = slot;
this.objectiveName = objectiveName;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardDisplayObjective{slot=%s,objectiveName=%s}", slot, objectiveName);
}
/**
* Returns {@link #slot}
* @return slot
*/
public int getSlot() {
return slot;
}
/**
* Returns {@link #objectiveName}
* @return objectiveName
*/
public String getObjectiveName() {
return objectiveName;
}
}
@@ -1,109 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardObjective implements TabPacket {
/** Up to 16 characters long objective name */
private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Packet action.
* 0 = register,
* 1 = unregister,
* 2 = update title
*/
private final int method;
/**
* Constructs new instance with given parameters.
*
* @param method
* Packet action (0 = add, 1 = remove, 2 = update title)
* @param objectiveName
* objective name, up to 16 characters long
* @param displayName - title
* @param renderType - display type
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(int method, String objectiveName, String displayName, EnumScoreboardHealthDisplay renderType) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
this.objectiveName = objectiveName;
this.displayName = displayName;
this.renderType = renderType;
this.method = method;
}
/**
* Constructs new packet with given objective name and 1 (unregister) action.
*
* @param objectiveName
* objective name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(String objectiveName) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
this.method = 1;
this.renderType = null;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardObjective{objectiveName=%s,displayName=%s,renderType=%s,method=%s}",
objectiveName, displayName, renderType, method);
}
/**
* Returns {@link #displayName}
* @return displayName
*/
public String getDisplayName() {
return displayName;
}
/**
* Returns {@link #objectiveName}
* @return objectiveName
*/
public String getObjectiveName() {
return objectiveName;
}
/**
* Returns {@link #renderType}
* @return renderType
*/
public EnumScoreboardHealthDisplay getRenderType() {
return renderType;
}
/**
* Returns {@link #method}
* @return packet action
*/
public int getMethod() {
return method;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
*/
public enum EnumScoreboardHealthDisplay {
INTEGER, HEARTS
}
}
@@ -1,89 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
private final Action action;
/** Objective name */
private final String objectiveName;
/** Affected player */
private final String player;
/** Player's score */
private final int score;
/**
* Constructs new instance with given parameters
*
* @param action
* Packet action
* @param objectiveName
* Objective name
* @param player
* Affected player
* @param score
* Player's score
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardScore(Action action, String objectiveName, String player, int score) {
if (objectiveName == null) throw new IllegalArgumentException("objectiveName cannot be null");
if (objectiveName.length() > 16) throw new IllegalArgumentException("objectiveName cannot be longer than 16 character (is " + objectiveName.length() + ")");
this.action = action;
this.objectiveName = objectiveName;
this.player = player;
this.score = score;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardScore{action=%s,objectiveName=%s,player=%s,score=%s}",
action, objectiveName, player, score);
}
/**
* Returns {@link #action}
* @return packet action
*/
public Action getAction() {
return action;
}
/**
* Returns {@link #objectiveName}
* @return objective name
*/
public String getObjectiveName() {
return objectiveName;
}
/**
* Returns {@link #player}
* @return player
*/
public String getPlayer() {
return player;
}
/**
* Returns {@link #score}
* @return score
*/
public int getScore() {
return score;
}
/**
* An enum representing action types
*/
public enum Action {
CHANGE,
REMOVE
}
}
@@ -1,264 +0,0 @@
package me.neznamy.tab.api.protocol;
import java.util.Collection;
import java.util.Collections;
import me.neznamy.tab.api.chat.EnumChatFormat;
/**
* A class representing platform specific packet class
*/
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Up to 16 characters long team name.
* It is used to sort players alphabetically in TabList by team names.
* Team-less players appear on top, sorted by usernames.
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
private final String name;
/** Team prefix, limited to 16 characters, on <1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on <1.13 */
private String playerSuffix;
/**
* NameTag visibility rule since 1.8. Possible options are:
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
*/
private String nameTagVisibility;
/**
* Collision rule added in 1.9. Possible options are:
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
*/
private String collisionRule;
/**
* Team color field added in 1.13. It determines name color,
* prefix and suffix color start.
*/
private EnumChatFormat color;
/**
* Affected entities. For players, it is their name, for other entities their UUID
* Player name length is limited to 16 characters for <1.8, 40 characters since 1.8.
* Entity UUID option was added in 1.8.
*/
private Collection<String> players = Collections.emptyList();
/**
* Packet action.
* 0 = create team,
* 1 = remove team,
* 2 = update team info,
* 3 = add entries,
* 4 = remove entries
*/
private final int method;
/**
* Bit mask.
* 0x01: Allow friendly fire,
* 0x02: Can see invisible players on the same team.
*/
private int options;
/**
* Constructs new instance with given parameters. Private constructor used
* internally to validate team name and set action.
*
* @param method
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
* 3 = add entries, 4 = remove entries)
* @param name
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
private PacketPlayOutScoreboardTeam(int method, String name) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Team name cannot be null/empty");
if (name.length() > 16) throw new IllegalArgumentException("name cannot be longer than 16 character (is " + name.length() + ")");
this.method = method;
this.name = name;
}
/**
* Constructs new instance with given parameters and 0 (CREATE) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param players
* Affected entities
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
this(0, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.players = players;
this.options = options;
}
/**
* Constructs new instance with given parameters and 1 (REMOVE) action
*
* @param team
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team) {
this(1, team);
}
/**
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, String prefix, String suffix, String visibility, String collision, int options) {
this(2, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.options = options;
}
/**
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
*
* @param team
* Team name, up to 16 characters long
* @param players
* Affected entities
* @param add
* {@code true} if players should be added, {@code false} if removed from team
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
this.players = players;
}
@Override
public String toString() {
return String.format("PacketPlayOutScoreboardTeam{name=%s,playerPrefix=%s,playerSuffix=%s,nameTagVisibility=%s,"
+ "collisionRule=%s,color=%s,players=%s,method=%s,options=%s}",
name, playerPrefix, playerSuffix, nameTagVisibility, collisionRule, color, players, method, options);
}
/**
* Returns {@link #name}
* @return team name
*/
public String getName() {
return name;
}
/**
* Returns {@link #playerPrefix}
* @return playerPrefix
*/
public String getPlayerPrefix() {
return playerPrefix;
}
/**
* Returns {@link #playerSuffix}
* @return playerSuffix
*/
public String getPlayerSuffix() {
return playerSuffix;
}
/**
* Returns {@link #nameTagVisibility}
* @return nameTagVisibility
*/
public String getNameTagVisibility() {
return nameTagVisibility;
}
/**
* Returns {@link #collisionRule}
* @return collisionRule
*/
public String getCollisionRule() {
return collisionRule;
}
/**
* Sets {@link #color} to specified color
*
* @param color
* color to use
*/
public void setColor(EnumChatFormat color) {
this.color = color;
}
/**
* Returns {@link #color}
* @return color
*/
public EnumChatFormat getColor() {
return color;
}
/**
* Returns {@link #players}
* @return players
*/
public Collection<String> getPlayers() {
return players;
}
/**
* Returns {@link #options}
* @return team options
*/
public int getOptions() {
return options;
}
/**
* Returns {@link #method}
* @return packet action
*/
public int getMethod() {
return method;
}
}
@@ -1,16 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* Interface representing a packet that can be sent to client independently of platform.
* It is technically empty, existence is only present to restrict type access
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
* be called with an actual packet.
*/
public interface TabPacket {
/**
* Returns user-friendly string representation of the packet
* @return user-friendly string representation of the packet
*/
String toString();
}
@@ -1,5 +1,8 @@
package me.neznamy.tab.api.scoreboard;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
/**
* An interface allowing to work with a line of
* text in scoreboard.
@@ -7,23 +10,23 @@ package me.neznamy.tab.api.scoreboard;
*/
public interface Line {
/**
* Returns configured raw text of this line. Placeholders
* remain in raw format.
*
* @return raw text of this line
*/
String getText();
/**
* Changes text to new value. Supports placeholders, which are
* automatically registered if needed and refreshed periodically
* based on configuration. No need to call this method to try to
* keep placeholders up to date. If value is identical to previous
* one, nothing happens.
*
* @param text
* Raw text to display in the line
*/
void setText(String text);
/**
* Returns configured raw text of this line. Placeholders
* remain in raw format.
*
* @return raw text of this line
*/
@NotNull String getText();
/**
* Changes text to new value. Supports placeholders, which are
* automatically registered if needed and refreshed periodically
* based on configuration. No need to call this method to try to
* keep placeholders up to date. If value is identical to previous
* one, nothing happens.
*
* @param text
* Raw text to display in the line
*/
void setText(@NonNull String text);
}
@@ -1,5 +1,8 @@
package me.neznamy.tab.api.scoreboard;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
@@ -10,78 +13,78 @@ import java.util.List;
*/
public interface Scoreboard {
/**
* Returns internal name of this scoreboard. Name is defined in registration,
* scoreboards from config use name they were defined with. This value is
* used internally and in /tab announce scoreboard command.
*
* @return custom name of this scoreboard
*/
String getName();
/**
* Returns internal name of this scoreboard. Name is defined in registration,
* scoreboards from config use name they were defined with. This value is
* used internally and in /tab announce scoreboard command.
*
* @return custom name of this scoreboard
*/
@NotNull String getName();
/**
* Returns raw title of this scoreboard. Placeholders stay in their
* raw format.
*
* @return scoreboard title
* @see #setTitle(String)
*/
String getTitle();
/**
* Returns raw title of this scoreboard. Placeholders stay in their
* raw format.
*
* @return scoreboard title
* @see #setTitle(String)
*/
@NotNull String getTitle();
/**
* Sets title to specified value. Placeholders are refreshed automatically
* with refresh intervals defined in config. No need to call this method
* to try to keep placeholders up to date. Supports RGB codes using any of
* the supported formats.
* <p>
* Length is limited to 32 characters on <1.13. If the limit is exceeded,
* text will be cut to 32 characters.
* <p>
* Calling this method with same title as before will not do anything.
*
* @param title
* New title to use with placeholder support
* @see #getTitle()
*/
void setTitle(String title);
/**
* Sets title to specified value. Placeholders are refreshed automatically
* with refresh intervals defined in config. No need to call this method
* to try to keep placeholders up to date. Supports RGB codes using any of
* the supported formats.
* <p>
* Length is limited to 32 characters on &lt;1.13. If the limit is exceeded,
* text will be cut to 32 characters.
* <p>
* Calling this method with same title as before will not do anything.
*
* @param title
* New title to use with placeholder support
* @see #getTitle()
*/
void setTitle(@NonNull String title);
/**
* Returns list of lines in this scoreboard in the order they appear
* in game (first line is on top). This list should only be used for reading,
* for adding/removing lines see {@link #addLine(String)} and {@link #removeLine(int)}.
*
* @return list of lines in this scoreboard
* @see #addLine(String)
* @see #removeLine(int)
*/
List<Line> getLines();
/**
* Returns list of lines in this scoreboard in the order they appear
* in game (first line is on top). This list should only be used for reading,
* for adding/removing lines see {@link #addLine(String)} and {@link #removeLine(int)}.
*
* @return list of lines in this scoreboard
* @see #addLine(String)
* @see #removeLine(int)
*/
@NotNull List<Line> getLines();
/**
* Adds line with specified text on the bottom of scoreboard. Supports
* placeholders, which will automatically be refreshed.
*
* @param text
* Text to display
* @see #getLines()
* @see #removeLine(int)
*/
void addLine(String text);
/**
* Adds line with specified text on the bottom of scoreboard. Supports
* placeholders, which will automatically be refreshed.
*
* @param text
* Text to display
* @see #getLines()
* @see #removeLine(int)
*/
void addLine(@NonNull String text);
/**
* Removes line with specified index. Index starts at {@code 0} and ends at
* {@link #getLines()}.size()-1.
*
* @param index
* Index of line to remove, starting at 0
* @throws IndexOutOfBoundsException
* if the index is out of range (index < 0 || index >= {@link #getLines()}.size())
* @see #getLines()
* @see #addLine(String)
*/
void removeLine(int index);
/**
* Removes line with specified index. Index starts at {@code 0} and ends at
* {@link #getLines()}.size()-1.
*
* @param index
* Index of line to remove, starting at 0
* @throws IndexOutOfBoundsException
* if the index is out of range (index &lt; 0 || index &gt;= {@link #getLines()}.size())
* @see #getLines()
* @see #addLine(String)
*/
void removeLine(int index);
/**
* Unregisters this scoreboard from all players who can see it.
*/
void unregister();
/**
* Unregisters this scoreboard from all players who can see it.
*/
void unregister();
}
@@ -3,7 +3,10 @@ package me.neznamy.tab.api.scoreboard;
import java.util.List;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface allowing work with scoreboards, such as creating,
@@ -11,155 +14,156 @@ import me.neznamy.tab.api.TabPlayer;
* <p>
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getScoreboardManager()}.
* This requires the scoreboard feature to be enabled in config,
* the method will return null otherwise.
* the method will return {@code null} otherwise.
*/
public interface ScoreboardManager {
/**
* Creates new scoreboard, registers and returns it. {@code name}
* is internal name of the scoreboard, which it will be registered
* with and further work where scoreboard name is used allowed.
* <p>
* Scoreboard {@code title} is limited to 32 characters on <1.13. If needed,
* it will be cut to 32 character for those players (after replacing
* placeholders). RGB is supported using any of the supported formats.
* Placeholders are supported.
* <p>
* {@code lines} will display in the same order as contained in the list.
* Client can only see up to 15 lines at a time, however you can define
* more, if some of them only consist of placeholder that might be empty.
*
* @param name
* Internal name of the scoreboard
* @param title
* The scoreboard title
* @param lines
* Lines of text in scoreboard (supports placeholders)
* @return The new scoreboard with given parameters
* @throws NullPointerException
* if {@code name} or {@code lines} is {@code null}
*/
Scoreboard createScoreboard(String name, String title, List<String> lines);
/**
* Returns map of registered scoreboards via config and API.
* Map key is name of scoreboard, which is defined either in config
* or using {@link #createScoreboard(String, String, List)}.
* Map value is reference to the actual scoreboard.
*
* @return map of registered scoreboards
*/
Map<String, Scoreboard> getRegisteredScoreboards();
/**
* Displays scoreboard to defined player. This will disable all display
* condition checks until the scoreboard is unregistered using
* {@link #resetScoreboard(TabPlayer)}. If the player already sees
* this scoreboard, nothing will happen.
*
* @param player
* Player to send scoreboard to
* @param scoreboard
* Scoreboard to display
* @throws NullPointerException
* if {@code player} or {@code scoreboard} is {@code null}
* @see #hasCustomScoreboard(TabPlayer)
* @see #resetScoreboard(TabPlayer)
*/
void showScoreboard(TabPlayer player, Scoreboard scoreboard);
/**
* Returns {@code true} if player has custom scoreboard set using
* {@link #showScoreboard(TabPlayer, Scoreboard)}, {@code false} if not.
*
* @param player
* Player to check
* @return {@code true} if player has custom scoreboard set using API,
* {@code false} if not
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #resetScoreboard(TabPlayer)
*/
boolean hasCustomScoreboard(TabPlayer player);
/**
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
* and re-enables internal display logic with conditions. If player does not
* have any forced scoreboard, nothing happens.
*
* @param player
* Player to hide custom scoreboard from
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #hasCustomScoreboard(TabPlayer)
*/
void resetScoreboard(TabPlayer player);
/**
* Returns {@code true} if player has scoreboard enabled, {@code false}
* if disabled (toggled) using either toggle command,
* {@link #toggleScoreboard(TabPlayer, boolean)} or
* {@link #setScoreboardVisible(TabPlayer, boolean, boolean)}.
*
* @param player
* Player to get visibility status of
* @return {@code true} if visible, {@code false} if disabled
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
* @see #toggleScoreboard(TabPlayer, boolean)
*/
boolean hasScoreboardVisible(TabPlayer player);
/**
* Sets scoreboard visibility of player to defined value. If visibility status
* is same as player had before, nothing happens. If this call changes
* visibility, scoreboard is toggled. If {@code sendToggleMessage} is {@code true},
* toggle message defined in configuration is sent.
*
* @param player
* Player to set visibility for
* @param visible
* New visibility status
* @param sendToggleMessage
* whether to send toggle message if status changed or not
* @see #toggleScoreboard(TabPlayer, boolean)
* @see #hasScoreboardVisible(TabPlayer)
*/
void setScoreboardVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Toggles scoreboard for specified player. If player had scoreboard visible,
* hides it. If hidden, shows it. If {@code sendToggleMessage} is {@code true},
* toggle message defined in configuration is sent.
*
* @param player
* Player to toggle scoreboard for
* @param sendToggleMessage
* Whether to send toggle message or not
* @see #hasScoreboardVisible(TabPlayer)
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
*/
void toggleScoreboard(TabPlayer player, boolean sendToggleMessage);
/**
* Temporarily displays scoreboard to all players for specified amount of
* time (in milliseconds). Scoreboard name is either defined in config, or
* through API in {@link #createScoreboard(String, String, List)}.
*
* @param scoreboard
* Scoreboard from config or registered via API
* @param duration
* Duration in milliseconds
* @throws IllegalArgumentException
* if no scoreboard was found with such name or {@code duration}
* is < 0.
*/
void announceScoreboard(String scoreboard, int duration);
/**
* Creates new scoreboard, registers and returns it. {@code name}
* is internal name of the scoreboard, which it will be registered
* with and further work where scoreboard name is used allowed.
* <p>
* Scoreboard {@code title} is limited to 32 characters on &lt;1.13. If needed,
* it will be cut to 32 character for those players (after replacing
* placeholders). RGB is supported using any of the supported formats.
* Placeholders are supported.
* <p>
* {@code lines} will display in the same order as contained in the list.
* Client can only see up to 15 lines at a time, however you can define
* more, if some of them only consist of placeholder that might be empty.
*
* @param name
* Internal name of the scoreboard
* @param title
* The scoreboard title
* @param lines
* Lines of text in scoreboard (supports placeholders)
* @return The new scoreboard with given parameters
* @throws NullPointerException
* if {@code name} or {@code lines} is {@code null}
*/
@NotNull Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull List<String> lines);
/**
* Returns map of registered scoreboards via config and API.
* Map key is name of scoreboard, which is defined either in config
* or using {@link #createScoreboard(String, String, List)}.
* Map value is reference to the actual scoreboard.
*
* @return map of registered scoreboards
*/
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
/**
* Displays scoreboard to defined player. This will disable all display
* condition checks until the scoreboard is unregistered using
* {@link #resetScoreboard(TabPlayer)}. If the player already sees
* this scoreboard, nothing will happen.
*
* @param player
* Player to send scoreboard to
* @param scoreboard
* Scoreboard to display
* @throws NullPointerException
* if {@code player} or {@code scoreboard} is {@code null}
* @see #hasCustomScoreboard(TabPlayer)
* @see #resetScoreboard(TabPlayer)
*/
void showScoreboard(@NonNull TabPlayer player, @NonNull Scoreboard scoreboard);
/**
* Returns {@code true} if player has custom scoreboard set using
* {@link #showScoreboard(TabPlayer, Scoreboard)}, {@code false} if not.
*
* @param player
* Player to check
* @return {@code true} if player has custom scoreboard set using API,
* {@code false} if not
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #resetScoreboard(TabPlayer)
*/
boolean hasCustomScoreboard(@NonNull TabPlayer player);
/**
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
* and re-enables internal display logic with conditions. If player does not
* have any forced scoreboard, nothing happens.
*
* @param player
* Player to hide custom scoreboard from
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #hasCustomScoreboard(TabPlayer)
*/
void resetScoreboard(@NonNull TabPlayer player);
/**
* Returns {@code true} if player has scoreboard enabled, {@code false}
* if disabled (toggled) using either toggle command,
* {@link #toggleScoreboard(TabPlayer, boolean)} or
* {@link #setScoreboardVisible(TabPlayer, boolean, boolean)}.
*
* @param player
* Player to get visibility status of
* @return {@code true} if visible, {@code false} if disabled
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
* @see #toggleScoreboard(TabPlayer, boolean)
*/
boolean hasScoreboardVisible(@NonNull TabPlayer player);
/**
* Sets scoreboard visibility of player to defined value. If visibility status
* is same as player had before, nothing happens. If this call changes
* visibility, scoreboard is toggled. If {@code sendToggleMessage} is {@code true},
* toggle message defined in configuration is sent.
*
* @param player
* Player to set visibility for
* @param visible
* New visibility status
* @param sendToggleMessage
* whether to send toggle message if status changed or not
* @see #toggleScoreboard(TabPlayer, boolean)
* @see #hasScoreboardVisible(TabPlayer)
*/
void setScoreboardVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Toggles scoreboard for specified player. If player had scoreboard visible,
* hides it. If hidden, shows it. If {@code sendToggleMessage} is {@code true},
* toggle message defined in configuration is sent.
*
* @param player
* Player to toggle scoreboard for
* @param sendToggleMessage
* Whether to send toggle message or not
* @see #hasScoreboardVisible(TabPlayer)
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
*/
void toggleScoreboard(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Temporarily displays scoreboard to all players for specified amount of
* time (in milliseconds). Scoreboard name is either defined in config, or
* through API in {@link #createScoreboard(String, String, List)}.
*
* @param scoreboard
* Scoreboard from config or registered via API
* @param duration
* Duration in milliseconds
* @throws IllegalArgumentException
* if no scoreboard was found with such name or {@code duration}
* is &lt; 0.
*/
void announceScoreboard(@NonNull String scoreboard, int duration);
/**
* Returns player's currently displayed scoreboard. This can be either with
* configuration, overridden with commands or the API. Will return {@code null}
* if player does not see any scoreboard due to not meeting any display condition.
* @param player
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
Scoreboard getActiveScoreboard(TabPlayer player);
/**
* Returns player's currently displayed scoreboard. This can be either with
* configuration, overridden with commands or the API. Will return {@code null}
* if player does not see any scoreboard due to not meeting any display condition.
*
* @param player
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
}
@@ -0,0 +1,57 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* Interface allowing to send header/footer to players.
* <p>
* Instance can be obtained using {@link TabAPI#getHeaderFooterManager()}.
* This requires the Header/footer feature to be enabled in config, otherwise
* the method will return {@code null}.
*/
public interface HeaderFooterManager {
/**
* Sets raw value of header to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value. Use {@code null} to reset value back to original.
*
* @param player
* Player to set header for
* @param header
* Raw header value
*/
void setHeader(@NonNull TabPlayer player, @Nullable String header);
/**
* Sets raw value of footer to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value. Use {@code null} to reset value back to original.
*
* @param player
* Player to set header for
* @param footer
* Raw footer value
*/
void setFooter(@NonNull TabPlayer player, @Nullable String footer);
/**
* Sets raw value of header and footer to specified values. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value. Use {@code null} to reset value back to original.
*
* @param player
* Player to set header for
* @param header
* Raw header value
* @param footer
* Raw footer value
*/
void setHeaderAndFooter(@NonNull TabPlayer player, @Nullable String header, @Nullable String footer);
}
@@ -0,0 +1,38 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface SortingManager {
/**
* 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 player
* player to set team name of
* @param teamName
* forced team name
*/
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
/**
* Returns forced team name of player or null if not forced
*
* @param player
* player to check forced team name of
* @return forced team name of player or null if not forced
*/
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
/**
* Returns original team name of the player set by the plugin.
*
* @param player
* Player to get team name of
* @return Player's team name based on configuration.
*/
@NotNull String getOriginalTeamName(@NonNull TabPlayer player);
}
@@ -0,0 +1,132 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An interface allowing to modify TabList display names
* of players.
* <p>
* Instance can be obtained using {@link TabAPI#getTabListFormatManager()}.
* This requires the feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
public interface TabListFormatManager {
/**
* Changes player's prefix to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
/**
* Changes player's name to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param customName
* new customName value
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
void setName(@NonNull TabPlayer player, @Nullable String customName);
/**
* Changes player's suffix to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
/**
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
* If the method was not used, {@code null} is returned.
*
* @param player
* player to get custom prefix of
* @return player's custom prefix or {@code null} if not defined
* @see #setPrefix(TabPlayer, String)
* @see #getOriginalPrefix(TabPlayer)
*/
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
/**
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
* If the method was not used, {@code null} is returned.
*
* @param player
* player to get custom name of
* @return player's custom name or {@code null} if not defined
* @see #setName(TabPlayer, String)
* @see #getOriginalName(TabPlayer)
*/
@Nullable String getCustomName(@NonNull TabPlayer player);
/**
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
* If the method was not used, {@code null} is returned.
*
* @param player
* player to get custom suffix of
* @return player's custom suffix or {@code null} if not defined
* @see #setSuffix(TabPlayer, String)
* @see #getOriginalSuffix(TabPlayer)
*/
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
/**
* Returns player's original prefix applied using plugin's internal logic.
* This value is not affected by API calls in any way.
*
* @param player
* player to get original prefix of
* @return player's original prefix
* @see #setPrefix(TabPlayer, String)
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns player's original name applied using plugin's internal logic.
* This value is not affected by API calls in any way.
*
* @param player
* player to get original name of
* @return player's original name
* @see #setName(TabPlayer, String)
* @see #getCustomName(TabPlayer)
*/
@NotNull String getOriginalName(@NonNull TabPlayer player);
/**
* Returns player's original suffix applied using plugin's internal logic.
* This value is not affected by API calls in any way.
*
* @param player
* player to get original suffix of
* @return player's original suffix
* @see #setSuffix(TabPlayer, String)
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
}
@@ -0,0 +1,78 @@
package me.neznamy.tab.api.tablist.layout;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface representing a layout.
*/
public interface Layout {
/**
* Returns name of the layout.
*
* @return name of the layout
*/
@NotNull String getName();
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
*/
void addFixedSlot(int slot, @NonNull String text);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param skin
* Skin definition like in config
*/
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param ping
* Slot's ping
*/
void addFixedSlot(int slot, @NonNull String text, int ping);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param skin
* Skin definition like in config
* @param ping
* Slot's ping
*/
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
/**
* Adds a player group.
*
* @param condition
* Condition that must be met for player to appear in the group.
* May be null for no requirement. Syntax is like in config -
* either condition name or short format.
* @param slots
* Slots for this player group
*/
void addGroup(@Nullable String condition, int[] slots);
}
@@ -0,0 +1,44 @@
package me.neznamy.tab.api.tablist.layout;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* Interface for working with layouts. Instance can be obtained using
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
* config, it will return {@code null}.
*/
public interface LayoutManager {
/**
* Creates new layout
*
* @param name
* Unique layout name
* @return Created layout
*/
Layout createNewLayout(String name);
/**
* Sends layout to player. Set to {@code null} to make player not see
* any layout, but default TabList instead.
*
* @param player
* Player to send layout to
* @param layout
* Layout to send
* @see #resetLayout(TabPlayer)
*/
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
/**
* Reset layout back to original one based on configuration.
*
* @param player
* Player to reset layout for
* @see #sendLayout(TabPlayer, Layout)
*/
void resetLayout(@NonNull TabPlayer player);
}
@@ -1,12 +0,0 @@
package me.neznamy.tab.api.task;
public interface RepeatingTask {
int getInterval();
void setInterval(int paramInt);
void cancel();
void setRunnable(Runnable paramRunnable);
}
@@ -1,56 +0,0 @@
package me.neznamy.tab.api.task;
import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
public interface ThreadManager {
/**
* Starts a task in new thread and measures how long it took to process
* @param errorDescription - description to use if this task throws an error
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*/
Future<Void> runMeasuredTask(String errorDescription, TabFeature feature, String type, Runnable task);
/**
* Starts a task in new thread and measures how long it took to process
* @param errorDescription - description to use if this task throws an error
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*/
Future<Void> runMeasuredTask(String errorDescription, String feature, String type, Runnable task);
/**
* Runs task in a new thread
* @param errorDescription - description to use if this task throws an error
* @param task - the task
*/
Future<Void> runTask(String errorDescription, Runnable task);
/**
* Starts a new task with defined repeat interval that measures cpu usage
* @param intervalMilliseconds - task interval
* @param errorDescription - description to use if this task throws an error
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
*/
RepeatingTask startRepeatingMeasuredTask(int intervalMilliseconds, String errorDescription, TabFeature feature, String type, Runnable task);
/**
* Runs task with a delay and measures how long it took to process
* @param delayMilliseconds - how long to run the task after
* @param errorDescription - description to use if this task throws an error
* @param feature - feature to add cpu usage to
* @param type - usage type to add cpu usage to
* @param task - the task
* @return future allowing to cancel the task
*/
Future<?> runTaskLater(int delayMilliseconds, String errorDescription, TabFeature feature, String type, Runnable task);
Future<?> runTaskLater(int delayMilliseconds, String errorDescription, Runnable task);
}
@@ -1,113 +0,0 @@
package me.neznamy.tab.api.team;
import me.neznamy.tab.api.TabPlayer;
public interface TeamManager {
/**
* Makes player's NameTag globally invisible
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void hideNametag(TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
* @param viewer - player to hide NameTag for
*/
void hideNametag(TabPlayer player, TabPlayer viewer);
/**
* Makes player's NameTag visible again
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void showNametag(TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
* @param viewer - player to show NameTag back for
*/
void showNametag(TabPlayer player, TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
* @return Whether player has hidden NameTag or not
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
*/
boolean hasHiddenNametag(TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
* @param viewer - player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
*/
void pauseTeamHandling(TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*/
void resumeTeamHandling(TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
* it was resumed already using resumeTeamHandling
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(TabPlayer player);
/**
* Forces new team name for the player until this method is called again with null argument and
* performs all actions to change player's team name
* @param teamName - forced team name
*/
void forceTeamName(TabPlayer player, String teamName);
/**
* Returns forced team name of player or null if not forced
* @return forced team name of player or null if not forced
*/
String getForcedTeamName(TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
* @param collision - forced collision rule
*/
void setCollisionRule(TabPlayer player, Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
* @return forced value or null if not forced
*/
Boolean getCollisionRule(TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
* @param player - player to update team data of
*/
void updateTeamData(TabPlayer player);
void setPrefix(TabPlayer player, String prefix);
void setSuffix(TabPlayer player, String suffix);
void resetPrefix(TabPlayer player);
void resetSuffix(TabPlayer player);
String getCustomPrefix(TabPlayer player);
String getCustomSuffix(TabPlayer player);
String getOriginalPrefix(TabPlayer player);
String getOriginalSuffix(TabPlayer player);
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.api.team;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
public interface UnlimitedNametagManager extends TeamManager {
void disableArmorStands(TabPlayer player);
void enableArmorStands(TabPlayer player);
boolean hasDisabledArmorStands(TabPlayer player);
void setName(TabPlayer player, String customName);
void setLine(TabPlayer player, String line, String value);
void resetName(TabPlayer player);
void resetLine(TabPlayer player, String line);
String getCustomName(TabPlayer player);
String getCustomLineValue(TabPlayer player, String line);
String getOriginalName(TabPlayer player);
String getOriginalLineValue(TabPlayer player, String line);
List<String> getDefinedLines();
}
@@ -1,18 +0,0 @@
package me.neznamy.tab.api.util;
/**
* An interface for BiFunction which can throw an exception.
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* call build method for packet classes, which may throw an
* exception due to reflection.
* @param <A>
* First function argument
* @param <B>
* Second function argument
* @param <C>
* Function return type
*/
public interface BiFunctionWithException<A, B, C> {
C apply(A a, B b) throws ReflectiveOperationException;
}
@@ -1,13 +0,0 @@
package me.neznamy.tab.api.util;
public class Preconditions {
public static void checkNotNull(Object obj, String exceptionMessage){
if (obj == null) throw new IllegalArgumentException(exceptionMessage);
}
public static void checkRange(Number number, Number min, Number max, String variable){
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
throw new IllegalArgumentException("Number index out of range (" + min + " - " + max + ") for " + variable);
}
}
+3 -6
View File
@@ -2,11 +2,8 @@ plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
dependencies {
implementation("gradle.plugin.com.github.johnrengelman", "shadow", "7.1.0")
implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("io.freefair.gradle:lombok-plugin:8.0.1")
implementation("fabric-loom:fabric-loom.gradle.plugin:1.1-SNAPSHOT")
}
+9
View File
@@ -0,0 +1,9 @@
rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
maven("https://maven.fabricmc.net/")
gradlePluginPortal()
mavenCentral()
}
}
@@ -2,17 +2,23 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins {
`java-library`
id("io.freefair.lombok")
}
tasks {
processResources {
filter<ReplaceTokens>("tokens" to mapOf(
"id" to rootProject.ext.get("id")!!.toString(),
"name" to rootProject.name,
"version" to project.version,
"description" to project.description
"description" to project.description,
"website" to rootProject.ext.get("website")!!.toString(),
"author" to rootProject.ext.get("author")!!.toString()
))
}
javadoc {
// This saves a decent bit of processing power on slow machines
enabled = false
options.encoding = Charsets.UTF_8.name()
(options as StandardJavadocDocletOptions).addStringOption("Xdoclint:none", "-quiet")
}
@@ -9,5 +9,7 @@ tasks {
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
}
}
@@ -1,9 +1,3 @@
import gradle.kotlin.dsl.accessors._4cbb0020bd75d48acc42cabded5fdbb7.publishing
import org.gradle.api.artifacts.repositories.PasswordCredentials
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.repositories
plugins {
id("tab.base-conventions")
`maven-publish`
+9 -3
View File
@@ -4,15 +4,21 @@ plugins {
allprojects {
group = "me.neznamy"
version = property("projectVersion") as String // from gradle.properties
version = "4.0.7"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
ext.set("author", "NEZNAMY")
}
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.krypton,
projects.velocity
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.fabric
).map { it.dependencyProject }
val special = setOf(
+10 -8
View File
@@ -1,11 +1,13 @@
dependencies {
implementation(projects.shared)
implementation(libs.bstats.bukkit)
compileOnly(libs.bukkit)
compileOnly(libs.placeholderapi)
compileOnly(libs.vault)
compileOnly(libs.via)
compileOnly(libs.authlib)
compileOnly(libs.libsDisguises)
compileOnly(libs.essentials)
implementation("org.bstats:bstats-bukkit:3.0.1")
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.2")
compileOnly("com.mojang:authlib:1.5.25")
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
exclude("org.bukkit", "bukkit")
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
}
}
@@ -1,5 +1,10 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -7,57 +12,36 @@ 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 me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
/**
* The core for bukkit forwarding events into all enabled features
*/
public class BukkitEventListener implements Listener {
public class BukkitEventListener extends EventListener<Player> implements Listener {
private final Main main;
public BukkitEventListener(Main main) {
this.main = main;
}
/**
* Listener to PlayerQuitEvent to remove player data and forward the event to features
* @param e quit event
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e){
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask("processing PlayerQuitEvent", () -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
* Listener to PlayerJoinEvent to create player data and forward the event to features
* @param e join event
*/
@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(), main.getProtocolVersion(e.getPlayer()))));
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e) {
quit(e.getPlayer().getUniqueId());
}
/**
* Listener to PlayerChangedWorldEvent to forward the event to features
* @param e world changed event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e){
if (TAB.getInstance().isDisabled()) return;
TAB.getInstance().getCPUManager().runTask("processing PlayerChangedWorldEvent", () -> TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
}
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent e) {
join(e.getPlayer());
}
/**
* Listener to PlayerChangedWorldEvent to forward the event to features
* @param e command preprocess event
*/
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (TAB.getInstance().isDisabled()) return;
if (TAB.getInstance().getFeatureManager().onCommand(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
}
}
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
}
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
}
@Override
@NotNull
public TabPlayer createPlayer(@NotNull Player player) {
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -1,558 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import com.google.gson.JsonObject;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatComponentEntity;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.TextColor;
import me.neznamy.tab.api.protocol.PacketBuilder;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitPacketBuilder extends PacketBuilder {
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
//entity type ids
private final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
private final Map<IChatBaseComponent, Object> componentCacheModern = new HashMap<>();
private final Map<IChatBaseComponent, Object> componentCacheLegacy = new HashMap<>();
private Object emptyScoreboard;
private Object dummyEntity;
/**
* Constructs new instance
*/
public BukkitPacketBuilder() {
if (nms.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
entityIds.put(EntityType.WITHER, 83);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8){
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
try {
emptyScoreboard = nms.newScoreboard.newInstance();
} catch (ReflectiveOperationException e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Failed to create instance of \"Scoreboard\""));
}
if (nms.getMinorVersion() >= 8) {
try {
dummyEntity = nms.newEntityArmorStand.newInstance(nms.World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
} catch (ReflectiveOperationException e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Failed to create instance of \"EntityArmorStand\""));
}
}
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> build((PacketPlayOutEntityMetadata)packet));
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> build((PacketPlayOutEntityTeleport)packet));
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> build((PacketPlayOutEntityDestroy)packet));
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> build((PacketPlayOutSpawnEntityLiving)packet));
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
//1.9+ server or client, handled by bukkit api or ViaVersion
return packet;
}
//<1.9 client and server
return buildBossPacketEntity(packet, clientVersion);
}
@Override
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
Object component = toNMSComponent(packet.getMessage(), clientVersion);
if (nms.getMinorVersion() >= 16) {
return nms.newPacketPlayOutChat.newInstance(component, nms.ChatMessageType_values[packet.getType().ordinal()], UUID.randomUUID());
}
if (nms.getMinorVersion() >= 12) {
return nms.newPacketPlayOutChat.newInstance(component, Enum.valueOf((Class<Enum>) nms.ChatMessageType, packet.getType().toString()));
}
if (nms.getMinorVersion() >= 8) {
return nms.newPacketPlayOutChat.newInstance(component, (byte) packet.getType().ordinal());
}
if (nms.getMinorVersion() == 7) {
return nms.newPacketPlayOutChat.newInstance(component);
}
throw new IllegalStateException("Not supported on <1.7");
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
Object nmsPacket = nms.newPacketPlayOutPlayerInfo.newInstance(nms.EnumPlayerInfoAction_values[packet.getAction().ordinal()], Array.newInstance(nms.EntityPlayer, 0));
List<Object> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().putAll((PropertyMap) data.getSkin());
List<Object> parameters = new ArrayList<>();
if (nms.newPlayerInfoData.getParameterCount() == 5) {
parameters.add(nmsPacket);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : nms.EnumGamemode_values[nms.EnumGamemode_values.length-EnumGamemode.values().length+data.getGameMode().ordinal()]); //not_set was removed in 1.17
parameters.add(data.getDisplayName() == null ? null : toNMSComponent(data.getDisplayName(), clientVersion));
items.add(nms.newPlayerInfoData.newInstance(parameters.toArray()));
}
nms.setField(nmsPacket, nms.PacketPlayOutPlayerInfo_PLAYERS, items);
return nmsPacket;
}
@Override
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
if (nms.newPacketPlayOutPlayerListHeaderFooter.getParameterCount() == 2) {
return nms.newPacketPlayOutPlayerListHeaderFooter.newInstance(toNMSComponent(packet.getHeader(), clientVersion), toNMSComponent(packet.getFooter(), clientVersion));
}
Object nmsPacket = nms.newPacketPlayOutPlayerListHeaderFooter.newInstance();
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_HEADER, toNMSComponent(packet.getHeader(), clientVersion));
nms.setField(nmsPacket, nms.PacketPlayOutPlayerListHeaderFooter_FOOTER, toNMSComponent(packet.getFooter(), clientVersion));
return nmsPacket;
}
@Override
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return nms.newPacketPlayOutScoreboardDisplayObjective.newInstance(packet.getSlot(), newScoreboardObjective(packet.getObjectiveName()));
}
@Override
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
String displayName = clientVersion.getMinorVersion() < 13 ? cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
if (nms.getMinorVersion() >= 13) {
return nms.newPacketPlayOutScoreboardObjective.newInstance(nms.newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
packet.getRenderType() == null ? null : nms.EnumScoreboardHealthDisplay_values[packet.getRenderType().ordinal()]),
packet.getMethod());
}
Object nmsPacket = nms.newPacketPlayOutScoreboardObjective.newInstance();
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME, packet.getObjectiveName());
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_DISPLAYNAME, displayName);
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_RENDERTYPE, Enum.valueOf((Class<Enum>) nms.EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
}
nms.setField(nmsPacket, nms.PacketPlayOutScoreboardObjective_METHOD, packet.getMethod());
return nmsPacket;
}
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 13) {
return nms.newPacketPlayOutScoreboardScore_1_13.newInstance(nms.EnumScoreboardAction_values[packet.getAction().ordinal()], packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
}
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
return nms.newPacketPlayOutScoreboardScore_String.newInstance(packet.getPlayer());
}
Object score = nms.newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
nms.ScoreboardScore_setScore.invoke(score, packet.getScore());
if (nms.getMinorVersion() >= 8) {
return nms.newPacketPlayOutScoreboardScore.newInstance(score);
}
return nms.newPacketPlayOutScoreboardScore.newInstance(score, 0);
}
@Override
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.PacketPlayOutScoreboardTeam == null) return null; //fabric
Object team = nms.newScoreboardTeam.newInstance(emptyScoreboard, packet.getName());
String prefix = packet.getPlayerPrefix();
String suffix = packet.getPlayerSuffix();
if (clientVersion.getMinorVersion() < 13) {
prefix = cutTo(prefix, 16);
suffix = cutTo(suffix, 16);
}
((Collection<String>)nms.ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(packet.getPlayers());
nms.ScoreboardTeam_setAllowFriendlyFire.invoke(team, (packet.getOptions() & 0x1) > 0);
nms.ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (packet.getOptions() & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(packet, clientVersion, team, prefix, suffix);
} else {
createTeamLegacy(packet, team, prefix, suffix);
}
if (nms.getMinorVersion() >= 17) {
switch (packet.getMethod()) {
case 0:
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, true);
case 1:
return nms.PacketPlayOutScoreboardTeam_of.invoke(null, team);
case 2:
return nms.PacketPlayOutScoreboardTeam_ofBoolean.invoke(null, team, false);
case 3:
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[0]);
case 4:
return nms.PacketPlayOutScoreboardTeam_ofString.invoke(null, team, packet.getPlayers().toArray(new String[0])[0], nms.PacketPlayOutScoreboardTeam_PlayerAction_values[1]);
default:
throw new IllegalArgumentException("Invalid action: " + packet.getMethod());
}
}
return nms.newPacketPlayOutScoreboardTeam.newInstance(team, packet.getMethod());
}
/**
* Builds entity destroy packet with given parameter
* @return destroy packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutEntityDestroy packet) throws ReflectiveOperationException {
try {
return nms.newPacketPlayOutEntityDestroy.newInstance(new Object[]{packet.getEntities()});
} catch (IllegalArgumentException e) {
//1.17.0
return nms.newPacketPlayOutEntityDestroy.newInstance(packet.getEntities()[0]);
}
}
public Object build(PacketPlayOutEntityMetadata packet) throws ReflectiveOperationException {
return nms.newPacketPlayOutEntityMetadata.newInstance(packet.getEntityId(), packet.getDataWatcher().toNMS(), true);
}
/**
* Builds entity spawn packet with given parameters
*
* @return entity spawn packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutSpawnEntityLiving packet) throws ReflectiveOperationException {
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance(dummyEntity);
} else {
nmsPacket = nms.newPacketPlayOutSpawnEntityLiving.newInstance();
}
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYID, packet.getEntityId());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_ENTITYTYPE, entityIds.get(packet.getEntityType()));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_YAW, (byte)(packet.getLocation().getYaw() * 256.0f / 360.0f));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_PITCH, (byte)(packet.getLocation().getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_DATAWATCHER, packet.getDataWatcher().toNMS());
}
if (nms.getMinorVersion() >= 9) {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_UUID, packet.getUniqueId());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, packet.getLocation().getX());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, packet.getLocation().getY());
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, packet.getLocation().getZ());
} else {
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_X, floor(packet.getLocation().getX()*32));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Y, floor(packet.getLocation().getY()*32));
nms.setField(nmsPacket, nms.PacketPlayOutSpawnEntityLiving_Z, floor(packet.getLocation().getZ()*32));
}
return nmsPacket;
}
/**
* Builds entity teleport packet with given parameters
* @return entity teleport packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutEntityTeleport packet) throws ReflectiveOperationException {
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance(dummyEntity);
} else {
nmsPacket = nms.newPacketPlayOutEntityTeleport.newInstance();
}
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_ENTITYID, packet.getEntityId());
if (nms.getMinorVersion() >= 9) {
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, packet.getLocation().getX());
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, packet.getLocation().getY());
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, packet.getLocation().getZ());
} else {
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_X, floor(packet.getLocation().getX()*32));
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Y, floor(packet.getLocation().getY()*32));
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_Z, floor(packet.getLocation().getZ()*32));
}
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_YAW, (byte) (packet.getLocation().getYaw()/360*256));
nms.setField(nmsPacket, nms.PacketPlayOutEntityTeleport_PITCH, (byte) (packet.getLocation().getPitch()/360*256));
return nmsPacket;
}
private void createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion, Object team, String prefix, String suffix) throws ReflectiveOperationException {
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
nms.ScoreboardTeam_setColor.invoke(team, nms.EnumChatFormat_values[format.ordinal()]);
nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
}
private void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix) throws ReflectiveOperationException {
if (prefix != null) nms.ScoreboardTeam_setPrefix.invoke(team, prefix);
if (suffix != null) nms.ScoreboardTeam_setSuffix.invoke(team, suffix);
if (nms.getMinorVersion() >= 8) nms.ScoreboardTeam_setNameTagVisibility.invoke(team, String.valueOf(packet.getNameTagVisibility()).equals("always") ? nms.EnumNameTagVisibility_values[0] : nms.EnumNameTagVisibility_values[1]);
if (nms.getMinorVersion() >= 9) nms.ScoreboardTeam_setCollisionRule.invoke(team, String.valueOf(packet.getCollisionRule()).equals("always") ? nms.EnumTeamPush_values[0] : nms.EnumTeamPush_values[1]);
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return null;
EnumPlayerInfoAction action = EnumPlayerInfoAction.valueOf(nms.PacketPlayOutPlayerInfo_ACTION.get(nmsPacket).toString());
List<PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) nms.PacketPlayOutPlayerInfo_PLAYERS.get(nmsPacket)) {
Object nmsGameMode = nms.PlayerInfoData_getGamemode.invoke(nmsData);
EnumGamemode gameMode = (nmsGameMode == null) ? null : EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) nms.PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = nms.PlayerInfoData_getDisplayName.invoke(nmsData);
IChatBaseComponent listName = nmsComponent == null ? null : fromNMSComponent(nmsComponent);
PropertyMap map = new PropertyMap();
map.putAll(profile.getProperties());
listData.add(new PlayerInfoData(profile.getName(), profile.getId(), map, (int) nms.PlayerInfoData_getLatency.invoke(nmsData), gameMode, listName));
}
return new PacketPlayOutPlayerInfo(action, listData);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardObjective(nms.PacketPlayOutScoreboardObjective_METHOD.getInt(nmsPacket),
(String) nms.PacketPlayOutScoreboardObjective_OBJECTIVENAME.get(nmsPacket), null, null
);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardDisplayObjective(
nms.PacketPlayOutScoreboardDisplayObjective_POSITION.getInt(nmsPacket),
(String) nms.PacketPlayOutScoreboardDisplayObjective_OBJECTIVENAME.get(nmsPacket)
);
}
/**
* Builds entity BossBar packet
* @param packet - packet to build
* @param clientVersion - client version
* @return entity BossBar packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (packet.getOperation() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getOperation() == Action.REMOVE) {
return build(new PacketPlayOutEntityDestroy(entityId));
}
DataWatcher w = new DataWatcher();
if (packet.getOperation() == Action.UPDATE_PCT || packet.getOperation() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.helper().setHealth(health);
}
if (packet.getOperation() == Action.UPDATE_NAME || packet.getOperation() == Action.ADD) {
w.helper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getOperation() == Action.ADD) {
w.helper().setEntityFlags((byte) 32);
return build(new PacketPlayOutSpawnEntityLiving(entityId, null, EntityType.WITHER, new Location(null, 0,0,0), w));
} else {
return build(new PacketPlayOutEntityMetadata(entityId, w));
}
}
/**
* A method taken from minecraft code used to convert double to int
* @param paramDouble double value
* @return int value
*/
private int floor(double paramDouble){
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
/**
* Converts minecraft IChatBaseComponent into TAB's component class. Currently, does not support show_item hover event on 1.16+.
* @param component - component to convert
* @return converted component
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private IChatBaseComponent fromNMSComponent(Object component) throws ReflectiveOperationException {
if (!nms.ChatComponentText.isInstance(component)) return null; //paper
IChatBaseComponent chat = new IChatBaseComponent((String) nms.ChatComponentText_text.get(component));
Object modifier = nms.ChatBaseComponent_modifier.get(component);
if (modifier != null) {
chat.getModifier().setColor(fromNMSColor(nms.ChatModifier_color.get(modifier)));
chat.getModifier().setBold((Boolean) nms.ChatModifier_bold.get(modifier));
chat.getModifier().setItalic((Boolean) nms.ChatModifier_italic.get(modifier));
chat.getModifier().setObfuscated((Boolean) nms.ChatModifier_obfuscated.get(modifier));
chat.getModifier().setStrikethrough((Boolean) nms.ChatModifier_strikethrough.get(modifier));
chat.getModifier().setUnderlined((Boolean) nms.ChatModifier_underlined.get(modifier));
Object clickEvent = nms.ChatModifier_clickEvent.get(modifier);
if (clickEvent != null) {
chat.getModifier().onClick(EnumClickAction.valueOf(nms.ChatClickable_action.get(clickEvent).toString().toUpperCase()), (String) nms.ChatClickable_value.get(clickEvent));
}
Object hoverEvent = nms.ChatModifier_hoverEvent.get(modifier);
if (hoverEvent != null) {
EnumHoverAction action;
IChatBaseComponent value;
if (nms.getMinorVersion() >= 16) {
//does not support show_item on 1.16+
JsonObject json = (JsonObject) nms.ChatHoverable_serialize.invoke(hoverEvent);
action = EnumHoverAction.valueOf(json.get("action").getAsString().toUpperCase());
value = IChatBaseComponent.deserialize(json.get("contents").getAsJsonObject().toString());
} else {
action = EnumHoverAction.valueOf(nms.ChatHoverable_getAction.invoke(hoverEvent).toString().toUpperCase());
value = fromNMSComponent(nms.ChatHoverable_getValue.invoke(hoverEvent));
}
chat.getModifier().onHover(action, value);
}
}
for (Object extra : (List<Object>) nms.ChatBaseComponent_extra.get(component)) {
chat.addExtra(fromNMSComponent(extra));
}
return chat;
}
private TextColor fromNMSColor(Object color) throws ReflectiveOperationException {
if (color == null) return null;
if (nms.getMinorVersion() >= 16) {
String name = (String) nms.ChatHexColor_name.get(color);
if (name != null) {
//legacy code
return new TextColor(EnumChatFormat.valueOf(name.toUpperCase(Locale.US)));
} else {
int rgb = (int) nms.ChatHexColor_rgb.get(color);
return new TextColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
} else {
return new TextColor(EnumChatFormat.valueOf(((Enum)color).name()));
}
}
/**
* Converts TAB's IChatBaseComponent into minecraft's component. Currently, does not support hover event.
* @param component - component to convert
* @param clientVersion - client version used to decide RGB conversion
* @return converted component
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
Object obj;
if (clientVersion.getMinorVersion() >= 16) {
if (componentCacheModern.containsKey(component)) return componentCacheModern.get(component);
obj = toNMSComponent0(component, clientVersion);
if (componentCacheModern.size() > 10000) componentCacheModern.clear();
componentCacheModern.put(component, obj);
} else {
if (componentCacheLegacy.containsKey(component)) return componentCacheLegacy.get(component);
obj = toNMSComponent0(component, clientVersion);
if (componentCacheLegacy.size() > 10000) componentCacheLegacy.clear();
componentCacheLegacy.put(component, obj);
}
return obj;
}
//separate method to prevent extras counting cpu again due to recursion and finally showing higher usage than real
private Object toNMSComponent0(IChatBaseComponent component, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (component == null) return null;
Object chat = nms.newChatComponentText.newInstance(component.getText());
Object modifier;
Object clickEvent = component.getModifier().getClickEvent() == null ? null : nms.newChatClickable.newInstance(Enum.valueOf((Class<Enum>) nms.EnumClickAction,
component.getModifier().getClickEvent().getAction().toString().toUpperCase()), component.getModifier().getClickEvent().getValue());
if (nms.getMinorVersion() >= 16) {
Object color = null;
if (component.getModifier().getColor() != null) {
if (clientVersion.getMinorVersion() >= 16) {
color = nms.ChatHexColor_ofInt.invoke(null, (component.getModifier().getColor().getRed() << 16) + (component.getModifier().getColor().getGreen() << 8) + component.getModifier().getColor().getBlue());
} else {
color = nms.ChatHexColor_ofString.invoke(null, component.getModifier().getColor().getLegacyColor().toString().toLowerCase());
}
}
Object hoverEvent = null;
if (component.getModifier().getHoverEvent() != null) {
Object nmsAction = nms.EnumHoverAction_a.invoke(null, component.getModifier().getHoverEvent().getAction().toString().toLowerCase());
switch (component.getModifier().getHoverEvent().getAction()) {
case SHOW_TEXT:
hoverEvent = nms.newChatHoverable.newInstance(nmsAction, toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion));
break;
case SHOW_ENTITY:
hoverEvent = nms.EnumHoverAction_fromJson.invoke(nmsAction, ((ChatComponentEntity) component.getModifier().getHoverEvent().getValue()).toJson());
break;
case SHOW_ITEM:
hoverEvent = nms.EnumHoverAction_fromLegacyComponent.invoke(nmsAction, toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion));
break;
default:
break;
}
}
modifier = nms.newChatModifier.newInstance(
color,
component.getModifier().getBold(),
component.getModifier().getItalic(),
component.getModifier().getUnderlined(),
component.getModifier().getStrikethrough(),
component.getModifier().getObfuscated(),
clickEvent, hoverEvent, null, null);
} else {
modifier = nms.newChatModifier.newInstance();
if (component.getModifier().getColor() != null) nms.setField(modifier, nms.ChatModifier_color, Enum.valueOf((Class<Enum>) nms.EnumChatFormat, component.getModifier().getColor().getLegacyColor().toString()));
nms.setField(modifier, nms.ChatModifier_bold, component.getModifier().getBold());
nms.setField(modifier, nms.ChatModifier_italic, component.getModifier().getItalic());
nms.setField(modifier, nms.ChatModifier_underlined, component.getModifier().getUnderlined());
nms.setField(modifier, nms.ChatModifier_strikethrough, component.getModifier().getStrikethrough());
nms.setField(modifier, nms.ChatModifier_obfuscated, component.getModifier().getObfuscated());
if (clickEvent != null) nms.setField(modifier, nms.ChatModifier_clickEvent, clickEvent);
if (component.getModifier().getHoverEvent() != null) {
nms.setField(modifier, nms.ChatModifier_hoverEvent, nms.newChatHoverable.newInstance(nms.EnumHoverAction_a.invoke(null,
component.getModifier().getHoverEvent().getAction().toString().toLowerCase()), toNMSComponent0(component.getModifier().getHoverEvent().getValue(), clientVersion)));
}
}
nms.setField(chat, nms.ChatBaseComponent_modifier, modifier);
for (IChatBaseComponent extra : component.getExtra()) {
nms.ChatComponentText_addSibling.invoke(chat, toNMSComponent0(extra, clientVersion));
}
return chat;
}
private Object newScoreboardObjective(String objectiveName) throws ReflectiveOperationException {
if (nms.getMinorVersion() >= 13) {
return nms.newScoreboardObjective.newInstance(null, objectiveName, null, nms.newChatComponentText.newInstance(""), null);
}
return nms.newScoreboardObjective.newInstance(null, objectiveName, nms.IScoreboardCriteria_self.get(null));
}
}
@@ -1,128 +1,180 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Collection;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.NickCompatibility;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class BukkitPipelineInjector extends PipelineInjector {
import java.util.*;
import java.util.stream.Collectors;
//nms storage
private final NMSStorage nms = NMSStorage.getInstance();
/**
* Pipeline injection for bukkit
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
/**
* Constructs new instance
*/
public BukkitPipelineInjector(){
super("packet_handler");
channelFunction = BukkitChannelDuplexHandler::new;
}
/**
* Constructs new instance
*/
public BukkitPipelineInjector() {
super("packet_handler");
}
/**
* Custom channel duplex handler override
*/
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
//injected player
private final TabPlayer player;
/**
* Constructs new instance with given player
* @param player - player to inject
*/
public BukkitChannelDuplexHandler(TabPlayer player) {
this.player = player;
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
}
@Override
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
try {
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
super.channelRead(context, packet);
} catch (Exception e){
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (nms.PacketPlayOutPlayerInfo.isInstance(packet)) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && nms.PacketPlayOutScoreboardTeam != null && nms.PacketPlayOutScoreboardTeam.isInstance(packet)) {
modifyPlayers(packet);
super.write(context, packet, channelPromise);
return;
}
if (nms.PacketPlayOutScoreboardDisplayObjective.isInstance(packet)){
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
}
if (nms.PacketPlayOutScoreboardObjective.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Exception e){
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
/**
* Removes all real players from team if packet does not come from TAB and reports this to override log
* @param packetPlayOutScoreboardTeam - team packet
* @throws ReflectiveOperationException
* nmsGameMode
*/
@SuppressWarnings("unchecked")
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
long time = System.nanoTime();
Collection<String> players = (Collection<String>) nms.PacketPlayOutScoreboardTeam_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) nms.PacketPlayOutScoreboardTeam_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(p.getTeamName())) {
logTeamOverride(teamName, p.getName(), p.getTeamName());
} else {
newList.add(entry);
}
}
nms.setField(packetPlayOutScoreboardTeam, nms.PacketPlayOutScoreboardTeam_PLAYERS, newList);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
}
private TabPlayer getPlayer(String name) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (getName(p).equals(name)) return p;
}
return null;
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
}
private String getName(TabPlayer p) {
return ((NickCompatibility) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.NICK_COMPATIBILITY)).getNickname(p);
}
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(@NotNull Object packet) {
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(@NotNull Object packet) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PlayerInfoClass.isInstance(packet);
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
} else {
//1.19.2-
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id;
if (BukkitReflection.is1_19_3Plus()) {
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
} else {
id = profile.getId();
}
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
if (BukkitReflection.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -1,209 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.Locale;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.entity.EntityRegainHealthEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import com.earth2me.essentials.Essentials;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.placeholders.PlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
/**
* Bukkit registry to register bukkit-only placeholders
*/
public class BukkitPlaceholderRegistry implements PlaceholderRegistry {
//formatter for 2 decimal places
public final DecimalFormat decimal2 = ((DecimalFormat)NumberFormat.getNumberInstance(Locale.US));
private final Plugin plugin;
private Object chat;
private final Plugin essentials = Bukkit.getPluginManager().getPlugin("Essentials");
private Object server;
private Field recentTps;
private boolean paperTps;
private boolean paperMspt;
private boolean purpur;
private Method playerIsAfk;
private Listener healthListener = null;
/**
* Constructs new instance with given parameter
* @param plugin - plugin instance
*/
public BukkitPlaceholderRegistry(Plugin plugin) {
this.plugin = plugin;
decimal2.applyPattern("#.##");
try {
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<?> rspChat = Bukkit.getServicesManager().getRegistration(Class.forName("net.milkbowl.vault.chat.Chat"));
if (rspChat != null) chat = rspChat.getProvider();
}
} catch (ClassNotFoundException e) {
//modded server without vault
}
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
try {
Bukkit.class.getMethod("getTPS");
paperTps = true;
} catch (NoSuchMethodException e) {
//not paper
}
try {
Bukkit.class.getMethod("getAverageTickTime");
paperMspt = true;
} catch (NoSuchMethodException e) {
//not paper
}
try {
playerIsAfk = Player.class.getMethod("isAfk");
purpur = true;
} catch (NoSuchMethodException e) {
//not purpur
}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(PlaceholderManager manager) {
NumberFormat roundDown = NumberFormat.getNumberInstance(Locale.ENGLISH);
roundDown.setRoundingMode(RoundingMode.DOWN);
roundDown.setMaximumFractionDigits(2);
manager.registerPlayerPlaceholder("%displayname%", 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps) {
manager.registerServerPlaceholder("%tps%", 1000, () -> formatTPS(Bukkit.getTPS()[0]));
} else if (recentTps != null) {
manager.registerServerPlaceholder("%tps%", 1000, () -> {
try {
return formatTPS(((double[]) recentTps.get(server))[0]);
} catch (IllegalAccessException e) {
return "-1";
}
});
} else {
manager.registerServerPlaceholder("%tps%", -1, () -> "-1").enableTriggerMode();
}
if (paperMspt) {
manager.registerServerPlaceholder("%mspt%", 1000, () -> roundDown.format(Bukkit.getAverageTickTime()));
}
manager.registerPlayerPlaceholder("%afk%", 500, p -> {
if (essentials != null && ((Essentials)essentials).getUser(p.getUniqueId()).isAfk()) return true;
if (!purpur || playerIsAfk == null) return false;
try {
return playerIsAfk.invoke(p.getPlayer());
} catch (final IllegalAccessException | InvocationTargetException exception) {
TAB.getInstance().getErrorManager().printError("Failed to invoke isAfk!", exception);
}
return false;
});
manager.registerPlayerPlaceholder("%essentialsnick%", 1000, p -> {
String nickname = null;
if (essentials != null)
nickname = ((Essentials)essentials).getUser(p.getUniqueId()).getNickname();
return nickname == null || nickname.length() == 0 ? p.getName() : nickname;
});
if (chat != null) {
manager.registerPlayerPlaceholder("%vault-prefix%", 500, p -> ((Chat) chat).getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 500, p -> ((Chat) chat).getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "").enableTriggerMode();
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "").enableTriggerMode();
}
registerOnlinePlaceholders(manager);
registerHealthPlaceholder(manager);
}
private String formatTPS(double tps) {
return decimal2.format(Math.min(20, tps));
}
private void registerOnlinePlaceholders(PlaceholderManager manager) {
manager.registerPlayerPlaceholder("%online%", 1000, p -> {
int count = 0;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()){
if (((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return count;
});
manager.registerPlayerPlaceholder("%staffonline%", 2000, p -> {
int count = 0;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()){
if (all.hasPermission(TabConstants.Permission.STAFF) && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return count;
});
manager.registerPlayerPlaceholder("%nonstaffonline%", 2000, p -> {
int count = 0;
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()){
if (!all.hasPermission(TabConstants.Permission.STAFF) && ((Player) p.getPlayer()).canSee((Player) all.getPlayer())) count++;
}
return count;
});
}
@SuppressWarnings("deprecation")
private void registerHealthPlaceholder(PlaceholderManager manager) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
PlayerPlaceholder health = manager.registerPlayerPlaceholder("%health%", -1, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
health.enableTriggerMode(() -> {
healthListener = new Listener() {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onDamage(EntityDamageEvent e) {
if (e.getEntity() instanceof Player) {
Player p = (Player) e.getEntity();
TabPlayer tabp = TAB.getInstance().getPlayer(e.getEntity().getUniqueId());
if (tabp == null) return;
health.updateValue(tabp, (int) Math.ceil(Math.max(p.getHealth() - e.getFinalDamage(), 0)));
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onRegain(EntityRegainHealthEvent e) {
if (e.getEntity() instanceof Player) {
Player p = (Player) e.getEntity();
TabPlayer tabp = TAB.getInstance().getPlayer(e.getEntity().getUniqueId());
if (tabp == null) return;
health.updateValue(tabp, (int) Math.ceil(Math.min(p.getHealth() + e.getAmount(), p.getMaxHealth())));
}
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onRespawn(PlayerRespawnEvent e) {
health.updateValue(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId()), (int) e.getPlayer().getMaxHealth());
}
};
Bukkit.getPluginManager().registerEvents(healthListener, plugin);
}, () -> HandlerList.unregisterAll(healthListener));
}
}
}
@@ -1,274 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import java.io.File;
import java.util.Collection;
import java.util.List;
import me.neznamy.tab.shared.TabConstants;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.earth2me.essentials.Essentials;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.protocol.PacketBuilder;
import me.neznamy.tab.platforms.bukkit.event.TabLoadEvent;
import me.neznamy.tab.platforms.bukkit.event.TabPlayerLoadEvent;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.PetFix;
import me.neznamy.tab.platforms.bukkit.features.TabExpansion;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.NameTagX;
import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.Platform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.permission.LuckPerms;
import me.neznamy.tab.shared.permission.None;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.permission.UltraPermissions;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.plugin.RegisteredServiceProvider;
/**
* Bukkit's implementation of Platform interface
*/
public class BukkitPlatform implements Platform {
//plugin instance
private final Main plugin;
private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
//booleans to check plugin presence
private boolean placeholderAPI;
private boolean libsDisguises;
private Plugin essentials;
/**
* Constructs new instance with given parameters
* @param plugin - plugin instance
*/
public BukkitPlatform(Main plugin) {
this.plugin = plugin;
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
return new LuckPerms(getPluginVersion("LuckPerms"));
} else if (Bukkit.getPluginManager().isPluginEnabled("UltraPermissions")) {
return new UltraPermissions(getPluginVersion("UltraPermissions"));
} else if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider == null) return new None();
return new Vault(provider.getProvider(), getPluginVersion("Vault"));
} else {
return new None();
}
}
@Override
public void loadFeatures() {
if (Bukkit.getPluginManager().isPluginEnabled("ViaVersion")) {
try {
Class.forName("com.viaversion.viaversion.api.Via");
} catch (ClassNotFoundException e) {
TAB.getInstance().sendConsoleMessage("&c[TAB] An outdated version of ViaVersion (" + getPluginVersion("ViaVersion") + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&c[TAB] TAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&c[TAB] This might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
if (Bukkit.getPluginManager().isPluginEnabled("Tablisknu")) {
TAB.getInstance().sendConsoleMessage("&c[TAB] Detected plugin \"Tablisknu\", which causes TAB to not work properly. Consider removing the plugin.", true);
}
placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
libsDisguises = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises");
essentials = Bukkit.getPluginManager().getPlugin("Essentials");
TAB tab = TAB.getInstance();
if (tab.getConfiguration().isPipelineInjection())
tab.getFeatureManager().registerFeature(TabConstants.Feature.PIPELINE_INJECTION, new BukkitPipelineInjector());
new BukkitPlaceholderRegistry(plugin).registerPlaceholders(tab.getPlaceholderManager());
new UniversalPlaceholderRegistry().registerPlaceholders(tab.getPlaceholderManager());
loadNameTagFeature(tab);
tab.loadUniversalFeatures();
if (tab.getConfiguration().getConfig().getBoolean("bossbar.enabled", false)) {
if (tab.getServerVersion().getMinorVersion() < 9) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new WitherBossBar(plugin));
} else {
tab.getFeatureManager().registerFeature(TabConstants.Feature.BOSS_BAR, new BossBarManagerImpl());
}
}
if (tab.getServerVersion().getMinorVersion() >= 9 && tab.getConfiguration().getConfig().getBoolean("fix-pet-names.enabled", false))
tab.getFeatureManager().registerFeature(TabConstants.Feature.PET_FIX, new PetFix());
if (tab.getConfiguration().getConfig().getBoolean("per-world-playerlist.enabled", false))
tab.getFeatureManager().registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, new PerWorldPlayerList(plugin));
if (placeholderAPI) {
new TabExpansion(plugin);
}
for (Player p : getOnlinePlayers()) {
tab.addPlayer(new BukkitTabPlayer(p, plugin.getProtocolVersion(p)));
}
}
private String getPluginVersion(String plugin) {
Plugin pl = Bukkit.getPluginManager().getPlugin(plugin);
return pl == null ? "" : pl.getDescription().getVersion();
}
/**
* Loads NameTag feature from config
* @param tab - tab instance
*/
private void loadNameTagFeature(TAB tab) {
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.enabled", true)) {
if (tab.getConfiguration().getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && tab.getServerVersion().getMinorVersion() >= 8) {
tab.getFeatureManager().registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, new NameTagX(plugin));
} else {
tab.getFeatureManager().registerFeature(TabConstants.Feature.NAME_TAGS, new NameTag());
}
}
}
/**
* Returns list of online players from Bukkit API
* @return list of online players from Bukkit API
*/
@SuppressWarnings("unchecked")
private Player[] getOnlinePlayers() {
try {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7.x
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get online players", e);
return new Player[0];
}
}
@Override
public void sendConsoleMessage(String message, boolean translateColors) {
Bukkit.getConsoleSender().sendMessage(translateColors ? EnumChatFormat.color(message) : message);
}
@Override
public void registerUnknownPlaceholder(String identifier) {
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
} else {
//normal placeholder
if (identifier.startsWith("%sync:")) {
int refresh = pl.getServerPlaceholderRefreshIntervals().getOrDefault(identifier,
pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh()));
pl.registerPlaceholder(new PlayerPlaceholderImpl(identifier, refresh, null) {
@Override
public Object request(TabPlayer p) {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
String syncedPlaceholder = identifier.substring(6, identifier.length()-1);
String value = placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), "%" + syncedPlaceholder + "%") : identifier;
getLastValues().put(p, value);
getForceUpdate().add(p);
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
});
String value = getLastValues().get(p);
return value == null ? identifier : value;
}
});
return;
}
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier), () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
} else {
int refresh = pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh());
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
}
}
}
@Override
public String getServerVersion() {
return Bukkit.getBukkitVersion().split("-")[0] + " (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public void callLoadEvent() {
Bukkit.getPluginManager().callEvent(new TabLoadEvent());
}
@Override
public void callLoadEvent(TabPlayer player) {
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, () -> Bukkit.getPluginManager().callEvent(new TabPlayerLoadEvent(player)));
}
@Override
public int getMaxPlayers() {
return Bukkit.getMaxPlayers();
}
public boolean isLibsDisguisesEnabled() {
return libsDisguises;
}
public void setLibsDisguisesEnabled(boolean enabled) {
libsDisguises = enabled;
}
public Essentials getEssentials() {
return (Essentials) essentials;
}
@Override
public PacketBuilder getPacketBuilder() {
return packetBuilder;
}
@SuppressWarnings("unchecked")
@Override
public Object getSkin(List<String> properties) {
PropertyMap map = new PropertyMap();
map.put("textures", new Property("textures", properties.get(0), properties.get(1)));
return map;
}
@Override
public boolean isProxy() {
return false;
}
@Override
public boolean isPluginEnabled(String plugin) {
return Bukkit.getPluginManager().isPluginEnabled(plugin);
}
@Override
public String getConfigName() {
return "bukkitconfig.yml";
}
}
@@ -0,0 +1,58 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Main class for Bukkit.
*/
public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable() {
if (!isVersionSupported()) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this));
}
@Override
public void onDisable() {
//null check due to compatibility check making instance not get set on unsupported versions
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
/**
* Initializes all used NMS classes, constructors, fields and methods.
* Returns {@code true} if everything went successfully,
* {@code false} if anything went wrong.
*
* @return {@code true} if server version is compatible, {@code false} if not
*/
private boolean isVersionSupported() {
try {
NMSStorage.setInstance(new NMSStorage());
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(String.format(
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5, ProtocolVersion.LATEST_KNOWN_VERSION
));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug.");
}
return false;
}
}
}
@@ -0,0 +1,49 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.TabPlayer;
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.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.List;
/**
* Command handler for /tab command
*/
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
}
} 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
@NotNull
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
}
@@ -0,0 +1,232 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList which support modifying many entries at once
* for significantly better performance. For 1.7 players,
* ViaVersion properly splits the packet into multiple, so
* we don't need to worry about that here.
* <p>
* This class does not support server versions of 1.7 and
* below, because of the massive differences in tab list
* and packet fields.
*/
@RequiredArgsConstructor
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
private static Class<Enum> ActionClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
private static Class<?> RemoteChatSession$Data;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
public static Constructor<?> newPlayerInfoData;
public static Field PlayerInfoData_UUID;
public static Field PlayerInfoData_Profile;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
private static Object[] gameModes;
@Getter
private static boolean available;
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported (yet?)
// Classes
Class<?> playerInfoDataClass;
Class<?> IChatBaseComponent;
Class<Enum> EnumGamemodeClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
if (BukkitReflection.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
} else {
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
}
gameModes = new Object[] {
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
available = true;
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (!available) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
} else {
//1.19.2-
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
if (!available) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
}
@SneakyThrows
@NotNull
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
Object packet;
List<Object> players = new ArrayList<>();
if (BukkitReflection.is1_19_3Plus()) {
EnumSet<?> actions;
if (action == Action.ADD_PLAYER) {
actions = EnumSet.allOf(ActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
}
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
players.add(newPlayerInfoData.newInstance(
entry.getUniqueId(),
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
true,
entry.getLatency(),
gameModes[entry.getGameMode()],
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
null
));
} else {
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
parameters.add(packet);
}
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
parameters.add(entry.getLatency());
parameters.add(gameModes[entry.getGameMode()]);
parameters.add(entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()));
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
}
PLAYERS.set(packet, players);
return packet;
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
@NotNull
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name == null ? "" : name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
}
@@ -1,261 +1,162 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarFlag;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.earth2me.essentials.Essentials;
import com.mojang.authlib.GameProfile;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import me.libraryaddict.disguise.DisguiseAPI;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.shared.TAB;
import java.util.Collection;
/**
* TabPlayer for Bukkit
* TabPlayer implementation for Bukkit platform
*/
public class BukkitTabPlayer extends ITabPlayer {
@SuppressWarnings("deprecation")
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
//nms handle
private Object handle;
/** Player's NMS handle (EntityPlayer), preloading for speed */
@NotNull
private final Object handle;
//nms player connection
private Object playerConnection;
//player's visible boss bars
private final Map<UUID, BossBar> bossBars = new HashMap<>();
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
/** Player's connection for sending packets, preloading for speed */
@NotNull
private final Object playerConnection;
/**
* Constructs new instance with given parameter
* @param p - bukkit player
*/
public BukkitTabPlayer(Player p, int protocolVersion){
super(p, p.getUniqueId(), p.getName(), "N/A", p.getWorld().getName());
try {
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
if (NMSStorage.getInstance().CHANNEL != null)
channel = (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection or channel of " + p.getName(), e);
}
version = ProtocolVersion.fromNetworkId(protocolVersion);
}
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
@Override
public boolean hasPermission(String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
}
@NotNull
private final TabList tabList = new BukkitTabList(this);
@Override
public int getPing() {
try {
int ping = NMSStorage.getInstance().PING.getInt(handle);
if (ping > 10000 || ping < 0) ping = -1;
return ping;
} catch (IllegalAccessException e) {
return -1;
}
}
@NotNull
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@Override
public void sendPacket(Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
long time = System.nanoTime();
try {
if (nmsPacket instanceof PacketPlayOutBoss) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
handle((PacketPlayOutBoss) nmsPacket);
} else {
handleVia((PacketPlayOutBoss) nmsPacket);
}
} else {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
}
private void handle(PacketPlayOutBoss packet) {
BossBar bar = bossBars.get(packet.getId());
if (packet.getOperation() == PacketPlayOutBoss.Action.ADD) {
if (bossBars.containsKey(packet.getId())) return;
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BarColor.valueOf(packet.getColor().name()),
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
bar.setProgress(packet.getPct());
bossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer());
return;
}
if (bar == null) return; //no idea how
switch (packet.getOperation()) {
case REMOVE:
bar.removePlayer(getPlayer());
bossBars.remove(packet.getId());
break;
case UPDATE_PCT:
bar.setProgress(packet.getPct());
break;
case UPDATE_NAME:
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
bar.setColor(BarColor.valueOf(packet.getColor().name()));
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = bossBars.get(packet.getId());
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
private void handleVia(PacketPlayOutBoss packet) {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
switch (packet.getOperation()) {
case ADD:
if (viaBossBars.containsKey(packet.getId())) return;
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
packet.getPct(),
BossColor.valueOf(packet.getColor().name()),
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
//fog missing from via API
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
viaBossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer().getUniqueId());
break;
case REMOVE:
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
viaBossBars.remove(packet.getId());
break;
case UPDATE_PCT:
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
break;
case UPDATE_NAME:
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = viaBossBars.get(packet.getId());
//fog missing from via API
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue) {
if (!bar.hasFlag(flag)) {
bar.addFlag(flag);
}
} else {
if (bar.hasFlag(flag)) {
bar.removeFlag(flag);
}
}
}
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
if (targetValue) {
if (!bar.hasFlag(flag)) {
bar.addFlag(flag);
}
} else {
if (bar.hasFlag(flag)) {
bar.removeFlag(flag);
}
}
}
@NotNull
private final EntityView entityView = new PacketEntityView(this);
@Override
public boolean hasInvisibilityPotion() {
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
}
/**
* Constructs new instance with given bukkit player
*
* @param platform
* Server platform
* @param p
* bukkit player
*/
@SneakyThrows
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@Override
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
return DisguiseAPI.isDisguised(getPlayer());
} catch (LinkageError e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
return false;
}
}
@Override
public boolean hasPermission(@NotNull String permission) {
return getPlayer().hasPermission(permission);
}
@Override
public Object getSkin() {
try {
return ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties();
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
return null;
}
}
@Override
@SneakyThrows
public int getPing() {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
return NMSStorage.getInstance().PING.getInt(handle);
}
@Override
public Player getPlayer() {
return (Player) player;
}
@SneakyThrows
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
@Override
public boolean isVanished() {
Essentials essentials = ((BukkitPlatform)TAB.getInstance().getPlatform()).getEssentials();
if (essentials != null && essentials.getUser(getUniqueId()).isVanished()) return true;
List<MetadataValue> metadata = getPlayer().getMetadata("vanished");
return !metadata.isEmpty() && metadata.get(0).asBoolean();
}
@Override
public boolean hasInvisibilityPotion() {
return getPlayer().hasPotionEffect(PotionEffectType.INVISIBILITY);
}
@SuppressWarnings("deprecation")
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public boolean isDisguised() {
return LibsDisguisesHook.isDisguised(this);
}
@Override
@SneakyThrows
@Nullable
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
if (BukkitReflection.is1_20_2Plus()) {
return new TabList.Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new TabList.Skin(property.getValue(), property.getSignature());
}
}
@Override
@NotNull
public Player getPlayer() {
return (Player) player;
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@Override
public BukkitPlatform getPlatform() {
return (BukkitPlatform) platform;
}
@Override
public boolean isVanished() {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
}
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName();
}
}
@@ -0,0 +1,76 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Class containing static util methods used on Bukkit.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BukkitUtils {
/**
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
* and we want to avoid errors.
*
* @return Online players from Bukkit API.
*/
@SuppressWarnings("unchecked")
@SneakyThrows
@NotNull
public static Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* If not, closest legacy color is used instead.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
*/
@NotNull
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
StringBuilder sb = new StringBuilder();
if (component.getModifier().getColor() != null) {
if (BukkitReflection.getMinorVersion() >= 16 && rgbClient) {
String hexCode = component.getModifier().getColor().getHexCode();
char c = EnumChatFormat.COLOR_CHAR;
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
} else {
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
}
}
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
}
return sb.toString();
}
}
Loaded 100 of 489 files, more files were not shown because too many files have changed in this diff. Show more