Compare commits

...
304 Commits
Author SHA1 Message Date
vytskalt 9fad798af8 add spigot 26.1 2026-03-25 18:41:00 +02:00
vytskalt f3cbbfb2b5 26.1 2026-03-23 19:12:23 +02:00
vytskalt 4d1d5ca25e synchronizeid 2026-03-01 22:54:26 +02:00
vytskalt cd53aaf8e3 final class 2026-03-01 22:42:42 +02:00
vytskalt 36a0ef0dfd cleaning up 2026-03-01 22:34:51 +02:00
vytskalt 232ed9be84 consolidate internals into implementation module, remove old modules 2026-03-01 22:22:44 +02:00
vytskalt 7e3d7d7918 [ci skip] update installation 2026-02-28 22:02:16 +02:00
vytskalt f277ee4de2 release 2.7.0 2026-02-27 20:13:59 +02:00
vytskalt 1613c8d2c5 testing stuff 2026-02-27 20:11:10 +02:00
vytskalt 91d74ddf4e Merge remote-tracking branch 'origin/main' into dev123 2026-02-25 23:35:14 +02:00
vytskalt 45544a71c7 Merge pull request #128 from huanmeng-qwq/add-entries
add bulk entry add/remove API
2026-02-25 17:17:56 +02:00
huanmeng-qwq a454dff208 fix bulk entry add/remove sending invalid entries 2026-02-25 22:26:14 +08:00
huanmeng-qwq 6075333d7b impl noop 2026-02-25 20:38:41 +08:00
huanmeng-qwq 9d16e5fe32 add bulk entry add/remove API 2026-02-25 20:35:11 +08:00
vytskalt b8babc7542 Merge branch 'main' into dev123 2026-02-24 23:15:30 +02:00
vytskalt 80cb76e98b removePlayerFuture API 2026-02-24 23:14:51 +02:00
vytskalt 1e6c8a033a docs updates 2026-02-24 22:32:36 +02:00
vytskalt cd09080ced cleaned up 2026-02-22 22:00:53 +02:00
vytskalt bf01337fcf workaround relocated gson 2026-02-22 21:49:33 +02:00
vytskalt ef3998bd3b fix 1.7 2026-02-20 17:58:48 +02:00
vytskalt 38d006cf6e almost 2026-02-20 17:24:45 +02:00
vytskalt d19f997846 resolve packet ordering 2026-02-20 16:33:06 +02:00
vytskalt 92d3281fad pull 2026-02-19 23:33:34 +02:00
vytskalt 538edffa1a begin via integration 2026-02-19 23:24:40 +02:00
vytskalt 1c82d3025f release 2.6.0 2026-02-14 12:19:59 +02:00
vytskalt d7558778bb test legacy versions 2026-02-14 01:53:34 +02:00
vytskalt b16dd7cbac beautiful 2026-02-14 01:31:04 +02:00
vytskalt ce1533ded5 yeet packetevents adapter 2026-02-14 01:21:47 +02:00
vytskalt 6158c71cc9 Merge pull request #125 from vytskalt/full-modern
1.13 - 1.16.5 modern support
2026-02-14 01:17:38 +02:00
vytskalt d9fcd8013c finish up 2026-02-14 01:16:11 +02:00
vytskalt a4b9c13bef works on 1.13 2026-02-13 19:20:50 +02:00
vytskalt b5bd7a543d implement 1.16.5 2026-02-11 22:38:45 +02:00
vytskalt 33144908eb begin pre 1.17 support 2026-02-07 00:16:17 +02:00
vytskalt 2c84532f79 release 2.5.0 2026-02-06 22:13:45 +02:00
vytskalt eeff691c95 native adventure test, remove references to mojmap variant 2026-02-06 22:12:36 +02:00
vytskalt 97e027c2d3 Merge pull request #121 from MegavexNetwork/modern-reflect-v2
Remove paperweight
2026-02-06 22:01:21 +02:00
vytskalt 59dbad8311 final touches 2026-02-06 21:59:34 +02:00
vytskalt c15e8c2b9d add spigot servers 2026-02-06 21:15:44 +02:00
vytskalt 8066116d7e fix for 1.20.2 and 1.17 2026-02-06 18:17:40 +02:00
vytskalt 96677233af add remaining paper servers 2026-02-06 17:34:13 +02:00
vytskalt c57aa1ee22 testing poc 2026-02-03 18:10:24 +02:00
vytskalt 8de8502b63 init test env 2026-02-03 02:24:48 +02:00
vytskalt 206bd2e56a Merge branch 'main' into modern-reflect-v2 2026-02-03 01:04:27 +02:00
vytskalt 54135c73b8 fix duplicate objective create packet 2026-02-03 01:03:39 +02:00
vytskalt f23d2e2804 init test-plugin 2026-01-25 23:14:39 +02:00
vytskalt 904b7e5475 works on spigot 2026-01-25 22:47:50 +02:00
vytskalt 7b9d274683 forgot to commit 2026-01-25 17:42:51 +02:00
vytskalt 9e6e1d506d part 7, remove paperweight 2025-12-11 20:39:09 +02:00
vytskalt 96a18d1632 part 6 2025-12-11 20:04:27 +02:00
vytskalt 2092517215 part 5 2025-12-11 19:21:15 +02:00
vytskalt dd735360ce part 4 2025-12-11 18:31:37 +02:00
vytskalt bb74f217f2 part 3 2025-12-10 22:00:54 +02:00
vytskalt b0a69da47c part 2 2025-12-10 21:27:27 +02:00
vytskalt d845cca062 part 1 2025-12-10 21:00:49 +02:00
vytskalt d71e029052 begin 2025-12-10 18:57:26 +02:00
vytskalt 4eb7ed3cd8 update dependencies 2025-12-10 18:33:10 +02:00
vytskalt f8fb6c2a8c release 2.4.4 2025-12-10 18:05:46 +02:00
vytskalt a3e0c6795c mark 1.21.11 as supported 2025-12-10 18:04:56 +02:00
vytskalt 9e37c3e627 release: 2.4.3 2025-10-09 23:26:06 +03:00
vytskalt 8fdf24e07a 1.21.10 2025-10-09 23:23:46 +03:00
vytskalt 1227b28189 ci fix part 2 2025-09-30 19:44:52 +03:00
vytskalt 6fe6200cda ci fix 2025-09-30 19:29:00 +03:00
vytskalt fb4863a9e3 release 2.4.2 2025-09-30 19:16:48 +03:00
vytskalt a298842bce 1.21.9 2025-09-30 19:15:53 +03:00
vytskalt 16314a7d14 fix build 2025-09-30 18:39:29 +03:00
vytskalt c1286520d3 draw up to sidebar#maxLines() 2025-09-30 18:38:58 +03:00
vytskalt bc8e3c2d2e release: 2.4.1 2025-07-17 18:37:46 +03:00
vytskalt 032349b190 mark 1.21.8 as supported 2025-07-17 18:37:08 +03:00
vytskalt d0d5eaa706 release: 2.4.0 2025-07-08 17:24:34 +03:00
vytskalt ac334838ee bump dependencies 2025-07-08 16:57:44 +03:00
vytskalt 162de14e0e Merge pull request #100 from huanmeng-qwq/feat/refresh
Add refresh-related methods
2025-07-08 16:53:38 +03:00
vytskalt 5f5b3efc5f improve docs 2025-07-08 13:55:05 +03:00
vytskalt 41c0a4b3c9 improve objective api 2025-07-08 13:52:12 +03:00
vytskalt a276708162 make sure to update the sidebar score format as well 2025-07-08 13:37:39 +03:00
huanmeng-qwq 793e78284f feat(api): Add refresh-related methods to ScoreboardObjective, Sidebar, and TeamDisplay. 2025-07-08 17:11:47 +08:00
vytskalt 38de97ba01 release: 2.3.3 2025-07-02 15:34:32 +03:00
vytskalt a535f37214 1.21.7 2025-07-02 15:34:04 +03:00
vytskalt 48351b6c81 release: 2.3.2 2025-06-23 01:11:13 +03:00
vytskalt 7dcf1e30eb add 1.21.6 support 2025-06-23 01:06:20 +03:00
vytskalt 2ca874346d release: 2.3.1 2025-05-04 19:29:04 +03:00
vytskalt cb08e5d3d9 Merge remote-tracking branch 'origin/main' 2025-04-26 23:53:16 +03:00
vytskalt eecde934a8 setup snapshot 2025-04-26 23:52:50 +03:00
vytskalt 2a070cdc3d remove transitive dependency on spigot-api 2025-04-26 23:52:11 +03:00
vytskalt 589ab095b2 [ci skip] Update INSTALLATION.md 2025-04-21 19:52:18 +03:00
vytskalt 41fb8aa4aa Update INSTALLATION.md 2025-04-21 19:43:22 +03:00
vytskalt da69391930 release: 2.3.0 2025-04-12 19:31:35 +03:00
vytskalt 47ef9a8143 update junit platform launcher 2025-04-12 19:27:25 +03:00
vytskalt ccf9aad96d update dependencies 2025-04-12 19:23:17 +03:00
vytskalt facb3a4d1c allow getting/setting sidebar objective name 2025-04-03 21:42:29 +03:00
vytskalt f41c1ce0b1 1.21.5 2025-03-27 19:59:49 +02:00
vytskalt e72d442f9b upgrade to gradle 8.13 2025-03-27 18:44:23 +02:00
vytskalt 9ef53fc36c release: 2.2.2 2024-12-08 19:58:38 +02:00
vytskalt 774ab670c9 1.21.4 2024-12-08 19:56:08 +02:00
vytskalt 30641a8c15 release: 2.2.1 2024-11-01 19:36:11 +02:00
vytskalt 01e1374db6 feat: add support for 1.21.2/1.21.3 2024-11-01 19:35:38 +02:00
vytskalt db58aa35c5 [ci skip] update maven docs 2024-10-13 17:45:21 +03:00
vytskalt 35a0565cd1 release 2.2.0 2024-10-13 17:11:05 +03:00
vytskalt 6fab408824 [ci skip] update docs for legacy adapter 2024-10-13 16:59:34 +03:00
vytskalt b0d6903262 fix removing score on packetevents 2024-10-13 16:35:14 +03:00
vytskalt acd64b884e update deps 2024-10-13 15:54:59 +03:00
vytskalt 70eb07c25d temp packetevents fix 2024-10-13 15:49:43 +03:00
vytskalt 50d567a4af fix 1.13+ client on 1.12.2- server sidebar line styling bug 2024-10-13 15:26:35 +03:00
vytskalt 97cd00b598 Merge pull request #59 from Nixuge/main
Legacy Packet Adapter
2024-10-13 14:55:07 +03:00
vytskalt bf0529a11e [ci skip] undo docs changes 2024-10-13 14:53:54 +03:00
vytskalt 1b752a0694 add safety check to packetevents legacy teams serializer 2024-10-13 14:46:54 +03:00
vytskalt 5f093db220 support collision rule for 1.9+ 2024-10-11 19:15:34 +03:00
vytskalt 651f5b24b7 fix 1.7.10 & 1.8.8 2024-10-11 18:50:41 +03:00
vytskalt e35fb3503c more cleanup 2024-10-11 16:03:02 +03:00
vytskalt 14e16b95a0 readd modeerrn 2024-10-11 11:48:45 +03:00
vytskalt b30ea7dc44 cleanup 2024-10-11 10:35:22 +03:00
vytskalt 8e09c6d1c1 ඞ 2024-10-08 23:25:33 +03:00
vytskalt 8aa61448e4 capital statics 2024-10-08 13:03:29 +03:00
vytskalt 3762419bc5 what the sigma 2024-10-08 13:00:11 +03:00
vytskalt 53bea167d7 docs and loader 2024-10-08 10:01:09 +03:00
vytskalt 5eaa7f3c07 legacyreflections -> legacy 2024-10-08 09:49:08 +03:00
vytskalt 699cbacf6e yeet 1.8 adapter 2024-10-08 09:44:18 +03:00
vytskalt e6fdd5e513 yeet tests 2024-10-08 09:41:45 +03:00
vytskalt a95c879d10 Merge remote-tracking branch 'upstream/main' 2024-10-08 09:18:18 +03:00
vytskalt 4f6d1dc5b1 fix: possible concurrent modification error in team manager 2024-09-09 20:05:08 +03:00
Nixuge 5c0709a8c4 [UNTESTED] Cached more reflections fields 2024-09-02 17:58:54 +02:00
Nixuge d5c61dc156 1.7 support, now 1.7 - 1.12.2, automatically grabbing NMS version 2024-08-25 20:12:30 +02:00
Nixuge 5466140dc4 Removed sneaky debug print i left there 2024-08-24 20:30:03 +02:00
Nixuge cc13241968 Added 1.7.10 case to PacketAdapterLoader using new LEGACY_REFLECTIONS 2024-08-24 20:28:01 +02:00
Nixuge 16592c2815 Fixed wrong thing I wrongfully copied 2024-08-24 20:00:03 +02:00
Nixuge 8c09c0e97c 1.8 folder back to actually 1.8 2024-08-24 19:08:24 +02:00
Nixuge 0bbc487f88 Merge branch 'MegavexNetwork:main' into main 2024-08-24 19:05:14 +02:00
Nixuge 0cbd54f8f1 Moved actual legacy reflections implementation to legacyreflections 2024-08-24 19:04:50 +02:00
Nixuge 0551a8f978 Base fully working with reflections 2024-08-24 18:42:40 +02:00
Nixuge dd65c812d2 Started removing v1_8_R3 imports in favor of reflections 2024-08-24 17:38:20 +02:00
vytskalt a88394223b fix: invalid packets in PacketEvents on 1.13+ servers with 1.12.2- clients and 1.12.2- servers with 1.13+ clients 2024-08-22 17:36:20 +03:00
vytskalt 0e579e603a release: 2.1.12 2024-08-14 21:03:18 +03:00
vytskalt 71aaa7ac50 1.21.1 2024-08-14 21:01:17 +03:00
vytskalt 8521d1deec ci: add caching for gradle 2024-08-10 11:57:23 +03:00
vytskalt 9c1a121e09 chore: make builds as reproducible as possible 2024-08-10 11:43:36 +03:00
vytskalt 7f3651d4bf chore: bump dependencies 2024-08-06 16:17:26 +03:00
vytskalt 2e06fc1d34 don't pass mutable entries lists to packets
fixes some invalid packets being sent
2024-08-06 16:11:33 +03:00
vytskalt cb391dd676 fix: sending create team packets with entries not yet synced 2024-08-06 13:45:46 +03:00
vytskalt a36a8c3fd2 use snapshot version 2024-08-05 23:53:37 +03:00
vytskalt 4f3cf55c01 fix ci failing due to maven repo being down 2024-07-12 15:14:06 +03:00
vytskalt 422a36a284 release: 2.1.11 2024-07-12 15:01:03 +03:00
vytskalt a2e5df14e6 fix possible CraftRegistry ClassNotFoundException 2024-07-04 22:57:59 +03:00
vytskalt 521bc47dd0 fix finding wrong fromJson method 2024-06-24 10:02:05 +03:00
vytskalt 09dc3a653d [ci skip] docs: mention 1.21 as supported 2024-06-16 18:09:03 +03:00
vytskalt b5181d0d81 [ci skip] add snapshots repo 2024-06-16 18:08:38 +03:00
vytskalt f01f4fd831 use thread safe collection for team entries 2024-06-16 18:03:26 +03:00
vytskalt 778b479b91 release: 2.1.10 2024-06-16 12:50:45 +03:00
vytskalt 7a1c47045b 1.21 2024-06-15 13:47:25 +03:00
vytskalt e2c8ff5eb8 release: 2.1.9 2024-05-20 16:36:42 +03:00
vytskalt af426bdfbc [ci skip] fix oopsie 2024-05-20 16:24:20 +03:00
vytskalt 641223e1f5 [ci skip] some docs improvements 2024-05-17 23:51:58 +03:00
vytskalt cf8fa59c86 fix adapter loading on customized bukkit version 2024-05-14 12:27:41 +03:00
vytskalt 96595d0a41 Merge pull request #40 from MegavexNetwork/renovate/com.github.retrooper.packetevents-spigot-2.x
fix(deps): update dependency com.github.retrooper.packetevents:spigot to v2.3.0
2024-05-12 21:02:20 +03:00
vytskalt ba6bff7ed1 Merge pull request #37 from MegavexNetwork/renovate/org.jetbrains.kotlin.jvm-1.x
chore(deps): update plugin kotlin to v1.9.24
2024-05-12 21:02:08 +03:00
vytskalt 0f96547b37 Merge pull request #38 from MegavexNetwork/renovate/adventure
fix(deps): update adventure to v4.17.0
2024-05-12 21:01:46 +03:00
renovate[bot] 5d02fd625b chore(deps): update plugin kotlin to v1.9.24 2024-05-12 18:01:44 +00:00
vytskalt fed9f4c64b Merge pull request #36 from MegavexNetwork/renovate/io.papermc.paperweight.userdev-1.x
chore(deps): update plugin paperweight to v1.7.1
2024-05-12 21:01:20 +03:00
renovate[bot] 0c3f942757 chore(deps): update plugin paperweight to v1.7.1 2024-05-11 21:24:49 +00:00
renovate[bot] c1c9d7258b fix(deps): update dependency com.github.retrooper.packetevents:spigot to v2.3.0 2024-05-09 03:46:27 +00:00
renovate[bot] 93cd55a626 fix(deps): update adventure to v4.17.0 2024-05-08 07:21:33 +00:00
vytskalt 0d979d6922 Mention 1.20.6 as supported in README 2024-05-01 15:07:01 +03:00
vytskalt cf6b51be40 Merge remote-tracking branch 'origin/main' 2024-04-30 17:37:28 +03:00
vytskalt c1199432eb Merge pull request #33 from MegavexNetwork/renovate/io.papermc.paperweight.userdev-1.x
chore(deps): update plugin paperweight to v1.6.3
2024-04-30 17:06:38 +03:00
vytskalt bccc777541 Merge pull request #32 from MegavexNetwork/renovate/gradle-gradle-build-action-3.x
chore(deps): update gradle/gradle-build-action action to v3.3.2
2024-04-30 17:06:28 +03:00
vytskalt d14ca9470a release 2.1.8 2024-04-30 17:02:15 +03:00
vytskalt 1889b84373 mark 1.20.6 as supported & add forceModern system property 2024-04-30 16:54:30 +03:00
renovate[bot] bf629bd7de chore(deps): update plugin paperweight to v1.6.3 2024-04-30 00:17:36 +00:00
vytskalt 690905774e release 2.1.7 2024-04-27 22:54:17 +03:00
renovate[bot] 40a35272ff chore(deps): update gradle/gradle-build-action action to v3.3.2 2024-04-27 19:48:44 +00:00
vytskalt f9fdc13443 Merge pull request #35 from MegavexNetwork/dev/1.20.5
Add support for 1.20.5
2024-04-27 22:48:19 +03:00
vytskalt bdce2423b1 finish up 2024-04-27 22:47:56 +03:00
vytskalt 4955c839a8 fix weird jvm issues 2024-04-27 22:25:54 +03:00
vytskalt 9b198da189 add mojmap artifact 2024-04-27 20:32:37 +03:00
vytskalt 32409d589c add backwards compat for older versions 2024-04-27 19:56:13 +03:00
vytskalt e016b43822 get it to work on 1.20.5 2024-04-27 14:32:45 +03:00
vytskalt 17137a88c4 update ci 2024-04-27 12:20:13 +03:00
vytskalt 4369acd756 get it to compile 2024-04-27 12:14:49 +03:00
vytskalt 4fa40ed032 [ci skip] remove jitpack.yml
it no longer works due to indra
2024-04-09 22:47:54 +03:00
vytskalt 3bf91896b0 attempt to fix jitpack build 2024-04-09 22:42:14 +03:00
vytskalt b71d555391 release: 2.1.6 2024-04-09 22:35:32 +03:00
vytskalt 25f236344f add missing null check for sidebar line 2024-04-09 22:33:33 +03:00
vytskalt 01d8ef9b29 update installation 2024-04-06 14:20:22 +03:00
vytskalt 98917376d0 release: 2.1.5 2024-04-06 14:20:22 +03:00
vytskalt cd0607a72c build: add nmcp plugin 2024-04-06 14:20:22 +03:00
vytskalt 8ba9ef40da that's the wrong url 2024-04-06 14:20:21 +03:00
vytskalt 3be7b09631 fix modern adapter build script 2024-04-06 14:19:16 +03:00
vytskalt 079e867325 Merge pull request #30 from MegavexNetwork/renovate/gradle-gradle-build-action-3.x
chore(deps): update gradle/gradle-build-action action to v3.2.0
2024-04-06 00:31:22 +03:00
vytskalt c9a3c6b3f5 Merge pull request #29 from MegavexNetwork/renovate/io.papermc.paperweight.userdev-1.x
chore(deps): update plugin paperweight to v1.5.13
2024-04-06 00:31:12 +03:00
vytskalt f8f542e739 Merge pull request #31 from MegavexNetwork/indra-publishing
Add indra publishing plugin
2024-04-06 00:31:00 +03:00
vytskalt fca17dbda4 get it to work 2024-04-06 00:30:20 +03:00
renovate[bot] 9a77454a9e chore(deps): update gradle/gradle-build-action action to v3.2.0 2024-04-04 23:35:39 +00:00
renovate[bot] 2d30af76f3 chore(deps): update plugin paperweight to v1.5.13 2024-04-04 23:35:35 +00:00
vytskalt 59198fb117 attempt to add indra-publishing 2024-04-04 21:24:55 +03:00
vytskalt 022968489c build: use indra 2024-04-04 20:19:51 +03:00
vytskalt afd64f4fd2 release: prepare for development on 2.1.5 2024-04-04 20:19:21 +03:00
vytskalt d4c553f93f release: 2.1.4 2024-04-02 20:26:07 +03:00
vytskalt 3f37335bac fix: rare bug in rendering sidebar scores 2024-04-02 20:02:10 +03:00
vytskalt deaf231965 [ci skip] docs: fix outdated comment 2024-04-02 16:55:23 +03:00
vytskalt d15ac8c2e8 move dependencies to libs.versions.toml 2024-03-28 19:01:50 +02:00
vytskalt 8408a3e279 move shared build logic to buildSrc 2024-03-26 20:57:28 +02:00
vytskalt c2ce803f30 Merge remote-tracking branch 'origin/main' 2024-03-26 19:52:04 +02:00
vytskalt 5f89a47798 slightly refactor sidebar line logic, fixes several instances of useless packets being sent & a rare NPE 2024-03-26 19:51:45 +02:00
vytskalt 4d23bc5cfd Update README.md 2024-03-23 18:50:46 +02:00
vytskalt d26eab2110 Merge pull request #27 from MegavexNetwork/renovate/gradle-8.x
Update dependency gradle to v8.7
2024-03-22 22:09:04 +02:00
renovate[bot] c0a6ec0ec1 Update dependency gradle to v8.7 2024-03-22 19:47:38 +00:00
vytskalt 20245b870f release 2.1.3 2024-03-16 11:54:26 +02:00
vytskalt 715c28732e slight build changes 2024-03-16 11:39:38 +02:00
vytskalt 408a379bfe fix incompatibility with old paper versions 2024-03-16 11:35:58 +02:00
vytskalt caeb7286a8 fix javadocs 2024-03-16 11:35:58 +02:00
vytskalt 0452126353 don't import Unsafe directly 2024-03-16 11:35:58 +02:00
vytskalt d8d6ba3a11 Merge pull request #20 from MegavexNetwork/renovate/org.jetbrains.kotlin.jvm-1.x
Update plugin org.jetbrains.kotlin.jvm to v1.9.23
2024-03-10 16:10:24 +02:00
vytskalt 30b52722cd Merge pull request #21 from MegavexNetwork/renovate/actions-checkout-4.x
Update actions/checkout action to v4
2024-03-10 16:10:14 +02:00
vytskalt 4ad96a9a79 Merge pull request #22 from MegavexNetwork/renovate/actions-setup-java-4.x
Update actions/setup-java action to v4
2024-03-10 16:10:03 +02:00
renovate[bot] 5524dd399f Update actions/checkout action to v4 2024-03-10 14:09:54 +00:00
vytskalt 17ba876a7a Merge pull request #23 from MegavexNetwork/renovate/gradle-gradle-build-action-3.x
Update gradle/gradle-build-action action to v3
2024-03-10 16:09:49 +02:00
vytskalt 4e7b21f0ca Merge pull request #24 from MegavexNetwork/renovate/gradle-wrapper-validation-action-2.x
Update gradle/wrapper-validation-action action to v2
2024-03-10 16:09:34 +02:00
renovate[bot] 25df9e7262 Update gradle/wrapper-validation-action action to v2 2024-03-10 14:02:55 +00:00
renovate[bot] d547387245 Update gradle/gradle-build-action action to v3 2024-03-10 14:02:51 +00:00
renovate[bot] 9fc2c23a9d Update actions/setup-java action to v4 2024-03-10 14:02:47 +00:00
renovate[bot] 2f07a0e68d Update plugin org.jetbrains.kotlin.jvm to v1.9.23 2024-03-10 14:02:39 +00:00
vytskalt 78442b652c Merge pull request #19 from MegavexNetwork/renovate/junit5-monorepo
Update junit5 monorepo to v5.10.2
2024-03-10 16:01:12 +02:00
vytskalt 57e8470cc7 Merge pull request #18 from MegavexNetwork/renovate/gradle-gradle-build-action-2.x
Update gradle/gradle-build-action action to v2.12.0
2024-03-10 16:00:54 +02:00
renovate[bot] ee16d324e1 Update junit5 monorepo to v5.10.2 2024-03-10 14:00:27 +00:00
renovate[bot] b0ec7d2d97 Update gradle/gradle-build-action action to v2.12.0 2024-03-10 14:00:22 +00:00
vytskalt 1c4ec63eea Merge pull request #16 from MegavexNetwork/renovate/gradle-8.x
Update dependency gradle to v8.6
2024-03-10 15:59:59 +02:00
renovate[bot] 8ec3e1082d Update dependency gradle to v8.6 2024-03-10 13:59:27 +00:00
vytskalt bdbbf155e7 Merge pull request #15 from MegavexNetwork/renovate/adventureversion
Update adventureVersion to v4.16.0
2024-03-10 15:55:47 +02:00
renovate[bot] 40c7fe49be Update adventureVersion to v4.16.0 2024-03-10 13:54:28 +00:00
vytskalt 3a0b0c1d94 Merge pull request #14 from MegavexNetwork/renovate/configure
Configure Renovate
2024-03-10 15:54:03 +02:00
vytskalt 226f39ee27 Update renovate.json 2024-03-10 15:52:17 +02:00
renovate[bot] 8c00d11070 Add renovate.json 2024-03-10 13:47:25 +00:00
vytskalt f809416599 release 2.1.2 2024-03-09 19:27:27 +02:00
vytskalt da176b8ea4 fix setting suffix of team 2024-03-09 19:23:56 +02:00
vytskalt 77dd1b8c65 release 2.1.1 2024-03-07 16:47:13 +02:00
vytskalt 736199d58f Merge remote-tracking branch 'origin/main' 2024-03-07 16:45:56 +02:00
vytskalt c2c1977885 fix sending scores on paper servers 2024-03-07 16:45:40 +02:00
vytskalt 045b558548 Update INSTALLATION.md 2024-03-07 08:00:06 +02:00
vytskalt 34f78377d9 prepare for development on 2.1.1 2024-03-06 20:00:01 +02:00
vytskalt f2bb69bd23 Release 2.1.0 2024-03-06 19:54:13 +02:00
vytskalt e2c4b6708e more documentation changes 2024-03-06 19:49:04 +02:00
vytskalt e957539c0d use ComponentLike instead of Component in API method arguments 2024-03-06 19:39:18 +02:00
vytskalt 0f2f3ba432 how did this even happen 2024-03-06 19:14:39 +02:00
vytskalt 7cf57c471f update readme 2024-03-06 19:11:31 +02:00
vytskalt ceabb2edc8 Merge pull request #13 from MegavexNetwork/1.20.4-features
Support new 1.20.4 features
2024-03-06 19:07:02 +02:00
vytskalt 7d12c7aa5d update readme 2024-03-06 19:05:13 +02:00
vytskalt c3f398ae88 finish up 2024-03-06 19:03:14 +02:00
vytskalt ebbb902343 Add score display and score format api 2024-03-06 16:47:44 +02:00
vytskalt ee418c87fd Add default objective score format api 2024-03-05 20:41:35 +02:00
vytskalt fcd8b81a8f Add sidebar line custom score format api 2024-03-05 20:05:01 +02:00
vytskalt fbe3d1c6cf Add 1.20.4 features to packet adapters 2024-03-04 18:25:06 +02:00
vytskalt ab7f574c14 Pass color parameter to team packet in 1.8 packet adapter 2024-03-04 16:44:27 +02:00
vytskalt b2c756c2cb More documentation changes 2024-03-04 16:43:11 +02:00
vytskalt 8b930bfe13 Properly document most API classes 2024-03-03 19:46:17 +02:00
vytskalt 07d868e180 update installation.md 2024-01-23 17:54:39 +02:00
vytskalt b98e1f007b Release 2.0.2 2024-01-23 17:54:00 +02:00
vytskalt 563f132528 Fix several bugs and slightly refactor 2024-01-20 18:47:15 +02:00
vytskalt eccc236d5d [ci skip] Fix broken link 2024-01-16 22:13:11 +02:00
vytskalt cc73eea58d Log warning if packet adapter isn't found in example 2024-01-16 21:57:35 +02:00
vytskalt 8b88a58668 Release 2.0.1 2023-12-09 21:55:18 +02:00
vytskalt c2c30b33d7 Add 1.20.4 support 2023-12-09 21:49:10 +02:00
vytskalt 3a497f7e0e release 2.0.0 2023-11-04 20:00:47 +02:00
vytskalt f5ff418eff remove unnecessary checks for old value
fixes some issues with 1.20.1
2023-10-29 09:44:07 +02:00
vytskalt eadf470946 refactor packet adapter internals 2023-10-08 19:22:50 +03:00
vytskalt 409d95b9a3 update objective example 2023-10-08 12:51:18 +03:00
vytskalt 83f44eca4a Merge remote-tracking branch 'origin/main' 2023-10-08 12:46:47 +03:00
vytskalt 67a8f209b0 release 2.0.0-RC14 2023-10-08 12:46:41 +03:00
vytskalt 07d8a377ae Merge pull request #5 from MegavexNetwork/feat/objectives
Objectives support
2023-10-08 12:44:26 +03:00
vytskalt 3bf265fe53 didn't know colored sidebars were added in 1.8 2023-10-08 11:36:12 +03:00
vytskalt b747d4bfeb make sidebars reuse the objective packet adapter internally 2023-10-07 21:27:32 +03:00
vytskalt fe96150add fix duplicate create packet 2023-10-07 21:08:17 +03:00
vytskalt 541c349f41 fix minor bug with packetevents adapter 2023-10-07 20:10:45 +03:00
vytskalt 7817ce08df implement packetevents adapter 2023-10-07 19:55:21 +03:00
vytskalt 087d086945 update readme 2023-10-07 15:01:51 +03:00
vytskalt feff32dc05 implement 1.8.8 2023-10-07 14:24:31 +03:00
vytskalt d9db488aca random fixes 2023-10-06 23:36:43 +03:00
vytskalt a2cdd2a7ae finish implementation hopefully 2023-10-05 21:11:19 +03:00
vytskalt 1536aacb0a add javadocs 2023-10-04 22:50:41 +03:00
vytskalt db9995dc47 displayName -> value 2023-10-04 22:11:02 +03:00
vytskalt de2d02b41f progress on implementation 2023-10-04 22:03:23 +03:00
vytskalt 710612bbc7 noop implementation 2023-10-03 22:22:22 +03:00
vytskalt 3d4ccd9d2c most of the boilerplate 2023-10-03 22:05:49 +03:00
vytskalt fcca0b0507 initial work on objective api 2023-10-01 17:52:35 +03:00
vytskalt cc6eae07ee Release 2.0.0-RC13 2023-09-24 17:11:21 +03:00
vytskalt 73dd4033d4 Add 1.20.2 support and prepare for CraftBukkit internal changes 2023-09-24 17:10:50 +03:00
vytskalt 61c59406cb default player color to white for packetevents adapter 2023-09-15 16:00:29 +03:00
vytskalt 74190b3e14 Merge remote-tracking branch 'origin/main' 2023-09-07 22:10:52 +03:00
vytskalt 8712a3a435 use packetevents 2.0.0 release 2023-09-07 22:10:23 +03:00
vytskalt 78c3ee78cf Update INSTALLATION.md 2023-09-07 21:38:51 +03:00
vytskalt daa32239b9 allow for more lines in sidebars than vanilla limit 2023-09-04 17:49:24 +03:00
vytskalt bab2448922 use java 17 for CI 2023-09-03 22:44:46 +03:00
vytskalt f82a3d2797 use java 11 for CI 2023-09-03 22:38:42 +03:00
vytskalt e744372afc add CI 2023-09-03 22:34:49 +03:00
vytskalt 36e63b4b54 add example how to load packetevents to installation 2023-08-21 00:20:24 +03:00
vytskalt 1c471be8a2 update installation 2023-08-21 00:00:31 +03:00
vytskalt a5f7cbaeec optimise imports 2023-08-20 23:27:43 +03:00
vytskalt b45a5d5f2b hopefully fix errors with relocated adventure 2023-08-20 23:26:18 +03:00
vytskalt 7388ce5cb0 release RC12 2023-08-20 22:42:18 +03:00
vytskalt 7991216122 try making modern adapter target java 8 2023-08-20 22:28:42 +03:00
vytskalt b4d8394992 refactor packet adapters 2023-07-31 12:15:42 +03:00
vytskalt 3ec2f4abf7 english 2023-07-29 11:30:22 +03:00
vytskalt 8d1c391c66 release 2.0.0-rc11 2023-07-29 11:28:51 +03:00
vytskalt 7dadc970ca consistency 2023-07-29 11:28:07 +03:00
vytskalt e4aab20c2f intellij moment 2 2023-07-29 11:25:28 +03:00
vytskalt 7954038d75 intellij moment 2023-07-29 11:23:03 +03:00
vytskalt 9b1e21e4f7 support 1.17+ 2023-07-29 11:17:45 +03:00
167 changed files with 6448 additions and 2771 deletions

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
name: CI
on:
pull_request:
push:
branches:
- main
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: cachix/install-nix-action@v31
- name: Cache Gradle
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: ./gradlew build
run: nix develop .#jdk25 -i -c sh -c "./gradlew build"
+9 -1
View File
@@ -2,4 +2,12 @@
*.iml
.gradle
build/
build/
.classpath
.project
.settings/
bin/
test-env/servers
test-env/cache
+25 -76
View File
@@ -1,111 +1,60 @@
# Installation
Latest version: `2.0.0-RC9`
Latest version: `2.7.0`
## Gradle
Make sure you have the Jitpack repository:
```kotlin
repositories {
maven("https://jitpack.io/")
mavenCentral()
}
```
Then add the dependencies:
```kotlin
dependencies {
val scoreboardLibraryVersion = "{VERSION HERE}"
implementation("com.github.megavexnetwork.scoreboard-library:scoreboard-library-api:$scoreboardLibraryVersion")
runtimeOnly("com.github.megavexnetwork.scoreboard-library:scoreboard-library-implementation:$scoreboardLibraryVersion")
implementation("com.github.megavexnetwork.scoreboard-library:scoreboard-library-extra-kotlin:$scoreboardLibraryVersion") // If using Kotlin
implementation("net.megavex:scoreboard-library-api:2.7.0")
runtimeOnly("net.megavex:scoreboard-library-implementation:2.7.0")
// Add packet adapter implementations you want:
runtimeOnly("com.github.megavexnetwork.scoreboard-library:scoreboard-library-v1_8_R3:$scoreboardLibraryVersion")
runtimeOnly("com.github.megavexnetwork.scoreboard-library:scoreboard-library-v1_20_R1:$scoreboardLibraryVersion")
runtimeOnly("com.github.megavexnetwork.scoreboard-library:scoreboard-library-packetevents:$scoreboardLibraryVersion")
// If using the PacketEvents implementation, scoreboard-library expects PacketEvents to be in the classpath.
// Follow either of:
// - https://github.com/retrooper/packetevents/wiki/Depending-on-pre-built-PacketEvents
// - https://github.com/retrooper/packetevents/wiki/Shading-PacketEvents
// If using the 1.8 version implementation, add Adventure as well:
implementation("net.kyori:adventure-platform-bukkit:4.0.1")
// If targeting a server version without native Adventure support, add it as well:
implementation("net.kyori:adventure-platform-bukkit:4.4.1")
}
```
You will need to shade these dependencies and relocate them with something
like [Shadow](https://imperceptiblethoughts.com/shadow/).
like [Shadow](https://gradleup.com/shadow/).
## Maven
Make sure you have the Jitpack repository:
```xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
```
Then add the dependencies:
```xml
<dependencies>
<dependency>
<groupId>com.github.megavexnetwork.scoreboard-library</groupId>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-api</artifactId>
<version>{VERSION HERE}</version>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.github.megavexnetwork.scoreboard-library</groupId>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-implementation</artifactId>
<version>{VERSION HERE}</version>
<scope>runtime</scope>
</dependency>
<!-- If using Kotlin: -->
<dependency>
<groupId>com.github.megavexnetwork.scoreboard-library</groupId>
<artifactId>scoreboard-library-extra-kotlin</artifactId>
<version>{VERSION HERE}</version>
</dependency>
<!-- Add packet adapter implementations you want: -->
<dependency>
<groupId>com.github.megavexnetwork.scoreboard-library</groupId>
<artifactId>scoreboard-library-v1_8_R3</artifactId>
<version>{VERSION HERE}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.megavexnetwork.scoreboard-library</groupId>
<artifactId>scoreboard-library-v1_20_R1</artifactId>
<version>{VERSION HERE}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.github.megavexnetwork.scoreboard-library</groupId>
<artifactId>scoreboard-library-packetevents</artifactId>
<version>{VERSION HERE}</version>
<version>2.7.0</version>
<scope>runtime</scope>
</dependency>
<!--
If using the PacketEvents implementation, scoreboard-library expects PacketEvents to be in the classpath.
Follow either of:
- https://github.com/retrooper/packetevents/wiki/Depending-on-pre-built-PacketEvents
- https://github.com/retrooper/packetevents/wiki/Shading-PacketEvents -->
<!-- If using the 1.8 version implementation, add Adventure as well: -->
<!-- If targeting a server version without native Adventure support, add it as well: -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bukkit</artifactId>
<version>4.0.1</version>
<version>4.4.1</version>
</dependency>
</dependencies>
```
You will need to shade these dependencies and relocate them with something
like [maven-shade-plugin](https://maven.apache.org/plugins/maven-shade-plugin/).
You will need to shade these dependencies and relocate them with [maven-shade-plugin](https://maven.apache.org/plugins/maven-shade-plugin/).
## ViaVersion awareness
For [ViaVersion awareness](https://github.com/vytskalt/scoreboard-library?tab=readme-ov-file#features) to be enabled, make sure to add `ViaVersion`
to `softdepend` or `depend` of your `plugin.yml`:
```yaml
softdepend: ["ViaVersion"]
# or
depend: ["ViaVersion"]
```
+49 -21
View File
@@ -1,29 +1,31 @@
# scoreboard-library
Powerful scoreboard library for Minecraft Paper/Spigot servers using the [adventure](https://github.com/KyoriPowered/adventure)
Powerful scoreboard library for Minecraft Paper/Spigot servers using
the [adventure](https://github.com/KyoriPowered/adventure)
component library
Join the [Discord](https://discord.gg/v7nmTDTW8W) or create an issue for support
## Features
- Sidebars. Up to 42 characters (depends on the formatting) for 1.12.2 and below, no limit for newer versions
- Teams. Supports showing different properties (display name, prefix, entries etc.) of the same team to different players
- Doesn't require extra dependencies (assuming you're targetting the latest version of Paper)
- Packet-level, meaning it works with other scoreboard plugins (and is faster)
- Sidebars. Up to 42 characters (depends on the formatting) for 1.12.2-, no limit for newer versions
- Teams. Supports showing different properties (display name, prefix, entries etc.) of the same team to different
players
- Objectives.
- Full support for new 1.20.4 features (score display names, custom score formats)
- No extra dependencies (assuming you're targeting modern versions of Paper)
- Packet-level, meaning it works with other scoreboard plugins
- Supports [Folia](https://github.com/PaperMC/Folia)
- Fully async. All packet work is done asynchronously so you can (but don't have to) use the library from the main
- Fully async. All packet work is done asynchronously, so you can use the library from the main
thread without sacrificing any performance
- Works with `TranslatableComponent`s, meaning all components are automatically translated using `GlobalTranslator` for
each players client locale (and automatically update whenever the player changes it in their settings)
- Automatically works with `TranslatableComponent`s. All components are translated using `GlobalTranslator` for
each player's client locale and automatically update whenever the player changes it in their settings
- ViaVersion awareness: automatically detects 1.12.2- on 1.13+ servers connected through ViaVersion and sends them
the optimal packets to maximise sidebar line lengths
## Packet Adapters
## Supported server versions
- **1.20.** Takes advantage of [Paper](https://papermc.io)s native adventure support to improve performance.
[Spigot](https://www.spigotmc.org/) is also supported, but will have worse performance
- **1.8.8.**
- **PacketEvents.** Requires [PacketEvents 2.0](https://github.com/retrooper/packetevents/tree/2.0) to be loaded in the
classpath. Should work on all versions 1.8+
All Paper and Spigot versions from 1.7.10 to 26.1 are natively supported.
## Installation
@@ -36,8 +38,9 @@ ScoreboardLibrary scoreboardLibrary;
try {
scoreboardLibrary = ScoreboardLibrary.loadScoreboardLibrary(plugin);
} catch (NoPacketAdapterAvailableException e) {
// If no packet adapter was found, you can fallback to the no-op implementation:
// If server version is not yet supported, you can fallback to the no-op implementation:
scoreboardLibrary = new NoopScoreboardLibrary();
plugin.getLogger().warning("Server version unsupported, scoreboard functionality will not be visible!");
}
// On plugin shutdown:
@@ -51,7 +54,6 @@ synchronisation to make sure you're using them from only one thread at a time.
### Sidebar (low-level)
```java
Sidebar sidebar = scoreboardLibrary.createSidebar();
@@ -64,25 +66,29 @@ sidebar.line(3, Component.text("epicserver.net"));
sidebar.addPlayer(player); // Add the player to the sidebar
// After you've finished using the Sidebar, make sure to close it to prevent a memory leak:
sidebar.close();
// Don't forget to call sidebar.close() once you don't need it anymore!
```
### Component sidebars
Component sidebars are an abstraction over the low-level `Sidebar`. They allow you to design sidebars in a clean way.
Here's how you create a `SidebarComponent` with a static line:
```java
SidebarComponent staticLine = SidebarComponent.staticLine(Component.text("A static line"));
```
You can chain multiple `SidebarComponent`s together using `SidebarComponent.builder()`:
```java
SidebarComponent lines = SidebarComponent.builder()
.addComponent(SidebarComponent.staticLine(Component.text("A static line")))
.addStaticLine(Component.text("Another static line")) // Shorthand for line above
.build();
```
To use these components, create a `ComponentSidebarLayout`:
```java
ComponentSidebarLayout layout = new ComponentSidebarLayout(
SidebarComponent.staticLine(Component.text("Sidebar Title")),
@@ -95,7 +101,9 @@ Sidebar sidebar = scoreboardLibrary.createSidebar();
// Do this every time the title or any line needs to be updated
layout.apply(sidebar);
```
You can make animations with `SidebarAnimation`:
```java
Component lineComponent = Component.text("Line that changes colors");
Set<NamedTextColor> colors = NamedTextColor.NAMES.values();
@@ -112,7 +120,9 @@ SidebarComponent line = SidebarComponent.animatedLine(animation);
// Advance to the next frame of the animation
animation.nextFrame();
```
Animations can be used for pagination:
```java
Player player = ...;
@@ -126,13 +136,15 @@ SidebarComponent secondPage = SidebarComponent.builder()
.addDynamicLine(() -> Component.text("Health: " + player.getHealth()))
.build();
List<SidebarComponent> pages = List.of(firstPage, secondPage);
List<SidebarComponent> pages = Arrays.asList(firstPage, secondPage);
SidebarAnimation<SidebarComponent> pageAnimation = new CollectionSidebarAnimation<>(pages);
SidebarComponent paginatedComponent = SidebarComponent.animatedComponent(pageAnimation);
// ...
```
You can also create your own `SidebarComponent`s:
```java
public class KeyValueSidebarComponent implements SidebarComponent {
private final Component key;
@@ -156,7 +168,9 @@ public class KeyValueSidebarComponent implements SidebarComponent {
}
}
```
Here's a full sidebar example:
```java
public class ExampleComponentSidebar {
private final Sidebar sidebar;
@@ -218,6 +232,7 @@ public class ExampleComponentSidebar {
}
}
```
![Sidebar example](./assets/sidebar.gif)`
### TeamManager
@@ -246,6 +261,19 @@ newTeamDisplay.displayName(Component.text("Other Team Display Name"));
// Change the TeamDisplay a player sees like this:
team.display(player, newTeamDisplay);
// After you've finished using the TeamManager, make sure to close it to prevent a memory leak:
teamManager.close();
// Don't forget to call teamManager.close() once you don't need it anymore!
```
### ObjectiveManager
```java
ObjectiveManager objectiveManager = scoreboardLibrary.createObjectiveManager();
ScoreboardObjective objective = objectiveManager.create("coolobjective");
objective.value(Component.text("Display name"));
objective.score(player.getName(), 69420);
objectiveManager.display(ObjectiveDisplaySlot.belowName(), objective);
objectiveManager.addPlayer(player); // Make a player see the objectives
// Don't forget to call objectiveManager.close() once you don't need it anymore!
```
+3 -3
View File
@@ -1,8 +1,8 @@
plugins {
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3")
compileOnlyApi(libs.bundles.adventure)
compileOnly(libs.spigotApi)
}
@@ -1,29 +1,33 @@
package net.megavex.scoreboardlibrary.api;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import net.megavex.scoreboardlibrary.api.exception.NoPacketAdapterAvailableException;
import net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import net.megavex.scoreboardlibrary.api.team.TeamManager;
import org.apache.commons.lang.RandomStringUtils;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
/**
* Allows creating {@link Sidebar}s and {@link TeamManager}s.
* Each plugin should have it's own instance of this interface.
* Note: this class is thread-safe.
* Entrypoint of the library. To create an instance of this interface, use {@link #loadScoreboardLibrary}.
* For unit testing, take a look at {@link NoopScoreboardLibrary}.
* Note: this class is thread-safe, meaning you can safely use it from multiple threads at a time.
*/
@ApiStatus.NonExtendable
public interface ScoreboardLibrary {
/**
* Creates an instance of {@link ScoreboardLibrary}.
*
* @param plugin The plugin that owns this instance
* @return A new instance of {@link ScoreboardLibrary}
* @throws NoPacketAdapterAvailableException if there is no packet adapter available in the classpath
* @param plugin your plugin instance
* @throws NoPacketAdapterAvailableException if server version is not supported
*/
static @NotNull ScoreboardLibrary loadScoreboardLibrary(@NotNull Plugin plugin) throws NoPacketAdapterAvailableException {
Class<?> implClass;
@@ -48,49 +52,72 @@ public interface ScoreboardLibrary {
}
/**
* Creates a {@link Sidebar}.
* Creates a new {@link Sidebar};
*
* @return Sidebar
* @return newly created sidebar
*/
default @NotNull Sidebar createSidebar() {
return createSidebar(Sidebar.MAX_LINES, null);
return createSidebar(Sidebar.MAX_LINES);
}
/**
* Creates a {@link Sidebar}.
* Creates a new {@link Sidebar}.
*
* @param maxLines Max amount of lines the sidebar will have
* @return Sidebar
* @param maxLines max amount of lines the sidebar will have.
* Note that vanilla clients can only display at most {@value Sidebar#MAX_LINES}
* @return newly created sidebar
*/
default @NotNull Sidebar createSidebar(@Range(from = 1, to = Sidebar.MAX_LINES) int maxLines) {
default @NotNull Sidebar createSidebar(@Range(from = 1, to = Integer.MAX_VALUE) int maxLines) {
return createSidebar(maxLines, null);
}
/**
* Creates a {@link Sidebar}.
* Creates a new {@link Sidebar}.
*
* @param maxLines Max amount of lines the sidebar will have
* @param locale Locale which will be used for translating {@link net.kyori.adventure.text.TranslatableComponent}s
* @param maxLines max amount of lines the sidebar will have.
* Note that vanilla clients can only display at most {@value Sidebar#MAX_LINES}
* @param locale locale which will be used for translating {@link net.kyori.adventure.text.TranslatableComponent}s,
* or null if the locale should depend on the player
* @return Sidebar
* @return newly created sidebar
*/
@NotNull Sidebar createSidebar(@Range(from = 1, to = Sidebar.MAX_LINES) int maxLines, @Nullable Locale locale);
default @NotNull Sidebar createSidebar(@Range(from = 1, to = Integer.MAX_VALUE) int maxLines, @Nullable Locale locale) {
return createSidebar(maxLines, locale, RandomStringUtils.randomAlphanumeric(16));
}
/**
* Creates a {@link TeamManager}.
* Creates a new {@link Sidebar}.
*
* @return TeamManager
* @param maxLines max amount of lines the sidebar will have.
* Note that vanilla clients can only display at most {@value Sidebar#MAX_LINES}
* @param locale locale which will be used for translating {@link net.kyori.adventure.text.TranslatableComponent}s,
* or null if the locale should depend on the player
* @param objectiveName objective name the sidebar should use
* @return newly created sidebar
*/
@NotNull Sidebar createSidebar(@Range(from = 1, to = Integer.MAX_VALUE) int maxLines, @Nullable Locale locale, @NotNull String objectiveName);
/**
* Creates a new {@link TeamManager}.
*
* @return newly created team manager
*/
@NotNull TeamManager createTeamManager();
/**
* Closes this scoreboard-library instance.
* Should always be called when the plugin is disabled
* Creates a new {@link ObjectiveManager}.
*
* @return newly created objective manager
*/
@NotNull ObjectiveManager createObjectiveManager();
/**
* Closes this scoreboard library instance.
* You should call this in {@link JavaPlugin#onDisable()}.
*/
void close();
/**
* @return Whether this scoreboard-library instance is closed
* @return whether this scoreboard library instance is closed
*/
boolean closed();
}
@@ -1,5 +1,9 @@
package net.megavex.scoreboardlibrary.api.exception;
/**
* Exception indicating that there is no packet adapter available for the current server version.
* As a fallback, consider using the {@link net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary} implementation.
*/
public final class NoPacketAdapterAvailableException extends Exception {
public NoPacketAdapterAvailableException() {
}
@@ -0,0 +1,61 @@
package net.megavex.scoreboardlibrary.api.noop;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager;
import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
class NoopObjectiveManager implements ObjectiveManager {
private final Map<String, ScoreboardObjective> objectives = new HashMap<>();
private final Set<Player> players = new HashSet<>();
private boolean isClosed = true;
@Override
public @NotNull ScoreboardObjective create(@NotNull String name) {
return objectives.computeIfAbsent(name, i -> new NoopScoreboardObjective());
}
@Override
public void remove(@NotNull ScoreboardObjective objective) {
objectives.values().remove(objective);
}
@Override
public void display(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjective objective) {
}
@Override
public @NotNull Collection<Player> players() {
return Collections.unmodifiableSet(players);
}
@Override
public boolean addPlayer(@NotNull Player player) {
return players.add(player);
}
@Override
public boolean removePlayer(@NotNull Player player) {
return players.remove(player);
}
@Override
public CompletableFuture<Void> removePlayerFuture(@NotNull Player player) {
players.remove(player);
return CompletableFuture.completedFuture(null);
}
@Override
public void close() {
isClosed = true;
}
@Override
public boolean closed() {
return isClosed;
}
}
@@ -1,24 +1,27 @@
package net.megavex.scoreboardlibrary.api.noop;
import java.util.Locale;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import net.megavex.scoreboardlibrary.api.team.TeamManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import java.util.Locale;
/**
* no-op implementation of ScoreboardLibrary.
* No-op implementation of {@link ScoreboardLibrary}.
* Can be used as a fallback when there is no packet adapter available
* and for unit testing.
*/
public final class NoopScoreboardLibrary implements ScoreboardLibrary {
private boolean closed = false;
@Override
public @NotNull Sidebar createSidebar(@Range(from = 1, to = Sidebar.MAX_LINES) int maxLines, @Nullable Locale locale) {
public @NotNull Sidebar createSidebar(@Range(from = 1, to = Integer.MAX_VALUE) int maxLines, @Nullable Locale locale, @NotNull String objectiveName) {
checkClosed();
return new NoopSidebar(maxLines, locale);
return new NoopSidebar(maxLines, objectiveName, locale);
}
@Override
@@ -27,6 +30,12 @@ public final class NoopScoreboardLibrary implements ScoreboardLibrary {
return new NoopTeamManager();
}
@Override
public @NotNull ObjectiveManager createObjectiveManager() {
checkClosed();
return new NoopObjectiveManager();
}
@Override
public void close() {
closed = true;
@@ -0,0 +1,79 @@
package net.megavex.scoreboardlibrary.api.noop;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
import static net.kyori.adventure.text.Component.empty;
public class NoopScoreboardObjective implements ScoreboardObjective {
private final Map<String, ObjectiveScore> scores = new HashMap<>();
private Component value = empty();
private ObjectiveRenderType renderType;
private ScoreFormat defaultScoreFormat;
@Override
public @NotNull Component value() {
return value;
}
@Override
public @NotNull ScoreboardObjective value(@NotNull ComponentLike value) {
this.value = value.asComponent();
return this;
}
@Override
public @NotNull ObjectiveRenderType renderType() {
return renderType;
}
@Override
public @NotNull ScoreboardObjective renderType(@NotNull ObjectiveRenderType renderType) {
this.renderType = renderType;
return this;
}
@Override
public @Nullable ScoreFormat defaultScoreFormat() {
return defaultScoreFormat;
}
@Override
public void defaultScoreFormat(@Nullable ScoreFormat defaultScoreFormat) {
this.defaultScoreFormat = defaultScoreFormat;
}
@Override
public void refreshProperties() {
}
@Override
public @Nullable ObjectiveScore scoreInfo(@NotNull String entry) {
return scores.get(entry);
}
@Override
public @NotNull ScoreboardObjective score(@NotNull String entry, ObjectiveScore score) {
scores.put(entry, score);
return this;
}
@Override
public @NotNull ScoreboardObjective removeScore(@NotNull String entry) {
scores.remove(entry);
return this;
}
@Override
public void refreshScore(@NotNull String entry) {
}
}
@@ -1,15 +1,16 @@
package net.megavex.scoreboardlibrary.api.noop;
import com.google.common.base.Preconditions;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
import net.megavex.scoreboardlibrary.api.team.TeamManager;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
class NoopScoreboardTeam implements ScoreboardTeam {
private final NoopTeamManager teamManager;
private final String name;
@@ -1,33 +1,32 @@
package net.megavex.scoreboardlibrary.api.noop;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import net.megavex.scoreboardlibrary.api.util.SidebarUtilities;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import java.util.*;
import static net.kyori.adventure.text.Component.empty;
class NoopSidebar implements Sidebar {
private final Set<Player> players = new HashSet<>();
private final int maxLines;
private final String objectiveName;
private final Locale locale;
private final Component[] lines;
private Component title = empty();
private boolean closed;
NoopSidebar(int maxLines, @Nullable Locale locale) {
NoopSidebar(int maxLines, String objectiveName, @Nullable Locale locale) {
this.maxLines = maxLines;
this.locale = locale;
this.objectiveName = objectiveName;
this.lines = new Component[maxLines];
}
@@ -43,7 +42,7 @@ class NoopSidebar implements Sidebar {
@Override
public @NotNull Collection<Player> players() {
return closed ? Set.of() : Collections.unmodifiableSet(players);
return closed ? Collections.emptySet() : Collections.unmodifiableSet(players);
}
@Override
@@ -65,21 +64,30 @@ class NoopSidebar implements Sidebar {
return maxLines;
}
@Override
public @NotNull String objectiveName() {
return objectiveName;
}
@Override
public @Nullable Locale locale() {
return locale;
}
@Override
public void line(int line, @Nullable Component value) {
SidebarUtilities.checkLineBounds(maxLines, line);
public void line(int index, @Nullable ComponentLike value, @Nullable ScoreFormat scoreFormat) {
checkLineBounds(index);
checkClosed();
lines[line] = value;
lines[index] = value == null ? null : value.asComponent();
}
@Override
public void refreshLine(@Range(from = 0, to = Integer.MAX_VALUE - 1) final int index) {
}
@Override
public @Nullable Component line(int line) {
SidebarUtilities.checkLineBounds(maxLines, line);
checkLineBounds(line);
checkClosed();
return lines[line];
}
@@ -90,10 +98,15 @@ class NoopSidebar implements Sidebar {
}
@Override
public void title(@NotNull Component title) {
public void title(@NotNull ComponentLike title) {
Preconditions.checkNotNull(title);
checkClosed();
this.title = title;
this.title = title.asComponent();
}
@Override
public void refreshTitle() {
}
private void checkClosed() {
@@ -101,4 +114,10 @@ class NoopSidebar implements Sidebar {
throw new IllegalStateException("NoopSidebar is closed");
}
}
private void checkLineBounds(int line) {
if (line >= maxLines || line < 0) {
throw new IndexOutOfBoundsException("invalid line " + line);
}
}
}
@@ -1,10 +1,7 @@
package net.megavex.scoreboardlibrary.api.noop;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
@@ -13,6 +10,10 @@ import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static net.kyori.adventure.text.Component.empty;
@@ -47,19 +48,29 @@ class NoopTeamDisplay implements TeamDisplay {
return entries.add(entry);
}
@Override
public boolean addEntries(@NotNull final Collection<String> entries) {
return this.entries.addAll(entries);
}
@Override
public boolean removeEntry(@NotNull String entry) {
return entries.remove(entry);
}
@Override
public boolean removeEntries(@NotNull final Collection<String> entries) {
return this.entries.removeAll(entries);
}
@Override
public @NotNull Component displayName() {
return displayName;
}
@Override
public @NotNull TeamDisplay displayName(@NotNull Component displayName) {
this.displayName = displayName;
public @NotNull TeamDisplay displayName(@NotNull ComponentLike displayName) {
this.displayName = displayName.asComponent();
return this;
}
@@ -69,8 +80,8 @@ class NoopTeamDisplay implements TeamDisplay {
}
@Override
public @NotNull TeamDisplay prefix(@NotNull Component prefix) {
this.prefix = prefix;
public @NotNull TeamDisplay prefix(@NotNull ComponentLike prefix) {
this.prefix = prefix.asComponent();
return this;
}
@@ -80,8 +91,8 @@ class NoopTeamDisplay implements TeamDisplay {
}
@Override
public @NotNull TeamDisplay suffix(@NotNull Component suffix) {
this.suffix = suffix;
public @NotNull TeamDisplay suffix(@NotNull ComponentLike suffix) {
this.suffix = suffix.asComponent();
return this;
}
@@ -139,4 +150,8 @@ class NoopTeamDisplay implements TeamDisplay {
this.playerColor = playerColor;
return this;
}
@Override
public void refresh() {
}
}
@@ -1,14 +1,6 @@
package net.megavex.scoreboardlibrary.api.noop;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
import net.megavex.scoreboardlibrary.api.team.TeamManager;
@@ -16,6 +8,11 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
class NoopTeamManager implements TeamManager {
private final Set<Player> players = new HashSet<>();
private final Map<String, NoopScoreboardTeam> teams = new HashMap<>();
@@ -52,6 +49,12 @@ class NoopTeamManager implements TeamManager {
return true;
}
@Override
public CompletableFuture<Void> removePlayerFuture(@NotNull Player player) {
this.removePlayer(player);
return CompletableFuture.completedFuture(null);
}
@Override
public @NotNull Collection<ScoreboardTeam> teams() {
return closed ? Collections.emptySet() : Collections.unmodifiableCollection(teams.values());
@@ -84,6 +87,7 @@ class NoopTeamManager implements TeamManager {
validateTeamDisplay(team, teamDisplay);
team.displayMap().put(player, teamDisplay);
}
teams.put(name, team);
return team;
}
@@ -0,0 +1,100 @@
package net.megavex.scoreboardlibrary.api.objective;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.format.NamedTextColor;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* Represents all valid display slots that objectives can be shown in.
*
* @see <a href="https://minecraft.wiki/w/Scoreboard#Display_slots">Minecraft Wiki</a>
*/
@ApiStatus.NonExtendable
public interface ObjectiveDisplaySlot {
static @NotNull PlayerList playerList() {
return PlayerList.INSTANCE;
}
static @NotNull Sidebar sidebar() {
return Sidebar.INSTANCE;
}
static @NotNull BelowName belowName() {
return BelowName.INSTANCE;
}
/**
* @since Minecraft 1.8
*/
static @NotNull TeamSidebar teamSidebar(@NotNull NamedTextColor teamColor) {
Preconditions.checkNotNull(teamColor);
return new TeamSidebar(teamColor);
}
class PlayerList implements ObjectiveDisplaySlot {
private static final PlayerList INSTANCE = new PlayerList();
private PlayerList() {
}
@Override
public String toString() {
return "PlayerList";
}
}
class Sidebar implements ObjectiveDisplaySlot {
private static final Sidebar INSTANCE = new Sidebar();
private Sidebar() {
}
@Override
public String toString() {
return "Sidebar";
}
}
class BelowName implements ObjectiveDisplaySlot {
private static final BelowName INSTANCE = new BelowName();
private BelowName() {
}
@Override
public String toString() {
return "BelowName";
}
}
class TeamSidebar implements ObjectiveDisplaySlot {
private final NamedTextColor teamColor;
private TeamSidebar(@NotNull NamedTextColor teamColor) {
this.teamColor = teamColor;
}
public @NotNull NamedTextColor teamColor() {
return teamColor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TeamSidebar that = (TeamSidebar) o;
return teamColor.equals(that.teamColor);
}
@Override
public int hashCode() {
return teamColor.hashCode();
}
@Override
public String toString() {
return "TeamSidebar{teamColor=" + teamColor + "}";
}
}
}
@@ -0,0 +1,108 @@
package net.megavex.scoreboardlibrary.api.objective;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
/**
* Represents a group of {@link ScoreboardObjective}s.
* To get an instance of this interface, use {@link ScoreboardLibrary#createObjectiveManager()}
* Note: this interface is not thread-safe, meaning you can only use it from one thread at a time,
* although it does not have to be the main thread.
*/
public interface ObjectiveManager {
/**
* Creates an objective with a name if one doesn't already exist and returns it.
*
* @param name name of the objective
* @return objective
*/
@NotNull ScoreboardObjective create(@NotNull String name);
/**
* Removes an objective.
*
* @param objective objective to remove
*/
void remove(@NotNull ScoreboardObjective objective);
/**
* Updates which objective is shown in a display slot.
*
* @param displaySlot display slot value to show objective in
* @param objective objective to display in that display slot
*/
void display(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjective objective);
/**
* @return unmodifiable collection of viewers in this ObjectiveManager
* @see #addPlayer
* @see #removePlayer
*/
@NotNull Collection<Player> players();
/**
* Adds a viewer to this ObjectiveManager.
* Note that a player can only see a single ObjectiveManager at a time.
* The ObjectiveManager will internally be added to a queue for this player who
* will start seeing it once they are removed from all previous ObjectiveManagers.
*
* @param player player to add
* @return whether the player was added
*/
boolean addPlayer(@NotNull Player player);
/**
* Adds multiple viewers to this ObjectiveManager.
*
* @param players viewers to add
* @see #addPlayer
*/
default void addPlayers(@NotNull Collection<Player> players) {
for (Player player : players) {
addPlayer(player);
}
}
/**
* Removes a viewer from this ObjectiveManager.
*
* @param player viewer to remove
* @return whether the viewer was removed
*/
boolean removePlayer(@NotNull Player player);
/**
* Removes multiple viewers from this ObjectiveManager
*
* @param players viewers to remove
*/
default void removePlayers(@NotNull Collection<Player> players) {
for (Player player : players) {
removePlayer(player);
}
}
/**
* Removes a viewer from this ObjectiveManager.
*
* @param player viewer to remove
* @return future that will be completed once all removal packets have been sent in an async task
*/
CompletableFuture<Void> removePlayerFuture(@NotNull Player player);
/**
* Closes this ObjectiveManager.
* This must be called once you no longer need this ObjectiveManager to prevent a memory leak.
*/
void close();
/**
* @return whether this ObjectiveManager is closed
* @see #close
*/
boolean closed();
}
@@ -0,0 +1,10 @@
package net.megavex.scoreboardlibrary.api.objective;
/**
* Represents all possible render types of an objective.
* The render type represents the way scores are displayed.
*/
public enum ObjectiveRenderType {
INTEGER,
HEARTS
}
@@ -0,0 +1,78 @@
package net.megavex.scoreboardlibrary.api.objective;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
/**
* Represents an objective score.
*/
public final class ObjectiveScore {
private final int value;
private final Component displayName;
private final ScoreFormat format;
public ObjectiveScore(int value, @Nullable ComponentLike displayName, @Nullable ScoreFormat format) {
this.value = value;
this.displayName = displayName == null ? null : displayName.asComponent();
this.format = format;
}
/**
* @return score value
*/
public int value() {
return value;
}
/**
* Gets the score display.
*
* @return score display
* @since Minecraft 1.20.3
*/
public @Nullable Component displayName() {
return displayName;
}
/**
* Gets the score format, which determines how the scores are rendered in clients.
*
* @return score format
* @since Minecraft 1.20.3
*/
public @Nullable ScoreFormat format() {
return format;
}
@Override
public String toString() {
return "ObjectiveScore{" +
"value=" + value +
", displayName=" + displayName +
", format=" + format +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ObjectiveScore that = (ObjectiveScore) o;
if (value != that.value) return false;
if (!Objects.equals(displayName, that.displayName)) return false;
return Objects.equals(format, that.format);
}
@Override
public int hashCode() {
int result = value;
result = 31 * result + (displayName != null ? displayName.hashCode() : 0);
result = 31 * result + (format != null ? format.hashCode() : 0);
return result;
}
}
@@ -0,0 +1,110 @@
package net.megavex.scoreboardlibrary.api.objective;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.StyleBuilderApplicable;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import static net.kyori.adventure.text.format.Style.style;
/**
* Represents all valid objective score formats.
*
* @since Minecraft 1.20.3
*/
@ApiStatus.NonExtendable
public interface ScoreFormat {
static @NotNull Blank blank() {
return Blank.INSTANCE;
}
static @NotNull Fixed fixed(@NotNull ComponentLike content) {
Preconditions.checkNotNull(content);
return new Fixed(content.asComponent());
}
static @NotNull Styled styled(@NotNull Style style) {
Preconditions.checkNotNull(style);
return new Styled(style);
}
static @NotNull Styled styled(@NotNull StyleBuilderApplicable @NotNull ... styleBuilderApplicables) {
Preconditions.checkNotNull(styleBuilderApplicables);
return new Styled(style(styleBuilderApplicables));
}
class Blank implements ScoreFormat {
private static final Blank INSTANCE = new Blank();
private Blank() {
}
@Override
public String toString() {
return "Blank";
}
}
class Fixed implements ScoreFormat {
private final Component content;
public Fixed(@NotNull Component content) {
this.content = content;
}
public @NotNull Component content() {
return content;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Fixed that = (Fixed) o;
return content.equals(that.content);
}
@Override
public int hashCode() {
return content.hashCode();
}
@Override
public String toString() {
return "Fixed{content=" + content + "}";
}
}
class Styled implements ScoreFormat {
private final Style style;
public Styled(@NotNull Style style) {
this.style = style;
}
public @NotNull Style style() {
return style;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Styled that = (Styled) o;
return style.equals(that.style);
}
@Override
public int hashCode() {
return style.hashCode();
}
@Override
public String toString() {
return "Styled{style=" + style + "}";
}
}
}
@@ -0,0 +1,156 @@
package net.megavex.scoreboardlibrary.api.objective;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a scoreboard objective.
* To get an instance of this interface, use {@link ObjectiveManager#create}.
* Note: this interface is not thread-safe, meaning you can only use it from one thread at a time,
* although it does not have to be the main thread.
*/
public interface ScoreboardObjective {
/**
* @return the value (display name) of the objective, defaults to {@link Component#empty}
*/
@NotNull Component value();
/**
* Sets the value (display name) of the objective.
*
* @param value new value
*/
@NotNull ScoreboardObjective value(@NotNull ComponentLike value);
/**
* @return the render type of the objective, defaults to {@link ObjectiveRenderType#INTEGER}
*/
@NotNull ObjectiveRenderType renderType();
/**
* Sets the render type of the objective.
*
* @param renderType new render type
*/
@NotNull ScoreboardObjective renderType(@NotNull ObjectiveRenderType renderType);
/**
* @return the default score format for all scores in this objective, defaults to null
*/
@Nullable ScoreFormat defaultScoreFormat();
/**
* Sets the default score format of this objective.
*
* @param defaultScoreFormat new default score format
* @see #defaultScoreFormat()
*/
void defaultScoreFormat(@Nullable ScoreFormat defaultScoreFormat);
/**
* Refreshes the objective's {@link #value()} and {@link #defaultScoreFormat()}, re-rendering any {@link net.kyori.adventure.text.TranslatableComponent}s.
*/
void refreshProperties();
/**
* Get the score for an entry, or null if the entry has no score.
*
* @param entry entry to get score of
* @return the score value, or null if it doesn't exist for the entry
*/
@Nullable ObjectiveScore scoreInfo(@NotNull String entry);
/**
* Gets the score value for an entry, or null if the entry has no score.
*
* @param entry entry to get score of
* @return the score value, or null if it doesn't exist for the entry
* @deprecated use {@link #scoreInfo(String)} and {@link ObjectiveScore#value()} instead
*/
@Deprecated
default @Nullable Integer score(@NotNull String entry) {
ObjectiveScore score = scoreInfo(entry);
return score == null ? null : score.value();
}
/**
* Updates the score of an entry.
*
* @param entry entry to update score of
* @param score score
*/
@NotNull ScoreboardObjective score(@NotNull String entry, ObjectiveScore score);
/**
* Updates the score of an entry.
*
* @param entry entry to update score of
* @param scoreValue score value
*/
default @NotNull ScoreboardObjective score(@NotNull String entry, int scoreValue) {
return score(entry, new ObjectiveScore(scoreValue, null, null));
}
/**
* Updates the score of an entry.
*
* @param entry entry to update score of
* @param scoreValue score value
* @param displayName score display name
* @param scoreFormat score format
*/
default @NotNull ScoreboardObjective score(
@NotNull String entry,
int scoreValue,
@Nullable ComponentLike displayName,
@Nullable ScoreFormat scoreFormat
) {
return score(entry, new ObjectiveScore(scoreValue, displayName, scoreFormat));
}
/**
* Updates the score of an entry.
*
* @param entry entry to update score of
* @param scoreValue score value
* @param displayName score display name
*/
default @NotNull ScoreboardObjective score(
@NotNull String entry,
int scoreValue,
@Nullable ComponentLike displayName
) {
return score(entry, new ObjectiveScore(scoreValue, displayName, null));
}
/**
* Updates the score of an entry.
*
* @param entry entry to update score of
* @param scoreValue score value
* @param scoreFormat score format
*/
default @NotNull ScoreboardObjective score(
@NotNull String entry,
int scoreValue,
@Nullable ScoreFormat scoreFormat
) {
return score(entry, new ObjectiveScore(scoreValue, null, scoreFormat));
}
/**
* Removes a score associated with an entry.
*
* @param entry entry to remove score of
*/
@NotNull ScoreboardObjective removeScore(@NotNull String entry);
/**
* Refreshes a score, re-rendering any {@link net.kyori.adventure.text.TranslatableComponent}s inside its format.
*
* @param entry entry to refresh score of
*/
void refreshScore(@NotNull String entry);
}
@@ -1,8 +1,11 @@
package net.megavex.scoreboardlibrary.api.sidebar;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import java.util.Collection;
import java.util.Locale;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -10,44 +13,73 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
/**
* Provides a low-level utility for showing sidebars to players.
* Note: this class is not thread-safe.
* Represents a sidebar.
* To get an instance of this interface, use {@link ScoreboardLibrary#createSidebar()}.
* Note: this interface is not thread-safe, meaning you can only use it from one thread at a time,
* although it does not have to be the main thread.
*/
@ApiStatus.NonExtendable
public interface Sidebar {
// Constants
/**
* The max amount of lines a vanilla client can display at once.
*/
int MAX_LINES = 15;
// Main
/**
* @return Max amount of lines this sidebar can have
* @return max amount of lines this sidebar can have
*/
@Range(from = 1, to = MAX_LINES) int maxLines();
@Range(from = 1, to = Integer.MAX_VALUE)
int maxLines();
/**
* @return {@link Locale} which is used to translate {@link net.kyori.adventure.text.TranslatableComponent}s
* @return objective name used by this sidebar
*/
@NotNull String objectiveName();
/**
* @return locale which is used for translating {@link net.kyori.adventure.text.TranslatableComponent}s,
* or null if it depends on each player's client locale.
* @see ScoreboardLibrary#createSidebar(int, Locale)
*/
@Nullable Locale locale();
// Lines
/**
* Gets a line's value.
* Gets a line's value, or null if line isn't set.
*
* @param line Line
* @return Value of line
* @param line line index
* @return value of line, or null if unset
*/
@Nullable Component line(@Range(from = 0, to = MAX_LINES - 1) int line);
@Nullable Component line(@Range(from = 0, to = Integer.MAX_VALUE - 1) int line);
/**
* Sets a line's value.
* A null value hides the line.
*
* @param line Line
* @param value Value
* @param index line index
* @param value new value, or null to hide
*/
void line(@Range(from = 0, to = MAX_LINES - 1) int line, @Nullable Component value);
default void line(@Range(from = 0, to = Integer.MAX_VALUE - 1) int index, @Nullable ComponentLike value) {
line(index, value, null);
}
/**
* Sets a line's value with a custom score format.
* A null value hides the line.
* Note that custom score formats are only supported in Minecraft 1.20.3+.
*
* @param index line index
* @param value new value, or null to hide
* @param scoreFormat score format
*/
void line(
@Range(from = 0, to = Integer.MAX_VALUE - 1) int index,
@Nullable ComponentLike value,
@Nullable ScoreFormat scoreFormat
);
/**
* Clears all lines in this sidebar.
@@ -58,47 +90,65 @@ public interface Sidebar {
}
}
/**
* Refreshes a specific line, re-rendering any {@link net.kyori.adventure.text.TranslatableComponent}s.
*
* @param index line index
*/
void refreshLine(@Range(from = 0, to = Integer.MAX_VALUE - 1) int index);
/**
* Refreshes all lines in this sidebar, re-rendering any {@link net.kyori.adventure.text.TranslatableComponent}s.
*/
default void refreshLines() {
for (int i = 0; i < maxLines(); i++) {
refreshLine(i);
}
}
// Title
/**
* @return Title of the sidebar
* @return title of the sidebar, defaults to {@link Component#empty}
*/
@NotNull Component title();
/**
* Sets the title of the sidebar.
*
* @param title Title
* @param title title
*/
void title(@NotNull Component title);
void title(@NotNull ComponentLike title);
/**
* Refresh the title of the sidebar, re-rendering any {@link net.kyori.adventure.text.TranslatableComponent}s.
*/
void refreshTitle();
// Players
/**
* @return The players that have been added to this sidebar.
* @return unmodifiable collection of viewers in this Sidebar
* @see #addPlayer
* @see #removePlayer
*/
@NotNull Collection<Player> players();
/**
* Adds a player to this sidebar, making it be able to see it.
* Adds a viewer to this Sidebar.
* Note that a player can only see a single Sidebar at a time.
* The Sidebar will internally be added to a queue for this player who
* will start seeing it once they are removed from all previous Sidebars.
*
* @param player Player to add
* @return Whether the player has been added
* @param player player to add
* @return whether the player was added
*/
boolean addPlayer(@NotNull Player player);
/**
* Removes a player from this sidebar, making it not able to see it.
* Adds multiple viewers to this Sidebar.
*
* @param player Player to remove
* @return Whether the player has been removed
*/
boolean removePlayer(@NotNull Player player);
/**
* Adds multiple players to this sidebar.
*
* @param players Players to add
* @param players viewers to add
* @see #addPlayer
*/
default void addPlayers(@NotNull Collection<Player> players) {
@@ -108,10 +158,17 @@ public interface Sidebar {
}
/**
* Removes multiple players from this sidebar.
* Removes a viewer from this Sidebar.
*
* @param players Players to remove
* @see #removePlayer
* @param player viewer to remove
* @return whether the viewer was removed
*/
boolean removePlayer(@NotNull Player player);
/**
* Removes multiple viewers from this Sidebar
*
* @param players viewers to remove
*/
default void removePlayers(@NotNull Collection<Player> players) {
for (Player player : players) {
@@ -119,15 +176,15 @@ public interface Sidebar {
}
}
// Close
/**
* Closes this sidebar
* Closes this Sidebar.
* This must be called once you no longer need this Sidebar to prevent a memory leak.
*/
void close();
/**
* @return Whether this sidebar is closed
* @see #close
*/
boolean closed();
}
@@ -2,12 +2,17 @@ package net.megavex.scoreboardlibrary.api.sidebar.component;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.kyori.adventure.text.Component.empty;
/**
* Represents a component sidebar layout that can be applied to a {@link Sidebar}.
*/
public final class ComponentSidebarLayout {
private final SidebarComponent titleComponent;
private final SidebarComponent linesComponent;
@@ -27,6 +32,12 @@ public final class ComponentSidebarLayout {
return linesComponent;
}
/**
* Applies the title and line components to a sidebar.
* Call this every time you want to update the sidebar.
*
* @param sidebar sidebar to apply title and line components to
*/
public void apply(@NotNull Sidebar sidebar) {
Preconditions.checkNotNull(sidebar);
@@ -46,11 +57,11 @@ public final class ComponentSidebarLayout {
private Component title;
@Override
public void drawLine(@NotNull Component line) {
public void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat) {
Preconditions.checkNotNull(line);
if (title == null) {
title = line;
title = line.asComponent();
}
}
}
@@ -64,11 +75,11 @@ public final class ComponentSidebarLayout {
}
@Override
public void drawLine(@NotNull Component line) {
public void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat) {
Preconditions.checkNotNull(line);
if (index < Sidebar.MAX_LINES) {
sidebar.line(index++, line);
if (index < sidebar.maxLines()) {
sidebar.line(index++, line, scoreFormat);
}
}
}
@@ -1,18 +1,30 @@
package net.megavex.scoreboardlibrary.api.sidebar.component;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Something lines can be drawn to. This is either the lines of a sidebar or the sidebar title
* Something lines can be drawn to line by line. This is either the lines of a sidebar or its title.
*/
@ApiStatus.NonExtendable
public interface LineDrawable {
/**
* Draws a line, or does nothing if has reached the limit
* Draws the next line, or does nothing if it has reached a limit.
*
* @param line Line component
* @param line line component
*/
void drawLine(@NotNull Component line);
default void drawLine(@NotNull ComponentLike line) {
drawLine(line, null);
}
/**
* Draws the next line with a custom score format, or does nothing if it has reached a limit.
*
* @param line line component
* @param scoreFormat score format
*/
void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat);
}
@@ -2,36 +2,42 @@ package net.megavex.scoreboardlibrary.api.sidebar.component;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.api.sidebar.component.animation.SidebarAnimation;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import static net.kyori.adventure.text.Component.empty;
public interface SidebarComponent {
static @NotNull SidebarComponent staticLine(@NotNull Component line) {
static @NotNull SidebarComponent staticLine(@NotNull ComponentLike line) {
Preconditions.checkNotNull(line);
return drawable -> drawable.drawLine(line);
}
static @NotNull SidebarComponent staticLine(@NotNull ComponentLike line, @NotNull ScoreFormat scoreFormat) {
Preconditions.checkNotNull(line);
return drawable -> drawable.drawLine(line, scoreFormat);
}
static @NotNull SidebarComponent blankLine() {
return drawable -> drawable.drawLine(empty());
}
static @NotNull SidebarComponent dynamicLine(@NotNull Supplier<Component> lineSupplier) {
static @NotNull <T extends ComponentLike> SidebarComponent dynamicLine(@NotNull Supplier<T> lineSupplier) {
return drawable -> drawable.drawLine(lineSupplier.get());
}
static @NotNull SidebarComponent animatedLine(@NotNull SidebarAnimation<Component> animation) {
static @NotNull <T extends ComponentLike> SidebarComponent animatedLine(@NotNull SidebarAnimation<T> animation) {
Preconditions.checkNotNull(animation);
return drawable -> drawable.drawLine(animation.currentFrame());
}
static @NotNull SidebarComponent animatedComponent(@NotNull SidebarAnimation<SidebarComponent> animation) {
static @NotNull <T extends SidebarComponent> SidebarComponent animatedComponent(@NotNull SidebarAnimation<T> animation) {
Preconditions.checkNotNull(animation);
return drawable -> animation.currentFrame().draw(drawable);
}
@@ -54,29 +60,28 @@ public interface SidebarComponent {
return this;
}
public @NotNull Builder addStaticLine(@NotNull Component line) {
addComponent(SidebarComponent.staticLine(line));
return this;
public @NotNull Builder addStaticLine(@NotNull ComponentLike line) {
return addComponent(SidebarComponent.staticLine(line));
}
public @NotNull Builder addStaticLine(@NotNull ComponentLike line, @NotNull ScoreFormat scoreFormat) {
return addComponent(SidebarComponent.staticLine(line, scoreFormat));
}
public @NotNull Builder addBlankLine() {
addComponent(SidebarComponent.blankLine());
return this;
return addComponent(SidebarComponent.blankLine());
}
public @NotNull Builder addDynamicLine(@NotNull Supplier<Component> lineSupplier) {
addComponent(SidebarComponent.dynamicLine(lineSupplier));
return this;
public @NotNull <T extends ComponentLike> Builder addDynamicLine(@NotNull Supplier<T> lineSupplier) {
return addComponent(SidebarComponent.dynamicLine(lineSupplier));
}
public @NotNull Builder addAnimatedLine(@NotNull SidebarAnimation<Component> animation) {
addComponent(SidebarComponent.animatedLine(animation));
return this;
public <T extends ComponentLike> @NotNull Builder addAnimatedLine(@NotNull SidebarAnimation<T> animation) {
return addComponent(SidebarComponent.animatedLine(animation));
}
public @NotNull Builder addAnimatedComponent(@NotNull SidebarAnimation<SidebarComponent> animation) {
addComponent(SidebarComponent.animatedComponent(animation));
return this;
public @NotNull <T extends SidebarComponent> Builder addAnimatedComponent(@NotNull SidebarAnimation<T> animation) {
return addComponent(SidebarComponent.animatedComponent(animation));
}
public @NotNull SidebarComponent build() {
@@ -2,11 +2,17 @@ package net.megavex.scoreboardlibrary.api.sidebar.component.animation;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.util.Collection;
import java.util.List;
/**
* Implementation of {@link FramedSidebarAnimation} using a predefined collection of frames.
*
* @param <T> frame type
*/
public final class CollectionSidebarAnimation<T> implements FramedSidebarAnimation<T> {
private final List<T> frames;
private int currentFrameIndex;
@@ -1,30 +1,31 @@
package net.megavex.scoreboardlibrary.api.sidebar.component.animation;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.util.List;
/**
* A {@link SidebarAnimation} with predetermined frames
* A {@link SidebarAnimation} with predetermined frames.
*
* @param <T> Frame type
* @param <T> frame type
*/
public interface FramedSidebarAnimation<T> extends SidebarAnimation<T> {
/**
* @return All frames in this animation
* @return all frames in this animation
*/
@Unmodifiable @NotNull List<T> frames();
/**
* @return The index of the current frame
* @return the index of the current frame
*/
int currentFrameIndex();
/**
* Switches the current frame
* Switches the current frame to specified index
*
* @param frameIndex The index of the new current frame
* @throws ArrayIndexOutOfBoundsException If frame is out of bounds
* @param frameIndex the index of the new current frame
* @throws ArrayIndexOutOfBoundsException if frame is out of bounds
*/
void switchFrame(int frameIndex);
}
@@ -6,11 +6,11 @@ import org.jetbrains.annotations.NotNull;
/**
* An animation that can be used with {@link ComponentSidebarLayout}s
*
* @param <T> Frame type
* @param <T> frame type
*/
public interface SidebarAnimation<T> {
/**
* @return The current frame of the animation
* @return the current frame of the animation
*/
@NotNull T currentFrame();
@@ -5,42 +5,54 @@ import org.jetbrains.annotations.NotNull;
/**
* Represents a scoreboard team.
* Note: this class is not thread-safe.
* To get an instance of this interface, use {@link TeamManager#createIfAbsent}.
* Note: this interface is not thread-safe, meaning you can only use it from one thread at a time,
* although it does not have to be the main thread.
*
* @see <a href="https://minecraft.fandom.com/wiki/Scoreboard#Teams">Minecraft Wiki</a>
*/
public interface ScoreboardTeam {
/**
* @return {@link TeamManager} of this team
* @return TeamManager of this team
*/
@NotNull TeamManager teamManager();
/**
* @return Name of this team
* @return name of this team
*/
@NotNull String name();
/**
* @return Default {@link TeamDisplay} of this team
* Gets the default {@link TeamDisplay} of this team.
* All players are added to this display by default.
*
* @return default team display
*/
@NotNull TeamDisplay defaultDisplay();
/**
* Gets the {@link TeamDisplay} of a player
* Gets the {@link TeamDisplay} that a player sees.
*
* @param player Player
* @return {@link TeamDisplay} of this player
* @param player player
* @return team display the player sees
* @throws IllegalArgumentException if player is not in the TeamManager
*/
@NotNull TeamDisplay display(@NotNull Player player);
/**
* Changes a player's visible {@link TeamDisplay}
* Updates the team {@link TeamDisplay} that a player sees for this team.
*
* @param player Player
* @param teamDisplay New {@link TeamDisplay} of Player
* @param player player
* @param teamDisplay new {@link TeamDisplay} of player
* @throws IllegalArgumentException if player is not in the TeamManager
*/
void display(@NotNull Player player, @NotNull TeamDisplay teamDisplay);
/**
* @return Newly created {@link TeamDisplay} which is assigned to this team
* Creates a new {@link TeamDisplay}.
* To show it to players, use {@link #display(Player, TeamDisplay)}.
*
* @return newly created team display
*/
@NotNull TeamDisplay createDisplay();
}
@@ -1,141 +1,196 @@
package net.megavex.scoreboardlibrary.api.team;
import java.util.Collection;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule;
import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Represents the display information of a {@link ScoreboardTeam}.
* This includes things like the entries, display name, prefix etc..
* Note: this class is not thread-safe.
* Represents the display properties of a scoreboard team.
* Note: this interface is not thread-safe, meaning you can only use it from one thread at a time,
* although it does not have to be the main thread.
*/
@ApiStatus.NonExtendable
public interface TeamDisplay {
/**
* @return Team which is assigned to this {@link TeamDisplay}
* @return team which this team display is assigned to
*/
@NotNull ScoreboardTeam team();
// Entries
/**
* Gets the entries. The entries specify which players are members of the team.
*
* @return entries of the team.
*/
@NotNull Collection<String> entries();
/**
* Adds an entry.
*
* @param entry Entry to add
* @return Whether the entry was added
* @param entry entry to add
* @return whether the entry was added
* @see #entries
*/
boolean addEntry(@NotNull String entry);
/**
* Adds multiple entries.
*
* @param entries entries to add
* @return whether any entry was added
* @see #entries
*/
boolean addEntries(@NotNull Collection<String> entries);
/**
* Removes an entry.
*
* @param entry Entry to remove
* @return Whether the entry was removed
* @param entry entry to remove
* @return whether the entry was removed
* @see #entries
*/
boolean removeEntry(@NotNull String entry);
/**
* Removes multiple entries.
*
* @param entries entries to remove
* @return whether any entry was removed
* @see #entries
*/
boolean removeEntries(@NotNull Collection<String> entries);
// Properties
/**
* @return Display name
* @return display name, defaults to {@link Component#empty()}
*/
@NotNull Component displayName();
/**
* Sets the display name.
* Updates the display name.
*
* @param displayName Display name
* @param displayName new display name
*/
@NotNull TeamDisplay displayName(@NotNull Component displayName);
@NotNull TeamDisplay displayName(@NotNull ComponentLike displayName);
/**
* @return Prefix
* Gets the prefix, which defaults to {@link Component#empty()}.
* The suffix is prepended to all team members' names.
*
* @return prefix, defaults to {@link Component#empty()}
*/
@NotNull Component prefix();
/**
* Sets the prefix.
* Updates the prefix.
*
* @param prefix Prefix
* @param prefix new prefix
* @see #prefix()
*/
@NotNull TeamDisplay prefix(@NotNull Component prefix);
@NotNull TeamDisplay prefix(@NotNull ComponentLike prefix);
/**
* @return Suffix
* Gets the suffix, which defaults to {@link Component#empty()}.
* The suffix is appended to all team members' names.
*
* @return suffix
*/
@NotNull Component suffix();
/**
* Sets the suffix.
* Updates the suffix.
*
* @param suffix Suffix
* @param suffix new suffix
* @see #suffix()
*/
@NotNull TeamDisplay suffix(@NotNull Component suffix);
@NotNull TeamDisplay suffix(@NotNull ComponentLike suffix);
/**
* @return Friendly fire rule
* @return friendly fire rule value, defaults to false
*/
boolean friendlyFire();
/**
* Sets whether friendly fire is allowed.
* Updates the friendly fire rule.
*
* @param friendlyFire whether friendly fire is allowed
* @param friendlyFire new rule value
*/
@NotNull TeamDisplay friendlyFire(boolean friendlyFire);
/**
* @return Can see friendly invisibles rule
* Gets the can see friendly invisibles rule value, which is false by default.
* If true, team members can see other members with the invisibility effect.
*
* @return rule value
*/
boolean canSeeFriendlyInvisibles();
/**
* Sets whether players can see friendly invisibles.
* Updates the can see friendly invisibles rule.
*
* @param canSeeFriendlyInvisibles whether players can see friendly invisibles
* @param canSeeFriendlyInvisibles new rule value
* @see #canSeeFriendlyInvisibles()
*/
@NotNull TeamDisplay canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles);
/**
* @return Name tag visibility rule
* @return name tag visibility rule value
* @since Minecraft 1.8
*/
@NotNull NameTagVisibility nameTagVisibility();
/**
* Sets the {@link NameTagVisibility}.
* Updates the name tag visibility rule.
*
* @param nameTagVisibility Name tag visibility rule
* @param nameTagVisibility new rule value
* @since Minecraft 1.8
*/
@NotNull TeamDisplay nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility);
/**
* @return Collision rule
* @return collision rule
* @since Minecraft 1.9
*/
@NotNull CollisionRule collisionRule();
/**
* Sets the {@link CollisionRule}.
* Updates the collision rule.
*
* @param collisionRule Collision rule
* @param collisionRule new rule value
* @since Minecraft 1.9
*/
@NotNull TeamDisplay collisionRule(@NotNull CollisionRule collisionRule);
/**
* @return Player color
* Gets the player color. The player color is the color player name tags are displayed in.
* It is also used for displaying team specific sidebars with the {@link ObjectiveDisplaySlot.TeamSidebar}} display slot.
*
* @return player color
* @since Minecraft 1.8
*/
@Nullable NamedTextColor playerColor();
/**
* Sets the player color.
* Updates the player color.
*
* @param playerColor Player color
* @param playerColor new player color
* @see #playerColor()
* @since Minecraft 1.8
*/
@NotNull TeamDisplay playerColor(@Nullable NamedTextColor playerColor);
/**
* Refreshes the team display, re-rendering any {@link net.kyori.adventure.text.TranslatableComponent}s.
*/
void refresh();
}
@@ -1,119 +1,132 @@
package net.megavex.scoreboardlibrary.api.team;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Function;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* Manages a group of {@link ScoreboardTeam}s.
* Note: this class is not thread-safe.
* Represents a group of {@link ScoreboardTeam}s.
* To get an instance of this interface, use {@link ScoreboardLibrary#createTeamManager()}.
* Note: this interface is not thread-safe, meaning you can only use it from one thread at a time,
* although it does not have to be the main thread.
*/
@ApiStatus.NonExtendable
public interface TeamManager {
// Teams
/**
* Returns an unmodifiable set of teams in this team manager
*
* @return Teams
* @return unmodifiable collection of teams in this TeamManager
*/
@NotNull Collection<ScoreboardTeam> teams();
/**
* Returns team based on its name
* Gets a team by its name.
*
* @param name Name of team
* @return Team with this name
* @param name name of team
* @return team with the name, or null if one doesn't exist
*/
@Nullable ScoreboardTeam team(@NotNull String name);
/**
* Returns whether a team by the name exists
* Check whether a team with a name exists.
*
* @param name Name of team
* @return Whether a team by the name exists
* @param name name of team
* @return whether a team by the name exists
*/
boolean teamExists(@NotNull String name);
/**
* Returns team based on its name. If it doesn't already exist, creates it
* Creates a team with a name if one doesn't already exist and returns it.
*
* @param name Name of team
* @return Existing or newly created team with the name
* @param name name of team
* @return team
*/
default @NotNull ScoreboardTeam createIfAbsent(@NotNull String name) {
return createIfAbsent(name, null);
}
/**
* Returns a team based on its name. If it doesn't already exist, it creates it
* Creates a team with a name if one doesn't already exist and returns it.
*
* @param name Name of team
* @param teamDisplayFunction A function that provides the team display to set for each player
* @return Team with this name
* @param name name of team
* @param teamDisplayFunction a function that provides the team display to set for each player
* @return team
*/
@NotNull ScoreboardTeam createIfAbsent(@NotNull String name, @Nullable BiFunction<Player, ScoreboardTeam, TeamDisplay> teamDisplayFunction);
/**
* Removes a team
* Removes a team by its name.
*
* @param name Name of team
* @return If there was a team with than name
* @param name name of team
* @return if there was a team with than name
*/
boolean removeTeam(@NotNull String name);
/**
* Removes a team
* Removes a team.
*
* @param team Team
* @throws IllegalArgumentException If the provided team is not owned by this TeamManager
* @param team team to remove
* @throws IllegalArgumentException if the provided team is not owned by this TeamManager
*/
void removeTeam(@NotNull ScoreboardTeam team);
// Players
/**
* @return Players in this TeamManager
* @return unmodifiable collection of viewers in this TeamManager
* @see #addPlayer
* @see #removePlayer
*/
@NotNull Collection<Player> players();
/**
* Adds a player to this TeamManager
* Adds a viewer to this TeamManager.
* Note that a player can only see a single TeamManager at a time.
* The TeamManager will internally be added to a queue for this player who
* will start seeing it once they are removed from all previous TeamManagers.
*
* @param player Player to add to TeamManager
* @return Whether the player was added
* @param player player to add
* @return whether the player was added
*/
default boolean addPlayer(@NotNull Player player) {
return addPlayer(player, null);
}
/**
* Adds a player to this TeamManager
* Adds a viewer to this TeamManager.
* Note that a player can only see a single TeamManager at a time.
* The TeamManager will internally be added to a queue for this player who
* will start seeing it once they are removed from all previous TeamManagers.
*
* @param player Player to add
* @param teamDisplayFunction A function that provides the team display to set for each team
* @return Whether the player was added
* @param player player to add
* @param teamDisplayFunction a function that provides the team display to set for each team
* @return whether the player was added
*/
boolean addPlayer(@NotNull Player player, @Nullable Function<ScoreboardTeam, TeamDisplay> teamDisplayFunction);
/**
* Adds a player to this TeamManager
* Adds multiple viewers to this TeamManager.
*
* @param players Players to add
* @param players viewers to add
* @see #addPlayer
*/
default void addPlayers(@NotNull Collection<Player> players) {
addPlayers(players, null);
}
/**
* Adds a collection of players to this TeamManager
* Adds multiple viewers to this TeamManager.
*
* @param players Players to add
* @param teamDisplayFunction A function that provides the team display to set for each team
* @param players viewers to add
* @param teamDisplayFunction a function that provides the team display to set for each team
* @see #addPlayer
*/
default void addPlayers(@NotNull Collection<Player> players, @Nullable Function<ScoreboardTeam, TeamDisplay> teamDisplayFunction) {
for (Player player : players) {
@@ -122,17 +135,17 @@ public interface TeamManager {
}
/**
* Removes a player from this TeamManager
* Removes a viewer from this TeamManager.
*
* @param player Player to remove
* @return Whether the player wasn't already in this TeamManager
* @param player viewer to remove
* @return whether the viewer was removed
*/
boolean removePlayer(@NotNull Player player);
/**
* Removes a collection of players from this TeamManager
* Removes multiple viewers from this TeamManager.
*
* @param players Players to remove
* @param players viewers to remove
*/
default void removePlayers(@NotNull Collection<Player> players) {
for (Player player : players) {
@@ -140,15 +153,25 @@ public interface TeamManager {
}
}
/**
* Removes a viewer from this TeamManager.
*
* @param player viewer to remove
* @return future that will be completed once all removal packets have been sent in an async task
*/
CompletableFuture<Void> removePlayerFuture(@NotNull Player player);
// Close
/**
* Closes this TeamManager
* Closes this TeamManager.
* This must be called once you no longer need this TeamManager to prevent a memory leak.
*/
void close();
/**
* @return Whether this TeamManager is closed
* @see #close
*/
boolean closed();
}
@@ -2,29 +2,23 @@ package net.megavex.scoreboardlibrary.api.team.enums;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents all possible collision rule values of a team.
*
* @since Minecraft 1.9
*/
public enum CollisionRule {
ALWAYS("always"),
NEVER("never"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam");
private static final CollisionRule[] values = values();
private final String key;
CollisionRule(String key) {
this.key = key;
}
@ApiStatus.Internal
public static CollisionRule of(byte id) {
return values[id];
}
@ApiStatus.Internal
public byte id() {
return (byte) ordinal();
}
@ApiStatus.Internal
public String key() {
return this.key;
@@ -2,29 +2,21 @@ package net.megavex.scoreboardlibrary.api.team.enums;
import org.jetbrains.annotations.ApiStatus;
/**
* Represents all possible name tag visibility rule values of a team.
*/
public enum NameTagVisibility {
ALWAYS("always"),
NEVER("never"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
private static final NameTagVisibility[] values = values();
private final String key;
NameTagVisibility(String key) {
this.key = key;
}
@ApiStatus.Internal
public static NameTagVisibility of(byte id) {
return values[id];
}
@ApiStatus.Internal
public byte id() {
return (byte) ordinal();
}
@ApiStatus.Internal
public String key() {
return this.key;
@@ -1,15 +0,0 @@
package net.megavex.scoreboardlibrary.api.util;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public final class SidebarUtilities {
private SidebarUtilities() {
}
public static void checkLineBounds(int maxLines, int line) {
if (line >= maxLines || line < 0) {
throw new IndexOutOfBoundsException("invalid line " + line);
}
}
}
@@ -1,10 +1,10 @@
package net.megavex.scoreboardlibrary.api.animation;
import java.util.Arrays;
import java.util.List;
import net.megavex.scoreboardlibrary.api.sidebar.component.animation.CollectionSidebarAnimation;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -1,6 +1,5 @@
package net.megavex.scoreboardlibrary.api.sidebar.component;
import java.util.Arrays;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
@@ -8,11 +7,10 @@ import net.megavex.scoreboardlibrary.api.sidebar.component.animation.CollectionS
import net.megavex.scoreboardlibrary.api.sidebar.component.animation.SidebarAnimation;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import static net.kyori.adventure.text.Component.text;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.*;
class ComponentSidebarLayoutTest {
private final Sidebar sidebar = new NoopScoreboardLibrary().createSidebar();
+2 -40
View File
@@ -1,43 +1,5 @@
plugins {
`java-library`
}
allprojects {
version = "2.0.0-RC10"
version = "2.7.1-SNAPSHOT"
group = "net.megavex"
description = "Powerful packet-level sidebar/teams library for Paper/Spigot servers"
repositories {
mavenCentral()
maven("https://oss.sonatype.org/content/groups/public/")
maven("https://repo.papermc.io/repository/maven-public/")
}
}
subprojects {
apply(plugin = "java-library")
dependencies {
compileOnly("org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT")
val adventureVersion = "4.14.0"
compileOnly("net.kyori:adventure-api:$adventureVersion")
compileOnly("net.kyori:adventure-text-serializer-legacy:$adventureVersion")
compileOnly("net.kyori:adventure-text-serializer-gson:$adventureVersion")
testImplementation("org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT")
testImplementation("net.kyori:adventure-api:$adventureVersion")
}
tasks.withType<JavaCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_1_8.toString()
targetCompatibility = JavaVersion.VERSION_1_8.toString()
options.encoding = "UTF-8"
options.isIncremental = true
options.compilerArgs = mutableListOf("-Xlint:-deprecation,-unchecked")
}
tasks.test {
useJUnitPlatform()
}
description = "Powerful packet-level scoreboard library for Paper/Spigot servers"
}
+7
View File
@@ -3,5 +3,12 @@ plugins {
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation(libs.buildIndra)
implementation(libs.buildNmcp)
compileOnly(files(libs::class.java.protectionDomain.codeSource.location))
}
+7
View File
@@ -0,0 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
register("libs") {
from(files("../gradle/libs.versions.toml")) // include from parent project
}
}
}
@@ -0,0 +1,75 @@
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.withType
plugins {
`maven-publish`
`java-library`
id("net.kyori.indra")
id("net.kyori.indra.publishing")
id("com.gradleup.nmcp")
}
val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class)
repositories {
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://hub.spigotmc.org/nexus/content/repositories/sonatype-nexus-snapshots/") {
content {
includeGroup("net.md-5")
}
}
}
dependencies {
testImplementation(libs.spigotApi)
testImplementation(libs.bundles.adventure)
testImplementation(libs.junitJupiter)
testRuntimeOnly(libs.junitPlatformLauncher)
}
indra {
github("MegavexNetwork", "scoreboard-library") {
ci(true)
}
mitLicense()
reproducibleBuilds(true)
javaVersions {
target(8)
minimumToolchain(21)
}
publishSnapshotsTo("forgejo", "https://git.megavex.net/api/packages/MegavexPublic/maven")
configurePublications {
pom {
url = "https://github.com/MegavexNetwork/scoreboard-library"
developers {
developer {
id = "vytskalt"
name = "vytskalt"
email = "vytskalt@protonmail.com"
}
}
}
}
}
nmcp {
val centralUsername = findProperty("centralUsername") as String?
val centralPassword = findProperty("centralPassword") as String?
if (centralUsername != null && centralPassword != null) {
publishAllPublications {
username = centralUsername
password = centralPassword
publicationType = "USER_MANAGED"
}
}
}
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs = mutableListOf("-Xlint:-deprecation,-unchecked")
}
@@ -1,10 +0,0 @@
plugins {
`java-library`
`maven-publish`
}
publishing {
publications.create<MavenPublication>("maven") {
from(components["java"])
}
}
-7
View File
@@ -1,7 +0,0 @@
plugins {
id("net.megavex.scoreboardlibrary.publish-conventions")
}
dependencies {
api(project(":scoreboard-library-api"))
}
@@ -1,70 +0,0 @@
package net.megavex.scoreboardlibrary.implementation.commons;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
public final class CollectionProvider {
private static final MethodHandle mapConstructor, setConstructor, listConstructor;
static {
Lookup lookup = MethodHandles.publicLookup();
MethodType parameters = MethodType.methodType(void.class, int.class);
mapConstructor = getConstructor(lookup, "it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap", HashMap.class, parameters);
setConstructor = getConstructor(lookup, "it.unimi.dsi.fastutil.objects.ObjectOpenHashSet", HashSet.class, parameters);
listConstructor = getConstructor(lookup, "it.unimi.dsi.fastutil.objects.ObjectArrayList", ArrayList.class, parameters);
}
private CollectionProvider() {
}
private static MethodHandle getConstructor(Lookup lookup, String fastUtilClass, Class<?> fallback, MethodType parameters) {
Class<?> clazz;
try {
clazz = Class.forName(fastUtilClass);
} catch (ClassNotFoundException e) {
clazz = fallback;
}
try {
return lookup.findConstructor(clazz, parameters);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
public static <K, V> @NotNull Map<K, V> map(int capacity) {
try {
// noinspection unchecked
return (Map<K, V>) mapConstructor.invokeWithArguments(capacity);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public static <E> @NotNull Set<E> set(int capacity) {
try {
// noinspection unchecked
return (Set<E>) setConstructor.invokeWithArguments(capacity);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public static <E> @NotNull List<E> list(int capacity) {
try {
// noinspection unchecked
return (List<E>) listConstructor.invokeWithArguments(capacity);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
+8 -6
View File
@@ -1,8 +1,9 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
plugins {
kotlin("jvm") version "1.8.21"
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
alias(libs.plugins.kotlin)
}
kotlin {
@@ -12,10 +13,11 @@ kotlin {
dependencies {
api(project(":scoreboard-library-api"))
testImplementation(kotlin("test"))
compileOnly(libs.spigotApi)
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "1.8"
tasks.withType<KotlinJvmCompile>().configureEach {
compilerOptions {
jvmTarget = JvmTarget.JVM_1_8
}
}
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1770056022,
"narHash": "sha256-yvCz+Qmci1bVucXEyac3TdoSPMtjqVJmVy5wro6j/70=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d04d8548aed39902419f14a8537006426dc1e4fa",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+46
View File
@@ -0,0 +1,46 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
};
outputs = { nixpkgs, ... }: let
forAllSystems = function:
nixpkgs.lib.genAttrs [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
] (system:
function (import nixpkgs {
inherit system;
}));
in {
devShells = forAllSystems (pkgs: rec {
default = jdk25;
jdk25 = pkgs.mkShellNoCC {
buildInputs = with pkgs; [
jdk25_headless
];
};
jdk21 = pkgs.mkShellNoCC {
buildInputs = with pkgs; [
jdk21_headless
];
};
jdk17 = pkgs.mkShellNoCC {
buildInputs = with pkgs; [
jdk17_headless
];
};
jdk8 = pkgs.mkShellNoCC {
buildInputs = with pkgs; [
jdk8_headless
];
};
});
};
}
+21
View File
@@ -0,0 +1,21 @@
[versions]
adventure = "4.25.0"
[libraries]
spigotApi = "org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT" # do not update
buildIndra = { module = "net.kyori:indra-common", version = "4.0.0" }
buildNmcp = "com.gradleup.nmcp:com.gradleup.nmcp.gradle.plugin:0.0.9"
adventureApi = { module = "net.kyori:adventure-api", version.ref = "adventure" }
adventureTextSerializerGson = { module = "net.kyori:adventure-text-serializer-gson", version.ref = "adventure" }
adventureTextSerializerLegacy = { module = "net.kyori:adventure-text-serializer-legacy", version.ref = "adventure" }
# TODO: v6 only supports java 17
junitJupiter = "org.junit.jupiter:junit-jupiter:5.13.4"
junitPlatformLauncher = "org.junit.platform:junit-platform-launcher:1.14.1"
[plugins]
kotlin = "org.jetbrains.kotlin.jvm:2.2.21"
[bundles]
adventure = [ "adventureApi", "adventureTextSerializerGson", "adventureTextSerializerLegacy" ]
Binary file not shown.
+3 -1
View File
@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+32 -18
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -80,13 +82,11 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -133,22 +132,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -165,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Vendored
+93 -89
View File
@@ -1,89 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+11 -2
View File
@@ -1,8 +1,17 @@
plugins {
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
//id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
}
repositories {
maven("https://repo.viaversion.com")
}
dependencies {
api(project(":scoreboard-library-api"))
implementation(project(":scoreboard-library-packet-adapter-base"))
compileOnly(libs.spigotApi)
compileOnly("com.viaversion:viaversion-api:5.7.1")
compileOnly("io.netty:netty-buffer:4.2.10.Final")
compileOnly("io.netty:netty-handler:4.2.10.Final")
}
@@ -1,43 +1,131 @@
package net.megavex.scoreboardlibrary.implementation;
import java.lang.reflect.InvocationTargetException;
import net.megavex.scoreboardlibrary.api.exception.NoPacketAdapterAvailableException;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ScoreboardLibraryPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
public final class PacketAdapterLoader {
private PacketAdapterLoader() {
}
public static @NotNull ScoreboardLibraryPacketAdapter<?> loadPacketAdapter() throws NoPacketAdapterAvailableException {
String versionName = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Class<?> nmsClass = tryLoadImplementationClass(versionName);
public static @NotNull PacketAdapterProvider loadPacketAdapter(Plugin plugin) throws NoPacketAdapterAvailableException {
Class<?> nmsClass = findAndLoadImplementationClass();
if (nmsClass == null) {
nmsClass = tryLoadImplementationClass("packetevents");
if (nmsClass == null) {
throw new NoPacketAdapterAvailableException();
}
try {
Class.forName("com.github.retrooper.packetevents.PacketEvents");
} catch (ClassNotFoundException ignored) {
throw new NoPacketAdapterAvailableException();
}
throw new NoPacketAdapterAvailableException();
}
try {
return (ScoreboardLibraryPacketAdapter<?>) nmsClass.getConstructors()[0].newInstance();
return (PacketAdapterProvider) nmsClass.getConstructors()[0].newInstance(plugin);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("couldn't initialize packet adapter", e);
}
}
private static @Nullable Class<?> tryLoadImplementationClass(@NotNull String name) {
private static @Nullable Class<?> findAndLoadImplementationClass() {
String version = Bukkit.getServer().getBukkitVersion();
int dashIndex = version.indexOf('-');
if (dashIndex != -1) {
version = version.substring(0, dashIndex);
}
return tryLoadVersion(version);
}
private static @Nullable Class<?> tryLoadVersion(@NotNull String serverVersion) {
// https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-legacy/
// https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-10-1-15/
// https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-16/
// https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-21/
switch (serverVersion) {
case "1.7.10":
case "1.8":
case "1.8.3":
case "1.8.4":
case "1.8.5":
case "1.8.6":
case "1.8.7":
case "1.8.8":
case "1.9":
case "1.9.2":
case "1.9.4":
case "1.10.2":
case "1.11":
case "1.11.2":
case "1.12":
case "1.12.1":
case "1.12.2":
try {
return Class.forName("net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy.PacketAdapterProviderImpl");
} catch (ClassNotFoundException ignored) {
return null;
}
case "1.13":
case "1.13.1":
case "1.13.2":
case "1.14":
case "1.14.1":
case "1.14.2":
case "1.14.3":
case "1.14.4":
case "1.15":
case "1.15.1":
case "1.15.2":
case "1.16":
case "1.16.1":
case "1.16.2":
case "1.16.3":
case "1.16.4":
case "1.16.5":
case "1.17":
case "1.17.1":
case "1.18":
case "1.18.1":
case "1.18.2":
case "1.19":
case "1.19.1":
case "1.19.2":
case "1.19.3":
case "1.19.4":
case "1.20":
case "1.20.1":
case "1.20.2":
case "1.20.3":
case "1.20.4":
case "1.20.5":
case "1.20.6":
case "1.21":
case "1.21.1":
case "1.21.2":
case "1.21.3":
case "1.21.4":
case "1.21.5":
case "1.21.6":
case "1.21.7":
case "1.21.8":
case "1.21.9":
case "1.21.10":
case "1.21.11":
case "26.1":
return tryLoadModern();
default:
// Hide from relocation checkers
String property = "net.mega".concat("vex.scoreboardlibrary.forceModern");
if (System.getProperty(property, "").equalsIgnoreCase("true")) {
return tryLoadModern();
}
return null;
}
}
private static Class<?> tryLoadModern() {
try {
return Class.forName("net.megavex.scoreboardlibrary.implementation.packetAdapter." + name + ".PacketAdapterImpl");
return Class.forName("net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl");
} catch (ClassNotFoundException ignored) {
return null;
}
@@ -2,16 +2,15 @@ package net.megavex.scoreboardlibrary.implementation;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.exception.NoPacketAdapterAvailableException;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import net.megavex.scoreboardlibrary.implementation.commons.LocaleProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ScoreboardLibraryPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerImpl;
import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveUpdaterTask;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider;
import net.megavex.scoreboardlibrary.implementation.player.LocaleListener;
import net.megavex.scoreboardlibrary.implementation.player.ScoreboardLibraryPlayer;
import net.megavex.scoreboardlibrary.implementation.scheduler.TaskScheduler;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import net.megavex.scoreboardlibrary.implementation.sidebar.PlayerDependantLocaleSidebar;
@@ -25,19 +24,26 @@ import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ScoreboardLibraryImpl implements ScoreboardLibrary {
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class ScoreboardLibraryImpl implements ScoreboardLibrary {
private final Plugin plugin;
private final ScoreboardLibraryPacketAdapter<?> packetAdapter;
private final LocaleProvider localeProvider;
private final PacketAdapterProvider packetAdapter;
private final TaskScheduler taskScheduler;
private final Map<Player, ScoreboardLibraryPlayer> playerMap = new MapMaker().weakKeys().makeMap();
private volatile Set<TeamManagerImpl> teamManagers;
private volatile Set<ObjectiveManagerImpl> objectiveManagers;
private volatile Set<AbstractSidebar> sidebars;
private final LocaleListener localeListener;
private TeamUpdaterTask teamTask;
private ObjectiveUpdaterTask objectiveTask;
private SidebarUpdaterTask sidebarTask;
private final Object lock = new Object();
@@ -53,8 +59,7 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
}
this.plugin = plugin;
this.packetAdapter = PacketAdapterLoader.loadPacketAdapter();
this.localeProvider = this.packetAdapter.localeProvider;
this.packetAdapter = PacketAdapterLoader.loadPacketAdapter(plugin);
this.taskScheduler = TaskScheduler.create(plugin);
boolean localeEventExists = false;
@@ -76,31 +81,27 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
return plugin;
}
public @NotNull ScoreboardLibraryPacketAdapter<?> packetAdapter() {
public @NotNull PacketAdapterProvider packetAdapter() {
return packetAdapter;
}
public @NotNull LocaleProvider localeProvider() {
return localeProvider;
}
public @NotNull TaskScheduler taskScheduler() {
return taskScheduler;
}
@Override
public @NotNull Sidebar createSidebar(int maxLines, @Nullable Locale locale) {
public @NotNull Sidebar createSidebar(int maxLines, @Nullable Locale locale, @NotNull String objectiveName) {
checkClosed();
if (maxLines <= 0 || maxLines > Sidebar.MAX_LINES) {
throw new IllegalArgumentException("maxLines");
if (maxLines <= 0) {
throw new IllegalArgumentException("invalid maxLines value: " + maxLines);
}
AbstractSidebar sidebar;
if (locale == null) {
sidebar = new PlayerDependantLocaleSidebar(this, maxLines);
sidebar = new PlayerDependantLocaleSidebar(this, maxLines, objectiveName);
} else {
sidebar = new SingleLocaleSidebar(this, maxLines, locale);
sidebar = new SingleLocaleSidebar(this, maxLines, objectiveName, locale);
}
sidebars().add(sidebar);
@@ -116,6 +117,15 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
return teamManager;
}
@Override
public @NotNull ObjectiveManager createObjectiveManager() {
checkClosed();
ObjectiveManagerImpl objectiveManager = new ObjectiveManagerImpl(this);
objectiveManagers().add(objectiveManager);
return objectiveManager;
}
@Override
public void close() {
if (closed) {
@@ -142,6 +152,16 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
}
}
if (objectiveManagers != null) {
objectiveTask.task().cancel();
synchronized (objectiveTask.lock()) {
for (ObjectiveManagerImpl objectiveManager : objectiveManagers) {
objectiveManager.close();
objectiveManager.tick();
}
}
}
if (sidebars != null) {
sidebarTask.task().cancel();
synchronized (sidebarTask.lock()) {
@@ -171,6 +191,19 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
return teamManagers;
}
public Set<ObjectiveManagerImpl> objectiveManagers() {
if (objectiveManagers == null) {
synchronized (lock) {
if (objectiveManagers == null) {
objectiveManagers = Collections.newSetFromMap(new ConcurrentHashMap<>(4, 0.75f, 2));
objectiveTask = new ObjectiveUpdaterTask(this);
}
}
}
return objectiveManagers;
}
public Set<AbstractSidebar> sidebars() {
if (sidebars == null) {
synchronized (lock) {
@@ -1,95 +0,0 @@
package net.megavex.scoreboardlibrary.implementation;
import java.util.List;
import java.util.UUID;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import net.megavex.scoreboardlibrary.implementation.team.TeamManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ScoreboardLibraryPlayer {
private final UUID playerUuid;
private final List<TeamManagerImpl> teamManagers = CollectionProvider.list(1);
private final List<AbstractSidebar> sidebars = CollectionProvider.list(1);
public ScoreboardLibraryPlayer(@NotNull Player player) {
this.playerUuid = player.getUniqueId();
}
public synchronized @Nullable TeamManagerImpl teamManager() {
if (teamManagers.isEmpty()) {
return null;
} else {
return teamManagers.get(0);
}
}
public synchronized void addTeamManager(@NotNull TeamManagerImpl teamManager) {
if (teamManagers.contains(teamManager)) {
throw new RuntimeException("TeamManager already registered");
}
teamManagers.add(teamManager);
if (teamManager() == teamManager) {
Player player = Bukkit.getPlayer(playerUuid);
if (player != null) {
teamManager.show(player);
}
}
}
public synchronized void removeTeamManager(@NotNull TeamManagerImpl teamManager) {
if (!teamManagers.remove(teamManager)) {
throw new RuntimeException("TeamManager not registered");
}
TeamManagerImpl newTeamManager = teamManager();
if (newTeamManager != null) {
Player player = Bukkit.getPlayer(playerUuid);
if (player != null) {
newTeamManager.show(player);
}
}
}
public @Nullable AbstractSidebar sidebar() {
if (sidebars.isEmpty()) {
return null;
} else {
return sidebars.get(0);
}
}
public synchronized void addSidebar(@NotNull AbstractSidebar sidebar) {
if (sidebars.contains(sidebar)) {
throw new RuntimeException("Sidebar already registered");
}
sidebars.add(sidebar);
if (sidebar() == sidebar) {
Player player = Bukkit.getPlayer(playerUuid);
if (player != null) {
sidebar.show(player);
}
}
}
public synchronized void removeSidebar(@NotNull AbstractSidebar sidebar) {
if (!sidebars.remove(sidebar)) {
throw new RuntimeException("Sidebar not registered");
}
@Nullable AbstractSidebar newSidebar = sidebar();
if (newSidebar != null) {
Player player = Bukkit.getPlayer(playerUuid);
if (player != null) {
newSidebar.show(player);
}
}
}
}
@@ -1,8 +1,5 @@
package net.megavex.scoreboardlibrary.implementation.commons;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -11,23 +8,26 @@ import net.kyori.adventure.translation.GlobalTranslator;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import static net.kyori.adventure.text.Component.empty;
import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection;
import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.parseChar;
public final class LegacyFormatUtil {
private static final Map<NamedTextColor, Character> legacyMap;
private static final Map<NamedTextColor, ChatColor> chatColorMap;
static {
ChatColor[] values = ChatColor.values();
legacyMap = CollectionProvider.map(values.length);
chatColorMap = new HashMap<>(values.length);
for (ChatColor value : values) {
if (!value.isColor()) continue;
char c = value.getChar();
LegacyFormat format = Objects.requireNonNull(parseChar(c));
legacyMap.put((NamedTextColor) format.color(), c);
LegacyFormat format = Objects.requireNonNull(parseChar(value.getChar()));
chatColorMap.put((NamedTextColor) format.color(), value);
}
}
@@ -47,15 +47,28 @@ public final class LegacyFormatUtil {
return text.substring(0, lastNotColorCharIndex + 1);
}
public static String serialize(@Nullable Component component, Locale locale) {
public static String serialize(@Nullable Component component, @Nullable Locale locale) {
if (component == null || component == empty()) return "";
return legacySection().serialize(GlobalTranslator.render(component, locale));
Component translated;
if (locale != null) {
translated = GlobalTranslator.render(component, locale);
} else {
translated = component;
}
return legacySection().serialize(translated);
}
public static char getChar(NamedTextColor color) {
public static char getChar(@Nullable NamedTextColor color) {
if (color == null) return 'r';
return legacyMap.getOrDefault(color, '\0');
ChatColor chatColor = chatColorMap.getOrDefault(color, ChatColor.WHITE);
return chatColor.getChar();
}
public static int getIndex(@Nullable NamedTextColor color) {
ChatColor chatColor = chatColorMap.getOrDefault(color == null ? NamedTextColor.WHITE : color, ChatColor.WHITE);
return chatColor.ordinal();
}
}
@@ -0,0 +1,20 @@
package net.megavex.scoreboardlibrary.implementation.commons;
/**
* Represents different strategies of rendering sidebar lines.
*/
public enum LineRenderingStrategy {
/**
* For versions older than 1.13, where team properties are stored as strings and have a limit of 16 characters.
*/
LEGACY,
/**
* For versions 1.13+, where team properties are stored as components with no limits.
*/
MODERN,
/**
* For versions 1.20.3+, where the score display name can be used instead of team prefix team.
* Not currently being used.
*/
POST_MODERN
}
@@ -1,19 +1,30 @@
package net.megavex.scoreboardlibrary.implementation.commons;
import net.kyori.adventure.translation.Translator;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Locale;
import net.kyori.adventure.translation.Translator;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
public interface LocaleProvider {
Locale DEFAULT_LOCALE = Locale.US;
public final class LocaleProvider {
private static final Locale DEFAULT_LOCALE = Locale.US;
private static final Function<Player, Locale> PROVIDER = get();
static @NotNull LocaleProvider localeProvider() {
private LocaleProvider() {
}
public static @NotNull Locale locale(Player player) {
return PROVIDER.apply(player);
}
private static @NotNull Function<Player, Locale> get() {
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
try {
@SuppressWarnings("JavaLangInvokeHandleSignature")
MethodHandle adventureMethod = lookup.findVirtual(Player.class, "locale", MethodType.methodType(Locale.class));
return player -> {
try {
@@ -53,6 +64,4 @@ public interface LocaleProvider {
throw new RuntimeException("No way to get players locale found");
}
}
@NotNull Locale locale(@NotNull Player player);
}
@@ -0,0 +1,244 @@
package net.megavex.scoreboardlibrary.implementation.objective;
import com.google.common.base.Preconditions;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveManager;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore;
import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.player.PlayerDisplayable;
import net.megavex.scoreboardlibrary.implementation.player.ScoreboardLibraryPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public final class ObjectiveManagerImpl implements ObjectiveManager, PlayerDisplayable {
private final ScoreboardLibraryImpl library;
private final Map<String, ScoreboardObjectiveImpl> objectives = new ConcurrentHashMap<>();
private final Map<ObjectiveDisplaySlot, ScoreboardObjectiveImpl> displaySlots = new HashMap<>();
private final Set<Player> players = new HashSet<>(8);
private final Set<Player> displayingPlayers = new HashSet<>(8);
private final Queue<ObjectiveManagerTask> taskQueue = new ConcurrentLinkedQueue<>();
private boolean closed;
public ObjectiveManagerImpl(@NotNull ScoreboardLibraryImpl library) {
this.library = library;
}
public @NotNull Queue<ObjectiveManagerTask> taskQueue() {
return taskQueue;
}
@Override
public @NotNull ScoreboardObjective create(@NotNull String name) {
Preconditions.checkNotNull(name);
checkClosed();
return objectives.computeIfAbsent(name, i -> {
ObjectivePacketAdapter packetAdapter = library.packetAdapter().createObjectiveAdapter(name);
ScoreboardObjectiveImpl objective = new ScoreboardObjectiveImpl(packetAdapter, taskQueue, name);
taskQueue.add(new ObjectiveManagerTask.AddObjective(objective));
return objective;
});
}
@Override
public void remove(@NotNull ScoreboardObjective objective) {
Preconditions.checkNotNull(objective);
checkClosed();
if (!(objective instanceof ScoreboardObjectiveImpl)) {
return;
}
ScoreboardObjectiveImpl impl = (ScoreboardObjectiveImpl) objective;
if (objectives.values().remove(impl)) {
displaySlots.values().removeIf(e -> e == impl);
impl.close();
taskQueue.add(new ObjectiveManagerTask.RemoveObjective(impl));
}
}
@Override
public void display(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjective objective) {
Preconditions.checkNotNull(displaySlot);
Preconditions.checkNotNull(objective);
checkClosed();
if (!(objective instanceof ScoreboardObjectiveImpl)) {
throw new IllegalArgumentException("Invalid objective implementation");
}
ScoreboardObjectiveImpl impl = (ScoreboardObjectiveImpl) objective;
if (objectives.get(impl.name()) != impl) {
throw new IllegalArgumentException("Objective is from a different manager");
}
displaySlots.put(displaySlot, impl);
taskQueue.add(new ObjectiveManagerTask.UpdateDisplaySlot(displaySlot, impl));
}
@Override
public @NotNull Collection<Player> players() {
return Collections.unmodifiableSet(players);
}
@Override
public boolean addPlayer(@NotNull Player player) {
Preconditions.checkNotNull(player);
checkClosed();
if (!players.add(player)) {
return false;
}
taskQueue.add(new ObjectiveManagerTask.AddPlayer(player));
return true;
}
@Override
public boolean removePlayer(@NotNull Player player) {
Preconditions.checkNotNull(player);
checkClosed();
if (!players.remove(player)) {
return false;
}
taskQueue.add(new ObjectiveManagerTask.RemovePlayer(player, null));
return true;
}
@Override
public CompletableFuture<Void> removePlayerFuture(@NotNull Player player) {
Preconditions.checkNotNull(player);
checkClosed();
if (!players.remove(player)) {
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Void> future = new CompletableFuture<>();
taskQueue.add(new ObjectiveManagerTask.RemovePlayer(player, future));
return future;
}
@Override
public void close() {
if (!closed) {
closed = true;
taskQueue.add(ObjectiveManagerTask.Close.INSTANCE);
}
}
@Override
public boolean closed() {
return closed;
}
public boolean tick() {
while (true) {
ObjectiveManagerTask task = taskQueue.poll();
if (task == null) {
break;
}
if (task instanceof ObjectiveManagerTask.Close) {
for (ScoreboardObjectiveImpl objective : objectives.values()) {
objective.packetAdapter().remove(displayingPlayers);
}
for (Player player : players) {
Objects.requireNonNull(library.getPlayer(player)).objectiveManagerQueue().remove(this);
}
return false;
} else if (task instanceof ObjectiveManagerTask.AddPlayer) {
Player player = ((ObjectiveManagerTask.AddPlayer) task).player();
@NotNull ScoreboardLibraryPlayer slPlayer = library.getOrCreatePlayer(player);
slPlayer.objectiveManagerQueue().add(this);
} else if (task instanceof ObjectiveManagerTask.RemovePlayer) {
ObjectiveManagerTask.RemovePlayer removePlayerTask = (ObjectiveManagerTask.RemovePlayer) task;
Collection<Player> singleton = Collections.singleton(removePlayerTask.player());
for (ScoreboardObjectiveImpl objective : objectives.values()) {
objective.packetAdapter().remove(singleton);
}
displayingPlayers.remove(removePlayerTask.player());
Objects.requireNonNull(library.getPlayer(removePlayerTask.player())).objectiveManagerQueue().remove(this);
if (removePlayerTask.future() != null) {
removePlayerTask.future().complete(null);
}
} else if (task instanceof ObjectiveManagerTask.ReloadPlayer) {
Player player = ((ObjectiveManagerTask.ReloadPlayer) task).player();
Collection<Player> singleton = Collections.singleton(player);
for (ScoreboardObjectiveImpl objective : objectives.values()) {
objective.sendProperties(singleton, PropertiesPacketType.UPDATE);
for (Map.Entry<String, ObjectiveScore> entry : objective.scores().entrySet()) {
ObjectiveScore score = entry.getValue();
objective.packetAdapter().sendScore(singleton, entry.getKey(), score.value(), score.displayName(), score.format());
}
}
} else if (task instanceof ObjectiveManagerTask.AddObjective) {
ScoreboardObjectiveImpl objective = ((ObjectiveManagerTask.AddObjective) task).objective();
objective.sendProperties(displayingPlayers, PropertiesPacketType.CREATE);
objective.wasDisplayed(true);
} else if (task instanceof ObjectiveManagerTask.RemoveObjective) {
ScoreboardObjectiveImpl objective = ((ObjectiveManagerTask.RemoveObjective) task).objective();
objective.packetAdapter().remove(displayingPlayers);
} else if (task instanceof ObjectiveManagerTask.UpdateObjective) {
ScoreboardObjectiveImpl objective = ((ObjectiveManagerTask.UpdateObjective) task).objective();
objective.sendProperties(displayingPlayers, PropertiesPacketType.UPDATE);
} else if (task instanceof ObjectiveManagerTask.UpdateScore) {
ObjectiveManagerTask.UpdateScore updateScoreTask = ((ObjectiveManagerTask.UpdateScore) task);
ScoreboardObjectiveImpl objective = updateScoreTask.objective();
String entry = updateScoreTask.entry();
ObjectiveScore score = updateScoreTask.score();
if (score != null) {
objective.packetAdapter().sendScore(displayingPlayers, entry, score.value(), score.displayName(), score.format());
} else {
objective.packetAdapter().removeScore(displayingPlayers, entry);
}
} else if (task instanceof ObjectiveManagerTask.UpdateDisplaySlot) {
ObjectiveManagerTask.UpdateDisplaySlot updateDisplaySlotTask = (ObjectiveManagerTask.UpdateDisplaySlot) task;
ObjectiveDisplaySlot slot = updateDisplaySlotTask.displaySlot();
ScoreboardObjectiveImpl objective = updateDisplaySlotTask.objective();
objective.packetAdapter().display(displayingPlayers, slot);
}
}
return true;
}
@Override
public void display(@NotNull Player player) {
displayingPlayers.add(player);
Collection<Player> singleton = Collections.singleton(player);
for (ScoreboardObjectiveImpl objective : objectives.values()) {
if (!objective.wasDisplayed()) {
continue;
}
objective.sendProperties(singleton, PropertiesPacketType.CREATE);
for (Map.Entry<String, ObjectiveScore> entry : objective.scores().entrySet()) {
ObjectiveScore score = entry.getValue();
objective.packetAdapter().sendScore(singleton, entry.getKey(), score.value(), score.displayName(), score.format());
}
}
for (Map.Entry<ObjectiveDisplaySlot, ScoreboardObjectiveImpl> entry : displaySlots.entrySet()) {
if (!entry.getValue().wasDisplayed()) {
continue;
}
entry.getValue().packetAdapter().display(singleton, entry.getKey());
}
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException("ObjectiveManager is closed");
}
}
}
@@ -0,0 +1,141 @@
package net.megavex.scoreboardlibrary.implementation.objective;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public class ObjectiveManagerTask {
private ObjectiveManagerTask() {
}
public static final class Close extends ObjectiveManagerTask {
public static final Close INSTANCE = new Close();
private Close() {
}
}
public static final class AddPlayer extends ObjectiveManagerTask {
private final Player player;
public AddPlayer(@NotNull Player player) {
this.player = player;
}
public @NotNull Player player() {
return player;
}
}
public static final class RemovePlayer extends ObjectiveManagerTask {
private final Player player;
private final CompletableFuture<Void> future;
public RemovePlayer(@NotNull Player player, @Nullable CompletableFuture<Void> future) {
this.player = player;
this.future = future;
}
public @NotNull Player player() {
return player;
}
public @Nullable CompletableFuture<Void> future() {
return future;
}
}
public static final class ReloadPlayer extends ObjectiveManagerTask {
private final Player player;
public ReloadPlayer(@NotNull Player player) {
this.player = player;
}
public @NotNull Player player() {
return player;
}
}
public static final class AddObjective extends ObjectiveManagerTask {
private final ScoreboardObjectiveImpl objective;
public AddObjective(@NotNull ScoreboardObjectiveImpl team) {
this.objective = team;
}
public @NotNull ScoreboardObjectiveImpl objective() {
return objective;
}
}
public static final class RemoveObjective extends ObjectiveManagerTask {
private final ScoreboardObjectiveImpl objective;
public RemoveObjective(@NotNull ScoreboardObjectiveImpl team) {
this.objective = team;
}
public @NotNull ScoreboardObjectiveImpl objective() {
return objective;
}
}
public static final class UpdateObjective extends ObjectiveManagerTask {
private final ScoreboardObjectiveImpl objective;
public UpdateObjective(@NotNull ScoreboardObjectiveImpl objective) {
this.objective = objective;
}
public @NotNull ScoreboardObjectiveImpl objective() {
return objective;
}
}
public static final class UpdateScore extends ObjectiveManagerTask {
private final ScoreboardObjectiveImpl objective;
private final String entry;
private final ObjectiveScore score;
public UpdateScore(@NotNull ScoreboardObjectiveImpl objective, @NotNull String entry, @Nullable ObjectiveScore score) {
this.objective = objective;
this.entry = entry;
this.score = score;
}
public @NotNull ScoreboardObjectiveImpl objective() {
return objective;
}
public @NotNull String entry() {
return entry;
}
public @Nullable ObjectiveScore score() {
return score;
}
}
public static final class UpdateDisplaySlot extends ObjectiveManagerTask {
private final ObjectiveDisplaySlot displaySlot;
private final ScoreboardObjectiveImpl objective;
public UpdateDisplaySlot(@NotNull ObjectiveDisplaySlot displaySlot, @NotNull ScoreboardObjectiveImpl objective) {
this.displaySlot = displaySlot;
this.objective = objective;
}
public @NotNull ObjectiveDisplaySlot displaySlot() {
return displaySlot;
}
public @NotNull ScoreboardObjectiveImpl objective() {
return objective;
}
}
}
@@ -0,0 +1,48 @@
package net.megavex.scoreboardlibrary.implementation.objective;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.scheduler.RunningTask;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.logging.Level;
public final class ObjectiveUpdaterTask implements Runnable {
private final ScoreboardLibraryImpl scoreboardLibrary;
private final RunningTask task;
private final Object lock = new Object();
public ObjectiveUpdaterTask(@NotNull ScoreboardLibraryImpl scoreboardLibrary) {
this.scoreboardLibrary = scoreboardLibrary;
this.task = scoreboardLibrary.taskScheduler().runEveryTick(this);
}
public @NotNull RunningTask task() {
return task;
}
public @NotNull Object lock() {
return lock;
}
@Override
public void run() {
synchronized (lock) {
Iterator<ObjectiveManagerImpl> iterator = scoreboardLibrary.objectiveManagers().iterator();
while (iterator.hasNext()) {
ObjectiveManagerImpl objectiveManager = iterator.next();
boolean result;
try {
result = objectiveManager.tick();
} catch (Exception e) {
scoreboardLibrary.plugin().getLogger().log(Level.WARNING, "an error occurred while updating an ObjectiveManager instance", e);
continue;
}
if (!result) {
iterator.remove();
}
}
}
}
}
@@ -0,0 +1,147 @@
package net.megavex.scoreboardlibrary.implementation.objective;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveScore;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.api.objective.ScoreboardObjective;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static net.kyori.adventure.text.Component.empty;
public final class ScoreboardObjectiveImpl implements ScoreboardObjective {
private final ObjectivePacketAdapter packetAdapter;
private final Queue<ObjectiveManagerTask> taskQueue;
private final String name;
private final Map<String, ObjectiveScore> scores = new HashMap<>();
private Component value = empty();
private ObjectiveRenderType renderType = ObjectiveRenderType.INTEGER;
private ScoreFormat defaultScoreFormat;
private boolean closed;
private boolean wasDisplayed;
public ScoreboardObjectiveImpl(@NotNull ObjectivePacketAdapter packetAdapter, @NotNull Queue<ObjectiveManagerTask> taskQueue, @NotNull String name) {
this.packetAdapter = packetAdapter;
this.taskQueue = taskQueue;
this.name = name;
}
public ObjectivePacketAdapter packetAdapter() {
return packetAdapter;
}
public @NotNull Map<String, ObjectiveScore> scores() {
return scores;
}
public @NotNull String name() {
return name;
}
public void close() {
closed = true;
}
@Override
public @NotNull Component value() {
return value;
}
@Override
public @NotNull ScoreboardObjective value(@NotNull ComponentLike value) {
Component component = value.asComponent();
if (!this.value.equals(component)) {
this.value = component;
if (!closed) {
taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this));
}
}
return this;
}
@Override
public @NotNull ObjectiveRenderType renderType() {
return renderType;
}
@Override
public @NotNull ScoreboardObjective renderType(@NotNull ObjectiveRenderType renderType) {
if (this.renderType != renderType) {
this.renderType = renderType;
if (!closed) {
taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this));
}
}
return this;
}
@Override
public @Nullable ScoreFormat defaultScoreFormat() {
return defaultScoreFormat;
}
@Override
public void defaultScoreFormat(@Nullable ScoreFormat defaultScoreFormat) {
if (!Objects.equals(this.defaultScoreFormat, defaultScoreFormat)) {
this.defaultScoreFormat = defaultScoreFormat;
taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this));
}
}
@Override
public void refreshProperties() {
if (!closed) {
taskQueue.add(new ObjectiveManagerTask.UpdateObjective(this));
}
}
@Override
public @Nullable ObjectiveScore scoreInfo(@NotNull String entry) {
return scores.get(entry);
}
@Override
public @NotNull ScoreboardObjective score(@NotNull String entry, ObjectiveScore score) {
ObjectiveScore oldScore = scores.put(entry, score);
if (!Objects.equals(oldScore, score)) {
taskQueue.add(new ObjectiveManagerTask.UpdateScore(this, entry, score));
}
return this;
}
@Override
public @NotNull ScoreboardObjective removeScore(@NotNull String entry) {
if (scores.remove(entry) != null) {
taskQueue.add(new ObjectiveManagerTask.UpdateScore(this, entry, null));
}
return this;
}
@Override
public void refreshScore(@NotNull String entry) {
ObjectiveScore score = scores.get(entry);
if (score != null) {
taskQueue.add(new ObjectiveManagerTask.UpdateScore(this, entry, score));
}
}
public void sendProperties(@NotNull Collection<Player> players, @NotNull PropertiesPacketType packetType) {
packetAdapter.sendProperties(players, packetType, value, renderType, defaultScoreFormat);
}
public boolean wasDisplayed() {
return wasDisplayed;
}
public void wasDisplayed(boolean wasDisplayed) {
this.wasDisplayed = wasDisplayed;
}
}
@@ -1,16 +1,17 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter;
import java.util.Collection;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule;
import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public interface ImmutableTeamProperties<T> {
default @NotNull Collection<String> entries() {
return Set.of();
default @NotNull Collection<String> syncedEntries() {
return ImmutableList.of();
}
@NotNull T displayName();
@@ -0,0 +1,15 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter;
import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public interface PacketAdapterProvider {
@NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName);
@NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName);
@NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player);
}
@@ -0,0 +1,13 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter;
import org.bukkit.entity.Player;
public interface PacketSender<T> {
void sendPacket(Player player, T packet);
default void sendPacket(Iterable<Player> players, T packet) {
for (Player player : players) {
sendPacket(player, packet);
}
}
}
@@ -0,0 +1,6 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter;
public enum PropertiesPacketType {
CREATE,
UPDATE
}
@@ -0,0 +1,41 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
public final class ChatColorUtil {
private static final MethodHandle FROM_NAME_METHOD, GET_INDEX_METHOD;
static {
Class<?> enumChatFormatClass = ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("EnumChatFormat"));
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
Method fromNameMethod = enumChatFormatClass.getMethod("b", String.class);
FROM_NAME_METHOD = lookup.unreflect(fromNameMethod);
Method getIndexMethod = enumChatFormatClass.getMethod("b");
GET_INDEX_METHOD = lookup.unreflect(getIndexMethod);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
private ChatColorUtil() {
}
public static int getColorIndex(@NotNull NamedTextColor color) {
String name = NamedTextColor.NAMES.key(color);
try {
Object format = FROM_NAME_METHOD.invoke(name);
return (int) GET_INDEX_METHOD.invoke(format);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,16 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
// TODO: should be moved to MinecraftReflection at some point
public final class LegacyMinecraftClasses {
private static final String NMS_VERSION_STRING = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
private LegacyMinecraftClasses() {
}
public static @NotNull String server(String path) {
return "net.minecraft.server." + NMS_VERSION_STRING + "." + path;
}
}
@@ -0,0 +1,50 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.MinecraftClasses;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil;
import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public final class LegacyPacketSender implements PacketSender<Object> {
public static final LegacyPacketSender INSTANCE = new LegacyPacketSender();
private static final MethodHandle GET_HANDLE_METHOD, PLAYER_CONNECTION_FIELD, SEND_PACKET_METHOD;
static {
Class<?> packetClass = ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("Packet"));
Class<?> craftPlayerClass = ReflectUtil.getClassOrThrow(MinecraftClasses.craftBukkit("entity.CraftPlayer"));
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
Method getHandleMethod = craftPlayerClass.getMethod("getHandle");
GET_HANDLE_METHOD = lookup.unreflect(getHandleMethod);
Field playerConnectionField = getHandleMethod.getReturnType().getField("playerConnection");
PLAYER_CONNECTION_FIELD = lookup.unreflectGetter(playerConnectionField);
Method sendPacketMethod = playerConnectionField.getType().getMethod("sendPacket", packetClass);
SEND_PACKET_METHOD = lookup.unreflect(sendPacketMethod);
} catch (NoSuchMethodException | IllegalAccessException | NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
}
private LegacyPacketSender() {
}
@Override
public void sendPacket(Player player, Object packet) {
try {
Object handle = GET_HANDLE_METHOD.invoke(player);
Object playerConnection = PLAYER_CONNECTION_FIELD.invoke(handle);
SEND_PACKET_METHOD.invoke(playerConnection, packet);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,124 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectiveConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection;
public final class ObjectivePacketAdapterImpl implements ObjectivePacketAdapter {
private final String objectiveName;
private Object removePacket;
public ObjectivePacketAdapterImpl(@NotNull String objectiveName) {
this.objectiveName = objectiveName;
}
@Override
public @NotNull String objectiveName() {
return objectiveName;
}
@Override
public void display(@NotNull Collection<Player> players, @NotNull ObjectiveDisplaySlot slot) {
Object packet = PacketAccessors.DISPLAY_OBJECTIVE_CONSTRUCTOR.invoke();
PacketAccessors.DISPLAY_OBJECTIVE_POSITION.set(packet, ObjectiveConstants.displaySlotIndex(slot, false));
PacketAccessors.DISPLAY_OBJECTIVE_NAME.set(packet, objectiveName);
LegacyPacketSender.INSTANCE.sendPacket(players, packet);
}
@Override
public void sendProperties(
@NotNull Collection<Player> players,
@NotNull PropertiesPacketType packetType,
@NotNull Component value,
@NotNull ObjectiveRenderType renderType,
@Nullable ScoreFormat scoreFormat
) {
LocalePacketUtil.sendLocalePackets(
LegacyPacketSender.INSTANCE,
players,
locale -> createPropertiesPacket(packetType, GlobalTranslator.render(value, locale), renderType)
);
}
@Override
public void remove(@NotNull Collection<Player> players) {
if (removePacket == null) {
removePacket = PacketAccessors.OBJECTIVE_CONSTRUCTOR.invoke();
PacketAccessors.OBJECTIVE_NAME_FIELD.set(removePacket, objectiveName);
PacketAccessors.OBJECTIVE_DISPLAY_NAME_FIELD.set(removePacket, "");
PacketAccessors.OBJECTIVE_MODE_FIELD.set(removePacket, ObjectiveConstants.MODE_REMOVE);
}
LegacyPacketSender.INSTANCE.sendPacket(players, removePacket);
}
@Override
public void sendScore(
@NotNull Collection<Player> players,
@NotNull String entry,
int value,
@Nullable Component display,
@Nullable ScoreFormat scoreFormat
) {
Object packet = PacketAccessors.SCORE_CONSTRUCTOR.invoke(entry);
PacketAccessors.SCORE_OBJECTIVE_NAME_FIELD.set(packet, objectiveName);
PacketAccessors.SCORE_VALUE_FIELD.set(packet, value);
if (PacketAccessors.SCORE_ACTION_FIELD_1_8 != null) {
PacketAccessors.SCORE_ACTION_FIELD_1_8.set(packet, PacketAccessors.SCORE_ACTION_CHANGE_1_8);
} else {
PacketAccessors.SCORE_ACTION_FIELD_1_7.set(packet, 0);
}
LegacyPacketSender.INSTANCE.sendPacket(players, packet);
}
@Override
public void removeScore(@NotNull Collection<Player> players, @NotNull String entry) {
Object packet = PacketAccessors.SCORE_CONSTRUCTOR.invoke(entry);
PacketAccessors.SCORE_OBJECTIVE_NAME_FIELD.set(packet, objectiveName);
LegacyPacketSender.INSTANCE.sendPacket(players, packet);
}
private @NotNull Object createPropertiesPacket(
@NotNull PropertiesPacketType packetType,
@NotNull Component value,
@NotNull ObjectiveRenderType renderType
) {
Object packet = PacketAccessors.OBJECTIVE_CONSTRUCTOR.invoke();
PacketAccessors.OBJECTIVE_NAME_FIELD.set(packet, objectiveName);
PacketAccessors.OBJECTIVE_MODE_FIELD.set(packet, ObjectiveConstants.mode(packetType));
String legacyValue = LegacyFormatUtil.limitLegacyText(legacySection().serialize(value), ObjectiveConstants.LEGACY_VALUE_CHAR_LIMIT);
PacketAccessors.OBJECTIVE_DISPLAY_NAME_FIELD.set(packet, legacyValue);
if (PacketAccessors.OBJECTIVE_HEALTH_DISPLAY_FIELD != null) {
Object nmsRenderType;
switch (renderType) {
case INTEGER:
nmsRenderType = PacketAccessors.HEALTH_DISPLAY_INTEGER;
break;
case HEARTS:
nmsRenderType = PacketAccessors.HEALTH_DISPLAY_HEARTS;
break;
default:
throw new IllegalStateException();
}
PacketAccessors.OBJECTIVE_HEALTH_DISPLAY_FIELD.set(packet, nmsRenderType);
}
return packet;
}
}
@@ -0,0 +1,80 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ConstructorAccessor;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.FieldAccessor;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil;
import java.util.Collection;
public final class PacketAccessors {
public static final Class<?> HEALTH_DISPLAY_CLASS =
ReflectUtil.getOptionalClass(LegacyMinecraftClasses.server("IScoreboardCriteria$EnumScoreboardHealthDisplay"));
public static final Object HEALTH_DISPLAY_INTEGER = HEALTH_DISPLAY_CLASS != null ? ReflectUtil.getEnumInstance(HEALTH_DISPLAY_CLASS, "INTEGER") : null;
public static final Object HEALTH_DISPLAY_HEARTS = HEALTH_DISPLAY_CLASS != null ? ReflectUtil.getEnumInstance(HEALTH_DISPLAY_CLASS, "HEARTS") : null;
private static final boolean IS_AT_LEAST_1_8 = HEALTH_DISPLAY_INTEGER != null;
private static final boolean IS_AT_LEAST_1_9 = ReflectUtil.getOptionalClass(LegacyMinecraftClasses.server("ScoreboardTeamBase$EnumTeamPush")) != null;
public static final Class<?> OBJECTIVE_CLASS =
ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("PacketPlayOutScoreboardObjective"));
public static final ConstructorAccessor<?> OBJECTIVE_CONSTRUCTOR = ReflectUtil.findConstructor(OBJECTIVE_CLASS);
public static final FieldAccessor<Object, Integer> OBJECTIVE_MODE_FIELD =
ReflectUtil.findFieldUnchecked(OBJECTIVE_CLASS, 0, int.class);
public static final FieldAccessor<Object, String> OBJECTIVE_NAME_FIELD =
ReflectUtil.findFieldUnchecked(OBJECTIVE_CLASS, 0, String.class);
public static final FieldAccessor<Object, String> OBJECTIVE_DISPLAY_NAME_FIELD =
ReflectUtil.findFieldUnchecked(OBJECTIVE_CLASS, 1, String.class);
public static final FieldAccessor<Object, Object> OBJECTIVE_HEALTH_DISPLAY_FIELD =
HEALTH_DISPLAY_CLASS != null ? ReflectUtil.findFieldUnchecked(OBJECTIVE_CLASS, 0, HEALTH_DISPLAY_CLASS) : null;
public static final Class<?> DISPLAY_OBJECTIVE_CLASS =
ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("PacketPlayOutScoreboardDisplayObjective"));
public static final ConstructorAccessor<?> DISPLAY_OBJECTIVE_CONSTRUCTOR = ReflectUtil.findConstructor(DISPLAY_OBJECTIVE_CLASS);
public static final FieldAccessor<Object, Integer> DISPLAY_OBJECTIVE_POSITION =
ReflectUtil.findFieldUnchecked(DISPLAY_OBJECTIVE_CLASS, 0, int.class);
public static final FieldAccessor<Object, String> DISPLAY_OBJECTIVE_NAME =
ReflectUtil.findFieldUnchecked(DISPLAY_OBJECTIVE_CLASS, 0, String.class);
public static final Class<?> SCORE_CLASS =
ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("PacketPlayOutScoreboardScore"));
public static final ConstructorAccessor<?> SCORE_CONSTRUCTOR = ReflectUtil.findConstructor(SCORE_CLASS, String.class);
public static final FieldAccessor<Object, String> SCORE_OBJECTIVE_NAME_FIELD =
ReflectUtil.findFieldUnchecked(SCORE_CLASS, 1, String.class);
public static final FieldAccessor<Object, Integer> SCORE_VALUE_FIELD =
ReflectUtil.findFieldUnchecked(SCORE_CLASS, 0, int.class);
public static final Class<?> SCORE_ACTION_CLASS =
ReflectUtil.getOptionalClass(LegacyMinecraftClasses.server("PacketPlayOutScoreboardScore$EnumScoreboardAction"));
public static final FieldAccessor<Object, Object> SCORE_ACTION_FIELD_1_8 =
SCORE_ACTION_CLASS != null ? ReflectUtil.findFieldUnchecked(SCORE_CLASS, 0, SCORE_ACTION_CLASS) : null;
public static final Object SCORE_ACTION_CHANGE_1_8 = SCORE_ACTION_FIELD_1_8 != null ? ReflectUtil.getEnumInstance(SCORE_ACTION_CLASS, "CHANGE") : null;
public static final FieldAccessor<Object, Integer> SCORE_ACTION_FIELD_1_7 =
SCORE_ACTION_CLASS == null ? ReflectUtil.findFieldUnchecked(SCORE_CLASS, 1, int.class) : null;
public static final Class<?> TEAM_CLASS =
ReflectUtil.getClassOrThrow(LegacyMinecraftClasses.server("PacketPlayOutScoreboardTeam"));
public static final ConstructorAccessor<?> TEAM_CONSTRUCTOR = ReflectUtil.findConstructor(TEAM_CLASS);
public static final FieldAccessor<Object, String> TEAM_NAME_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, 0, String.class);
public static final FieldAccessor<Object, String> TEAM_DISPLAY_NAME_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, 1, String.class);
public static final FieldAccessor<Object, String> TEAM_PREFIX_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, 2, String.class);
public static final FieldAccessor<Object, String> TEAM_SUFFIX_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, 3, String.class);
public static final FieldAccessor<Object, String> TEAM_NAME_TAG_VISIBILITY_FIELD =
IS_AT_LEAST_1_8 ? ReflectUtil.findFieldUnchecked(TEAM_CLASS, 4, String.class) : null;
public static final FieldAccessor<Object, String> TEAM_COLLISION_RULE_FIELD =
IS_AT_LEAST_1_9 ? ReflectUtil.findFieldUnchecked(TEAM_CLASS, 5, String.class) : null;
public static final FieldAccessor<Object, Integer> TEAM_COLOR_FIELD =
IS_AT_LEAST_1_8 ? ReflectUtil.findFieldUnchecked(TEAM_CLASS, 0, int.class) : null;
@SuppressWarnings("rawtypes")
public static final FieldAccessor<Object, Collection> TEAM_ENTRIES_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, 0, Collection.class);
public static final FieldAccessor<Object, Integer> TEAM_MODE_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, IS_AT_LEAST_1_8 ? 1 : 0, int.class);
public static final FieldAccessor<Object, Integer> TEAM_RULES_FIELD =
ReflectUtil.findFieldUnchecked(TEAM_CLASS, IS_AT_LEAST_1_8 ? 2 : 1, int.class);
private PacketAccessors() {
}
}
@@ -0,0 +1,29 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
@SuppressWarnings("unused")
public final class PacketAdapterProviderImpl implements PacketAdapterProvider {
public PacketAdapterProviderImpl(Plugin plugin) {}
@Override
public @NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName) {
return new TeamsPacketAdapterImpl(teamName);
}
@Override
public @NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName) {
return new ObjectivePacketAdapterImpl(objectiveName);
}
@Override
public @NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player) {
return LineRenderingStrategy.LEGACY;
}
}
@@ -0,0 +1,131 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.legacy;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Locale;
import static net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil.limitLegacyText;
public final class TeamsPacketAdapterImpl implements TeamsPacketAdapter {
private final String teamName;
private Object removePacket;
public TeamsPacketAdapterImpl(@NotNull String teamName) {
this.teamName = teamName;
}
@Override
public @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties<Component> properties) {
return new AdventureTeamDisplayPacketAdapter(properties);
}
@Override
public @NotNull TeamDisplayPacketAdapter createLegacyTeamDisplayAdapter(@NotNull ImmutableTeamProperties<String> properties) {
return new LegacyTeamDisplayPacketAdapter(properties);
}
private abstract class AbstractTeamDisplayPacketAdapter<C> implements TeamDisplayPacketAdapter {
protected final ImmutableTeamProperties<C> properties;
public AbstractTeamDisplayPacketAdapter(@NotNull ImmutableTeamProperties<C> properties) {
this.properties = properties;
}
@Override
public void removeTeam(@NotNull Iterable<Player> players) {
if (removePacket == null) {
removePacket = PacketAccessors.TEAM_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(removePacket, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(removePacket, TeamConstants.MODE_REMOVE);
}
LegacyPacketSender.INSTANCE.sendPacket(players, removePacket);
}
@Override
public void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection<Player> players, @NotNull Collection<String> entries) {
Object packet = PacketAccessors.TEAM_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(packet, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(packet, TeamConstants.mode(packetType));
PacketAccessors.TEAM_ENTRIES_FIELD.set(packet, entries);
LegacyPacketSender.INSTANCE.sendPacket(players, packet);
}
@Override
public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection<Player> players) {
LocalePacketUtil.sendLocalePackets(
LegacyPacketSender.INSTANCE,
players,
locale -> {
String displayName = limitLegacyText(toLegacy(properties.displayName(), locale), TeamConstants.DISPLAY_NAME_LEGACY_LIMIT);
String prefix = limitLegacyText(toLegacy(properties.prefix(), locale), TeamConstants.PREFIX_SUFFIX_LEGACY_LIMIT);
String suffix = limitLegacyText(toLegacy(properties.suffix(), locale), TeamConstants.PREFIX_SUFFIX_LEGACY_LIMIT);
Object packet = PacketAccessors.TEAM_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(packet, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(packet, TeamConstants.mode(packetType));
PacketAccessors.TEAM_DISPLAY_NAME_FIELD.set(packet, displayName);
PacketAccessors.TEAM_PREFIX_FIELD.set(packet, prefix);
PacketAccessors.TEAM_SUFFIX_FIELD.set(packet, suffix);
PacketAccessors.TEAM_RULES_FIELD.set(packet, properties.packOptions());
if (PacketAccessors.TEAM_NAME_TAG_VISIBILITY_FIELD != null) {
PacketAccessors.TEAM_NAME_TAG_VISIBILITY_FIELD.set(packet, properties.nameTagVisibility().key());
}
NamedTextColor color = properties.playerColor();
if (PacketAccessors.TEAM_COLOR_FIELD != null && color != null) {
int teamColorField = ChatColorUtil.getColorIndex(color);
PacketAccessors.TEAM_COLOR_FIELD.set(packet, teamColorField);
}
if (PacketAccessors.TEAM_COLLISION_RULE_FIELD != null) {
PacketAccessors.TEAM_COLLISION_RULE_FIELD.set(packet, properties.collisionRule().key());
}
if (packetType == PropertiesPacketType.CREATE) {
PacketAccessors.TEAM_ENTRIES_FIELD.set(packet, ImmutableList.copyOf(properties.syncedEntries()));
}
return packet;
}
);
}
protected abstract @NotNull String toLegacy(@NotNull C component, @NotNull Locale locale);
}
private class AdventureTeamDisplayPacketAdapter extends AbstractTeamDisplayPacketAdapter<Component> {
public AdventureTeamDisplayPacketAdapter(@NotNull ImmutableTeamProperties<Component> properties) {
super(properties);
}
@Override
protected @NotNull String toLegacy(@NotNull Component component, @NotNull Locale locale) {
return LegacyFormatUtil.serialize(component, locale);
}
}
private class LegacyTeamDisplayPacketAdapter extends AbstractTeamDisplayPacketAdapter<String> {
public LegacyTeamDisplayPacketAdapter(@NotNull ImmutableTeamProperties<String> properties) {
super(properties);
}
@Override
protected @NotNull String toLegacy(@NotNull String component, @NotNull Locale locale) {
return component;
}
}
}
@@ -0,0 +1,361 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule;
import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility;
import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.*;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodType;
import java.util.*;
public final class PacketAccessors {
private PacketAccessors() {
}
public static final boolean IS_1_17_OR_ABOVE, IS_1_20_2_OR_ABOVE, IS_1_20_3_OR_ABOVE, IS_1_20_5_OR_ABOVE, IS_1_21_5_OR_ABOVE, IS_1_21_6_OR_ABOVE;
private static final String OLD_NMS_VERSION_STRING;
static {
boolean is1_17OrAbove = false;
try {
Class.forName("net.minecraft.world.item.BundleItem");
is1_17OrAbove = true;
} catch (ClassNotFoundException ignored) {
}
IS_1_17_OR_ABOVE = is1_17OrAbove;
if (!is1_17OrAbove) {
OLD_NMS_VERSION_STRING = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
} else {
OLD_NMS_VERSION_STRING = null;
}
boolean is1_20_2OrAbove = false;
try {
Class.forName("net.minecraft.world.scores.DisplaySlot");
is1_20_2OrAbove = true;
} catch (ClassNotFoundException ignored) {
}
IS_1_20_2_OR_ABOVE = is1_20_2OrAbove;
boolean is1_20_3OrAbove = false;
try {
Class.forName("net.minecraft.network.chat.numbers.NumberFormat");
is1_20_3OrAbove = true;
} catch (ClassNotFoundException ignored) {
}
IS_1_20_3_OR_ABOVE = is1_20_3OrAbove;
boolean is1_20_5OrAbove = false;
try {
Class.forName("net.minecraft.network.protocol.common.ClientboundTransferPacket");
is1_20_5OrAbove = true;
} catch (ClassNotFoundException ignored) {
}
IS_1_20_5_OR_ABOVE = is1_20_5OrAbove;
boolean is1_21_5OrAbove = false;
try {
Class.forName("net.minecraft.world.item.component.BlocksAttacks");
is1_21_5OrAbove = true;
} catch (ClassNotFoundException ignored) {
}
IS_1_21_5_OR_ABOVE = is1_21_5OrAbove;
boolean is1_21_6OrAbove = false;
try {
Class.forName("net.minecraft.server.dialog.Dialog");
is1_21_6OrAbove = true;
} catch (ClassNotFoundException ignored) {
}
IS_1_21_6_OR_ABOVE = is1_21_6OrAbove;
}
public static final Class<?> PKT_CLASS,
SET_OBJECTIVE_PKT_CLASS,
SET_DISPLAY_OBJECTIVE_PKT_CLASS,
SET_SCORE_PKT_CLASS,
RESET_SCORE_PKT_CLASS,
SET_PLAYER_TEAM_PKT_CLASS,
TEAM_PARAMETERS_PKT_CLASS, // 1.17
COMPONENT_CLASS,
CHAT_SERIALIZER_CLASS,
COMPONENT_SERIALIZATION_CLASS,
STYLE_CLASS,
STYLE_SERIALIZER_CLASS,
NUMBER_FORMAT_CLASS,
DISPLAY_SLOT_CLASS, // 1.20.2
OBJECTIVE_CLASS,
TEAM_VISIBILITY_CLASS,
TEAM_COLLISION_RULE_CLASS,
CHAT_FORMATTING_CLASS,
OBJECTIVE_CRITERIA_RENDER_TYPE_CLASS,
DATA_RESULT_CLASS,
DYNAMIC_OPS_CLASS,
JSON_OPS_CLASS,
CODEC_CLASS,
SERVER_PLAYER_CLASS,
PLAYER_CONNECTION_CLASS,
ADVENTURE_COMPONENT_CLASS;
static {
PKT_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.protocol.Packet", oldSpigotClassName("Packet"));
SET_OBJECTIVE_PKT_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective", oldSpigotClassName("PacketPlayOutScoreboardObjective"));
SET_DISPLAY_OBJECTIVE_PKT_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective", oldSpigotClassName("PacketPlayOutScoreboardDisplayObjective"));
SET_SCORE_PKT_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.protocol.game.ClientboundSetScorePacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore", oldSpigotClassName("PacketPlayOutScoreboardScore"));
RESET_SCORE_PKT_CLASS = ReflectUtil.getOptionalClass("net.minecraft.network.protocol.game.ClientboundResetScorePacket", "net.minecraft.network.protocol.game.ClientboundResetScorePacket");
SET_PLAYER_TEAM_PKT_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam", oldSpigotClassName("PacketPlayOutScoreboardTeam"));
TEAM_PARAMETERS_PKT_CLASS = ReflectUtil.getOptionalClass("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket$Parameters", "net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$b");
COMPONENT_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.Component", "net.minecraft.network.chat.IChatBaseComponent", oldSpigotClassName("IChatBaseComponent"));
CHAT_SERIALIZER_CLASS = ReflectUtil.getOptionalClass("net.minecraft.network.chat.Component$Serializer", "net.minecraft.network.chat.IChatBaseComponent$ChatSerializer", oldSpigotClassName("IChatBaseComponent$ChatSerializer"));
COMPONENT_SERIALIZATION_CLASS = ReflectUtil.getOptionalClass("net.minecraft.network.chat.ComponentSerialization");
STYLE_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.Style", "net.minecraft.network.chat.ChatModifier", oldSpigotClassName("ChatModifier"));
STYLE_SERIALIZER_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.Style$Serializer", "net.minecraft.network.chat.ChatModifier$ChatModifierSerializer", oldSpigotClassName("ChatModifier$ChatModifierSerializer"));
NUMBER_FORMAT_CLASS = ReflectUtil.getOptionalClass("net.minecraft.network.chat.numbers.NumberFormat");
DISPLAY_SLOT_CLASS = ReflectUtil.getOptionalClass("net.minecraft.world.scores.DisplaySlot");
OBJECTIVE_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.world.scores.Objective", "net.minecraft.world.scores.ScoreboardObjective", oldSpigotClassName("ScoreboardObjective"));
TEAM_VISIBILITY_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.world.scores.Team$Visibility", "net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility", oldSpigotClassName("ScoreboardTeamBase$EnumNameTagVisibility"));
TEAM_COLLISION_RULE_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.world.scores.Team$CollisionRule", "net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush", oldSpigotClassName("ScoreboardTeamBase$EnumTeamPush"));
CHAT_FORMATTING_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.ChatFormatting", "net.minecraft.EnumChatFormat", oldSpigotClassName("EnumChatFormat"));
OBJECTIVE_CRITERIA_RENDER_TYPE_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType", "net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay", oldSpigotClassName("IScoreboardCriteria$EnumScoreboardHealthDisplay"));
DATA_RESULT_CLASS = ReflectUtil.getOptionalClass("com.mojang.serialization.DataResult");
DYNAMIC_OPS_CLASS = ReflectUtil.getOptionalClass("com.mojang.serialization.DynamicOps");
JSON_OPS_CLASS = ReflectUtil.getOptionalClass("com.mojang.serialization.JsonOps");
CODEC_CLASS = ReflectUtil.getOptionalClass("com.mojang.serialization.Codec");
SERVER_PLAYER_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.server.level.ServerPlayer", "net.minecraft.server.level.EntityPlayer", oldSpigotClassName("EntityPlayer"));
PLAYER_CONNECTION_CLASS = ReflectUtil.getClassOrThrow("net.minecraft.server.network.ServerGamePacketListenerImpl", "net.minecraft.server.network.PlayerConnection", oldSpigotClassName("PlayerConnection"));
ADVENTURE_COMPONENT_CLASS = ReflectUtil.getOptionalClass("io.papermc.paper.adventure.AdventureComponent");
}
static {
if (IS_1_20_3_OR_ABOVE) {
RESET_SCORE_CONSTRUCTOR = ReflectUtil.findConstructor(Objects.requireNonNull(RESET_SCORE_PKT_CLASS), String.class, String.class);
} else {
RESET_SCORE_CONSTRUCTOR = null;
}
if (IS_1_20_5_OR_ABOVE) {
OBJECTIVE_NUMBER_FORMAT_FIELD = ReflectUtil.findFieldUnchecked(SET_OBJECTIVE_PKT_CLASS, 0, Optional.class);
SCORE_CONSTRUCTOR = ReflectUtil.findConstructor(SET_SCORE_PKT_CLASS, String.class, String.class, int.class, Optional.class, Optional.class);
SCORE_1_20_2_METHOD_CHANGE = null;
SCORE_1_20_2_METHOD_REMOVE = null;
} else if (IS_1_20_3_OR_ABOVE) {
assert NUMBER_FORMAT_CLASS != null;
OBJECTIVE_NUMBER_FORMAT_FIELD = ReflectUtil.findFieldUnchecked(SET_OBJECTIVE_PKT_CLASS, 0, NUMBER_FORMAT_CLASS);
SCORE_CONSTRUCTOR = ReflectUtil.findConstructor(SET_SCORE_PKT_CLASS, String.class, String.class, int.class, COMPONENT_CLASS, NUMBER_FORMAT_CLASS);
SCORE_1_20_2_METHOD_CHANGE = null;
SCORE_1_20_2_METHOD_REMOVE = null;
} else {
OBJECTIVE_NUMBER_FORMAT_FIELD = null;
Class<?> methodClass = ReflectUtil.getClassOrThrow("net.minecraft.server.ServerScoreboard$Method", "net.minecraft.server.ScoreboardServer$Action", oldSpigotClassName("ScoreboardServer$Action"));
SCORE_1_20_2_METHOD_CHANGE = ReflectUtil.getEnumInstance(methodClass, "CHANGE", "a");
SCORE_1_20_2_METHOD_REMOVE = ReflectUtil.getEnumInstance(methodClass, "REMOVE", "b");
SCORE_CONSTRUCTOR = ReflectUtil.findConstructor(SET_SCORE_PKT_CLASS, methodClass, String.class, String.class, int.class);
}
if (PacketAccessors.DATA_RESULT_CLASS != null) {
RESULT_UNWRAP_METHOD = ReflectUtil.findMethod(PacketAccessors.DATA_RESULT_CLASS, false, MethodType.methodType(Optional.class), "result");
try {
JSON_OPS = PacketAccessors.JSON_OPS_CLASS.getField("INSTANCE").get(null);
} catch (IllegalAccessException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
CODEC_PARSE = ReflectUtil.findMethod(PacketAccessors.CODEC_CLASS, false, MethodType.methodType(PacketAccessors.DATA_RESULT_CLASS, PacketAccessors.DYNAMIC_OPS_CLASS, Object.class), "parse");
} else {
RESULT_UNWRAP_METHOD = null;
JSON_OPS = null;
CODEC_PARSE = null;
}
}
public static final MethodAccessor RESULT_UNWRAP_METHOD;
public static final MethodAccessor CODEC_PARSE;
public static final Object JSON_OPS;
public static final Map<NamedTextColor, Object> NMS_CHAT_FORMATTING_MAP = new HashMap<>();
static {
Object[] chatFormattings = CHAT_FORMATTING_CLASS.getEnumConstants();
FieldAccessor<Object, Object> charField = ReflectUtil.findFieldUnchecked(CHAT_FORMATTING_CLASS, 0, char.class);
outer:
for (NamedTextColor color : NamedTextColor.NAMES.values()) {
char c = LegacyFormatUtil.getChar(color);
for (Object chatFormatting : chatFormattings) {
if (c == (char) charField.get(chatFormatting)) {
NMS_CHAT_FORMATTING_MAP.put(color, chatFormatting);
continue outer;
}
}
throw new RuntimeException("No chat formatting enum constant found for " + color.toString());
}
}
public static final Object NAME_TAG_VISIBILITY_ALWAYS = ReflectUtil.getEnumInstance(TEAM_VISIBILITY_CLASS, "ALWAYS");
public static final Object NAME_TAG_VISIBILITY_NEVER = ReflectUtil.getEnumInstance(TEAM_VISIBILITY_CLASS, "NEVER");
public static final Object NAME_TAG_VISIBILITY_HIDE_FOR_OTHER_TEAMS = ReflectUtil.getEnumInstance(TEAM_VISIBILITY_CLASS, "HIDE_FOR_OTHER_TEAMS");
public static final Object NAME_TAG_VISIBILITY_HIDE_FOR_OWN_TEAM = ReflectUtil.getEnumInstance(TEAM_VISIBILITY_CLASS, "HIDE_FOR_OWN_TEAM");
public static final Object COLLISION_RULE_ALWAYS = ReflectUtil.getEnumInstance(TEAM_COLLISION_RULE_CLASS, "ALWAYS");
public static final Object COLLISION_RULE_NEVER = ReflectUtil.getEnumInstance(TEAM_COLLISION_RULE_CLASS, "NEVER");
public static final Object COLLISION_RULE_PUSH_OTHER_TEAMS = ReflectUtil.getEnumInstance(TEAM_COLLISION_RULE_CLASS, "PUSH_OTHER_TEAMS");
public static final Object COLLISION_RULE_PUSH_OWN_TEAM = ReflectUtil.getEnumInstance(TEAM_COLLISION_RULE_CLASS, "PUSH_OWN_TEAM");
public static final Object RENDER_TYPE_INTEGER = ReflectUtil.getEnumInstance(OBJECTIVE_CRITERIA_RENDER_TYPE_CLASS, "INTEGER");
public static final Object RENDER_TYPE_HEARTS = ReflectUtil.getEnumInstance(OBJECTIVE_CRITERIA_RENDER_TYPE_CLASS, "HEARTS");
public static final List<Object> DISPLAY_SLOT_VALUES = DISPLAY_SLOT_CLASS == null ? null : Arrays.asList(DISPLAY_SLOT_CLASS.getEnumConstants());
public static final ConstructorAccessor<?> ADVENTURE_COMPONENT_CONSTRUCTOR =
ADVENTURE_COMPONENT_CLASS != null ? ReflectUtil.findOptionalConstructor(ADVENTURE_COMPONENT_CLASS, Component.class) : null;
// --- OBJECTIVES ---
public static final PacketConstructor<?> OBJECTIVE_PACKET_CONSTRUCTOR =
ReflectUtil.getEmptyConstructor(SET_OBJECTIVE_PKT_CLASS);
public static final FieldAccessor<Object, String> OBJECTIVE_NAME_FIELD =
ReflectUtil.findFieldUnchecked(SET_OBJECTIVE_PKT_CLASS, 0, String.class);
public static final FieldAccessor<Object, Object> OBJECTIVE_VALUE_FIELD =
ReflectUtil.findFieldUnchecked(SET_OBJECTIVE_PKT_CLASS, 0, COMPONENT_CLASS);
public static final FieldAccessor<Object, Object> OBJECTIVE_RENDER_TYPE_FIELD =
ReflectUtil.findFieldUnchecked(SET_OBJECTIVE_PKT_CLASS, 0, OBJECTIVE_CRITERIA_RENDER_TYPE_CLASS);
// Optional<NumberFormat> for 1.20.5+, NumberFormat for below
public static final FieldAccessor<Object, Object> OBJECTIVE_NUMBER_FORMAT_FIELD;
public static final FieldAccessor<Object, Integer> OBJECTIVE_MODE_FIELD =
ReflectUtil.findFieldUnchecked(SET_OBJECTIVE_PKT_CLASS, 0, int.class);
public static final ConstructorAccessor<?> DISPLAY_CONSTRUCTOR;
public static final FieldAccessor<Object, String> DISPLAY_OBJECTIVE_NAME =
ReflectUtil.findFieldUnchecked(SET_DISPLAY_OBJECTIVE_PKT_CLASS, 0, String.class);
public static final FieldAccessor<Object, Object> DISPLAY_SLOT;
static {
if (IS_1_20_2_OR_ABOVE) {
assert DISPLAY_SLOT_CLASS != null;
DISPLAY_CONSTRUCTOR = ReflectUtil.findConstructor(SET_DISPLAY_OBJECTIVE_PKT_CLASS, DISPLAY_SLOT_CLASS, OBJECTIVE_CLASS);
DISPLAY_SLOT = null;
} else if (IS_1_17_OR_ABOVE) {
DISPLAY_CONSTRUCTOR = ReflectUtil.findConstructor(SET_DISPLAY_OBJECTIVE_PKT_CLASS, int.class, OBJECTIVE_CLASS);
DISPLAY_SLOT = null;
} else {
DISPLAY_CONSTRUCTOR = ReflectUtil.findConstructor(SET_DISPLAY_OBJECTIVE_PKT_CLASS);
DISPLAY_SLOT = ReflectUtil.findFieldUnchecked(SET_DISPLAY_OBJECTIVE_PKT_CLASS, 0, int.class);
}
}
public static final ConstructorAccessor<?> RESET_SCORE_CONSTRUCTOR; // 1.20.3+
public static final ConstructorAccessor<?> SCORE_CONSTRUCTOR;
public static final Object SCORE_1_20_2_METHOD_CHANGE, SCORE_1_20_2_METHOD_REMOVE;
// --- TEAMS ---
public static final PacketConstructor<?> PARAMETERS_CONSTRUCTOR;
public static final ConstructorAccessor<?> TEAM_PACKET_CONSTRUCTOR;
public static final FieldAccessor<Object, String> TEAM_NAME_FIELD;
public static final FieldAccessor<Object, Integer> TEAM_MODE_FIELD;
public static final FieldAccessor<Object, Collection<String>> TEAM_ENTRIES_FIELD;
public static final FieldAccessor<Object, Object> DISPLAY_NAME_FIELD;
public static final FieldAccessor<Object, Object> PREFIX_FIELD;
public static final FieldAccessor<Object, Object> SUFFIX_FIELD;
public static final FieldAccessor<Object, Object> NAME_TAG_VISIBILITY_FIELD;
public static final FieldAccessor<Object, Object> COLLISION_RULE_FIELD;
public static final FieldAccessor<Object, Object> COLOR_FIELD;
public static final FieldAccessor<Object, Integer> OPTIONS_FIELD;
static {
if (IS_1_17_OR_ABOVE) {
assert TEAM_PARAMETERS_PKT_CLASS != null;
PARAMETERS_CONSTRUCTOR = ReflectUtil.getEmptyConstructor(TEAM_PARAMETERS_PKT_CLASS);
TEAM_PACKET_CONSTRUCTOR = ReflectUtil.findConstructor(SET_PLAYER_TEAM_PKT_CLASS, String.class, int.class, Optional.class, Collection.class);
TEAM_NAME_FIELD = null;
TEAM_MODE_FIELD = null;
TEAM_ENTRIES_FIELD = null;
DISPLAY_NAME_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 0, COMPONENT_CLASS);
PREFIX_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 1, COMPONENT_CLASS);
SUFFIX_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 2, COMPONENT_CLASS);
if (IS_1_21_5_OR_ABOVE) {
NAME_TAG_VISIBILITY_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 0, TEAM_VISIBILITY_CLASS);
COLLISION_RULE_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 0, TEAM_COLLISION_RULE_CLASS);
} else {
NAME_TAG_VISIBILITY_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 0, String.class);
COLLISION_RULE_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 1, String.class);
}
COLOR_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 0, CHAT_FORMATTING_CLASS);
OPTIONS_FIELD = ReflectUtil.findFieldUnchecked(TEAM_PARAMETERS_PKT_CLASS, 0, int.class);
} else {
PARAMETERS_CONSTRUCTOR = null;
TEAM_PACKET_CONSTRUCTOR = ReflectUtil.findConstructor(SET_PLAYER_TEAM_PKT_CLASS);
TEAM_NAME_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 0, String.class);
TEAM_MODE_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 0, int.class);
TEAM_ENTRIES_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 0, Collection.class);
DISPLAY_NAME_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 0, COMPONENT_CLASS);
PREFIX_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 1, COMPONENT_CLASS);
SUFFIX_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 2, COMPONENT_CLASS);
NAME_TAG_VISIBILITY_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 1, String.class);
COLLISION_RULE_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 2, String.class);
COLOR_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 0, CHAT_FORMATTING_CLASS);
OPTIONS_FIELD = ReflectUtil.findFieldUnchecked(SET_PLAYER_TEAM_PKT_CLASS, 1, int.class);
}
}
public static Object nameTagVisibility(NameTagVisibility value) {
switch (value) {
case NEVER:
return PacketAccessors.NAME_TAG_VISIBILITY_NEVER;
case ALWAYS:
return PacketAccessors.NAME_TAG_VISIBILITY_ALWAYS;
case HIDE_FOR_OTHER_TEAMS:
return PacketAccessors.NAME_TAG_VISIBILITY_HIDE_FOR_OTHER_TEAMS;
case HIDE_FOR_OWN_TEAM:
return PacketAccessors.NAME_TAG_VISIBILITY_HIDE_FOR_OWN_TEAM;
default:
throw new IllegalStateException("unknown name tag visibility " + value.name());
}
}
public static Object collisionRule(CollisionRule value) {
switch (value) {
case NEVER:
return PacketAccessors.COLLISION_RULE_NEVER;
case ALWAYS:
return PacketAccessors.COLLISION_RULE_ALWAYS;
case PUSH_OTHER_TEAMS:
return PacketAccessors.COLLISION_RULE_PUSH_OTHER_TEAMS;
case PUSH_OWN_TEAM:
return PacketAccessors.COLLISION_RULE_PUSH_OWN_TEAM;
default:
throw new IllegalStateException("unknown collision rule " + value.name());
}
}
public static @NotNull Object fromAdventureComponent(@NotNull Component component) {
return Objects.requireNonNull(PacketAccessors.ADVENTURE_COMPONENT_CONSTRUCTOR).invoke(component);
}
public static @Nullable String oldSpigotClassName(String clazz) {
if (OLD_NMS_VERSION_STRING != null) {
return "net.minecraft.server." + OLD_NMS_VERSION_STRING + "." + clazz;
}
return null;
}
}
@@ -0,0 +1,80 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.ViaAPI;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketAdapterProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective.PaperObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective.SpigotObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team.PaperTeamsPacketAdapterImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team.SpigotTeamsPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.ModernComponentProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.ModernPacketSender;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.WeakHashMap;
@SuppressWarnings("unused")
public final class PacketAdapterProviderImpl implements PacketAdapterProvider {
private final ViaAPI<Player> via;
private final ModernPacketSender packetSender;
private final WeakHashMap<Player, Integer> viaTeamPacketIds = new WeakHashMap<>();
public PacketAdapterProviderImpl(Plugin plugin) {
final String viaPlugin = "ViaVersion";
boolean isViaEnabled = plugin.getServer().getPluginManager().isPluginEnabled(viaPlugin);
boolean isViaAllowed = plugin.getDescription().getSoftDepend().contains(viaPlugin) || plugin.getDescription().getDepend().contains(viaPlugin);
if (isViaEnabled && isViaAllowed) {
//noinspection unchecked
this.via = (ViaAPI<Player>) Via.getAPI();
} else {
this.via = null;
}
this.packetSender = new ModernPacketSender(this.via);
}
@Override
public @NotNull ObjectivePacketAdapter createObjectiveAdapter(@NotNull String objectiveName) {
return ModernComponentProvider.IS_NATIVE_ADVENTURE
? new PaperObjectivePacketAdapter(this, objectiveName)
: new SpigotObjectivePacketAdapter(this, objectiveName);
}
@Override
public @NotNull TeamsPacketAdapter createTeamPacketAdapter(@NotNull String teamName) {
return ModernComponentProvider.IS_NATIVE_ADVENTURE
? new PaperTeamsPacketAdapterImpl(this, teamName)
: new SpigotTeamsPacketAdapter(this, teamName);
}
@Override
public @NotNull LineRenderingStrategy lineRenderingStrategy(@NotNull Player player) {
if (this.via != null) {
final ProtocolVersion ver = this.via.getPlayerProtocolVersion(player);
if (ver.olderThan(ProtocolVersion.v1_13)) {
//System.out.println("[DEBUG] " + player.getName() + " is legacy player");
return LineRenderingStrategy.LEGACY;
}
}
//System.out.println("[DEBUG] is modern player");
return LineRenderingStrategy.MODERN;
}
public @Nullable ViaAPI<Player> via() {
return via;
}
public @NotNull ModernPacketSender packetSender() {
return packetSender;
}
public @NotNull WeakHashMap<Player, Integer> viaTeamPacketIds() {
return viaTeamPacketIds;
}
}
@@ -0,0 +1,46 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern;
import com.google.gson.JsonElement;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.MethodAccessor;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
public final class RelocatedGson {
public static final String SERVER_GSON_PKG = "com.go".concat("ogle.gson");
public static final boolean IS_GSON_RELOCATED;
public static final Object JSON_PARSER;
public static final MethodAccessor PARSE_STRING_METHOD;
private RelocatedGson() {
}
static {
IS_GSON_RELOCATED = !JsonElement.class.getName().equals(SERVER_GSON_PKG + ".JsonElement");
if (IS_GSON_RELOCATED) {
try {
Class<?> jsonElementClass = Class.forName(SERVER_GSON_PKG + ".JsonElement");
Class<?> jsonParserClass = Class.forName(SERVER_GSON_PKG + ".JsonParser");
JSON_PARSER = jsonParserClass.getConstructors()[0].newInstance();
PARSE_STRING_METHOD = ReflectUtil.findMethod(jsonParserClass, false, MethodType.methodType(jsonElementClass, String.class), "parse");
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else {
JSON_PARSER = null;
PARSE_STRING_METHOD = null;
}
//System.out.println("GSON RELOCATED = " + IS_GSON_RELOCATED);
}
public static Object convertToServerGson(final JsonElement element) {
if (IS_GSON_RELOCATED) {
assert PARSE_STRING_METHOD != null;
//System.out.println("fixed");
return PARSE_STRING_METHOD.invoke(JSON_PARSER, element.toString());
}
return element;
}
}
@@ -0,0 +1,133 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectiveConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
public abstract class AbstractObjectivePacketAdapter implements ObjectivePacketAdapter {
protected final PacketAdapterProviderImpl provider;
protected final String objectiveName;
private Object removePacket;
public AbstractObjectivePacketAdapter(@NotNull PacketAdapterProviderImpl provider, @NotNull String objectiveName) {
this.provider = provider;
this.objectiveName = objectiveName;
}
@Override
public @NotNull String objectiveName() {
return objectiveName;
}
@Override
public void display(@NotNull Collection<Player> players, @NotNull ObjectiveDisplaySlot slot) {
provider.packetSender().sendPacket(players, createDisplayPacket(slot));
}
@Override
public void remove(@NotNull Collection<Player> players) {
if (removePacket == null) {
this.removePacket = PacketAccessors.OBJECTIVE_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.OBJECTIVE_NAME_FIELD.set(removePacket, objectiveName);
PacketAccessors.OBJECTIVE_MODE_FIELD.set(removePacket, ObjectiveConstants.MODE_REMOVE);
}
provider.packetSender().sendPacket(players, removePacket);
}
@Override
public void removeScore(@NotNull Collection<Player> players, @NotNull String entry) {
Object packet;
if (PacketAccessors.IS_1_20_3_OR_ABOVE) {
packet = Objects.requireNonNull(PacketAccessors.RESET_SCORE_CONSTRUCTOR)
.invoke(entry, objectiveName);
} else {
packet = Objects.requireNonNull(PacketAccessors.SCORE_CONSTRUCTOR)
.invoke(PacketAccessors.SCORE_1_20_2_METHOD_REMOVE, objectiveName, entry, 0);
}
provider.packetSender().sendPacket(players, packet);
}
protected @NotNull Object createDisplayPacket(@NotNull ObjectiveDisplaySlot displaySlot) {
Object packet;
if (PacketAccessors.IS_1_20_2_OR_ABOVE) {
packet = Objects.requireNonNull(PacketAccessors.DISPLAY_CONSTRUCTOR)
.invoke(PacketAccessors.DISPLAY_SLOT_VALUES.get(ObjectiveConstants.displaySlotIndex(displaySlot)), null);
} else if (PacketAccessors.IS_1_17_OR_ABOVE){
packet = Objects.requireNonNull(PacketAccessors.DISPLAY_CONSTRUCTOR)
.invoke(ObjectiveConstants.displaySlotIndex(displaySlot), null);
} else {
assert PacketAccessors.DISPLAY_SLOT != null;
packet = Objects.requireNonNull(PacketAccessors.DISPLAY_CONSTRUCTOR).invoke();
PacketAccessors.DISPLAY_SLOT.set(packet, ObjectiveConstants.displaySlotIndex(displaySlot));
}
PacketAccessors.DISPLAY_OBJECTIVE_NAME.set(packet, objectiveName);
return packet;
}
protected @NotNull Object createScorePacket(
@NotNull String entry,
int value,
@Nullable Object nmsDisplay,
@Nullable Object numberFormat
) {
if (PacketAccessors.IS_1_20_5_OR_ABOVE) {
return Objects.requireNonNull(PacketAccessors.SCORE_CONSTRUCTOR)
.invoke(entry, objectiveName, value, Optional.ofNullable(nmsDisplay), Optional.ofNullable(numberFormat));
} else if (PacketAccessors.IS_1_20_3_OR_ABOVE) {
return Objects.requireNonNull(PacketAccessors.SCORE_CONSTRUCTOR)
.invoke(entry, objectiveName, value, nmsDisplay, numberFormat);
} else {
return Objects.requireNonNull(PacketAccessors.SCORE_CONSTRUCTOR)
.invoke(PacketAccessors.SCORE_1_20_2_METHOD_CHANGE, objectiveName, entry, value);
}
}
protected @NotNull Object createObjectivePacket(
@NotNull PropertiesPacketType packetType,
@NotNull Object nmsValue,
@NotNull ObjectiveRenderType renderType,
@Nullable Object numberFormat
) {
Object packet = PacketAccessors.OBJECTIVE_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.OBJECTIVE_MODE_FIELD.set(packet, ObjectiveConstants.mode(packetType));
PacketAccessors.OBJECTIVE_NAME_FIELD.set(packet, objectiveName);
PacketAccessors.OBJECTIVE_VALUE_FIELD.set(packet, nmsValue);
if (PacketAccessors.IS_1_20_3_OR_ABOVE) {
assert PacketAccessors.OBJECTIVE_NUMBER_FORMAT_FIELD != null;
Object value;
if (PacketAccessors.IS_1_20_5_OR_ABOVE) {
value = Optional.ofNullable(numberFormat);
} else {
value = numberFormat;
}
PacketAccessors.OBJECTIVE_NUMBER_FORMAT_FIELD.set(packet, value);
}
Object nmsRenderType;
switch (renderType) {
case INTEGER:
nmsRenderType = PacketAccessors.RENDER_TYPE_INTEGER;
break;
case HEARTS:
nmsRenderType = PacketAccessors.RENDER_TYPE_HEARTS;
break;
default:
throw new IllegalStateException("unknown render type " + renderType);
}
PacketAccessors.OBJECTIVE_RENDER_TYPE_FIELD.set(packet, nmsRenderType);
return packet;
}
}
@@ -0,0 +1,42 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.ModernComponentProvider;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public final class PaperObjectivePacketAdapter extends AbstractObjectivePacketAdapter {
public PaperObjectivePacketAdapter(@NotNull PacketAdapterProviderImpl provider, @NotNull String objectiveName) {
super(provider, objectiveName);
}
@Override
public void sendScore(@NotNull Collection<Player> players, @NotNull String entry, int value, @Nullable Component display, @Nullable ScoreFormat scoreFormat) {
Object nmsDisplay = display == null ? null : ModernComponentProvider.fromAdventure(display, null);
Object numberFormat = ScoreFormatConverter.convert(null, scoreFormat);
Object packet = createScorePacket(entry, value, nmsDisplay, numberFormat);
provider.packetSender().sendPacket(players, packet);
}
@Override
public void sendProperties(
@NotNull Collection<Player> players,
@NotNull PropertiesPacketType packetType,
@NotNull Component value,
@NotNull ObjectiveRenderType renderType,
@Nullable ScoreFormat scoreFormat
) {
Object nmsValue = PacketAccessors.fromAdventureComponent(value);
Object numberFormat = ScoreFormatConverter.convert(null, scoreFormat);
Object packet = createObjectivePacket(packetType, nmsValue, renderType, numberFormat);
provider.packetSender().sendPacket(players, packet);
}
}
@@ -0,0 +1,64 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.RelocatedGson;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.ModernComponentProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ConstructorAccessor;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
import java.util.Optional;
import static net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson;
public final class ScoreFormatConverter {
private static final Object STYLE_CODEC;
private static final Object BLANK;
private static final ConstructorAccessor<?> STYLED_CONSTRUCTOR;
private static final ConstructorAccessor<?> FIXED_CONSTRUCTOR;
static {
if (PacketAccessors.IS_1_20_3_OR_ABOVE) {
STYLE_CODEC = ReflectUtil.findFieldUnchecked(PacketAccessors.STYLE_SERIALIZER_CLASS, 0, PacketAccessors.CODEC_CLASS, true).get(null);
Class<?> blankFormatClass = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.numbers.BlankFormat");
BLANK = ReflectUtil.findFieldUnchecked(blankFormatClass, 0, blankFormatClass, true).get(null);
Class<?> styledFormatClass = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.numbers.StyledFormat");
STYLED_CONSTRUCTOR = ReflectUtil.findConstructor(styledFormatClass, PacketAccessors.STYLE_CLASS);
Class<?> fixedFormatClass = ReflectUtil.getClassOrThrow("net.minecraft.network.chat.numbers.FixedFormat");
FIXED_CONSTRUCTOR = ReflectUtil.findConstructor(fixedFormatClass, PacketAccessors.COMPONENT_CLASS);
} else {
STYLE_CODEC = null;
BLANK = null;
STYLED_CONSTRUCTOR = null;
FIXED_CONSTRUCTOR = null;
}
}
private ScoreFormatConverter() {
}
public static @Nullable Object convert(@Nullable Locale locale, @Nullable ScoreFormat format) {
if (format == null || !PacketAccessors.IS_1_20_3_OR_ABOVE) {
return null;
}
if (format == ScoreFormat.blank()) {
return BLANK;
} else if (format instanceof ScoreFormat.Styled) {
Object json = RelocatedGson.convertToServerGson(gson().serializer().toJsonTree(((ScoreFormat.Styled) format).style()));
Object result = PacketAccessors.CODEC_PARSE.invoke(STYLE_CODEC, PacketAccessors.JSON_OPS, json);
//noinspection OptionalGetWithoutIsPresent
Object style = ((Optional<?>) PacketAccessors.RESULT_UNWRAP_METHOD.invoke(result)).get();
return STYLED_CONSTRUCTOR.invoke(style);
} else if (format instanceof ScoreFormat.Fixed) {
return FIXED_CONSTRUCTOR.invoke(ModernComponentProvider.fromAdventure(((ScoreFormat.Fixed) format).content(), locale));
} else {
throw new IllegalArgumentException("Invalid score format: " + format);
}
}
}
@@ -0,0 +1,47 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.objective;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.ModernComponentProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public final class SpigotObjectivePacketAdapter extends AbstractObjectivePacketAdapter {
public SpigotObjectivePacketAdapter(@NotNull PacketAdapterProviderImpl provider, @NotNull String objectiveName) {
super(provider, objectiveName);
}
@Override
public void sendScore(@NotNull Collection<Player> players, @NotNull String entry, int value, @Nullable Component display, @Nullable ScoreFormat scoreFormat) {
LocalePacketUtil.sendLocalePackets(provider.packetSender(), players, locale -> {
Object nmsDisplay = display == null ? null : ModernComponentProvider.fromAdventure(display, locale);
Object numberFormat = ScoreFormatConverter.convert(locale, scoreFormat);
return createScorePacket(entry, value, nmsDisplay, numberFormat);
});
}
@Override
public void sendProperties(
@NotNull Collection<Player> players,
@NotNull PropertiesPacketType packetType,
@NotNull Component value,
@NotNull ObjectiveRenderType renderType,
@Nullable ScoreFormat scoreFormat
) {
LocalePacketUtil.sendLocalePackets(
provider.packetSender(),
players,
locale -> {
Object numberFormat = ScoreFormatConverter.convert(locale, scoreFormat);
return createObjectivePacket(packetType, ModernComponentProvider.fromAdventure(value, locale), renderType, numberFormat);
}
);
}
}
@@ -0,0 +1,102 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Optional;
public abstract class AbstractTeamsPacketAdapterImpl implements TeamsPacketAdapter {
protected final PacketAdapterProviderImpl provider;
protected final String teamName;
private Object removePacket;
public AbstractTeamsPacketAdapterImpl(@NotNull PacketAdapterProviderImpl provider, @NotNull String teamName) {
this.provider = provider;
this.teamName = teamName;
}
@Override
public @NotNull TeamDisplayPacketAdapter createLegacyTeamDisplayAdapter(@NotNull ImmutableTeamProperties<String> properties) {
return new LegacyTeamDisplayPacketAdapter(provider, teamName, properties);
}
public abstract class TeamDisplayPacketAdapterImpl implements TeamDisplayPacketAdapter {
protected final ImmutableTeamProperties<Component> properties;
public TeamDisplayPacketAdapterImpl(ImmutableTeamProperties<Component> properties) {
this.properties = properties;
}
@Override
public void removeTeam(@NotNull Iterable<Player> players) {
if (removePacket == null) {
if (PacketAccessors.IS_1_17_OR_ABOVE) {
removePacket = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke(
teamName,
TeamConstants.MODE_REMOVE,
null,
Collections.emptyList()
);
} else {
assert PacketAccessors.TEAM_NAME_FIELD != null;
assert PacketAccessors.TEAM_MODE_FIELD != null;
removePacket = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(removePacket, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(removePacket, TeamConstants.MODE_REMOVE);
}
}
provider.packetSender().sendPacket(players, removePacket);
}
@Override
public void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection<Player> players, @NotNull Collection<String> entries) {
if (PacketAccessors.IS_1_17_OR_ABOVE) {
Object packet = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke(
teamName,
TeamConstants.mode(packetType),
Optional.empty(),
entries
);
provider.packetSender().sendPacket(players, packet);
} else {
assert PacketAccessors.TEAM_NAME_FIELD != null;
assert PacketAccessors.TEAM_MODE_FIELD != null;
assert PacketAccessors.TEAM_ENTRIES_FIELD != null;
Object packet = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(packet, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(packet, TeamConstants.mode(packetType));
PacketAccessors.TEAM_ENTRIES_FIELD.set(packet, entries);
provider.packetSender().sendPacket(players, packet);
}
}
protected void fillParameters(@NotNull Object parameters, @UnknownNullability Locale locale) {
if (PacketAccessors.IS_1_21_5_OR_ABOVE) {
PacketAccessors.NAME_TAG_VISIBILITY_FIELD.set(parameters, PacketAccessors.nameTagVisibility(properties.nameTagVisibility()));
PacketAccessors.COLLISION_RULE_FIELD.set(parameters, PacketAccessors.collisionRule(properties.collisionRule()));
} else {
PacketAccessors.NAME_TAG_VISIBILITY_FIELD.set(parameters, properties.nameTagVisibility().key());
PacketAccessors.COLLISION_RULE_FIELD.set(parameters, properties.collisionRule().key());
}
Object color = PacketAccessors.NMS_CHAT_FORMATTING_MAP.get(properties.playerColor() != null ? properties.playerColor() : NamedTextColor.WHITE);
PacketAccessors.COLOR_FIELD.set(parameters, color);
PacketAccessors.OPTIONS_FIELD.set(parameters, properties.packOptions());
}
}
}
@@ -0,0 +1,162 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team;
import com.viaversion.viaversion.api.ViaAPI;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.protocol.packet.provider.PacketTypeMap;
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
import com.viaversion.viaversion.api.type.Types;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import static net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil.limitLegacyText;
public final class LegacyTeamDisplayPacketAdapter implements TeamDisplayPacketAdapter {
private final PacketAdapterProviderImpl provider;
private final String teamName;
private final ImmutableTeamProperties<String> properties;
public LegacyTeamDisplayPacketAdapter(final PacketAdapterProviderImpl provider, final String teamName, final ImmutableTeamProperties<String> properties) {
this.provider = provider;
this.teamName = teamName;
this.properties = properties;
}
// have to send every team packet from viaversion otherwise it won't work on 1.7 players
// because ViaRewind's 1.8->1.7 conversion cancels team packets it doesn't recognize and fucks everything up
// https://github.com/ViaVersion/ViaRewind/blob/e0c1f5311521bf062bbdae9f96dc9149d8c13e28/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/rewriter/ScoreboardPacketRewriter1_8.java#L179
@Override
public void removeTeam(@NotNull Iterable<Player> players) {
ViaAPI<Player> via = provider.via();
assert via != null;
for (final Player player : players) {
final UserConnection conn = via.getConnection(player.getUniqueId());
if (conn == null) continue;
final ByteBuf buf = Unpooled.buffer(128);
Types.VAR_INT.writePrimitive(buf, teamsPacketId(player, conn));
Types.STRING.write(buf, teamName);
Types.BYTE.writePrimitive(buf, (byte) TeamConstants.MODE_REMOVE);
via.sendRawPacket(player.getUniqueId(), buf);
}
}
@Override
public void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection<Player> players, @NotNull Collection<String> entries) {
ViaAPI<Player> via = provider.via();
assert via != null;
for (final Player player : players) {
final UserConnection conn = via.getConnection(player.getUniqueId());
if (conn == null) continue;
final ByteBuf buf = Unpooled.buffer(128);
Types.VAR_INT.writePrimitive(buf, teamsPacketId(player, conn));
Types.STRING.write(buf, teamName);
Types.BYTE.writePrimitive(buf, (byte) TeamConstants.mode(packetType));
if (conn.getProtocolInfo().protocolVersion().olderThanOrEqualTo(ProtocolVersion.v1_7_6)) {
Types.SHORT.writePrimitive(buf, (short) properties.syncedEntries().size());
} else {
Types.VAR_INT.writePrimitive(buf, properties.syncedEntries().size());
}
for (final String entry : entries) {
Types.STRING.write(buf, entry);
}
via.sendRawPacket(player.getUniqueId(), buf);
}
}
@Override
public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection<Player> players) {
ViaAPI<Player> via = provider.via();
assert via != null;
for (final Player player : players) {
final UserConnection conn = via.getConnection(player.getUniqueId());
if (conn == null) continue;
final ByteBuf buf = Unpooled.buffer(128);
Types.VAR_INT.writePrimitive(buf, teamsPacketId(player, conn));
Types.STRING.write(buf, teamName);
Types.BYTE.writePrimitive(buf, (byte) TeamConstants.mode(packetType));
final String displayName = limitLegacyText(properties.displayName(), TeamConstants.DISPLAY_NAME_LEGACY_LIMIT);
final String prefix = limitLegacyText(properties.prefix(), TeamConstants.PREFIX_SUFFIX_LEGACY_LIMIT);
final String suffix = limitLegacyText(properties.suffix(), TeamConstants.PREFIX_SUFFIX_LEGACY_LIMIT);
Types.STRING.write(buf, displayName);
Types.STRING.write(buf, prefix);
Types.STRING.write(buf, suffix);
Types.BYTE.writePrimitive(buf, (byte) properties.packOptions());
final boolean is1_7 = conn.getProtocolInfo().protocolVersion().olderThanOrEqualTo(ProtocolVersion.v1_7_6);
if (!is1_7) {
Types.STRING.write(buf, properties.nameTagVisibility().key());
}
if (conn.getProtocolInfo().protocolVersion().newerThanOrEqualTo(ProtocolVersion.v1_9)) {
Types.STRING.write(buf, properties.collisionRule().key());
}
if (!is1_7) {
Types.BYTE.writePrimitive(buf, (byte) LegacyFormatUtil.getIndex(properties.playerColor()));
}
if (packetType == PropertiesPacketType.CREATE) {
if (is1_7) {
Types.SHORT.writePrimitive(buf, (short) properties.syncedEntries().size());
} else {
Types.VAR_INT.writePrimitive(buf, properties.syncedEntries().size());
}
for (final String entry : properties.syncedEntries()) {
Types.STRING.write(buf, entry);
}
}
via.sendRawPacket(player.getUniqueId(), buf);
}
}
private @NotNull Integer teamsPacketId(final Player player, final UserConnection conn) {
ViaAPI<Player> via = provider.via();
assert via != null;
Integer teamsPacketId = this.provider.viaTeamPacketIds().get(player);
if (teamsPacketId != null) return teamsPacketId;
for (final Protocol<?, ?, ?, ?> proto : conn.getProtocolInfo().getPipeline().pipes()) {
final PacketTypeMap<?> map = proto.getPacketTypesProvider().mappedClientboundPacketTypes().get(State.PLAY);
if (map == null) continue;
final PacketType type = map.typeByName("SET_PLAYER_TEAM");
if (type == null) continue;
teamsPacketId = type.getId();
break;
}
if (teamsPacketId == null) {
throw new IllegalStateException("team packet id for " + conn.getProtocolInfo().protocolVersion() + " not found");
}
this.provider.viaTeamPacketIds().put(player, teamsPacketId);
return teamsPacketId;
}
}
@@ -0,0 +1,106 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Collection;
import java.util.Locale;
import java.util.Optional;
public final class PaperTeamsPacketAdapterImpl extends AbstractTeamsPacketAdapterImpl {
public PaperTeamsPacketAdapterImpl(PacketAdapterProviderImpl provider, @NotNull String teamName) {
super(provider, teamName);
}
@Override
public @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties<Component> properties) {
return new TeamDisplayPacketAdapterImpl(properties);
}
private class TeamDisplayPacketAdapterImpl extends AbstractTeamsPacketAdapterImpl.TeamDisplayPacketAdapterImpl {
private final Object parameters;
private Object createPacket = null;
private Object updatePacket = null;
public TeamDisplayPacketAdapterImpl(@NotNull ImmutableTeamProperties<Component> properties) {
super(properties);
if (PacketAccessors.IS_1_17_OR_ABOVE) {
this.parameters = PacketAccessors.PARAMETERS_CONSTRUCTOR.invoke();
} else {
this.parameters = null;
}
}
@Override
public void updateTeamPackets() {
if (parameters != null) {
fillParameters(parameters, null);
}
createPacket = null;
updatePacket = null;
}
@Override
public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection<Player> players) {
if (createPacket == null || updatePacket == null) {
Collection<String> entries = ImmutableList.copyOf(properties.syncedEntries());
if (parameters != null) {
createPacket = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke(
teamName,
TeamConstants.MODE_CREATE,
Optional.of(parameters),
entries
);
updatePacket = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke(
teamName,
TeamConstants.MODE_UPDATE,
Optional.of(parameters),
entries
);
} else {
assert PacketAccessors.TEAM_NAME_FIELD != null;
assert PacketAccessors.TEAM_MODE_FIELD != null;
createPacket = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(createPacket, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(createPacket, TeamConstants.MODE_CREATE);
PacketAccessors.TEAM_ENTRIES_FIELD.set(createPacket, entries);
fillParameters(createPacket, null);
updatePacket = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(updatePacket, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(updatePacket, TeamConstants.MODE_UPDATE);
PacketAccessors.TEAM_ENTRIES_FIELD.set(updatePacket, entries);
fillParameters(updatePacket, null);
}
}
switch (packetType) {
case CREATE:
provider.packetSender().sendPacket(players, createPacket);
break;
case UPDATE:
provider.packetSender().sendPacket(players, updatePacket);
break;
}
}
@Override
protected void fillParameters(@NotNull Object parameters, @UnknownNullability Locale locale) {
super.fillParameters(parameters, locale);
PacketAccessors.DISPLAY_NAME_FIELD.set(parameters, PacketAccessors.fromAdventureComponent(properties.displayName()));
PacketAccessors.PREFIX_FIELD.set(parameters, PacketAccessors.fromAdventureComponent(properties.prefix()));
PacketAccessors.SUFFIX_FIELD.set(parameters, PacketAccessors.fromAdventureComponent(properties.suffix()));
}
}
}
@@ -0,0 +1,80 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.team;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAdapterProviderImpl;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util.ModernComponentProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamConstants;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.LocalePacketUtil;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.util.Collection;
import java.util.Locale;
import java.util.Optional;
public final class SpigotTeamsPacketAdapter extends AbstractTeamsPacketAdapterImpl {
public SpigotTeamsPacketAdapter(PacketAdapterProviderImpl provider, @NotNull String teamName) {
super(provider, teamName);
}
@Override
public @NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties<Component> properties) {
return new TeamDisplayPacketAdapterImpl(properties);
}
private class TeamDisplayPacketAdapterImpl extends AbstractTeamsPacketAdapterImpl.TeamDisplayPacketAdapterImpl {
public TeamDisplayPacketAdapterImpl(ImmutableTeamProperties<Component> properties) {
super(properties);
}
@Override
public void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection<Player> players) {
Collection<String> entries = ImmutableList.copyOf(properties.syncedEntries());
LocalePacketUtil.sendLocalePackets(
provider.packetSender(),
players,
locale -> {
if (PacketAccessors.IS_1_17_OR_ABOVE) {
assert PacketAccessors.PARAMETERS_CONSTRUCTOR != null;
Object parameters = PacketAccessors.PARAMETERS_CONSTRUCTOR.invoke();
fillParameters(parameters, locale);
return PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke(
teamName,
TeamConstants.mode(packetType),
Optional.of(parameters),
entries
);
} else {
assert PacketAccessors.TEAM_NAME_FIELD != null;
assert PacketAccessors.TEAM_MODE_FIELD != null;
assert PacketAccessors.TEAM_ENTRIES_FIELD != null;
Object packet = PacketAccessors.TEAM_PACKET_CONSTRUCTOR.invoke();
PacketAccessors.TEAM_NAME_FIELD.set(packet, teamName);
PacketAccessors.TEAM_MODE_FIELD.set(packet, TeamConstants.mode(packetType));
PacketAccessors.TEAM_ENTRIES_FIELD.set(packet, entries);
fillParameters(packet, locale);
return packet;
}
}
);
}
@Override
protected void fillParameters(@NotNull Object parameters, @UnknownNullability Locale locale) {
super.fillParameters(parameters, locale);
PacketAccessors.DISPLAY_NAME_FIELD.set(parameters, ModernComponentProvider.fromAdventure(properties.displayName(), locale));
PacketAccessors.PREFIX_FIELD.set(parameters, ModernComponentProvider.fromAdventure(properties.prefix(), locale));
PacketAccessors.SUFFIX_FIELD.set(parameters, ModernComponentProvider.fromAdventure(properties.suffix(), locale));
}
}
}
@@ -0,0 +1,117 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.RelocatedGson;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.MinecraftClasses;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.ReflectUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Locale;
import java.util.Optional;
import static net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson;
public final class ModernComponentProvider {
public static final boolean IS_NATIVE_ADVENTURE;
private static final Object MINECRAFT_REGISTRY;
private static final Object CODEC;
private static final MethodHandle FROM_JSON_METHOD;
private ModernComponentProvider() {
}
static {
// Hide from relocation checkers
String notRelocatedPackage = "net.ky".concat("ori.adventure.text");
// The native adventure optimisations only work when the adventure library isn't relocated
IS_NATIVE_ADVENTURE = PacketAccessors.ADVENTURE_COMPONENT_CLASS != null && Component.class.getPackage().getName().equals(notRelocatedPackage);
if (PacketAccessors.IS_1_20_5_OR_ABOVE) {
Class<?> craftRegistry;
try {
craftRegistry = Class.forName(MinecraftClasses.craftBukkit("CraftRegistry"));
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError(e);
}
try {
Method method = craftRegistry.getMethod("getMinecraftRegistry");
MINECRAFT_REGISTRY = method.invoke(null);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
} else {
MINECRAFT_REGISTRY = null;
}
if (PacketAccessors.IS_1_21_6_OR_ABOVE) {
CODEC = ReflectUtil.findFieldUnchecked(PacketAccessors.COMPONENT_SERIALIZATION_CLASS, 0, PacketAccessors.CODEC_CLASS, true).get(null);
FROM_JSON_METHOD = null;
} else {
MethodHandle handle = null;
for (Method method : PacketAccessors.CHAT_SERIALIZER_CLASS.getMethods()) {
if (method.getParameterCount() >= 1 &&
method.getParameterCount() <= 2 &&
method.getParameterTypes()[0].getName().equals(RelocatedGson.SERVER_GSON_PKG + ".JsonElement")
) {
try {
handle = MethodHandles.lookup().unreflect(method);
break;
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
}
if (handle == null) {
throw new ExceptionInInitializerError("failed to find chat component fromJson method");
}
FROM_JSON_METHOD = handle;
CODEC = null;
}
}
public static @NotNull Object fromAdventure(@NotNull Component adventure, @Nullable Locale locale) {
if (IS_NATIVE_ADVENTURE) {
return PacketAccessors.fromAdventureComponent(adventure);
}
Component translated = adventure;
if (locale != null) {
translated = GlobalTranslator.render(adventure, locale);
}
Object json = RelocatedGson.convertToServerGson(gson().serializeToTree(translated));
if (FROM_JSON_METHOD == null) {
// 1.21.6+
Object result = PacketAccessors.CODEC_PARSE.invoke(CODEC, PacketAccessors.JSON_OPS, json);
//noinspection OptionalGetWithoutIsPresent
return ((Optional<?>) PacketAccessors.RESULT_UNWRAP_METHOD.invoke(result)).get();
}
Object[] args;
if (MINECRAFT_REGISTRY != null) {
args = new Object[]{json, MINECRAFT_REGISTRY};
} else {
args = new Object[]{json};
}
try {
return FROM_JSON_METHOD.invokeWithArguments(args);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,106 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.util;
import com.viaversion.viaversion.api.ViaAPI;
import com.viaversion.viaversion.api.connection.UserConnection;
import io.netty.channel.Channel;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.modern.PacketAccessors;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect.MinecraftClasses;
import org.bukkit.entity.Player;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
public final class ModernPacketSender implements PacketSender<Object> {
private static final MethodHandle GET_HANDLE;
private static final MethodHandle PLAYER_CONNECTION;
private static final MethodHandle SEND_PACKET;
private final ViaAPI<Player> via;
public ModernPacketSender(final ViaAPI<Player> via) {
this.via = via;
}
static {
Class<?> craftPlayer;
try {
craftPlayer = Class.forName(MinecraftClasses.craftBukkit("entity.CraftPlayer"));
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError(e);
}
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
MethodType methodType = MethodType.methodType(PacketAccessors.SERVER_PLAYER_CLASS);
try {
GET_HANDLE = lookup.findVirtual(craftPlayer, "getHandle", methodType);
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
MethodHandle playerConnection = null;
for (Field field : PacketAccessors.SERVER_PLAYER_CLASS.getFields()) {
if (field.getType() == PacketAccessors.PLAYER_CONNECTION_CLASS) {
try {
playerConnection = lookup.unreflectGetter(field);
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
}
if (playerConnection == null) {
throw new ExceptionInInitializerError("failed to find player connection field");
}
PLAYER_CONNECTION = playerConnection;
MethodType sendMethodType = MethodType.methodType(void.class, PacketAccessors.PKT_CLASS);
MethodHandle sendPacket = null;
String[] sendPacketNames = {"a", "sendPacket", "b", "send"};
for (String name : sendPacketNames) {
try {
sendPacket = lookup.findVirtual(PacketAccessors.PLAYER_CONNECTION_CLASS, name, sendMethodType);
} catch (NoSuchMethodException ignored) {
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
if (sendPacket == null) {
throw new ExceptionInInitializerError(new RuntimeException("Couldn't find send packet method"));
}
SEND_PACKET = sendPacket;
}
@Override
public void sendPacket(Player player, Object packet) {
if (this.via != null) {
final UserConnection conn = this.via.getConnection(player.getUniqueId());
if (conn != null) {
final Channel channel = conn.getChannel();
if (channel != null) {
// Paper has some "network optimization" patch that makes the NMS sendPacket method
// not always send the packet immediately to the player's netty channel,
// and this is a problem when we send some packets using ViaVersion API because it always sends
// the packet to the channel immediately, so the packet order between NMS and ViaVersion API can
// get messed up. So for ViaVersion players we send the packet directly to the player's channel
// bypassing Paper logic to work around this. Fortunately ViaVersion provides easy access to the player's channel.
channel.eventLoop().execute(() -> channel.writeAndFlush(packet));
return;
}
}
}
try {
Object handle = GET_HANDLE.invoke(player);
Object connection = PLAYER_CONNECTION.invoke(handle);
SEND_PACKET.invoke(connection, packet);
} catch (Throwable e) {
throw new IllegalStateException("couldn't send packet to player", e);
}
}
}
@@ -0,0 +1,52 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.objective;
import net.kyori.adventure.text.format.NamedTextColor;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.implementation.commons.LegacyFormatUtil;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;
public final class ObjectiveConstants {
public static final int MODE_CREATE = 0,
MODE_REMOVE = 1,
MODE_UPDATE = 2,
LEGACY_VALUE_CHAR_LIMIT = 32;
private ObjectiveConstants() {
}
public static int mode(@NotNull PropertiesPacketType packetType) {
switch (packetType) {
case CREATE:
return MODE_CREATE;
case UPDATE:
return MODE_UPDATE;
default:
throw new IllegalStateException();
}
}
public static int displaySlotIndex(@NotNull ObjectiveDisplaySlot slot) {
return displaySlotIndex(slot, true);
}
public static int displaySlotIndex(@NotNull ObjectiveDisplaySlot slot, boolean isAbove1_8) {
if (slot instanceof ObjectiveDisplaySlot.PlayerList) {
return 0;
}
if (slot instanceof ObjectiveDisplaySlot.Sidebar || (!isAbove1_8 && slot instanceof ObjectiveDisplaySlot.TeamSidebar)) {
return 1;
}
if (slot instanceof ObjectiveDisplaySlot.BelowName) {
return 2;
}
NamedTextColor color = ((ObjectiveDisplaySlot.TeamSidebar) slot).teamColor();
char legacyChar = LegacyFormatUtil.getChar(color);
ChatColor chatColor = ChatColor.getByChar(legacyChar);
return 3 + chatColor.ordinal();
}
}
@@ -0,0 +1,38 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.objective;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveDisplaySlot;
import net.megavex.scoreboardlibrary.api.objective.ObjectiveRenderType;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public interface ObjectivePacketAdapter {
@NotNull String objectiveName();
void display(@NotNull Collection<Player> players, @NotNull ObjectiveDisplaySlot slot);
void sendProperties(
@NotNull Collection<Player> players,
@NotNull PropertiesPacketType packetType,
@NotNull Component value,
@NotNull ObjectiveRenderType renderType,
@Nullable ScoreFormat scoreFormat
);
void remove(@NotNull Collection<Player> players);
void sendScore(
@NotNull Collection<Player> players,
@NotNull String entry,
int value,
@Nullable Component display,
@Nullable ScoreFormat scoreFormat
);
void removeScore(@NotNull Collection<Player> players, @NotNull String entry);
}
@@ -0,0 +1,6 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.team;
public enum EntriesPacketType {
ADD,
REMOVE
}
@@ -0,0 +1,40 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.team;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import org.jetbrains.annotations.NotNull;
public final class TeamConstants {
public static final int DISPLAY_NAME_LEGACY_LIMIT = 32;
public static final int PREFIX_SUFFIX_LEGACY_LIMIT = 16;
public static final int MODE_CREATE = 0,
MODE_REMOVE = 1,
MODE_UPDATE = 2,
MODE_ADD_ENTRIES = 3,
MODE_REMOVE_ENTRIES = 4;
private TeamConstants() {
}
public static int mode(@NotNull PropertiesPacketType packetType) {
switch (packetType) {
case CREATE:
return MODE_CREATE;
case UPDATE:
return MODE_UPDATE;
default:
throw new IllegalStateException();
}
}
public static int mode(@NotNull EntriesPacketType packetType) {
switch (packetType) {
case ADD:
return MODE_ADD_ENTRIES;
case REMOVE:
return MODE_REMOVE_ENTRIES;
default:
throw new IllegalStateException();
}
}
}
@@ -0,0 +1,18 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.team;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
public interface TeamDisplayPacketAdapter {
void removeTeam(@NotNull Iterable<Player> players);
default void updateTeamPackets() {
}
void sendEntries(@NotNull EntriesPacketType packetType, @NotNull Collection<Player> players, @NotNull Collection<String> entries);
void sendProperties(@NotNull PropertiesPacketType packetType, @NotNull Collection<Player> players);
}
@@ -0,0 +1,14 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.team;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public interface TeamsPacketAdapter {
@NotNull TeamDisplayPacketAdapter createTeamDisplayAdapter(@NotNull ImmutableTeamProperties<Component> properties);
default @NotNull TeamDisplayPacketAdapter createLegacyTeamDisplayAdapter(@NotNull ImmutableTeamProperties<String> properties) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,41 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util;
import net.megavex.scoreboardlibrary.implementation.commons.LocaleProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PacketSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
public final class LocalePacketUtil {
private LocalePacketUtil() {
}
public static <P> void sendLocalePackets(
@NotNull PacketSender<P> sender,
@NotNull Collection<Player> players,
@NotNull Function<Locale, P> packetFunction
) {
if (players.isEmpty()) {
return;
}
if (players.size() == 1) {
Player player = players.iterator().next();
P packet = packetFunction.apply(LocaleProvider.locale(player));
sender.sendPacket(player, packet);
return;
}
Map<Locale, P> map = new HashMap<>(1);
for (Player player : players) {
Locale locale = LocaleProvider.locale(player);
P packet = map.computeIfAbsent(locale, i -> packetFunction.apply(locale));
sender.sendPacket(player, packet);
}
}
}
@@ -0,0 +1,23 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.lang.invoke.MethodHandle;
public final class ConstructorAccessor<T> {
private final MethodHandle handle;
public ConstructorAccessor(@NotNull MethodHandle handle) {
this.handle = handle;
}
public @NotNull T invoke(@UnknownNullability Object... args) {
try {
//noinspection unchecked
return (T) handle.invokeExact(args);
} catch (Throwable e) {
throw new IllegalStateException("couldn't instantiate constructor", e);
}
}
}
@@ -0,0 +1,39 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import java.lang.invoke.MethodHandle;
public final class FieldAccessor<T, V> {
private final MethodHandle getter, setter;
public FieldAccessor(@NotNull MethodHandle getter, @Nullable MethodHandle setter) {
this.getter = getter;
this.setter = setter;
}
public Object get(@Nullable T instance) {
try {
if (instance == null) {
return getter.invoke();
} else {
return getter.invoke(instance);
}
} catch (Throwable e) {
throw new IllegalStateException("couldn't set value of field", e);
}
}
public void set(T instance, @UnknownNullability V value) {
if (this.setter == null) {
throw new IllegalStateException("cannot set static final field");
}
try {
setter.invokeExact(instance, value);
} catch (Throwable e) {
throw new IllegalStateException("couldn't set value of field", e);
}
}
}
@@ -0,0 +1,22 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import java.lang.invoke.MethodHandle;
public final class MethodAccessor {
private final MethodHandle handle;
public MethodAccessor(@NotNull MethodHandle handle) {
this.handle = handle;
}
public Object invoke(@UnknownNullability Object... args) {
try {
return handle.invokeExact(args);
} catch (Throwable e) {
throw new IllegalStateException("couldn't set value of field", e);
}
}
}
@@ -0,0 +1,15 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
public final class MinecraftClasses {
private static final String CB_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
private MinecraftClasses() {
}
public static @NotNull String craftBukkit(String className) {
return CB_PACKAGE + '.' + className;
}
}
@@ -0,0 +1,7 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect;
import org.jetbrains.annotations.NotNull;
public interface PacketConstructor<T> {
@NotNull T invoke();
}
@@ -0,0 +1,192 @@
package net.megavex.scoreboardlibrary.implementation.packetAdapter.util.reflect;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public final class ReflectUtil {
// Inspired by
// https://github.com/dmulloy2/ProtocolLib/blob/02e917cd08cf5b37a52052e22b223272a040e0df/src/main/java/com/comphenix/protocol/reflect/accessors/MethodHandleHelper.java
private static final Object UNSAFE;
private static final MethodHandles.Lookup LOOKUP;
private static final MethodHandle ALLOCATE_INSTANCE_HANDLE;
private static final MethodType VOID_METHOD_TYPE = MethodType.methodType(void.class);
private static final MethodType VIRTUAL_FIELD_SETTER = MethodType.methodType(void.class, Object.class, Object.class);
static {
MethodHandles.Lookup normalLookup = MethodHandles.lookup();
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = theUnsafe.get(null);
ALLOCATE_INSTANCE_HANDLE = normalLookup.findVirtual(UNSAFE.getClass(), "allocateInstance", MethodType.methodType(Object.class, Class.class));
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | NoSuchMethodException e) {
throw new ExceptionInInitializerError(e);
}
MethodHandles.Lookup lookup;
try {
MethodHandle staticFieldOffset = normalLookup.findVirtual(UNSAFE.getClass(), "staticFieldOffset", MethodType.methodType(long.class, Field.class));
MethodHandle staticFieldBase = normalLookup.findVirtual(UNSAFE.getClass(), "staticFieldBase", MethodType.methodType(Object.class, Field.class));
MethodHandle getObject = normalLookup.findVirtual(UNSAFE.getClass(), "getObject", MethodType.methodType(Object.class, Object.class, long.class));
Field trustedLookup = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
long offset = (long) staticFieldOffset.invoke(UNSAFE, trustedLookup);
Object baseValue = staticFieldBase.invoke(UNSAFE, trustedLookup);
lookup = (MethodHandles.Lookup) getObject.invoke(UNSAFE, baseValue, offset);
} catch (Throwable e) {
e.printStackTrace();
lookup = normalLookup;
}
LOOKUP = lookup;
}
private ReflectUtil() {
}
public static @NotNull Class<?> getClassOrThrow(String... names) {
Class<?> c = getOptionalClass(names);
if (c == null) {
throw new IllegalStateException("Class with names either of " + String.join(", ", names) + " not found");
}
return c;
}
public static @Nullable Class<?> getOptionalClass(String... names) {
for (String name : names) {
if (name == null) continue;
try {
return Class.forName(name);
} catch (ClassNotFoundException ignored) {
}
}
return null;
}
public static @NotNull Object getEnumInstance(@NotNull Class<?> clazz, @NotNull String... names) {
for (String name : names) {
try {
//noinspection unchecked,rawtypes
return Enum.valueOf((Class<? extends Enum>) clazz, name);
} catch (IllegalArgumentException ignored) {
}
}
throw new IllegalStateException("Enum " + clazz.getName() + " instance with names either of " + String.join(",", names) + " not found");
}
public static <T, V> @NotNull FieldAccessor<T, V> findFieldUnchecked(@NotNull Class<?> clazz, int index, @NotNull Class<?> valueClass) {
return findFieldUnchecked(clazz, index, valueClass, false);
}
public static <T, V> @NotNull FieldAccessor<T, V> findFieldUnchecked(@NotNull Class<?> clazz, int index, @NotNull Class<?> valueClass, boolean isStatic) {
int i = 0;
for (Field field : clazz.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) != isStatic || field.getType() != valueClass) {
continue;
}
if (i == index) {
try {
MethodHandle getter = LOOKUP.unreflectGetter(field);
MethodHandle setter = !isStatic || !Modifier.isFinal(field.getModifiers()) ? LOOKUP.unreflectSetter(field).asType(VIRTUAL_FIELD_SETTER) : null;
return new FieldAccessor<>(getter, setter);
} catch (IllegalAccessException e) {
throw new RuntimeException("failed to unreflect field setter", e);
}
}
i++;
}
throw new IllegalStateException(
"couldn't find field with class " + valueClass.getSimpleName() + " on " + clazz.getSimpleName() + " at index " + index
);
}
public static <T> @Nullable ConstructorAccessor<T> findOptionalConstructor(@NotNull Class<T> clazz, @NotNull Class<?>... args) {
try {
MethodHandle handle = LOOKUP.findConstructor(clazz, MethodType.methodType(void.class, args));
return new ConstructorAccessor<>(convertToGeneric(handle));
} catch (NoSuchMethodException | IllegalAccessException e) {
return null;
}
}
public static <T> @NotNull ConstructorAccessor<T> findConstructor(@NotNull Class<T> clazz, @NotNull Class<?>... args) {
ConstructorAccessor<T> accessor = findOptionalConstructor(clazz, args);
if (accessor == null) {
throw new RuntimeException("Constructor for class " + clazz.getName() + " not found");
}
return accessor;
}
public static <T> @NotNull PacketConstructor<T> getEmptyConstructor(@NotNull Class<T> packetClass) {
try {
MethodHandle constructor = LOOKUP.findConstructor(packetClass, VOID_METHOD_TYPE);
return () -> {
try {
// noinspection unchecked
return (T) constructor.invoke();
} catch (Throwable e) {
throw new IllegalStateException("couldn't create packet instance", e);
}
};
} catch (NoSuchMethodException | IllegalAccessException ignored) {
}
return () -> {
try {
// noinspection unchecked
return (T) ALLOCATE_INSTANCE_HANDLE.invoke(UNSAFE, packetClass);
} catch (Throwable e) {
throw new IllegalStateException("couldn't allocate packet instance using Unsafe", e);
}
};
}
public static @NotNull MethodAccessor findMethod(@NotNull Class<?> clazz, boolean isStatic, @NotNull MethodType methodType, @NotNull String... names) {
MethodAccessor accessor = findOptionalMethod(clazz, isStatic, methodType, names);
if (accessor == null) {
throw new RuntimeException("Method with the names either of " + String.join(",", names) + " for class " + clazz.getName() + " not found");
}
return accessor;
}
public static <T> @Nullable MethodAccessor findOptionalMethod(@NotNull Class<T> clazz, boolean isStatic, @NotNull MethodType methodType, @NotNull String... names) {
if (names.length == 0) {
throw new IllegalArgumentException("no method names passed");
}
for (String name : names) {
MethodHandle handle;
try {
if (isStatic) {
handle = LOOKUP.findStatic(clazz, name, methodType);
} else {
handle = LOOKUP.findVirtual(clazz, name, methodType);
}
return new MethodAccessor(convertToGeneric(handle));
} catch (NoSuchMethodException ignored) {
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return null;
}
private static @NotNull MethodHandle convertToGeneric(@NotNull MethodHandle handle) {
MethodHandle target = handle.asFixedArity();
MethodType methodType = MethodType.genericMethodType(0, true);
target = target.asSpreader(Object[].class, handle.type().parameterCount());
return target.asType(methodType);
}
}
@@ -0,0 +1,67 @@
package net.megavex.scoreboardlibrary.implementation.player;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public final class DisplayableQueue<T extends PlayerDisplayable> {
private final UUID playerUuid;
private final List<T> queue = new ArrayList<>(1);
public DisplayableQueue(@NotNull UUID playerUuid) {
this.playerUuid = playerUuid;
}
public @Nullable T current() {
synchronized (this) {
if (queue.isEmpty()) {
return null;
} else {
return queue.get(0);
}
}
}
public void add(@NotNull T displayable) {
synchronized (this) {
if (queue.contains(displayable)) {
throw new IllegalStateException("displayable already registered");
}
queue.add(displayable);
if (current() == displayable) {
Player player = Bukkit.getPlayer(playerUuid);
if (player != null) {
displayable.display(player);
}
}
}
}
public void remove(@NotNull T displayable) {
synchronized (this) {
boolean wasCurrent = displayable == current();
if (!queue.remove(displayable)) {
throw new IllegalStateException("displayable not registered");
}
if (!wasCurrent) {
return;
}
T newDisplayable = current();
if (newDisplayable != null) {
Player player = Bukkit.getPlayer(playerUuid);
if (player != null) {
newDisplayable.display(player);
}
}
}
}
}
@@ -1,5 +1,8 @@
package net.megavex.scoreboardlibrary.implementation;
package net.megavex.scoreboardlibrary.implementation.player;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerImpl;
import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerTask;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import net.megavex.scoreboardlibrary.implementation.sidebar.PlayerDependantLocaleSidebar;
import net.megavex.scoreboardlibrary.implementation.sidebar.SidebarTask;
@@ -12,7 +15,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLocaleChangeEvent;
import org.jetbrains.annotations.NotNull;
public class LocaleListener implements Listener {
public final class LocaleListener implements Listener {
private final ScoreboardLibraryImpl scoreboardLibrary;
public LocaleListener(@NotNull ScoreboardLibraryImpl scoreboardLibrary) {
@@ -27,12 +30,17 @@ public class LocaleListener implements Listener {
scoreboardLibrary.taskScheduler().runNextTick(() -> {
ScoreboardLibraryPlayer slPlayer = scoreboardLibrary.getPlayer(player);
if (slPlayer != null) {
TeamManagerImpl teamManager = slPlayer.teamManager();
TeamManagerImpl teamManager = slPlayer.teamManagerQueue().current();
if (teamManager != null) {
teamManager.taskQueue().add(new TeamManagerTask.ReloadPlayer(player));
}
AbstractSidebar sidebar = slPlayer.sidebar();
ObjectiveManagerImpl objectiveManager = slPlayer.objectiveManagerQueue().current();
if (objectiveManager != null) {
objectiveManager.taskQueue().add(new ObjectiveManagerTask.ReloadPlayer(player));
}
AbstractSidebar sidebar = slPlayer.sidebarQueue().current();
if (sidebar instanceof PlayerDependantLocaleSidebar) {
sidebar.taskQueue().add(new SidebarTask.ReloadPlayer(player));
}
Loaded 100 of 167 files, more files were not shown because too many files have changed in this diff. Show more