Compare commits

...
274 Commits
Author SHA1 Message Date
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
vytskalt 92cbf54fc1 release 2.0.0-RC10 2023-07-08 13:31:54 +03:00
vytskalt cc4a8fb4e5 fix disconnect when sidebar title is longer than 32 characters on 1.8 adapter 2023-07-08 13:31:33 +03:00
VytskaLT 2ca345c3f7 update README 2023-06-12 21:40:34 +03:00
VytskaLT af58ce7423 target Java 8 2023-06-12 21:39:35 +03:00
VytskaLT c43966026c remove deprecated sidebar features 2023-06-12 20:47:47 +03:00
VytskaLT d9fcaf7f62 complete javadocs 2023-06-12 20:45:39 +03:00
VytskaLT 71c8985d64 don't depend on folia-api 2023-06-12 20:28:04 +03:00
VytskaLT 46ab9d56f6 update description 2023-06-12 18:05:44 +03:00
VytskaLT 6142ce4c53 add maven installation instructions 2023-06-12 17:59:50 +03:00
VytskaLT 2f52aa52c4 update installation for 1.20 2023-06-11 21:47:30 +03:00
VytskaLT d4e5bc8d6e release 2.0.0-RC9 2023-06-09 16:49:38 +03:00
VytskaLT a6a81da88d prepare for 1.20 2023-06-08 18:11:36 +03:00
vytskalt dc6bb4ef6c fix teams example 2023-06-06 14:03:36 +00:00
VytskaLT 31d51d0ce9 update readme 2023-05-10 19:16:35 +03:00
vytskalt a78cab78e1 Merge pull request #2 from MegavexNetwork/dev
2.0.0-RC8
2023-05-10 19:08:33 +03:00
VytskaLT 6dbecb310d v2.0.0-RC8 2023-05-10 19:08:14 +03:00
VytskaLT 23173d1446 update readme 2023-05-10 19:05:23 +03:00
VytskaLT 093950dc94 add Folia support 2023-05-10 18:34:06 +03:00
vytskalt c8cc9c00ef Merge pull request #1 from MegavexNetwork/feat/component-sidebars
Component sidebars
2023-05-10 18:02:57 +03:00
VytskaLT 863ba3951f reformat 2023-05-10 18:02:29 +03:00
VytskaLT b074ce8ea2 remove static imports 2023-05-10 17:59:54 +03:00
VytskaLT 2cf6bf2ce7 update readme 2023-05-10 17:54:39 +03:00
VytskaLT 247b55a04e fix deprecation message 2023-05-07 22:24:27 +03:00
VytskaLT 2226a509a4 make immutable 2023-05-07 22:23:48 +03:00
VytskaLT faccce273d more api changes 2023-05-07 22:05:16 +03:00
VytskaLT 1ff5b78098 make it better 2023-05-06 20:04:15 +03:00
VytskaLT 682634de5c deprecate old stuff 2023-05-05 22:55:28 +03:00
VytskaLT 32469565e1 add some unit tests 2023-05-05 22:51:08 +03:00
VytskaLT db269edff9 start 2023-05-05 20:42:43 +03:00
VytskaLT 6ec3393aab update dependencies 2023-05-02 21:37:46 +03:00
VytskaLT 079c774cc4 remove some usage of ChatColor 2023-05-02 21:37:46 +03:00
VytskaLT 0ae845ea1a update README 2023-05-02 21:35:36 +03:00
VytskaLT fc8e26af88 change version to 2.0.0-RC7 2023-04-02 00:39:35 +03:00
VytskaLT 8fb5080916 probably fix issues with bungeecord 2023-04-02 00:01:08 +03:00
VytskaLT 5c023d7bc3 change version to 2.0.0-RC6 2023-03-16 16:38:21 +02:00
VytskaLT 3a7a684d0b 1.19.4 2023-03-15 21:23:01 +02:00
VytskaLT 3f5099fdfb new papermc repo 2023-03-04 23:07:08 +02:00
VytskaLT 1fc3b0ba0a fix kotlin extension variable name 2023-02-27 20:51:59 +02:00
VytskaLT 0a989cf946 change version to 2.0.0-RC5 2023-02-27 20:45:10 +02:00
VytskaLT 2e34db1672 global -> default 2023-02-27 20:43:33 +02:00
VytskaLT c284022a2e Rename TeamInfo to TeamDisplay 2023-02-27 20:21:35 +02:00
VytskaLT 23f9e1202d Add players extension variable 2023-02-25 13:55:43 +02:00
VytskaLT f38d1ec04f Update INSTALLATION.md 2023-02-25 13:51:31 +02:00
VytskaLT 035a342b41 update paperweight 2023-02-19 18:20:54 +02:00
VytskaLT d0f828d372 update gradle to 8.0 2023-02-16 13:39:26 +02:00
VytskaLT 0df617048c Update INSTALLATION.md again 2023-01-28 16:11:53 +02:00
VytskaLT 5822d20c6c Update INSTALLATION.md 2023-01-28 16:04:20 +02:00
VytskaLT 0b3babc869 Change version to 2.0.0-RC4 2023-01-28 15:38:47 +02:00
VytskaLT d99134804d Update README 2023-01-28 15:37:09 +02:00
VytskaLT 90a73045f2 Build changes 2023-01-28 15:37:09 +02:00
VytskaLT fc67738bc9 Refactor some stuff 2023-01-28 15:37:09 +02:00
VytskaLT eb5b355bc5 Fix TeamManager sometimes sending invalid packets 2023-01-28 15:00:57 +02:00
VytskaLT 98b5cd9a3b Make some Kotlin extensions inline 2023-01-22 17:06:10 +02:00
VytskaLT f29ea8a077 Change version to 2.0.0-RC3 2023-01-08 19:19:16 +02:00
VytskaLT c30fc0c80e Fix sidebar titles not updating 2023-01-08 19:18:36 +02:00
VytskaLT 9fd98caa33 Update LICENSE 2023-01-01 13:03:35 +02:00
VytskaLT c364479e70 2023 2023-01-01 00:08:34 +02:00
VytskaLT 18dfc725a4 change version to 2.0.0-RC2 2022-12-16 21:28:11 +02:00
VytskaLT f4e1b7bb30 Fix NPE when creating a team in a TeamManager and adding a player to it at the same time 2022-12-16 20:59:04 +02:00
VytskaLT a561faef12 throw NoPacketAdapterAvailableException when packetevents packet adapter is found but packetevents isn't in the classpath 2022-12-12 14:37:31 +02:00
VytskaLT 841c66e3b1 fix installation link 2022-12-11 20:14:14 +02:00
174 changed files with 6415 additions and 3257 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@v4
- uses: cachix/install-nix-action@v30
- 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 -i -c sh -c "./gradlew build"
+6 -1
View File
@@ -2,4 +2,9 @@
*.iml
.gradle
build/
build/
.classpath
.project
.settings/
bin/
+80 -17
View File
@@ -1,38 +1,101 @@
# Installation
I'm only going to show the installation for Gradle, however everything can be applied to Maven too.
Latest version: `2.2.2`
First, add the Jitpack repository:
## Gradle
```kotlin
repositories {
maven("https://jitpack.io/")
mavenCentral()
}
```
Then add the dependencies:
```kotlin
dependencies {
val scoreboardLibraryVersion = "2.0.0-RC1"
implementation("com.github.MegavexNetwork.scoreboard-library:api:$scoreboardLibraryVersion")
implementation("com.github.MegavexNetwork.scoreboard-library:extra-kotlin:$scoreboardLibraryVersion") // If using Kotlin
runtimeOnly("com.github.MegavexNetwork.scoreboard-library:implementation:$scoreboardLibraryVersion")
val scoreboardLibraryVersion = "{VERSION HERE}"
implementation("net.megavex:scoreboard-library-api:$scoreboardLibraryVersion")
runtimeOnly("net.megavex:scoreboard-library-implementation:$scoreboardLibraryVersion")
implementation("net.megavex:scoreboard-library-extra-kotlin:$scoreboardLibraryVersion") // Kotlin specific extensions (optional)
// Add version implementations you want:
runtimeOnly("com.github.MegavexNetwork.scoreboard-library:v1_8_R3:$scoreboardLibraryVersion")
runtimeOnly("com.github.MegavexNetwork.scoreboard-library:v1_19_R2:$scoreboardLibraryVersion")
runtimeOnly("com.github.MegavexNetwork.scoreboard-library:packetevents:$scoreboardLibraryVersion")
// Add packet adapter implementations you want:
runtimeOnly("net.megavex:scoreboard-library-modern:$scoreboardLibraryVersion") // 1.17+
runtimeOnly("net.megavex:scoreboard-library-modern:$scoreboardLibraryVersion:mojmap") // Mojang mapped variant (only use if you know what you're doing!)
runtimeOnly("net.megavex:scoreboard-library-packetevents:$scoreboardLibraryVersion") // 1.8+
runtimeOnly("net.megavex:scoreboard-library-legacy:$scoreboardLibraryVersion") // 1.7.10-1.12.2
// 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/Depending-on-pre%E2%80%90built-PacketEvents
// - https://github.com/retrooper/packetevents/wiki/Shading-PacketEvents
// Example how to load PacketEvents in your plugin:
// https://github.com/retrooper/packetevents-example/blob/24f0c842d47362aef122b794dea29b8fee113fa3/thread-safe-listener/src/main/java/main/Main.java
// If using the 1.8 version implementation, add Adventure as well:
// If targeting a Minecraft version without native Adventure support, add it as well:
implementation("net.kyori:adventure-platform-bukkit:4.0.1")
}
```
You will need to shade these dependencies and relocate them with something
like [Shadow](https://imperceptiblethoughts.com/shadow/).
## Maven
```xml
<dependencies>
<dependency>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-api</artifactId>
<version>{VERSION HERE}</version>
</dependency>
<dependency>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-implementation</artifactId>
<version>{VERSION HERE}</version>
<scope>runtime</scope>
</dependency>
<!-- Kotlin specific extensions (optional) -->
<dependency>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-extra-kotlin</artifactId>
<version>{VERSION HERE}</version>
</dependency>
<!-- Add packet adapter implementations you want: -->
<dependency>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-modern</artifactId>
<version>{VERSION HERE}</version>
<scope>runtime</scope>
<!-- For a Mojang mapped variant, uncomment line below (only use if you know what you're doing!): -->
<!-- <classifier>mojmap</classifier> -->
</dependency>
<dependency>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-packetevents</artifactId>
<version>{VERSION HERE}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>net.megavex</groupId>
<artifactId>scoreboard-library-legacy</artifactId>
<version>{VERSION HERE}</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
Example how to load PacketEvents in your plugin:
https://github.com/retrooper/packetevents-example/blob/24f0c842d47362aef122b794dea29b8fee113fa3/thread-safe-listener/src/main/java/main/Main.java
-->
<!-- If targeting a Minecraft version without native Adventure support, add it as well: -->
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bukkit</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
```
You will need to shade these dependencies and relocate them with [maven-shade-plugin](https://maven.apache.org/plugins/maven-shade-plugin/).
+2 -2
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 Megavex
Copyright (c) 2021-2023 Megavex and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
+209 -82
View File
@@ -1,32 +1,38 @@
# scoreboard-library
Powerful Scoreboard library for 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
- Teams
- 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)
- Doesn't require extra dependencies (assuming you're targeting modern versions of Paper)
- Packet-level, meaning it works with other scoreboard plugins
- Fully async. All packet work is done asynchronously so you can (but don't have to) use the library from the main
- Supports [Folia](https://github.com/PaperMC/Folia)
- 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)
- 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
## Packet Adapters
## Available Packet Adapters
- **1.19.3.** [Spigot](https://www.spigotmc.org/) does work, but [Paper](https://papermc.io/) is recommended because
scoreboard-library can take advantage of the native [Adventure](https://github.com/KyoriPowered/adventure) feature to
improve performance
- **1.8.8.** Note that you'll still need to use Java 17
- **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+
- **modern.** Supports 1.17-1.21.4. Can take advantage of [Paper](https://papermc.io)'s native adventure support to be more efficient.
- **legacy.** Supports 1.7.10-1.12.2.
- **PacketEvents.** Supports 1.8+. Requires [PacketEvents 2.0](https://github.com/retrooper/packetevents/tree/2.0) to be shaded or installed as a plugin.
> [!NOTE]
> You can add multiple packet adapters, the best one will automatically be picked depending on the server version.
## Installation
See installation instructions [here](https://github.com/MegavexNetwork/scoreboard-library/blob/master/INSTALLATION.md)
See installation instructions [here](https://github.com/MegavexNetwork/scoreboard-library/blob/dev/2.0/INSTALLATION.md)
### Getting started
@@ -35,16 +41,21 @@ 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 no packet adapter was found, you can fallback to the no-op implementation:
scoreboardLibrary = new NoopScoreboardLibrary();
plugin.getLogger().warning("No scoreboard packet adapter available!");
}
// On plugin shutdown:
scoreboardLibrary.close();
```
### Sidebar
### Thread safety warning (Folia)
`Sidebar`s and `TeamManager`s are not thread safe, so you will need to add some
synchronisation to make sure you're using them from only one thread at a time.
### Sidebar (low-level)
```java
Sidebar sidebar = scoreboardLibrary.createSidebar();
@@ -54,102 +65,218 @@ sidebar.line(0, Component.empty());
sidebar.line(1, Component.text("Line 1"));
sidebar.line(2, Component.text("Line 2"));
sidebar.line(2, Component.empty());
sidebar.line(3, Component.text("yourserver.net"));
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!
```
### Sidebar (Kotlin)
### Component sidebars
```kotlin
val sidebar = scoreboardLibrary.createSidebar(4)
val timerLine: LinesBuilder.DynamicLine
var timer = 0
sidebar.title(Component.text("Timer Example", NamedTextColor.AQUA))
sidebar.lines {
emptyLine()
timerLine = dynamicLine { Component.text("Timer: $timer") }
emptyLine()
line(Component.text("yourserver.net", NamedTextColor.AQUA))
}
plugin.server.scheduler.runTaskTimerAsynchronously(
plugin,
Runnable {
timer++
timerLine.update()
},
20,
20
)
sidebar.addPlayer(player)
```
### AbstractSidebar
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
public class TimerSidebar extends AbstractSidebar {
private final DynamicLine timerLine;
private int timer;
SidebarComponent staticLine = SidebarComponent.staticLine(Component.text("A static line"));
```
private final BukkitTask task;
You can chain multiple `SidebarComponent`s together using `SidebarComponent.builder()`:
public TimerSidebar(@NotNull Plugin plugin, @NotNull ScoreboardLibrary scoreboardLibrary, @NotNull Player player) {
super(scoreboardLibrary.createSidebar(4));
```java
SidebarComponent lines = SidebarComponent.builder()
.addComponent(SidebarComponent.staticLine(Component.text("A static line")))
.addStaticLine(Component.text("Another static line")) // Shorthand for line above
.build();
```
sidebar.title(text("Timer Example", NamedTextColor.AQUA));
To use these components, create a `ComponentSidebarLayout`:
registerEmptyLine(0);
timerLine = registerDynamicLine(1, () -> text("Timer: " + timer));
registerEmptyLine(2);
registerStaticLine(3, text("epicserver.net", NamedTextColor.AQUA));
```java
ComponentSidebarLayout layout = new ComponentSidebarLayout(
SidebarComponent.staticLine(Component.text("Sidebar Title")),
lines
);
task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, () -> {
timer++;
timerLine.update();
}, 20, 20);
Sidebar sidebar = scoreboardLibrary.createSidebar();
sidebar.addPlayer(player);
// Apply the title & lines components to the Sidebar
// 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();
List<Component> frames = new ArrayList<>(colors.size());
for (NamedTextColor color : colors) {
frames.add(lineComponent.color(color));
}
SidebarAnimation<Component> animation = new CollectionSidebarAnimation<>(frames);
// You can also implement SidebarAnimation yourself
SidebarComponent line = SidebarComponent.animatedLine(animation);
// Advance to the next frame of the animation
animation.nextFrame();
```
Animations can be used for pagination:
```java
Player player = ...;
SidebarComponent firstPage = SidebarComponent.builder()
.addStaticLine(Component.text("First page"))
.addDynamicLine(() -> Component.text("Level: " + player.getLevel()))
.build();
SidebarComponent secondPage = SidebarComponent.builder()
.addStaticLine(Component.text("Second page"))
.addDynamicLine(() -> Component.text("Health: " + player.getHealth()))
.build();
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;
private final Supplier<Component> valueSupplier;
public KeyValueSidebarComponent(@NotNull Component key, @NotNull Supplier<Component> valueSupplier) {
this.key = key;
this.valueSupplier = valueSupplier;
}
@Override
protected void onClosed() {
task.cancel();
public void draw(@NotNull LineDrawable drawable) {
var value = valueSupplier.get();
var line = Component.text()
.append(key)
.append(Component.text(": "))
.append(value.colorIfAbsent(NamedTextColor.AQUA))
.build();
drawable.drawLine(line);
}
}
```
Here's a full sidebar example:
```java
public class ExampleComponentSidebar {
private final Sidebar sidebar;
private final ComponentSidebarLayout componentSidebar;
private final SidebarAnimation<Component> titleAnimation;
public ExampleComponentSidebar(@NotNull Plugin plugin, @NotNull Sidebar sidebar) {
this.sidebar = sidebar;
this.titleAnimation = createGradientAnimation(Component.text("Sidebar Example", Style.style(TextDecoration.BOLD)));
var title = SidebarComponent.animatedLine(titleAnimation);
SimpleDateFormat dtf = new SimpleDateFormat("HH:mm:ss");
// Custom SidebarComponent, see below for how an implementation might look like
SidebarComponent onlinePlayers = new KeyValueSidebarComponent(
Component.text("Online players"),
() -> Component.text(plugin.getServer().getOnlinePlayers().size())
);
SidebarComponent lines = SidebarComponent.builder()
.addDynamicLine(() -> {
var time = dtf.format(new Date());
return Component.text(time, NamedTextColor.GRAY);
})
.addBlankLine()
.addStaticLine(Component.text("A static line"))
.addComponent(onlinePlayers)
.addBlankLine()
.addStaticLine(Component.text("epicserver.net", NamedTextColor.AQUA))
.build();
this.componentSidebar = new ComponentSidebarLayout(title, lines);
}
// Called every tick
public void tick() {
// Advance title animation to the next frame
titleAnimation.nextFrame();
// Update sidebar title & lines
componentSidebar.apply(sidebar);
}
private @NotNull SidebarAnimation<Component> createGradientAnimation(@NotNull Component text) {
float step = 1f / 8f;
TagResolver.Single textPlaceholder = Placeholder.component("text", text);
List<Component> frames = new ArrayList<>((int) (2f / step));
float phase = -1f;
while (phase < 1) {
frames.add(MiniMessage.miniMessage().deserialize("<gradient:yellow:gold:" + phase + "><text>", textPlaceholder));
phase += step;
}
return new CollectionSidebarAnimation<>(frames);
}
}
```
![Sidebar example](./assets/sidebar.gif)`
### TeamManager
```java
TeamManager teamManager = scoreboardLibrary.createTeamManager();
ScoreboardTeam team = teamManager.createIfAbsent("team_name");
// A TeamInfo holds all the properties that a team can have (except the name).
// The global TeamInfo is the default one that will be applied to players,
// however you can give each player a different TeamInfo
TeamInfo teamInfo = team.globalInfo();
// A TeamDisplay holds all the display properties that a team can have (prefix, suffix etc.).
// You can apply different TeamDisplays for each player so different players can see
// different properties on a single ScoreboardTeam. However if you don't need that you can
// use the default TeamDisplay that is created in every ScoreboardTeam.
TeamDisplay teamDisplay = team.defaultDisplay();
teamDisplay.displayName(Component.text("Team Display Name"));
teamDisplay.prefix(Component.text("[Prefix] "));
teamDisplay.suffix(Component.text(" [Suffix]"));
teamDisplay.playerColor(NamedTextColor.RED);
teamDisplay.addEntry(player.getName());
teamInfo.displayName(Component.text("Team Name"));
teamInfo.prefix(Component.text("[Prefix] "));
teamInfo.suffix(Component.text(" [Suffix]"));
teamInfo.playerColor(NamedTextColor.RED);
teamManager.addPlayer(player); // Player will be added to the default TeamDisplay of each ScoreboardTeam
teamManager.addPlayer(player); // Player will be added to the global TeamInfo
// Create a new TeamDisplay like this:
TeamDisplay newTeamDisplay = team.createDisplay();
newTeamDisplay.displayName(Component.text("Other Team Display Name"));
// You can change the TeamInfo like this:
TeamInfo newTeamInfo = team.createTeamInfo();
team.teamInfo(player, newTeamInfo);
// 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!
```
For more examples, check out the [example plugin](https://github.com/MegavexNetwork/scoreboard-library-example)
### 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!
```
+1 -1
View File
@@ -1,3 +1,3 @@
plugins {
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
}
@@ -1,34 +1,48 @@
package net.megavex.scoreboardlibrary.api;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import javax.annotation.concurrent.ThreadSafe;
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.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;
/**
* 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
@ThreadSafe
public interface ScoreboardLibrary {
/**
* Creates an instance of {@link ScoreboardLibrary}.
*
* @param plugin your plugin instance
* @throws NoPacketAdapterAvailableException if there is no packet adapter available for the current server version
*/
static @NotNull ScoreboardLibrary loadScoreboardLibrary(@NotNull Plugin plugin) throws NoPacketAdapterAvailableException {
Class<?> clazz;
Class<?> implClass;
try {
clazz = Class.forName("net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl");
implClass = Class.forName("net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl");
} catch (ClassNotFoundException e) {
throw new RuntimeException("scoreboard-library implementation is not shaded into the classpath");
throw new IllegalStateException("scoreboard-library implementation is not shaded into the classpath");
}
try {
return (ScoreboardLibrary) clazz.getDeclaredConstructor(Plugin.class).newInstance(plugin);
return (ScoreboardLibrary) implClass.getDeclaredConstructor(Plugin.class).newInstance(plugin);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
if (e instanceof InvocationTargetException invocationTargetException) {
if (invocationTargetException.getTargetException() instanceof NoPacketAdapterAvailableException noPacketAdapterAvailableException) {
throw noPacketAdapterAvailableException;
if (e instanceof InvocationTargetException) {
Throwable targetException = ((InvocationTargetException) e).getTargetException();
if (targetException instanceof NoPacketAdapterAvailableException) {
throw (NoPacketAdapterAvailableException) targetException;
}
}
@@ -37,49 +51,58 @@ 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);
}
/**
* Creates a {@link Sidebar}
* Creates a new {@link Sidebar}.
*
* @param maxLines Max sidebar lines
* @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 sidebar lines
* @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);
@NotNull Sidebar createSidebar(@Range(from = 1, to = Integer.MAX_VALUE) int maxLines, @Nullable Locale locale);
/**
* Creates a {@link TeamManager}
* Creates a new {@link TeamManager}.
*
* @return TeamManager
* @return newly created team manager
*/
@NotNull TeamManager createTeamManager();
/**
* Closes this ScoreboardLibrary 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 ScoreboardLibrary 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,54 @@
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.*;
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 void close() {
isClosed = true;
}
@Override
public boolean closed() {
return isClosed;
}
}
@@ -1,16 +1,19 @@
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;
@@ -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,71 @@
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 @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;
}
}
@@ -1,20 +1,21 @@
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.TeamInfo;
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;
private final Map<Player, TeamInfo> teamInfoMap = new HashMap<>();
private final NoopTeamInfo globalInfo = new NoopTeamInfo(this);
private final Map<Player, TeamDisplay> displayMap = new HashMap<>();
private final NoopTeamDisplay defaultDisplay = new NoopTeamDisplay(this);
NoopScoreboardTeam(@NotNull NoopTeamManager teamManager, String name) {
this.teamManager = teamManager;
@@ -32,43 +33,43 @@ class NoopScoreboardTeam implements ScoreboardTeam {
}
@Override
public @NotNull NoopTeamInfo globalInfo() {
return globalInfo;
public @NotNull NoopTeamDisplay defaultDisplay() {
return defaultDisplay;
}
@Override
public @NotNull TeamInfo teamInfo(@NotNull Player player) {
public @NotNull TeamDisplay display(@NotNull Player player) {
Preconditions.checkNotNull(player);
if (!teamManager.players().contains(player)) {
throw new IllegalArgumentException("player not in TeamManager");
}
return Objects.requireNonNull(teamInfoMap.get(player));
return Objects.requireNonNull(displayMap.get(player));
}
@Override
public void teamInfo(@NotNull Player player, @NotNull TeamInfo teamInfo) {
public void display(@NotNull Player player, @NotNull TeamDisplay teamDisplay) {
Preconditions.checkNotNull(player);
Preconditions.checkNotNull(teamInfo);
Preconditions.checkNotNull(teamDisplay);
if (!teamManager.players().contains(player)) {
throw new IllegalArgumentException("player not in TeamManager");
}
if (teamInfo.team() != this || !(teamInfo instanceof NoopTeamInfo)) {
throw new IllegalArgumentException("invalid TeamInfo");
if (teamDisplay.team() != this || !(teamDisplay instanceof NoopTeamDisplay)) {
throw new IllegalArgumentException("invalid TeamDisplay");
}
teamInfoMap.put(player, teamInfo);
displayMap.put(player, teamDisplay);
}
@Override
public @NotNull TeamInfo createTeamInfo() {
return new NoopTeamInfo(this);
public @NotNull TeamDisplay createDisplay() {
return new NoopTeamDisplay(this);
}
public Map<Player, TeamInfo> teamInfoMap() {
return teamInfoMap;
@NotNull Map<Player, TeamDisplay> displayMap() {
return displayMap;
}
}
@@ -1,19 +1,16 @@
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;
@@ -43,7 +40,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
@@ -71,15 +68,15 @@ class NoopSidebar implements Sidebar {
}
@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 @Nullable Component line(int line) {
SidebarUtilities.checkLineBounds(maxLines, line);
checkLineBounds(line);
checkClosed();
return lines[line];
}
@@ -90,10 +87,10 @@ 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();
}
private void checkClosed() {
@@ -101,4 +98,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,22 +1,23 @@
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.TeamInfo;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
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;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static net.kyori.adventure.text.Component.empty;
class NoopTeamInfo implements TeamInfo {
class NoopTeamDisplay implements TeamDisplay {
private final NoopScoreboardTeam team;
private final Set<String> entries = new HashSet<>();
@@ -28,7 +29,7 @@ class NoopTeamInfo implements TeamInfo {
private CollisionRule collisionRule = CollisionRule.ALWAYS;
private NamedTextColor playerColor = null;
NoopTeamInfo(@NotNull NoopScoreboardTeam team) {
NoopTeamDisplay(@NotNull NoopScoreboardTeam team) {
this.team = team;
}
@@ -58,8 +59,8 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo displayName(@NotNull Component displayName) {
this.displayName = displayName;
public @NotNull TeamDisplay displayName(@NotNull ComponentLike displayName) {
this.displayName = displayName.asComponent();
return this;
}
@@ -69,8 +70,8 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo prefix(@NotNull Component prefix) {
this.prefix = prefix;
public @NotNull TeamDisplay prefix(@NotNull ComponentLike prefix) {
this.prefix = prefix.asComponent();
return this;
}
@@ -80,8 +81,8 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo suffix(@NotNull Component suffix) {
this.suffix = suffix;
public @NotNull TeamDisplay suffix(@NotNull ComponentLike suffix) {
this.suffix = suffix.asComponent();
return this;
}
@@ -91,7 +92,7 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo friendlyFire(boolean friendlyFire) {
public @NotNull TeamDisplay friendlyFire(boolean friendlyFire) {
this.friendlyFire = friendlyFire;
return this;
}
@@ -102,7 +103,7 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
public @NotNull TeamDisplay canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
return this;
}
@@ -113,7 +114,7 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility) {
public @NotNull TeamDisplay nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
return this;
}
@@ -124,7 +125,7 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo collisionRule(@NotNull CollisionRule collisionRule) {
public @NotNull TeamDisplay collisionRule(@NotNull CollisionRule collisionRule) {
this.collisionRule = collisionRule;
return this;
}
@@ -135,7 +136,7 @@ class NoopTeamInfo implements TeamInfo {
}
@Override
public @NotNull TeamInfo playerColor(@Nullable NamedTextColor playerColor) {
public @NotNull TeamDisplay playerColor(@Nullable NamedTextColor playerColor) {
this.playerColor = playerColor;
return this;
}
@@ -1,21 +1,17 @@
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.TeamInfo;
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 org.jetbrains.annotations.Nullable;
import java.util.*;
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<>();
@@ -33,7 +29,7 @@ class NoopTeamManager implements TeamManager {
@Override
public @NotNull Collection<Player> players() {
return closed ? Set.of() : Collections.unmodifiableSet(players);
return closed ? Collections.emptySet() : Collections.unmodifiableSet(players);
}
@Override
@@ -45,8 +41,8 @@ class NoopTeamManager implements TeamManager {
return false;
}
for (var team : teams.values()) {
team.teamInfoMap().remove(player);
for (NoopScoreboardTeam team : teams.values()) {
team.displayMap().remove(player);
}
return true;
@@ -54,7 +50,7 @@ class NoopTeamManager implements TeamManager {
@Override
public @NotNull Collection<ScoreboardTeam> teams() {
return closed ? Set.of() : Collections.unmodifiableCollection(teams.values());
return closed ? Collections.emptySet() : Collections.unmodifiableCollection(teams.values());
}
@Override
@@ -68,21 +64,21 @@ class NoopTeamManager implements TeamManager {
}
@Override
public @NotNull ScoreboardTeam createIfAbsent(@NotNull String name, @Nullable BiFunction<Player, ScoreboardTeam, TeamInfo> teamInfoFunction) {
public @NotNull ScoreboardTeam createIfAbsent(@NotNull String name, @Nullable BiFunction<Player, ScoreboardTeam, TeamDisplay> teamDisplayFunction) {
Preconditions.checkNotNull(name);
checkClosed();
name = name.toLowerCase();
var team = teams.get(name);
NoopScoreboardTeam team = teams.get(name);
if (team != null) {
return team;
}
team = new NoopScoreboardTeam(this, name);
for (var player : players) {
var teamInfo = teamInfoFunction == null ? team.globalInfo() : teamInfoFunction.apply(player, team);
validateTeamInfo(team, teamInfo);
team.teamInfoMap().put(player, teamInfo);
for (Player player : players) {
TeamDisplay teamDisplay = teamDisplayFunction == null ? team.defaultDisplay() : teamDisplayFunction.apply(player, team);
validateTeamDisplay(team, teamDisplay);
team.displayMap().put(player, teamDisplay);
}
return team;
@@ -106,7 +102,7 @@ class NoopTeamManager implements TeamManager {
}
@Override
public boolean addPlayer(@NotNull Player player, @Nullable Function<ScoreboardTeam, TeamInfo> teamInfoFunction) {
public boolean addPlayer(@NotNull Player player, @Nullable Function<ScoreboardTeam, TeamDisplay> teamDisplayFunction) {
Preconditions.checkNotNull(player);
checkClosed();
@@ -114,22 +110,22 @@ class NoopTeamManager implements TeamManager {
return false;
}
for (var team : teams.values()) {
var teamInfo = teamInfoFunction == null ? team.globalInfo() : teamInfoFunction.apply(team);
validateTeamInfo(team, teamInfo);
team.teamInfoMap().put(player, teamInfo);
for (NoopScoreboardTeam team : teams.values()) {
TeamDisplay teamDisplay = teamDisplayFunction == null ? team.defaultDisplay() : teamDisplayFunction.apply(team);
validateTeamDisplay(team, teamDisplay);
team.displayMap().put(player, teamDisplay);
}
return true;
}
private void validateTeamInfo(@NotNull ScoreboardTeam team, @Nullable TeamInfo teamInfo) {
if (teamInfo == null || teamInfo.team() != team) {
throw new IllegalArgumentException("invalid TeamInfo");
private void validateTeamDisplay(@NotNull ScoreboardTeam team, @Nullable TeamDisplay teamDisplay) {
if (teamDisplay == null || teamDisplay.team() != team) {
throw new IllegalArgumentException("invalid TeamDisplay");
}
if (!(teamInfo instanceof NoopTeamInfo)) {
throw new IllegalArgumentException("must be TeamInfoImpl");
if (!(teamDisplay instanceof NoopTeamDisplay)) {
throw new IllegalArgumentException("must be TeamDisplayImpl");
}
}
@@ -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,99 @@
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;
/**
* 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);
}
}
/**
* 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,144 @@
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);
/**
* 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);
}
@@ -1,175 +0,0 @@
package net.megavex.scoreboardlibrary.api.sidebar;
import com.google.common.base.Preconditions;
import java.util.function.Supplier;
import javax.annotation.concurrent.NotThreadSafe;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.api.util.SidebarUtilities;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import static net.kyori.adventure.text.Component.empty;
@NotThreadSafe
public class AbstractSidebar {
protected final Sidebar sidebar;
private final Line[] lines;
private boolean closed;
public AbstractSidebar(@NotNull Sidebar sidebar) {
this.sidebar = sidebar;
this.lines = new Line[sidebar.maxLines()];
}
public final @NotNull Sidebar sidebar() {
return sidebar;
}
@ApiStatus.OverrideOnly
protected void onClosed() {
}
/**
* Registers a static line for this AbstractSidebar
*
* @param line Line to register
* @param value Static value
*/
protected final void registerStaticLine(@Range(from = 0, to = Sidebar.MAX_LINES - 1) int line, @NotNull Component value) {
SidebarUtilities.checkLineBounds(sidebar.maxLines(), line);
checkLineAvailable(line);
Preconditions.checkNotNull(value);
checkClosed();
lines[line] = new Line(line);
sidebar.line(line, value);
}
/**
* Registers a line that can change its value
*
* @param line Line to register
* @param lineSupplier Line value supplier
* @return The created LineSupplier
*/
protected final @NotNull AbstractSidebar.DynamicLine registerDynamicLine(@Range(from = 0, to = Sidebar.MAX_LINES - 1) int line, @NotNull Supplier<@Nullable Component> lineSupplier) {
SidebarUtilities.checkLineBounds(sidebar.maxLines(), line);
checkLineAvailable(line);
Preconditions.checkNotNull(lineSupplier);
checkClosed();
var sidebarLine = new DynamicLine(line, lineSupplier);
lines[line] = sidebarLine;
sidebar.line(line, lineSupplier.get());
return sidebarLine;
}
/**
* Registers an empty line
*
* @param line Line to register
*/
protected final void registerEmptyLine(@Range(from = 0, to = Sidebar.MAX_LINES - 1) int line) {
registerStaticLine(line, empty());
}
/**
* Unregisters a line
*
* @param line Line to unregister
*/
protected final void unregisterLine(@NotNull AbstractSidebar.Line line) {
Preconditions.checkNotNull(line);
checkClosed();
lines[line.line] = null;
sidebar.line(line.line, null);
}
/**
* Gets the {@link Line} of an index
*
* @param line Line
* @return Supplier of line
*/
protected @Nullable AbstractSidebar.Line getLine(@Range(from = 0, to = Sidebar.MAX_LINES - 1) int line) {
SidebarUtilities.checkLineBounds(sidebar.maxLines(), line);
checkClosed();
return lines[line];
}
/**
* Closes this AbstractSidebar
*/
public final void close() {
close(true);
}
/**
* Closes this AbstractSidebar
*
* @param closeWrappedSidebar Whether to close the wrapped {@link Sidebar} or just clear all lines from it
*/
public final void close(boolean closeWrappedSidebar) {
if (closed) return;
closed = true;
if (closeWrappedSidebar) {
sidebar.close();
} else {
for (var line : lines) {
if (line != null) {
sidebar.line(line.line, null);
}
}
}
onClosed();
}
/**
* @return Whether this AbstractSidebar is closed
*/
public final boolean closed() {
return closed;
}
@Override
public int hashCode() {
return sidebar.hashCode();
}
protected final void checkClosed() {
Preconditions.checkState(!closed, "AbstractSidebar is closed");
}
private void checkLineAvailable(int line) {
if (lines[line] != null) {
throw new IllegalArgumentException("line " + line + " is already registered");
}
}
public sealed class Line permits DynamicLine {
protected final int line;
private Line(int line) {
this.line = line;
}
}
public final class DynamicLine extends Line {
private final Supplier<Component> function;
private DynamicLine(int line, @NotNull Supplier<Component> function) {
super(line);
this.function = function;
}
public void update() {
sidebar.line(line, function.get());
}
}
}
@@ -1,54 +1,83 @@
package net.megavex.scoreboardlibrary.api.sidebar;
import java.util.Collection;
import java.util.Locale;
import javax.annotation.concurrent.NotThreadSafe;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.megavex.scoreboardlibrary.api.ScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
@ApiStatus.NonExtendable
@NotThreadSafe
public interface Sidebar {
// Constants
import java.util.Collection;
import java.util.Locale;
/**
* 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 {
/**
* 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 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
* 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);
}
/**
* Clears all lines
* 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.
*/
default void clearLines() {
for (int i = 0; i < maxLines(); i++) {
@@ -59,46 +88,77 @@ public interface Sidebar {
// Title
/**
* @return Current title of this Sidebar
* @return title of the sidebar, defaults to {@link Component#empty}
*/
@NotNull Component title();
/**
* Sets the title
* Sets the title of the sidebar.
*
* @param title Title
* @param title title
*/
void title(@NotNull Component title);
void title(@NotNull ComponentLike title);
// Players
/**
* @return unmodifiable collection of viewers in this Sidebar
* @see #addPlayer
* @see #removePlayer
*/
@NotNull Collection<Player> players();
/**
* 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 was added
*/
boolean addPlayer(@NotNull Player player);
boolean removePlayer(@NotNull Player player);
/**
* Adds multiple viewers to this Sidebar.
*
* @param players viewers to add
* @see #addPlayer
*/
default void addPlayers(@NotNull Collection<Player> players) {
for (var player : players) {
for (Player player : players) {
addPlayer(player);
}
}
/**
* Removes a viewer from this Sidebar.
*
* @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 (var player : players) {
for (Player player : players) {
removePlayer(player);
}
}
// 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
* @return Whether this sidebar is closed
* @see #close
*/
boolean closed();
}
@@ -0,0 +1,86 @@
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;
public ComponentSidebarLayout(@NotNull SidebarComponent titleComponent, @NotNull SidebarComponent linesComponent) {
Preconditions.checkNotNull(titleComponent);
Preconditions.checkNotNull(linesComponent);
this.titleComponent = titleComponent;
this.linesComponent = linesComponent;
}
public @NotNull SidebarComponent titleComponent() {
return titleComponent;
}
public @NotNull SidebarComponent linesComponent() {
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);
SidebarTitleDrawable titleDrawable = new SidebarTitleDrawable();
titleComponent.draw(titleDrawable);
sidebar.title(titleDrawable.title == null ? empty() : titleDrawable.title);
SidebarLineDrawable linesDrawable = new SidebarLineDrawable(sidebar);
linesComponent.draw(linesDrawable);
for (int i = linesDrawable.index; i < Sidebar.MAX_LINES; i++) {
sidebar.line(i, null);
}
}
private static class SidebarTitleDrawable implements LineDrawable {
private Component title;
@Override
public void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat) {
Preconditions.checkNotNull(line);
if (title == null) {
title = line.asComponent();
}
}
}
private static class SidebarLineDrawable implements LineDrawable {
private final Sidebar sidebar;
private int index = 0;
public SidebarLineDrawable(@NotNull Sidebar sidebar) {
this.sidebar = sidebar;
}
@Override
public void drawLine(@NotNull ComponentLike line, @Nullable ScoreFormat scoreFormat) {
Preconditions.checkNotNull(line);
if (index < Sidebar.MAX_LINES) {
sidebar.line(index++, line, scoreFormat);
}
}
}
}
@@ -0,0 +1,30 @@
package net.megavex.scoreboardlibrary.api.sidebar.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 line by line. This is either the lines of a sidebar or its title.
*/
@ApiStatus.NonExtendable
public interface LineDrawable {
/**
* Draws the next line, or does nothing if it has reached a limit.
*
* @param line line component
*/
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);
}
@@ -0,0 +1,96 @@
package net.megavex.scoreboardlibrary.api.sidebar.component;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
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 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 <T extends ComponentLike> SidebarComponent dynamicLine(@NotNull Supplier<T> lineSupplier) {
return drawable -> drawable.drawLine(lineSupplier.get());
}
static @NotNull <T extends ComponentLike> SidebarComponent animatedLine(@NotNull SidebarAnimation<T> animation) {
Preconditions.checkNotNull(animation);
return drawable -> drawable.drawLine(animation.currentFrame());
}
static @NotNull <T extends SidebarComponent> SidebarComponent animatedComponent(@NotNull SidebarAnimation<T> animation) {
Preconditions.checkNotNull(animation);
return drawable -> animation.currentFrame().draw(drawable);
}
static @NotNull Builder builder() {
return new Builder();
}
void draw(@NotNull LineDrawable drawable);
final class Builder {
private final List<SidebarComponent> children = new ArrayList<>(4);
private Builder() {
}
public @NotNull Builder addComponent(@NotNull SidebarComponent component) {
Preconditions.checkNotNull(component);
children.add(component);
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() {
return addComponent(SidebarComponent.blankLine());
}
public @NotNull <T extends ComponentLike> Builder addDynamicLine(@NotNull Supplier<T> lineSupplier) {
return addComponent(SidebarComponent.dynamicLine(lineSupplier));
}
public <T extends ComponentLike> @NotNull Builder addAnimatedLine(@NotNull SidebarAnimation<T> animation) {
return addComponent(SidebarComponent.animatedLine(animation));
}
public @NotNull <T extends SidebarComponent> Builder addAnimatedComponent(@NotNull SidebarAnimation<T> animation) {
return addComponent(SidebarComponent.animatedComponent(animation));
}
public @NotNull SidebarComponent build() {
ImmutableList<SidebarComponent> children = ImmutableList.copyOf(this.children);
return drawable -> {
for (SidebarComponent child : children) {
child.draw(drawable);
}
};
}
}
}
@@ -0,0 +1,56 @@
package net.megavex.scoreboardlibrary.api.sidebar.component.animation;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
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;
public CollectionSidebarAnimation(@NotNull Collection<T> frames) {
Preconditions.checkNotNull(frames);
Preconditions.checkArgument(!frames.isEmpty());
this.frames = ImmutableList.copyOf(frames);
}
@Override
public @Unmodifiable @NotNull List<T> frames() {
return frames;
}
@Override
public int currentFrameIndex() {
return currentFrameIndex;
}
@Override
public void switchFrame(int frameIndex) {
if (frameIndex < 0 || frameIndex >= frames.size()) {
throw new ArrayIndexOutOfBoundsException();
}
currentFrameIndex = frameIndex;
}
@Override
public @NotNull T currentFrame() {
return frames.get(currentFrameIndex);
}
@Override
public void nextFrame() {
if (++currentFrameIndex == frames.size()) {
currentFrameIndex = 0;
}
}
}
@@ -0,0 +1,31 @@
package net.megavex.scoreboardlibrary.api.sidebar.component.animation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.util.List;
/**
* A {@link SidebarAnimation} with predetermined frames.
*
* @param <T> frame type
*/
public interface FramedSidebarAnimation<T> extends SidebarAnimation<T> {
/**
* @return all frames in this animation
*/
@Unmodifiable @NotNull List<T> frames();
/**
* @return the index of the current frame
*/
int currentFrameIndex();
/**
* Switches the current frame to specified index
*
* @param frameIndex the index of the new current frame
* @throws ArrayIndexOutOfBoundsException if frame is out of bounds
*/
void switchFrame(int frameIndex);
}
@@ -0,0 +1,21 @@
package net.megavex.scoreboardlibrary.api.sidebar.component.animation;
import net.megavex.scoreboardlibrary.api.sidebar.component.ComponentSidebarLayout;
import org.jetbrains.annotations.NotNull;
/**
* An animation that can be used with {@link ComponentSidebarLayout}s
*
* @param <T> frame type
*/
public interface SidebarAnimation<T> {
/**
* @return the current frame of the animation
*/
@NotNull T currentFrame();
/**
* Advances to the next frame of the animation
*/
void nextFrame();
}
@@ -3,43 +3,56 @@ package net.megavex.scoreboardlibrary.api.team;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe
/**
* Represents a scoreboard team.
* 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 Global {@link TeamInfo} of this team
*/
@NotNull TeamInfo globalInfo();
/**
* Gets the {@link TeamInfo} of a player
* Gets the default {@link TeamDisplay} of this team.
* All players are added to this display by default.
*
* @param player Player
* @return {@link TeamInfo} of this player
* @return default team display
*/
@NotNull TeamInfo teamInfo(@NotNull Player player);
@NotNull TeamDisplay defaultDisplay();
/**
* Changes a player's visible {@link TeamInfo}
* Gets the {@link TeamDisplay} that a player sees.
*
* @param player Player
* @param teamInfo New {@link TeamInfo} of Player
* @param player player
* @return team display the player sees
* @throws IllegalArgumentException if player is not in the TeamManager
*/
void teamInfo(@NotNull Player player, @NotNull TeamInfo teamInfo);
@NotNull TeamDisplay display(@NotNull Player player);
/**
* @return New {@link TeamInfo} which is assigned to this team
* Updates the team {@link TeamDisplay} that a player sees for this team.
*
* @param player player
* @param teamDisplay new {@link TeamDisplay} of player
* @throws IllegalArgumentException if player is not in the TeamManager
*/
@NotNull TeamInfo createTeamInfo();
void display(@NotNull Player player, @NotNull TeamDisplay teamDisplay);
/**
* Creates a new {@link TeamDisplay}.
* To show it to players, use {@link #display(Player, TeamDisplay)}.
*
* @return newly created team display
*/
@NotNull TeamDisplay createDisplay();
}
@@ -0,0 +1,173 @@
package net.megavex.scoreboardlibrary.api.team;
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 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 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
* @see #entries
*/
boolean addEntry(@NotNull String entry);
/**
* Removes an entry.
*
* @param entry entry to remove
* @return whether the entry was removed
* @see #entries
*/
boolean removeEntry(@NotNull String entry);
// Properties
/**
* @return display name, defaults to {@link Component#empty()}
*/
@NotNull Component displayName();
/**
* Updates the display name.
*
* @param displayName new display name
*/
@NotNull TeamDisplay displayName(@NotNull ComponentLike displayName);
/**
* 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();
/**
* Updates the prefix.
*
* @param prefix new prefix
* @see #prefix()
*/
@NotNull TeamDisplay prefix(@NotNull ComponentLike prefix);
/**
* Gets the suffix, which defaults to {@link Component#empty()}.
* The suffix is appended to all team members' names.
*
* @return suffix
*/
@NotNull Component suffix();
/**
* Updates the suffix.
*
* @param suffix new suffix
* @see #suffix()
*/
@NotNull TeamDisplay suffix(@NotNull ComponentLike suffix);
/**
* @return friendly fire rule value, defaults to false
*/
boolean friendlyFire();
/**
* Updates the friendly fire rule.
*
* @param friendlyFire new rule value
*/
@NotNull TeamDisplay friendlyFire(boolean friendlyFire);
/**
* 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();
/**
* Updates the can see friendly invisibles rule.
*
* @param canSeeFriendlyInvisibles new rule value
* @see #canSeeFriendlyInvisibles()
*/
@NotNull TeamDisplay canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles);
/**
* @return name tag visibility rule value
* @since Minecraft 1.8
*/
@NotNull NameTagVisibility nameTagVisibility();
/**
* Updates the name tag visibility rule.
*
* @param nameTagVisibility new rule value
* @since Minecraft 1.8
*/
@NotNull TeamDisplay nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility);
/**
* @return collision rule
* @since Minecraft 1.9
*/
@NotNull CollisionRule collisionRule();
/**
* Updates the collision rule.
*
* @param collisionRule new rule value
* @since Minecraft 1.9
*/
@NotNull TeamDisplay collisionRule(@NotNull CollisionRule collisionRule);
/**
* 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();
/**
* Updates the player color.
*
* @param playerColor new player color
* @see #playerColor()
* @since Minecraft 1.8
*/
@NotNull TeamDisplay playerColor(@Nullable NamedTextColor playerColor);
}
@@ -1,139 +0,0 @@
package net.megavex.scoreboardlibrary.api.team;
import java.util.Collection;
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 org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
@ApiStatus.NonExtendable
@NotThreadSafe
public interface TeamInfo {
/**
* @return Team which is assigned to this {@link TeamInfo}
*/
@NotNull ScoreboardTeam team();
// Entries
@NotNull Collection<String> entries();
/**
* Adds an entry
*
* @param entry Entry to add
* @return Whether the entry was added
*/
boolean addEntry(@NotNull String entry);
/**
* Removes an entry
*
* @param entry Entry to remove
* @return Whether the entry was removed
*/
boolean removeEntry(@NotNull String entry);
// Properties
/**
* @return Display name
*/
@NotNull Component displayName();
/**
* Sets the display name
*
* @param displayName Display name
*/
@NotNull TeamInfo displayName(@NotNull Component displayName);
/**
* @return Prefix
*/
@NotNull Component prefix();
/**
* Sets the prefix
*
* @param prefix Prefix
*/
@NotNull TeamInfo prefix(@NotNull Component prefix);
/**
* @return Suffix
*/
@NotNull Component suffix();
/**
* Sets the suffix
*
* @param suffix Suffix
*/
@NotNull TeamInfo suffix(@NotNull Component suffix);
/**
* @return Friendly fire rule
*/
boolean friendlyFire();
/**
* Sets whether friendly fire is allowed
*
* @param friendlyFire whether friendly fire is allowed
*/
@NotNull TeamInfo friendlyFire(boolean friendlyFire);
/**
* @return Can see friendly invisibles rule
*/
boolean canSeeFriendlyInvisibles();
/**
* Sets whether players can see friendly invisibles
*
* @param canSeeFriendlyInvisibles whether players can see friendly invisibles
*/
@NotNull TeamInfo canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles);
/**
* @return Name tag visibility rule
*/
@NotNull NameTagVisibility nameTagVisibility();
/**
* Sets the {@link NameTagVisibility}
*
* @param nameTagVisibility Name tag visibility rule
*/
@NotNull TeamInfo nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility);
/**
* @return Collision rule
*/
@NotNull CollisionRule collisionRule();
/**
* Sets the {@link CollisionRule}.
*
* @param collisionRule Collision rule
*/
@NotNull TeamInfo collisionRule(@NotNull CollisionRule collisionRule);
/**
* @return Player color
*/
@Nullable NamedTextColor playerColor();
/**
* Sets the player color
*
* @param playerColor Player color
*/
@NotNull TeamInfo playerColor(@Nullable NamedTextColor playerColor);
}
@@ -1,139 +1,153 @@
package net.megavex.scoreboardlibrary.api.team;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Function;
import javax.annotation.concurrent.NotThreadSafe;
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.function.BiFunction;
import java.util.function.Function;
/**
* 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
@NotThreadSafe
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 Team with this name, null if not found
* @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 teamInfoFunction A function that provides team info's 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, TeamInfo> teamInfoFunction);
@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 teamInfoFunction A function that returns the {@link TeamInfo} that the player should have
* @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, TeamInfo> teamInfoFunction);
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 teamInfoFunction Function that returns the {@link TeamInfo} that the player should have
* @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, TeamInfo> teamInfoFunction) {
for (var player : players) {
addPlayer(player, teamInfoFunction);
default void addPlayers(@NotNull Collection<Player> players, @Nullable Function<ScoreboardTeam, TeamDisplay> teamDisplayFunction) {
for (Player player : players) {
addPlayer(player, teamDisplayFunction);
}
}
/**
* 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 (var player : players) {
for (Player player : players) {
removePlayer(player);
}
}
@@ -141,12 +155,14 @@ public interface TeamManager {
// 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);
}
}
}
@@ -0,0 +1,24 @@
package net.megavex.scoreboardlibrary.api.animation;
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;
class CollectionAnimationTest {
@Test
void loopTest() {
List<Integer> frames = Arrays.asList(0, 1, 2);
CollectionSidebarAnimation<Integer> animation = new CollectionSidebarAnimation<>(frames);
assertEquals(0, animation.currentFrame());
animation.nextFrame();
assertEquals(1, animation.currentFrame());
animation.nextFrame();
assertEquals(2, animation.currentFrame());
animation.nextFrame();
assertEquals(0, animation.currentFrame());
}
}
@@ -0,0 +1,90 @@
package net.megavex.scoreboardlibrary.api.sidebar.component;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary;
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar;
import net.megavex.scoreboardlibrary.api.sidebar.component.animation.CollectionSidebarAnimation;
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.*;
class ComponentSidebarLayoutTest {
private final Sidebar sidebar = new NoopScoreboardLibrary().createSidebar();
@Test
void maxLines() {
SidebarComponent.Builder builder = SidebarComponent.builder();
for (int i = 0; i < Sidebar.MAX_LINES + 1; i++) {
builder.addComponent(SidebarComponent.staticLine(text(i)));
}
ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(drawable -> {
}, builder.build());
componentSidebar.apply(sidebar);
for (int i = 0; i < Sidebar.MAX_LINES; i++) {
assertNotNull(sidebar.line(i));
}
}
@Test
void titleComponent() {
Component title = text("title");
ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(SidebarComponent.staticLine(title), drawable -> {
});
componentSidebar.apply(sidebar);
assertEquals(title, sidebar.title());
}
@Test
void animatedLines() {
SidebarAnimation<Component> animation = new CollectionSidebarAnimation<>(Arrays.asList(text("frame 1"), text("frame 2")));
SidebarComponent lines = SidebarComponent.builder().addAnimatedLine(animation).build();
ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(drawable -> {
}, lines);
componentSidebar.apply(sidebar);
assertEquals(animation.currentFrame(), sidebar.line(0));
animation.nextFrame();
componentSidebar.apply(sidebar);
assertEquals(animation.currentFrame(), sidebar.line(0));
}
@Test
void animatedComponents() {
Component frame1Line = text("frame with one line");
Component frame2Line1 = text("frame with");
Component frame2Line2 = text("two lines");
SidebarComponent frame1 = SidebarComponent.staticLine(frame1Line);
SidebarComponent frame2 = drawable -> {
drawable.drawLine(frame2Line1);
drawable.drawLine(frame2Line2);
};
SidebarAnimation<SidebarComponent> animation = new CollectionSidebarAnimation<>(Arrays.asList(frame1, frame2));
SidebarComponent lines = SidebarComponent.builder().addAnimatedComponent(animation).build();
ComponentSidebarLayout componentSidebar = new ComponentSidebarLayout(drawable -> {
}, lines);
componentSidebar.apply(sidebar);
assertEquals(frame1Line, sidebar.line(0));
assertNull(sidebar.line(1));
animation.nextFrame();
componentSidebar.apply(sidebar);
assertEquals(frame2Line1, sidebar.line(0));
assertEquals(frame2Line2, sidebar.line(1));
animation.nextFrame();
componentSidebar.apply(sidebar);
assertEquals(frame1Line, sidebar.line(0));
assertNull(sidebar.line(1));
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 931 KiB

+3 -38
View File
@@ -1,40 +1,5 @@
plugins {
`java-library`
}
allprojects {
version = "2.0.0-SNAPSHOT"
group = "net.megavex.scoreboardlibrary"
description = "Powerful packet-level Scoreboard library for Paper/Spigot servers"
repositories {
mavenCentral()
maven("https://oss.sonatype.org/content/groups/public/")
maven("https://papermc.io/repo/repository/maven-public/")
}
}
subprojects {
apply(plugin = "java-library")
dependencies {
compileOnly("io.papermc.paper:paper-api:1.19.3-R0.1-SNAPSHOT")
val adventureVersion = "4.12.0"
compileOnly("net.kyori:adventure-api:$adventureVersion")
compileOnly("net.kyori:adventure-text-serializer-legacy:$adventureVersion")
compileOnly("net.kyori:adventure-text-serializer-gson:$adventureVersion")
testImplementation("net.kyori:adventure-api:$adventureVersion")
testImplementation("net.kyori:adventure-text-serializer-legacy:$adventureVersion")
testImplementation("net.kyori:adventure-text-serializer-gson:$adventureVersion")
}
tasks.withType<JavaCompile>().configureEach {
sourceCompatibility = JavaVersion.VERSION_17.toString()
targetCompatibility = JavaVersion.VERSION_17.toString()
options.encoding = "UTF-8"
options.isIncremental = true
options.compilerArgs = mutableListOf("-Xlint:-deprecation,-unchecked")
}
version = "2.2.2"
group = "net.megavex"
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,73 @@
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/")
}
dependencies {
compileOnly(libs.spigotApi)
compileOnly(libs.bundles.adventure)
testImplementation(libs.spigotApi)
testImplementation(libs.bundles.adventure)
testImplementation(libs.junitJupiterApi)
testRuntimeOnly(libs.junitJupiterEngine)
}
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"])
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
plugins {
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
}
dependencies {
api(project(":api"))
api(project(":scoreboard-library-api"))
}
@@ -1,23 +1,19 @@
package net.megavex.scoreboardlibrary.implementation.commons;
import org.jetbrains.annotations.NotNull;
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;
import java.util.*;
public final class CollectionProvider {
private static final MethodHandle mapConstructor, setConstructor, listConstructor;
static {
Lookup lookup = MethodHandles.publicLookup();
var parameters = MethodType.methodType(void.class, int.class);
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);
@@ -1,14 +1,16 @@
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;
import net.kyori.adventure.text.serializer.legacy.LegacyFormat;
import net.kyori.adventure.translation.GlobalTranslator;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.Nullable;
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;
@@ -18,13 +20,13 @@ public final class LegacyFormatUtil {
private static final Map<NamedTextColor, Character> legacyMap;
static {
var values = ChatColor.values();
ChatColor[] values = ChatColor.values();
legacyMap = CollectionProvider.map(values.length);
for (var value : values) {
for (ChatColor value : values) {
if (!value.isColor()) continue;
char c = value.getChar();
var format = Objects.requireNonNull(parseChar(c));
LegacyFormat format = Objects.requireNonNull(parseChar(c));
legacyMap.put((NamedTextColor) format.color(), c);
}
}
@@ -37,21 +39,28 @@ public final class LegacyFormatUtil {
return text;
}
var lastNotColorCharIndex = limit - 1;
while (text.charAt(lastNotColorCharIndex) == ChatColor.COLOR_CHAR) {
int lastNotColorCharIndex = limit - 1;
while (text.charAt(lastNotColorCharIndex) == LegacyComponentSerializer.SECTION_CHAR) {
lastNotColorCharIndex--;
}
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');
@@ -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-1.20.2, 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,28 @@
package net.megavex.scoreboardlibrary.implementation.commons;
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;
public interface LocaleProvider {
Locale DEFAULT_LOCALE = Locale.US;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Locale;
import java.util.function.Function;
static @NotNull LocaleProvider localeProvider() {
var lookup = MethodHandles.publicLookup();
public final class LocaleProvider {
private static final Locale DEFAULT_LOCALE = Locale.US;
private static final Function<Player, Locale> provider = get();
public static @NotNull Locale locale(Player player) {
return provider.apply(player);
}
private static @NotNull Function<Player, Locale> get() {
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
try {
var adventureMethod = lookup.findVirtual(Player.class, "locale", MethodType.methodType(Locale.class));
@SuppressWarnings("JavaLangInvokeHandleSignature")
MethodHandle adventureMethod = lookup.findVirtual(Player.class, "locale", MethodType.methodType(Locale.class));
return player -> {
try {
return (Locale) adventureMethod.invokeExact(player);
@@ -24,12 +33,12 @@ public interface LocaleProvider {
} catch (IllegalAccessException | NoSuchMethodException ignored) {
}
var methodType = MethodType.methodType(String.class);
MethodType methodType = MethodType.methodType(String.class);
try {
var legacySpigotMethod = lookup.findVirtual(Player.Spigot.class, "getLocale", methodType);
MethodHandle legacySpigotMethod = lookup.findVirtual(Player.Spigot.class, "getLocale", methodType);
return player -> {
try {
var locale = Translator.parseLocale((String) legacySpigotMethod.invokeExact(player.spigot()));
Locale locale = Translator.parseLocale((String) legacySpigotMethod.invokeExact(player.spigot()));
return locale == null ? DEFAULT_LOCALE : locale;
} catch (Throwable e) {
throw new RuntimeException(e);
@@ -39,10 +48,10 @@ public interface LocaleProvider {
}
try {
var legacyMethod = lookup.findVirtual(Player.class, "getLocale", methodType);
MethodHandle legacyMethod = lookup.findVirtual(Player.class, "getLocale", methodType);
return player -> {
try {
var locale = Translator.parseLocale((String) legacyMethod.invokeExact(player));
@org.jetbrains.annotations.Nullable Locale locale = Translator.parseLocale((String) legacyMethod.invokeExact(player));
return locale == null ? DEFAULT_LOCALE : locale;
} catch (Throwable e) {
throw new RuntimeException(e);
@@ -52,6 +61,4 @@ public interface LocaleProvider {
throw new RuntimeException("No way to get players locale found");
}
}
@NotNull Locale locale(@NotNull Player player);
}
+9 -5
View File
@@ -1,6 +1,8 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.7.21"
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
alias(libs.plugins.kotlin)
}
kotlin {
@@ -8,10 +10,12 @@ kotlin {
}
dependencies {
api(project(":api"))
api(project(":scoreboard-library-api"))
testImplementation(kotlin("test"))
}
tasks.test {
useJUnitPlatform()
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "1.8"
}
}
@@ -0,0 +1,10 @@
package net.megavex.scoreboardlibrary.extra.kotlin
import net.megavex.scoreboardlibrary.api.sidebar.component.ComponentSidebarLayout
import net.megavex.scoreboardlibrary.api.sidebar.component.SidebarComponent
public inline val ComponentSidebarLayout.titleComponent: SidebarComponent
get() = titleComponent()
public inline val ComponentSidebarLayout.linesComponent: SidebarComponent
get() = linesComponent()
@@ -1,93 +0,0 @@
package net.megavex.scoreboardlibrary.extra.kotlin
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.Component.empty
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(ExperimentalContracts::class)
public fun Sidebar.lines(
direction: LinesBuilder.Direction = LinesBuilder.Direction.TOP_TO_BOTTOM,
block: LinesBuilder.() -> Unit
) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val range = when (direction) {
LinesBuilder.Direction.TOP_TO_BOTTOM -> IntProgression.fromClosedRange(0, maxLines - 1, 1)
LinesBuilder.Direction.BOTTOM_TO_TOP -> IntProgression.fromClosedRange(maxLines - 1, 0, -1)
}
block(LinesBuilder(this, range))
}
@OptIn(ExperimentalContracts::class)
public fun Sidebar.lines(
progression: IntProgression,
block: LinesBuilder.() -> Unit
) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
require(progression.first in 0 until maxLines) { "invalid range" }
require(progression.last in 0 until maxLines) { "invalid range" }
block(LinesBuilder(this, progression))
}
public class LinesBuilder internal constructor(private val sidebar: Sidebar, progression: IntProgression) {
public enum class Direction {
TOP_TO_BOTTOM,
BOTTOM_TO_TOP
}
public interface DynamicLine {
public fun update()
}
private var iterator = progression.iterator()
public fun line(value: Component) {
sidebar.line(nextLine(), value)
}
public fun emptyLine() {
line(empty())
}
public fun dynamicLine(valueProvider: () -> Component): DynamicLine {
val lineIndex = nextLine()
val dynamicLine = object : DynamicLine {
override fun update() {
sidebar.line(lineIndex, valueProvider())
}
}
dynamicLine.update()
return dynamicLine
}
public fun skip(lines: Int = 1) {
require(lines >= 1) { "lines cannot be less than 1" }
repeat(lines) {
if (!iterator.hasNext()) {
error("reached end of range")
}
iterator.nextInt()
}
}
public fun nextLine(): Int {
while (iterator.hasNext()) {
val line = iterator.nextInt()
if (sidebar.line(line) == null) {
return line
}
}
error("reached end of range")
}
}
@@ -1,12 +1,12 @@
package net.megavex.scoreboardlibrary.extra.kotlin
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam
import net.megavex.scoreboardlibrary.api.team.TeamInfo
import net.megavex.scoreboardlibrary.api.team.TeamDisplay
import net.megavex.scoreboardlibrary.api.team.TeamManager
import org.bukkit.entity.Player
public val ScoreboardTeam.teamManager: TeamManager get() = teamManager()
public val ScoreboardTeam.name: String get() = name()
public val ScoreboardTeam.globalInfo: TeamInfo get() = globalInfo()
public operator fun ScoreboardTeam.get(player: Player): TeamInfo = teamInfo(player)
public operator fun ScoreboardTeam.set(player: Player, teamInfo: TeamInfo): Unit = teamInfo(player, teamInfo)
public inline val ScoreboardTeam.teamManager: TeamManager get() = teamManager()
public inline val ScoreboardTeam.name: String get() = name()
public inline val ScoreboardTeam.defaultDisplay: TeamDisplay get() = defaultDisplay()
public operator fun ScoreboardTeam.get(player: Player): TeamDisplay = display(player)
public operator fun ScoreboardTeam.set(player: Player, teamDisplay: TeamDisplay): Unit = display(player, teamDisplay)
@@ -2,14 +2,16 @@ package net.megavex.scoreboardlibrary.extra.kotlin
import net.kyori.adventure.text.Component
import net.megavex.scoreboardlibrary.api.sidebar.Sidebar
import org.bukkit.entity.Player
import java.util.*
public val Sidebar.maxLines: Int get() = maxLines()
public val Sidebar.locale: Locale? get() = locale()
public inline val Sidebar.maxLines: Int get() = maxLines()
public inline val Sidebar.locale: Locale? get() = locale()
public operator fun Sidebar.get(line: Int): Component? = line(line)
public operator fun Sidebar.set(line: Int, value: Component?): Unit = line(line, value)
public var Sidebar.title: Component
public inline var Sidebar.title: Component
get() = title()
set(value) {
title(value)
}
public inline val Sidebar.players: Collection<Player> get() = players()
@@ -3,56 +3,56 @@ package net.megavex.scoreboardlibrary.extra.kotlin
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.format.NamedTextColor
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam
import net.megavex.scoreboardlibrary.api.team.TeamInfo
import net.megavex.scoreboardlibrary.api.team.TeamDisplay
import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule
import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility
public val TeamInfo.team: ScoreboardTeam get() = team()
public val TeamInfo.entries: Collection<String> get() = entries()
public inline val TeamDisplay.team: ScoreboardTeam get() = team()
public inline val TeamDisplay.entries: Collection<String> get() = entries()
public var TeamInfo.displayName: Component
public inline var TeamDisplay.displayName: Component
get() = displayName()
set(value) {
displayName(value)
}
public var TeamInfo.prefix: Component
public inline var TeamDisplay.prefix: Component
get() = prefix()
set(value) {
prefix(value)
}
public var TeamInfo.suffix: Component
public inline var TeamDisplay.suffix: Component
get() = suffix()
set(value) {
suffix(value)
}
public var TeamInfo.isFriendlyFire: Boolean
public inline var TeamDisplay.isFriendlyFire: Boolean
get() = friendlyFire()
set(value) {
friendlyFire(value)
}
public var TeamInfo.canSeeFriendlyInvisibles: Boolean
public inline var TeamDisplay.canSeeFriendlyInvisibles: Boolean
get() = canSeeFriendlyInvisibles()
set(value) {
canSeeFriendlyInvisibles(value)
}
public var TeamInfo.nameTagVisibility: NameTagVisibility
public inline var TeamDisplay.nameTagVisibility: NameTagVisibility
get() = nameTagVisibility()
set(value) {
nameTagVisibility(value)
}
public var TeamInfo.collisionRule: CollisionRule
public inline var TeamDisplay.collisionRule: CollisionRule
get() = collisionRule()
set(value) {
collisionRule(value)
}
public var TeamInfo.playerColor: NamedTextColor?
public inline var TeamDisplay.playerColor: NamedTextColor?
get() = playerColor()
set(value) {
playerColor(value)
@@ -2,6 +2,8 @@ package net.megavex.scoreboardlibrary.extra.kotlin
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam
import net.megavex.scoreboardlibrary.api.team.TeamManager
import org.bukkit.entity.Player
public val TeamManager.teams: Collection<ScoreboardTeam> get() = teams();
public inline val TeamManager.teams: Collection<ScoreboardTeam> get() = teams()
public operator fun TeamManager.get(name: String): ScoreboardTeam? = team(name)
public inline val TeamManager.players: Collection<Player> get() = players()
@@ -1,136 +0,0 @@
package net.megavex.scoreboardlibrary.extra.kotlin
import net.kyori.adventure.text.Component.empty
import net.kyori.adventure.text.Component.text
import net.megavex.scoreboardlibrary.api.noop.NoopScoreboardLibrary
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertEquals
internal class SidebarDslTest {
private val scoreboardLibrary = NoopScoreboardLibrary()
@Test
fun `puts lines in correct places`() {
val sidebar = scoreboardLibrary.createSidebar()
val line = text("Hello")
sidebar.lines {
emptyLine()
line(line)
emptyLine()
}
assertEquals(empty(), sidebar.line(0))
assertEquals(line, sidebar.line(1))
assertEquals(empty(), sidebar.line(2))
}
@Test
fun `respects maxLines property`() {
val sidebar = scoreboardLibrary.createSidebar()
assertThrows<IllegalStateException> {
sidebar.lines {
repeat(sidebar.maxLines + 1) {
emptyLine()
}
}
}
}
@Test
fun `skips existing lines`() {
val sidebar = scoreboardLibrary.createSidebar()
sidebar.lines {
line(text("1"))
}
sidebar.lines {
line(text("2"))
}
sidebar.lines {
line(text("3"))
}
assertEquals(text("1"), sidebar.line(0))
assertEquals(text("2"), sidebar.line(1))
assertEquals(text("3"), sidebar.line(2))
}
@Test
fun `throws if there are no available lines`() {
val sidebar = scoreboardLibrary.createSidebar()
sidebar.lines {
repeat(sidebar.maxLines) {
emptyLine()
}
}
assertThrows<IllegalStateException> {
sidebar.lines {
line(text("this shouldn't work"))
}
}
}
@Test
fun directions() {
val sidebar = scoreboardLibrary.createSidebar(5)
sidebar.lines(direction = LinesBuilder.Direction.BOTTOM_TO_TOP) {
line(text("1"))
line(text("2"))
line(text("3"))
}
assertEquals(text("1"), sidebar.line(4))
assertEquals(text("2"), sidebar.line(3))
assertEquals(text("3"), sidebar.line(2))
}
@Test
fun skip() {
val sidebar = scoreboardLibrary.createSidebar()
sidebar.lines {
line(text("1"))
skip(lines = 2)
line(text("3"))
}
sidebar.lines {
line(text("2"))
}
assertEquals(text("1"), sidebar.line(0))
assertEquals(text("2"), sidebar.line(1))
assertEquals(null, sidebar.line(2))
assertEquals(text("3"), sidebar.line(3))
}
@Test
fun `detects invalid progression`() {
val sidebar = scoreboardLibrary.createSidebar()
assertThrows<IllegalArgumentException> {
sidebar.lines(progression = -1..5) {}
}
assertThrows<IllegalArgumentException> {
sidebar.lines(progression = 0..sidebar.maxLines) {}
}
}
@Test
fun `dynamic line`() {
val sidebar = scoreboardLibrary.createSidebar()
var test = 0
var dynamicLine: LinesBuilder.DynamicLine
sidebar.lines {
dynamicLine = dynamicLine { text(test) }
}
assertEquals(text(test), sidebar.line(0))
test++
dynamicLine.update()
assertEquals(text(test), sidebar.line(0))
}
}
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1722987190,
"narHash": "sha256-68hmex5efCiM2aZlAAEcQgmFI4ZwWt8a80vOeB/5w3A=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "21cc704b5e918c5fbf4f9fff22b4ac2681706d90",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+26
View File
@@ -0,0 +1,26 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
};
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: {
default = pkgs.mkShell {
buildInputs = [
pkgs.temurin-bin-21
];
};
});
};
}
+24
View File
@@ -0,0 +1,24 @@
[versions]
adventure = "4.17.0"
junitJupiter = "5.11.3"
devBundle = "1.21.4-R0.1-20241208.061653-9" # find latest here: https://repo.papermc.io/repository/maven-snapshots/io/papermc/paper/dev-bundle/1.21.4-R0.1-SNAPSHOT/maven-metadata.xml
[libraries]
spigotApi = "org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT" # do not update
packetEvents = "com.github.retrooper:packetevents-spigot:2.6.0"
buildIndra = { module = "net.kyori:indra-common", version = "3.1.3" }
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" }
junitJupiterApi = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitJupiter" }
junitJupiterEngine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitJupiter" }
[plugins]
kotlin = "org.jetbrains.kotlin.jvm:2.1.0"
paperweight = "io.papermc.paperweight.userdev:1.7.7"
[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-7.4.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+31 -13
View File
@@ -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,12 @@ 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
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,22 +134,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
@@ -193,11 +201,15 @@ 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, 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" \
@@ -205,6 +217,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# 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
+94 -89
View File
@@ -1,89 +1,94 @@
@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
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% 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
+3 -3
View File
@@ -1,8 +1,8 @@
plugins {
id("net.megavex.scoreboardlibrary.publish-conventions")
id("net.megavex.scoreboardlibrary.base-conventions")
}
dependencies {
api(project(":api"))
implementation(project(":packet-adapter-base"))
api(project(":scoreboard-library-api"))
implementation(project(":scoreboard-library-packet-adapter-base"))
}
@@ -1,35 +0,0 @@
package net.megavex.scoreboardlibrary.implementation;
import net.megavex.scoreboardlibrary.implementation.sidebar.PlayerDependantLocaleSidebar;
import net.megavex.scoreboardlibrary.implementation.sidebar.SidebarTask;
import net.megavex.scoreboardlibrary.implementation.team.TeamManagerTask;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLocaleChangeEvent;
import org.bukkit.scheduler.BukkitRunnable;
public record LocaleListener(ScoreboardLibraryImpl scoreboardLibrary) implements Listener {
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerLocaleChanged(PlayerLocaleChangeEvent event) {
var player = event.getPlayer();
// Need to wait a tick because the locale didn't update yet
new BukkitRunnable() {
@Override
public void run() {
var slPlayer = scoreboardLibrary.getPlayer(player);
if (slPlayer != null) {
var teamManager = slPlayer.teamManager();
if (teamManager != null) {
teamManager.taskQueue().add(new TeamManagerTask.ReloadPlayer(player));
}
var sidebar = slPlayer.sidebar();
if (sidebar instanceof PlayerDependantLocaleSidebar) {
sidebar.taskQueue().add(new SidebarTask.ReloadPlayer(player));
}
}
}
}.runTaskLaterAsynchronously(scoreboardLibrary.plugin(), 1);
}
}
@@ -1,43 +1,125 @@
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.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
public final class PacketAdapterLoader {
private static final String MODERN = "modern",
LEGACY = "legacy",
PACKET_EVENTS = "packetevents";
private PacketAdapterLoader() {
}
public static @NotNull ScoreboardLibraryPacketAdapter<?> loadPacketAdapter() throws NoPacketAdapterAvailableException {
var versionName = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Class<?> nmsClass = tryLoadImplementationClass(versionName);
public static @NotNull PacketAdapterProvider loadPacketAdapter() 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 RuntimeException("packetevents is not in the classpath");
}
throw new NoPacketAdapterAvailableException();
}
try {
return (ScoreboardLibraryPacketAdapter<?>) nmsClass.getConstructors()[0].newInstance();
return (PacketAdapterProvider) nmsClass.getConstructors()[0].newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("couldn't initialize packet adapter", e);
}
}
private static @Nullable Class<?> findAndLoadImplementationClass() {
String version = Bukkit.getServer().getBukkitVersion();
int dashIndex = version.indexOf('-');
if (dashIndex != -1) {
version = version.substring(0, dashIndex);
}
Class<?> nmsClass = tryLoadVersion(version);
if (nmsClass != null) {
return nmsClass;
}
return tryLoadPacketEvents();
}
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":
return tryLoadImplementationClass(LEGACY);
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":
return tryLoadImplementationClass(MODERN);
default:
// Hide from relocation checkers
String property = "net.mega".concat("vex.scoreboardlibrary.forceModern");
if (System.getProperty(property, "").equalsIgnoreCase("true")) {
return tryLoadImplementationClass(MODERN);
}
return null;
}
}
private static @Nullable Class<?> tryLoadPacketEvents() {
Class<?> nmsClass = tryLoadImplementationClass(PACKET_EVENTS);
if (nmsClass == null) {
return null;
}
try {
Class.forName("com.github.retrooper.packetevents.PacketEvents");
return nmsClass;
} catch (ClassNotFoundException ignored) {
return null;
}
}
private static @Nullable Class<?> tryLoadImplementationClass(@NotNull String name) {
try {
return Class.forName("net.megavex.scoreboardlibrary.implementation.packetAdapter." + name + ".PacketAdapterImpl");
String path = "net.megavex.scoreboardlibrary.implementation.packetAdapter." + name + ".PacketAdapterProviderImpl";
return Class.forName(path);
} catch (ClassNotFoundException ignored) {
return null;
}
@@ -2,17 +2,16 @@ package net.megavex.scoreboardlibrary.implementation;
import com.google.common.base.Preconditions;
import com.google.common.collect.MapMaker;
import java.util.Collections;
import java.util.List;
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;
import net.megavex.scoreboardlibrary.implementation.sidebar.SidebarUpdaterTask;
@@ -25,18 +24,26 @@ import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public 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();
@@ -48,12 +55,12 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
try {
Class.forName("net.kyori.adventure.Adventure");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Adventure is not in the classpath");
throw new IllegalStateException("Adventure is not in the classpath");
}
this.plugin = plugin;
this.packetAdapter = PacketAdapterLoader.loadPacketAdapter();
this.localeProvider = this.packetAdapter.localeProvider;
this.taskScheduler = TaskScheduler.create(plugin);
boolean localeEventExists = false;
try {
@@ -74,20 +81,20 @@ 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) {
checkClosed();
if (maxLines <= 0 || maxLines > Sidebar.MAX_LINES) {
throw new IllegalArgumentException("maxLines");
if (maxLines <= 0) {
throw new IllegalArgumentException("invalid maxLines value: " + maxLines);
}
AbstractSidebar sidebar;
@@ -105,11 +112,20 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
public @NotNull TeamManagerImpl createTeamManager() {
checkClosed();
var teamManager = new TeamManagerImpl(this);
TeamManagerImpl teamManager = new TeamManagerImpl(this);
teamManagers().add(teamManager);
return teamManager;
}
@Override
public @NotNull ObjectiveManager createObjectiveManager() {
checkClosed();
ObjectiveManagerImpl objectiveManager = new ObjectiveManagerImpl(this);
objectiveManagers().add(objectiveManager);
return objectiveManager;
}
@Override
public void close() {
if (closed) {
@@ -127,19 +143,29 @@ public class ScoreboardLibraryImpl implements ScoreboardLibrary {
HandlerList.unregisterAll(localeListener);
if (teamManagers != null) {
teamTask.cancel();
teamTask.task().cancel();
synchronized (teamTask.lock()) {
for (var teamManager : teamManagers) {
for (TeamManagerImpl teamManager : teamManagers) {
teamManager.close();
teamManager.tick();
}
}
}
if (objectiveManagers != null) {
objectiveTask.task().cancel();
synchronized (objectiveTask.lock()) {
for (ObjectiveManagerImpl objectiveManager : objectiveManagers) {
objectiveManager.close();
objectiveManager.tick();
}
}
}
if (sidebars != null) {
sidebarTask.cancel();
sidebarTask.task().cancel();
synchronized (sidebarTask.lock()) {
for (var sidebar : sidebars) {
for (AbstractSidebar sidebar : sidebars) {
sidebar.close();
sidebar.tick();
}
@@ -165,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) {
var 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");
}
var newTeamManager = teamManager();
if (newTeamManager != null) {
var 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) {
var 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");
}
var newSidebar = sidebar();
if (newSidebar != null) {
var player = Bukkit.getPlayer(playerUuid);
if (player != null) {
newSidebar.show(player);
}
}
}
}
@@ -0,0 +1,220 @@
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.commons.CollectionProvider;
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.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public 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 = CollectionProvider.set(8);
private final Set<Player> displayingPlayers = CollectionProvider.set(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));
return true;
}
@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) {
Player player = ((ObjectiveManagerTask.RemovePlayer) task).player();
Collection<Player> singleton = Collections.singleton(player);
for (ScoreboardObjectiveImpl objective : objectives.values()) {
objective.packetAdapter().remove(singleton);
}
displayingPlayers.remove(player);
Objects.requireNonNull(library.getPlayer(player)).objectiveManagerQueue().remove(this);
} 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);
} 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()) {
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()) {
entry.getValue().packetAdapter().display(singleton, entry.getKey());
}
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException("ObjectiveManager is closed");
}
}
}
@@ -0,0 +1,133 @@
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;
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;
public RemovePlayer(@NotNull Player player) {
this.player = player;
}
public @NotNull Player player() {
return player;
}
}
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 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,123 @@
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 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;
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 @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;
}
public void sendProperties(@NotNull Collection<Player> players, @NotNull PropertiesPacketType packetType) {
packetAdapter.sendProperties(players, packetType, value, renderType, defaultScoreFormat);
}
}
@@ -0,0 +1,61 @@
package net.megavex.scoreboardlibrary.implementation.player;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public class DisplayableQueue<T extends PlayerDisplayable> {
private final UUID playerUuid;
private final List<T> queue = CollectionProvider.list(1);
public DisplayableQueue(@NotNull UUID playerUuid) {
this.playerUuid = playerUuid;
}
public synchronized @Nullable T current() {
if (queue.isEmpty()) {
return null;
} else {
return queue.get(0);
}
}
public synchronized void add(@NotNull T displayable) {
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 synchronized void remove(@NotNull T displayable) {
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);
}
}
}
}
@@ -0,0 +1,50 @@
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;
import net.megavex.scoreboardlibrary.implementation.team.TeamManagerImpl;
import net.megavex.scoreboardlibrary.implementation.team.TeamManagerTask;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLocaleChangeEvent;
import org.jetbrains.annotations.NotNull;
public class LocaleListener implements Listener {
private final ScoreboardLibraryImpl scoreboardLibrary;
public LocaleListener(@NotNull ScoreboardLibraryImpl scoreboardLibrary) {
this.scoreboardLibrary = scoreboardLibrary;
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
public void onPlayerLocaleChanged(PlayerLocaleChangeEvent event) {
Player player = event.getPlayer();
// Need to wait a tick because the locale didn't update yet
scoreboardLibrary.taskScheduler().runNextTick(() -> {
ScoreboardLibraryPlayer slPlayer = scoreboardLibrary.getPlayer(player);
if (slPlayer != null) {
TeamManagerImpl teamManager = slPlayer.teamManagerQueue().current();
if (teamManager != null) {
teamManager.taskQueue().add(new TeamManagerTask.ReloadPlayer(player));
}
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));
}
}
});
}
}
@@ -0,0 +1,8 @@
package net.megavex.scoreboardlibrary.implementation.player;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public interface PlayerDisplayable {
void display(@NotNull Player player);
}
@@ -0,0 +1,34 @@
package net.megavex.scoreboardlibrary.implementation.player;
import net.megavex.scoreboardlibrary.implementation.objective.ObjectiveManagerImpl;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import net.megavex.scoreboardlibrary.implementation.team.TeamManagerImpl;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class ScoreboardLibraryPlayer {
private final DisplayableQueue<TeamManagerImpl> teamManagerQueue;
private final DisplayableQueue<ObjectiveManagerImpl> objectiveManagerQueue;
private final DisplayableQueue<AbstractSidebar> sidebarQueue;
public ScoreboardLibraryPlayer(@NotNull Player player) {
UUID uuid = player.getUniqueId();
this.teamManagerQueue = new DisplayableQueue<>(uuid);
this.objectiveManagerQueue = new DisplayableQueue<>(uuid);
this.sidebarQueue = new DisplayableQueue<>(uuid);
}
public @NotNull DisplayableQueue<TeamManagerImpl> teamManagerQueue() {
return teamManagerQueue;
}
public @NotNull DisplayableQueue<ObjectiveManagerImpl> objectiveManagerQueue() {
return objectiveManagerQueue;
}
public @NotNull DisplayableQueue<AbstractSidebar> sidebarQueue() {
return sidebarQueue;
}
}
@@ -0,0 +1,23 @@
package net.megavex.scoreboardlibrary.implementation.scheduler;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
class BukkitTaskScheduler implements TaskScheduler {
private final Plugin plugin;
public BukkitTaskScheduler(@NotNull Plugin plugin) {
this.plugin = plugin;
}
@Override
public @NotNull RunningTask runEveryTick(@NotNull Runnable runnable) {
org.bukkit.scheduler.BukkitTask task = plugin.getServer().getScheduler().runTaskTimerAsynchronously(plugin, runnable, 1, 1);
return task::cancel;
}
@Override
public void runNextTick(@NotNull Runnable runnable) {
plugin.getServer().getScheduler().runTaskLaterAsynchronously(plugin, runnable, 1);
}
}
@@ -0,0 +1,114 @@
package net.megavex.scoreboardlibrary.implementation.scheduler;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
class FoliaTaskScheduler implements TaskScheduler {
private static final Class<?> asyncSchedulerClass;
private static final Class<?> scheduledTaskClass;
private static final Class<?> cancelledStateClass;
private static final MethodHandle getAsyncSchedulerMethod;
private static final MethodHandle cancelScheduledTaskMethod;
private static final MethodHandle runAtFixedRateMethod;
private static final MethodHandle runDelayedMethod;
static {
try {
asyncSchedulerClass = Class.forName("io.papermc.paper.threadedregions.scheduler.AsyncScheduler");
scheduledTaskClass = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask");
cancelledStateClass = Class.forName("io.papermc.paper.threadedregions.scheduler.ScheduledTask$CancelledState");
MethodHandles.Lookup lookup = MethodHandles.lookup();
getAsyncSchedulerMethod = lookup.findVirtual(
Server.class,
"getAsyncScheduler",
MethodType.methodType(asyncSchedulerClass)
);
cancelScheduledTaskMethod = lookup.findVirtual(scheduledTaskClass, "cancel", MethodType.methodType(cancelledStateClass));
runAtFixedRateMethod = lookup.findVirtual(
asyncSchedulerClass,
"runAtFixedRate",
MethodType.methodType(
scheduledTaskClass,
Plugin.class,
Consumer.class,
long.class,
long.class,
TimeUnit.class
)
);
runDelayedMethod = lookup.findVirtual(
asyncSchedulerClass,
"runDelayed",
MethodType.methodType(
scheduledTaskClass,
Plugin.class,
Consumer.class,
long.class,
TimeUnit.class
)
);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
private final Plugin plugin;
private final Object asyncScheduler;
public FoliaTaskScheduler(@NotNull Plugin plugin) {
this.plugin = plugin;
try {
this.asyncScheduler = getAsyncSchedulerMethod.invoke(plugin.getServer());
} catch (Throwable e) {
throw new RuntimeException("couldn't get async scheduler", e);
}
}
@Override
public @NotNull RunningTask runEveryTick(@NotNull Runnable runnable) {
Object scheduledTask;
try {
Consumer<Object> task = t -> runnable.run();
scheduledTask = runAtFixedRateMethod.invoke(
asyncScheduler,
plugin,
task,
Ticks.SINGLE_TICK_DURATION_MS,
Ticks.SINGLE_TICK_DURATION_MS,
TimeUnit.MILLISECONDS
);
} catch (Throwable e) {
throw new RuntimeException("couldn't schedule repeating task", e);
}
return () -> {
try {
cancelScheduledTaskMethod.invoke(scheduledTask);
} catch (Throwable e) {
throw new RuntimeException("couldn't cancel scheduled task", e);
}
};
}
@Override
public void runNextTick(@NotNull Runnable runnable) {
Consumer<Object> task = t -> runnable.run();
try {
runDelayedMethod.invoke(asyncScheduler, plugin, task, Ticks.SINGLE_TICK_DURATION_MS, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
throw new RuntimeException("couldn't schedule delayed task", e);
}
}
}
@@ -0,0 +1,5 @@
package net.megavex.scoreboardlibrary.implementation.scheduler;
public interface RunningTask {
void cancel();
}
@@ -0,0 +1,19 @@
package net.megavex.scoreboardlibrary.implementation.scheduler;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
public interface TaskScheduler {
static @NotNull TaskScheduler create(@NotNull Plugin plugin) {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return new FoliaTaskScheduler(plugin);
} catch (ClassNotFoundException ignored) {
return new BukkitTaskScheduler(plugin);
}
}
@NotNull RunningTask runEveryTick(@NotNull Runnable runnable);
void runNextTick(@NotNull Runnable runnable);
}
@@ -1,32 +1,37 @@
package net.megavex.scoreboardlibrary.implementation.sidebar;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
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.api.sidebar.Sidebar;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.SidebarPacketAdapter;
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 net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.LocaleLineHandler;
import org.apache.commons.lang.RandomStringUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Consumer;
import static net.kyori.adventure.text.Component.empty;
public abstract class AbstractSidebar implements Sidebar {
public abstract class AbstractSidebar implements Sidebar, PlayerDisplayable {
private final ScoreboardLibraryImpl scoreboardLibrary;
private final SidebarPacketAdapter<?, ?> packetAdapter;
private final ObjectivePacketAdapter packetAdapter;
private final GlobalLineInfo[] lines;
private Component title = empty();
@@ -39,13 +44,15 @@ public abstract class AbstractSidebar implements Sidebar {
public AbstractSidebar(@NotNull ScoreboardLibraryImpl scoreboardLibrary, int maxLines) {
this.scoreboardLibrary = scoreboardLibrary;
this.packetAdapter = scoreboardLibrary.packetAdapter().createSidebarPacketAdapter(this);
String objectiveName = RandomStringUtils.randomAlphanumeric(16);
this.packetAdapter = scoreboardLibrary.packetAdapter().createObjectiveAdapter(objectiveName);
this.lines = new GlobalLineInfo[maxLines];
}
protected abstract @NotNull Set<Player> internalPlayers();
protected abstract void forEachSidebar(@NotNull Consumer<LocaleLineHandler> consumer);
protected abstract void forEachLineHandler(@NotNull Consumer<LocaleLineHandler> consumer);
protected abstract @Nullable LocaleLineHandler addPlayer0(@NotNull Player player);
@@ -66,7 +73,7 @@ public abstract class AbstractSidebar implements Sidebar {
@Override
public final @NotNull Collection<Player> players() {
return closed ? Set.of() : Collections.unmodifiableSet(players);
return closed ? Collections.emptySet() : Collections.unmodifiableSet(players);
}
@Override
@@ -101,13 +108,18 @@ public abstract class AbstractSidebar implements Sidebar {
}
@Override
public final void line(@Range(from = 0, to = MAX_LINES - 1) int line, @Nullable Component value) {
public final void line(@Range(from = 0, to = MAX_LINES - 1) int index, @Nullable ComponentLike value, @Nullable ScoreFormat scoreFormat) {
checkClosed();
var globalLineInfo = getLineInfo(line);
if (!Objects.equals(globalLineInfo.value(), value)) {
globalLineInfo.value(value);
taskQueue.add(new SidebarTask.UpdateLine(line));
Component component = value == null ? null : value.asComponent();
GlobalLineInfo globalLineInfo = getLineInfo(index);
boolean updateValue = !Objects.equals(globalLineInfo.value(), component);
boolean updateScore = !Objects.equals(globalLineInfo.scoreFormat(), scoreFormat);
if (updateValue || updateScore) {
globalLineInfo.value(component);
globalLineInfo.scoreFormat(scoreFormat);
taskQueue.add(new SidebarTask.UpdateLine(index, updateValue, updateScore));
updateScores();
}
}
@@ -116,7 +128,7 @@ public abstract class AbstractSidebar implements Sidebar {
public final @Nullable Component line(@Range(from = 0, to = MAX_LINES - 1) int line) {
checkClosed();
var globalLineInfo = lines[line];
GlobalLineInfo globalLineInfo = lines[line];
return globalLineInfo == null ? null : globalLineInfo.value();
}
@@ -126,12 +138,13 @@ public abstract class AbstractSidebar implements Sidebar {
}
@Override
public final void title(@NotNull Component title) {
public final void title(@NotNull ComponentLike title) {
Preconditions.checkNotNull(title);
checkClosed();
if (!Objects.equals(this.title, title)) {
this.title = title;
Component component = title.asComponent();
if (!Objects.equals(this.title, component)) {
this.title = component;
taskQueue.add(SidebarTask.UpdateTitle.INSTANCE);
}
}
@@ -140,7 +153,7 @@ public abstract class AbstractSidebar implements Sidebar {
return scoreboardLibrary;
}
public final @NotNull SidebarPacketAdapter<?, ?> packetAdapter() {
public final ObjectivePacketAdapter packetAdapter() {
return packetAdapter;
}
@@ -152,84 +165,101 @@ public abstract class AbstractSidebar implements Sidebar {
return taskQueue;
}
public final void show(@NotNull Player player) {
packetAdapter.sendObjectivePacket(Set.of(player), SidebarPacketAdapter.ObjectivePacket.CREATE);
@Override
public final void display(@NotNull Player player) {
Collection<Player> singleton = Collections.singleton(player);
packetAdapter.sendProperties(singleton, PropertiesPacketType.CREATE, title, ObjectiveRenderType.INTEGER, ScoreFormat.blank());
var lineHandler = Objects.requireNonNull(addPlayer0(player));
LocaleLineHandler lineHandler = Objects.requireNonNull(addPlayer0(player));
lineHandler.addPlayer(player);
lineHandler.show(player);
scoreboardLibrary.packetAdapter().displaySidebar(Set.of(player));
packetAdapter.display(singleton, ObjectiveDisplaySlot.sidebar());
}
public final void tick() {
public final boolean tick() {
while (true) {
var task = taskQueue.poll();
SidebarTask task = taskQueue.poll();
if (task == null) {
break;
}
if (task instanceof SidebarTask.Close) {
forEachSidebar(LocaleLineHandler::hide);
scoreboardLibrary.packetAdapter().removeSidebar(internalPlayers());
forEachLineHandler(LocaleLineHandler::hide);
packetAdapter.remove(internalPlayers());
for (var player : internalPlayers()) {
Objects.requireNonNull(scoreboardLibrary.getPlayer(player)).removeSidebar(this);
for (Player player : internalPlayers()) {
Objects.requireNonNull(scoreboardLibrary.getPlayer(player)).sidebarQueue().remove(this);
}
return;
} else if (task instanceof SidebarTask.AddPlayer addPlayerTask) {
var slPlayer = scoreboardLibrary.getOrCreatePlayer(addPlayerTask.player());
slPlayer.addSidebar(this);
} else if (task instanceof SidebarTask.RemovePlayer removePlayerTask) {
var lineHandler = removePlayer0(removePlayerTask.player());
return false;
} else if (task instanceof SidebarTask.AddPlayer) {
SidebarTask.AddPlayer addPlayerTask = (SidebarTask.AddPlayer) task;
ScoreboardLibraryPlayer slPlayer = scoreboardLibrary.getOrCreatePlayer(addPlayerTask.player());
slPlayer.sidebarQueue().add(this);
} else if (task instanceof SidebarTask.RemovePlayer) {
SidebarTask.RemovePlayer removePlayerTask = (SidebarTask.RemovePlayer) task;
LocaleLineHandler lineHandler = removePlayer0(removePlayerTask.player());
if (lineHandler != null) {
lineHandler.hide(removePlayerTask.player());
lineHandler.removePlayer(removePlayerTask.player());
packetAdapter.remove(Collections.singleton(removePlayerTask.player()));
}
Objects.requireNonNull(scoreboardLibrary.getPlayer(removePlayerTask.player())).sidebarQueue().remove(this);
} else if (task instanceof SidebarTask.ReloadPlayer) {
SidebarTask.ReloadPlayer reloadPlayerTask = (SidebarTask.ReloadPlayer) task;
LocaleLineHandler lineHandler = removePlayer0(reloadPlayerTask.player());
if (lineHandler == null) {
continue;
}
lineHandler.hide(removePlayerTask.player());
lineHandler.removePlayer(removePlayerTask.player());
scoreboardLibrary.packetAdapter().removeSidebar(Set.of(removePlayerTask.player()));
Objects.requireNonNull(scoreboardLibrary.getPlayer(removePlayerTask.player())).removeSidebar(this);
} else if (task instanceof SidebarTask.ReloadPlayer reloadPlayerTask) {
var lineHandler = removePlayer0(reloadPlayerTask.player());
if (lineHandler == null) {
continue;
}
lineHandler.removePlayer(reloadPlayerTask.player());
packetAdapter.remove(Collections.singleton(reloadPlayerTask.player()));
lineHandler.hide(reloadPlayerTask.player());
lineHandler.removePlayer(reloadPlayerTask.player());
show(reloadPlayerTask.player());
} else if (task instanceof SidebarTask.UpdateLine updateLineTask) {
forEachSidebar(sidebar -> {
var value = lines[updateLineTask.line()].value();
var renderedValue = value == null ? null : GlobalTranslator.render(value, sidebar.locale());
sidebar.updateLine(updateLineTask.line(), renderedValue);
display(reloadPlayerTask.player());
} else if (task instanceof SidebarTask.UpdateLine) {
SidebarTask.UpdateLine updateLineTask = (SidebarTask.UpdateLine) task;
forEachLineHandler(sidebar -> {
if (updateLineTask.updateValue()) {
Component value = lines[updateLineTask.line()].value();
Component renderedValue = value == null ? null : GlobalTranslator.render(value, sidebar.locale());
sidebar.updateLine(updateLineTask.line(), renderedValue);
}
if (updateLineTask.updateScore()) {
sidebar.updateScoreFormat(updateLineTask.line());
}
});
} else if (task instanceof SidebarTask.UpdateScores) {
forEachSidebar(LocaleLineHandler::updateScores);
forEachLineHandler(LocaleLineHandler::updateScores);
for (GlobalLineInfo line : lines) {
if (line != null) {
line.updateScore(false);
}
}
} else if (task instanceof SidebarTask.UpdateTitle) {
packetAdapter.updateTitle(title);
packetAdapter.sendObjectivePacket(internalPlayers(), SidebarPacketAdapter.ObjectivePacket.UPDATE);
packetAdapter.sendProperties(internalPlayers(), PropertiesPacketType.UPDATE, title, ObjectiveRenderType.INTEGER, ScoreFormat.blank());
}
}
return true;
}
private void updateScores() {
int size = 0;
for (var line : lines) {
for (GlobalLineInfo line : lines) {
if (line != null && line.value() != null) {
size++;
}
}
boolean changed = false;
int i = 0;
for (var line : lines) {
for (GlobalLineInfo line : lines) {
if (line != null && line.value() != null) {
var newScore = size - i - 1;
int newScore = size - i - 1;
if (line.objectiveScore() != newScore) {
changed = true;
line.updateScore(true);
@@ -246,9 +276,12 @@ public abstract class AbstractSidebar implements Sidebar {
}
private @NotNull GlobalLineInfo getLineInfo(int line) {
var globalLineInfo = lines[line];
GlobalLineInfo globalLineInfo = lines[line];
if (globalLineInfo == null) {
globalLineInfo = lines[line] = new GlobalLineInfo(this, line);
char c = Character.toChars(128 + line)[0];
String playerName = "" + LegacyComponentSerializer.SECTION_CHAR + c;
globalLineInfo = lines[line] = new GlobalLineInfo(this, playerName, line);
updateScores();
}
@@ -1,21 +1,23 @@
package net.megavex.scoreboardlibrary.implementation.sidebar;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.commons.LocaleProvider;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.LocaleLineHandler;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.LocaleLineHandler;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PlayerDependantLocaleSidebar extends AbstractSidebar {
private final Map<Player, LocaleLineHandler> playerMap = new HashMap<>();
private final Map<Locale, LocaleLineHandler> localeMap = new HashMap<>();
public PlayerDependantLocaleSidebar(ScoreboardLibraryImpl scoreboardLibrary, int maxLines) {
public PlayerDependantLocaleSidebar(@NotNull ScoreboardLibraryImpl scoreboardLibrary, int maxLines) {
super(scoreboardLibrary, maxLines);
}
@@ -30,9 +32,9 @@ public class PlayerDependantLocaleSidebar extends AbstractSidebar {
}
@Override
protected void forEachSidebar(@NotNull Consumer<LocaleLineHandler> consumer) {
protected void forEachLineHandler(@NotNull Consumer<LocaleLineHandler> consumer) {
if (localeMap != null) {
for (var value : localeMap.values()) {
for (LocaleLineHandler value : localeMap.values()) {
consumer.accept(value);
}
}
@@ -40,12 +42,12 @@ public class PlayerDependantLocaleSidebar extends AbstractSidebar {
@Override
protected @Nullable LocaleLineHandler addPlayer0(@NotNull Player player) {
var sidebar = playerMap.get(player);
LocaleLineHandler sidebar = playerMap.get(player);
if (sidebar != null) {
return null;
}
var locale = scoreboardLibrary().localeProvider().locale(player);
Locale locale = LocaleProvider.locale(player);
sidebar = localeMap.get(locale);
if (sidebar != null) {
@@ -65,7 +67,7 @@ public class PlayerDependantLocaleSidebar extends AbstractSidebar {
protected @Nullable LocaleLineHandler removePlayer0(@NotNull Player player) {
if (playerMap == null) return null;
var lineHandler = playerMap.remove(player);
LocaleLineHandler lineHandler = playerMap.remove(player);
if (lineHandler == null) return null;
if (!lineHandler.hasPlayers() && localeMap != null) {
@@ -3,7 +3,10 @@ package net.megavex.scoreboardlibrary.implementation.sidebar;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public sealed class SidebarTask {
public class SidebarTask {
private SidebarTask() {
}
public static final class Close extends SidebarTask {
public static final Close INSTANCE = new Close();
@@ -49,14 +52,25 @@ public sealed class SidebarTask {
public static final class UpdateLine extends SidebarTask {
private final int line;
private final boolean updateValue, updateScore;
public UpdateLine(int line) {
public UpdateLine(int line, boolean updateValue, boolean updateScore) {
this.line = line;
this.updateValue = updateValue;
this.updateScore = updateScore;
}
public int line() {
return line;
}
public boolean updateValue() {
return updateValue;
}
public boolean updateScore() {
return updateScore;
}
}
public static final class UpdateScores extends SidebarTask {
@@ -67,11 +81,9 @@ public sealed class SidebarTask {
}
public static final class UpdateTitle extends SidebarTask {
public static final UpdateScores INSTANCE = new UpdateScores();
public static final UpdateTitle INSTANCE = new UpdateTitle();
private UpdateTitle() {
}
}
}
@@ -1,17 +1,24 @@
package net.megavex.scoreboardlibrary.implementation.sidebar;
import java.util.logging.Level;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import org.bukkit.scheduler.BukkitRunnable;
import net.megavex.scoreboardlibrary.implementation.scheduler.RunningTask;
import org.jetbrains.annotations.NotNull;
public class SidebarUpdaterTask extends BukkitRunnable {
import java.util.Iterator;
import java.util.logging.Level;
public class SidebarUpdaterTask implements Runnable {
private final ScoreboardLibraryImpl scoreboardLibrary;
private final RunningTask task;
private final Object lock = new Object();
public SidebarUpdaterTask(@NotNull ScoreboardLibraryImpl scoreboardLibrary) {
this.scoreboardLibrary = scoreboardLibrary;
runTaskTimerAsynchronously(scoreboardLibrary.plugin(), 1, 1);
this.task = scoreboardLibrary.taskScheduler().runEveryTick(this);
}
public @NotNull RunningTask task() {
return task;
}
public @NotNull Object lock() {
@@ -21,11 +28,19 @@ public class SidebarUpdaterTask extends BukkitRunnable {
@Override
public void run() {
synchronized (lock) {
for (var sidebar : scoreboardLibrary.sidebars()) {
Iterator<AbstractSidebar> iterator = scoreboardLibrary.sidebars().iterator();
while (iterator.hasNext()) {
AbstractSidebar sidebar = iterator.next();
boolean result;
try {
sidebar.tick();
result = sidebar.tick();
} catch (Exception e) {
scoreboardLibrary.plugin().getLogger().log(Level.WARNING, "an error occurred while updating a Sidebar instance", e);
continue;
}
if (!result) {
iterator.remove();
}
}
}
@@ -1,8 +1,5 @@
package net.megavex.scoreboardlibrary.implementation.sidebar;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.LocaleLineHandler;
@@ -10,16 +7,20 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Locale;
import java.util.Set;
import java.util.function.Consumer;
public class SingleLocaleSidebar extends AbstractSidebar {
private final Locale locale;
private final LocaleLineHandler sidebar;
private final Set<Player> internalPlayers;
public SingleLocaleSidebar(@NotNull ScoreboardLibraryImpl scoreboardLibrary, int size, Locale locale) {
public SingleLocaleSidebar(@NotNull ScoreboardLibraryImpl scoreboardLibrary, int size, @NotNull Locale locale) {
super(scoreboardLibrary, size);
this.locale = locale;
this.sidebar = new LocaleLineHandler(this, locale());
this.internalPlayers = CollectionProvider.set(1);
this.sidebar = new LocaleLineHandler(this, locale);
this.internalPlayers = CollectionProvider.set(8);
}
@Override
@@ -35,19 +36,17 @@ public class SingleLocaleSidebar extends AbstractSidebar {
@Override
protected @Nullable LocaleLineHandler addPlayer0(@NotNull Player player) {
if (!internalPlayers.add(player)) return null;
return sidebar;
}
@Override
protected @Nullable LocaleLineHandler removePlayer0(@NotNull Player player) {
if (!internalPlayers.remove(player)) return null;
return sidebar;
}
@Override
protected void forEachSidebar(@NotNull Consumer<LocaleLineHandler> consumer) {
protected void forEachLineHandler(@NotNull Consumer<LocaleLineHandler> consumer) {
consumer.accept(sidebar);
}
}
@@ -1,42 +1,39 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.TeamsPacketAdapter;
import net.megavex.scoreboardlibrary.api.objective.ScoreFormat;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class GlobalLineInfo {
private static final String[] lineColors = new String[15];
static {
var values = ChatColor.values();
for (int i = 0; i < lineColors.length; i++) {
lineColors[i] = values[i].toString();
}
}
private final String player;
private final int line;
private final TeamsPacketAdapter<?, ?> packetAdapter;
private final TeamsPacketAdapter packetAdapter;
private Component value;
private ScoreFormat scoreFormat;
private int objectiveScore;
private boolean updateScore;
public GlobalLineInfo(@NotNull AbstractSidebar sidebar, int line) {
public GlobalLineInfo(@NotNull AbstractSidebar sidebar, @NotNull String player, int line) {
this.player = player;
this.line = line;
this.packetAdapter = sidebar.scoreboardLibrary().packetAdapter().createTeamPacketAdapter("sidebar_line_" + line);
this.packetAdapter = sidebar
.scoreboardLibrary()
.packetAdapter()
.createTeamPacketAdapter("sidebar_line_" + line);
}
public @NotNull String player() {
return lineColors[line];
return player;
}
public int line() {
return line;
}
public @NotNull TeamsPacketAdapter<?, ?> packetAdapter() {
public @NotNull TeamsPacketAdapter packetAdapter() {
return packetAdapter;
}
@@ -48,6 +45,14 @@ public class GlobalLineInfo {
this.value = value;
}
public @Nullable ScoreFormat scoreFormat() {
return scoreFormat;
}
public void scoreFormat(@Nullable ScoreFormat scoreFormat) {
this.scoreFormat = scoreFormat;
}
public int objectiveScore() {
return objectiveScore;
}
@@ -1,117 +1,109 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line;
import java.util.Locale;
import java.util.Set;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.LineType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Locale;
public class LocaleLineHandler {
private final AbstractSidebar sidebar;
private final Locale locale;
private SidebarLineHandler modernLineHandler, legacyLineHandler;
private final SidebarLineHandler[] lineHandlers;
public LocaleLineHandler(AbstractSidebar sidebar, Locale locale) {
public LocaleLineHandler(@NotNull AbstractSidebar sidebar, @NotNull Locale locale) {
this.sidebar = sidebar;
this.locale = locale;
this.lineHandlers = new SidebarLineHandler[LineRenderingStrategy.values().length];
}
public AbstractSidebar sidebar() {
public @NotNull AbstractSidebar sidebar() {
return sidebar;
}
public Locale locale() {
public @NotNull Locale locale() {
return locale;
}
public boolean hasPlayers() {
return (modernLineHandler != null && !modernLineHandler.players().isEmpty()) || (legacyLineHandler != null && !legacyLineHandler.players().isEmpty());
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null && !lineHandler.players().isEmpty()) {
return true;
}
}
return false;
}
public void addPlayer(@NotNull Player player) {
var isLegacy = sidebar.scoreboardLibrary().packetAdapter().isLegacy(player);
var lineType = isLegacy ? LineType.LEGACY : LineType.MODERN;
lineHandler(lineType).players().add(player);
LineRenderingStrategy strategy = sidebar.scoreboardLibrary().packetAdapter().lineRenderingStrategy(player);
lineHandler(strategy).players().add(player);
}
public void removePlayer(@NotNull Player player) {
if (modernLineHandler != null && modernLineHandler.players().remove(player)) {
return;
}
if (legacyLineHandler != null) {
legacyLineHandler.players().remove(player);
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null && lineHandler.players().remove(player)) {
return;
}
}
}
public @NotNull SidebarLineHandler lineHandler(@NotNull LineType lineType) {
switch (lineType) {
case MODERN -> {
if (modernLineHandler == null) {
modernLineHandler = new SidebarLineHandler(LineType.MODERN, this);
}
return modernLineHandler;
}
case LEGACY -> {
if (legacyLineHandler == null) {
legacyLineHandler = new SidebarLineHandler(LineType.LEGACY, this);
}
return legacyLineHandler;
}
public @NotNull SidebarLineHandler lineHandler(@NotNull LineRenderingStrategy lineType) {
SidebarLineHandler lineHandler = lineHandlers[lineType.ordinal()];
if (lineHandler == null) {
lineHandler = lineHandlers[lineType.ordinal()] = new SidebarLineHandler(lineType, this);
}
return lineHandler;
}
throw new RuntimeException();
public void updateScoreFormat(int lineIndex) {
SidebarLineHandler lineHandler = lineHandlers[LineRenderingStrategy.MODERN.ordinal()];
if (lineHandler != null) {
lineHandler.updateScore(lineIndex);
}
}
public void updateLine(int lineIndex, Component renderedValue) {
if (modernLineHandler != null) {
modernLineHandler.setLine(lineIndex, renderedValue);
modernLineHandler.updateLine(lineIndex);
}
if (legacyLineHandler != null) {
legacyLineHandler.setLine(lineIndex, renderedValue);
legacyLineHandler.updateLine(lineIndex);
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null) {
lineHandler.setLine(lineIndex, renderedValue);
}
}
}
public void updateScores() {
if (modernLineHandler != null) {
modernLineHandler.updateScores();
}
if (legacyLineHandler != null) {
legacyLineHandler.updateScores();
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null) {
lineHandler.updateScores();
}
}
}
public void show(Player player) {
if (modernLineHandler != null && modernLineHandler.players().contains(player)) {
modernLineHandler.show(Set.of(player));
} else if (legacyLineHandler != null && legacyLineHandler.players().contains(player)) {
legacyLineHandler.show(Set.of(player));
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null && lineHandler.players().contains(player)) {
lineHandler.show(Collections.singleton(player));
return;
}
}
}
public void hide(Player player) {
if (modernLineHandler != null && modernLineHandler.players().contains(player)) {
modernLineHandler.hide(Set.of(player));
} else if (legacyLineHandler != null && legacyLineHandler.players().contains(player)) {
legacyLineHandler.hide(Set.of(player));
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null && lineHandler.players().contains(player)) {
lineHandler.hide(Collections.singleton(player));
return;
}
}
}
public void hide() {
if (modernLineHandler != null) {
modernLineHandler.hide(modernLineHandler.players());
}
if (legacyLineHandler != null) {
legacyLineHandler.hide(legacyLineHandler.players());
for (SidebarLineHandler lineHandler : lineHandlers) {
if (lineHandler != null) {
lineHandler.hide(lineHandler.players());
}
}
}
}
@@ -1,31 +1,35 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line;
import java.util.Collection;
import java.util.Set;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.translation.GlobalTranslator;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.LineType;
import net.megavex.scoreboardlibrary.implementation.commons.LineRenderingStrategy;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.LegacyLocaleLine;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.LocaleLine;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.ModernLocaleLine;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.locale.PostModernLocaleLine;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Set;
public class SidebarLineHandler {
private final LineType lineType;
private final LineRenderingStrategy strategy;
private final LocaleLineHandler localeLineHandler;
private final Set<Player> players = CollectionProvider.set(1);
private final LocaleLine<?>[] lines;
private final LocaleLine[] lines;
public SidebarLineHandler(@NotNull LineType lineType, @NotNull LocaleLineHandler localeLineHandler) {
this.lineType = lineType;
public SidebarLineHandler(@NotNull LineRenderingStrategy strategy, @NotNull LocaleLineHandler localeLineHandler) {
this.strategy = strategy;
this.localeLineHandler = localeLineHandler;
this.lines = new LocaleLine[localeLineHandler.sidebar().maxLines()];
for (var line : localeLineHandler.sidebar().lines()) {
for (GlobalLineInfo line : localeLineHandler.sidebar().lines()) {
if (line != null) {
var value = line.value();
Component value = line.value();
if (value != null) {
setLine(line.line(), GlobalTranslator.render(value, localeLineHandler.locale()), false);
setLine(line.line(), GlobalTranslator.render(value, localeLineHandler.locale()));
}
}
}
@@ -39,35 +43,28 @@ public class SidebarLineHandler {
return players;
}
public void updateLine(int lineIndex) {
var line = lines[lineIndex];
if (line != null) {
line.updateTeam();
}
}
public void updateScores() {
for (var line : lines) {
for (LocaleLine line : lines) {
if (line != null && line.info().updateScore()) {
line.sendScore(players);
line.info().updateScore(false);
}
}
}
public void setLine(int line, Component renderedLine) {
setLine(line, renderedLine, true);
public void updateScore(int line) {
LocaleLine localeLine = lines[line];
localeLine.sendScore(players);
}
private void setLine(int line, Component renderedLine, boolean sendPackets) {
var localeLine = lines[line];
public void setLine(int line, Component renderedLine) {
LocaleLine localeLine = lines[line];
if (renderedLine == null && localeLine == null) {
return;
}
var newlyCreated = false;
boolean newlyCreated = false;
if (localeLine == null) {
lines[line] = localeLine = lineType.create(localeLineHandler.sidebar().lines()[line], this);
lines[line] = localeLine = createLine(strategy, localeLineHandler.sidebar().lines()[line]);
newlyCreated = true;
}
@@ -77,17 +74,18 @@ public class SidebarLineHandler {
localeLine.value(renderedLine);
}
if (sendPackets) {
if (renderedLine == null) {
localeLine.hide(players);
} else if (newlyCreated) {
localeLine.show(players);
}
if (renderedLine == null) {
localeLine.hide(players);
} else if (newlyCreated) {
localeLine.resetOldPlayer();
localeLine.show(players);
} else {
localeLine.updateTeam();
}
}
public void show(Collection<Player> players) {
for (var line : lines) {
for (LocaleLine line : lines) {
if (line != null) {
line.show(players);
}
@@ -95,10 +93,23 @@ public class SidebarLineHandler {
}
public void hide(Collection<Player> players) {
for (var line : lines) {
for (LocaleLine line : lines) {
if (line != null) {
line.hide(players);
}
}
}
private @NotNull LocaleLine createLine(@NotNull LineRenderingStrategy strategy, @NotNull GlobalLineInfo line) {
switch (strategy) {
case LEGACY:
return new LegacyLocaleLine(line, this);
case MODERN:
return new ModernLocaleLine(line, this);
case POST_MODERN:
return new PostModernLocaleLine(line, this);
default:
throw new IllegalArgumentException();
}
}
}
@@ -1,22 +1,30 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale;
import java.util.Collection;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.TeamsPacketAdapter;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.objective.ObjectivePacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.SidebarLineHandler;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection;
class LegacyLocaleLine implements LocaleLine<String> {
// Implementation for versions below 1.13
public class LegacyLocaleLine implements LocaleLine, ImmutableTeamProperties<String> {
private final GlobalLineInfo info;
private final SidebarLineHandler handler;
private final TeamsPacketAdapter.TeamInfoPacketAdapter<String> packetAdapter;
private final TeamDisplayPacketAdapter packetAdapter;
private String player, oldPlayer;
private String prefix, suffix;
private String currentValue;
@@ -25,8 +33,8 @@ class LegacyLocaleLine implements LocaleLine<String> {
this.info = info;
this.handler = handler;
this.player = info.player();
this.packetAdapter = info.packetAdapter().createLegacyTeamInfoAdapter(this);
packetAdapter.updateTeamPackets(entries());
this.packetAdapter = info.packetAdapter().createLegacyTeamDisplayAdapter(this);
packetAdapter.updateTeamPackets();
}
public @NotNull GlobalLineInfo info() {
@@ -49,15 +57,16 @@ class LegacyLocaleLine implements LocaleLine<String> {
}
@Override
public @NotNull Collection<String> entries() {
return Set.of(player);
public @NotNull Collection<String> syncedEntries() {
return ImmutableList.of(player);
}
@Override
public void value(@NotNull Component renderedComponent) {
var legacyValue = legacySection().serialize(renderedComponent);
// I really need to rewrite this
String legacyValue = legacySection().serialize(renderedComponent);
oldPlayer = player;
String prevPlayer = player;
if (legacyValue.length() <= 16) {
this.prefix = legacyValue;
@@ -67,29 +76,27 @@ class LegacyLocaleLine implements LocaleLine<String> {
this.player = info.player();
}
} else {
var color = legacyValue.charAt(15) == ChatColor.COLOR_CHAR;
boolean endsWithSection = legacyValue.charAt(15) == LegacyComponentSerializer.SECTION_CHAR;
var prefixEnd = color ? 15 : 16;
int prefixEnd = endsWithSection ? 15 : 16;
this.prefix = legacyValue.substring(0, prefixEnd);
this.player = info.player() + ChatColor.RESET
+ ChatColor.getLastColors(prefix +
ChatColor.COLOR_CHAR + (color ? legacyValue.charAt(16) : ""));
String last = prefix + LegacyComponentSerializer.SECTION_CHAR + (endsWithSection ? legacyValue.charAt(16) : "");
this.player = info.player() + ChatColor.getLastColors(last);
var playerEnd = prefixEnd;
int playerEnd = prefixEnd;
if (legacyValue.length() > 32) {
var remaining = 16 - player.length();
int remaining = 16 - player.length();
assert remaining > 0;
playerEnd += remaining;
player += legacyValue.substring(prefixEnd, playerEnd);
}
this.suffix = legacyValue.substring(playerEnd + (color ? 2 : 0));
this.suffix = ChatColor.getLastColors(this.player) + legacyValue.substring(playerEnd + (endsWithSection ? 2 : 0));
if (suffix.length() > 16) {
var newSuffix = suffix.substring(0, 16);
if (newSuffix.endsWith(String.valueOf(ChatColor.COLOR_CHAR)) &&
ChatColor.getByChar(suffix.charAt(16)) != null) {
String newSuffix = suffix.substring(0, 16);
if (newSuffix.endsWith(String.valueOf(LegacyComponentSerializer.SECTION_CHAR))) {
newSuffix = newSuffix.substring(0, 15);
}
@@ -99,50 +106,57 @@ class LegacyLocaleLine implements LocaleLine<String> {
currentValue = legacyValue;
if (oldPlayer.equals(player)) {
oldPlayer = null;
if (!player.equals(prevPlayer)) {
oldPlayer = prevPlayer;
}
}
@Override
public void updateTeam() {
var players = handler.players();
Set<Player> players = handler.players();
if (oldPlayer != null) {
packetAdapter.removeEntries(players, Set.of(oldPlayer));
handler.localeLineHandler().sidebar().packetAdapter().removeLine(players, oldPlayer);
ObjectivePacketAdapter pd = handler.localeLineHandler().sidebar().packetAdapter();
packetAdapter.sendEntries(EntriesPacketType.REMOVE, players, Collections.singleton(oldPlayer));
pd.removeScore(players, oldPlayer);
oldPlayer = null;
var entries = entries();
packetAdapter.updateTeamPackets(entries);
packetAdapter.addEntries(players, entries);
handler.localeLineHandler().sidebar().packetAdapter().score(players, info.objectiveScore(), player);
packetAdapter.sendEntries(EntriesPacketType.ADD, players, Collections.singleton(player));
pd.sendScore(players, player, info.objectiveScore(), null, null);
}
packetAdapter.updateTeam(players);
packetAdapter.sendProperties(PropertiesPacketType.UPDATE, players);
}
@Override
public void resetOldPlayer() {
oldPlayer = null;
}
@Override
public void sendScore(@NotNull Collection<Player> players) {
ObjectivePacketAdapter pd = handler.localeLineHandler().sidebar().packetAdapter();
if (oldPlayer != null) {
handler.localeLineHandler().sidebar().packetAdapter().removeLine(players, oldPlayer);
pd.removeScore(players, oldPlayer);
}
handler.localeLineHandler().sidebar().packetAdapter().score(players, info.objectiveScore(), player);
pd.sendScore(players, player, info.objectiveScore(), null, null);
}
@Override
public void show(@NotNull Collection<Player> players) {
sendScore(players);
packetAdapter.createTeam(players);
packetAdapter.sendProperties(PropertiesPacketType.CREATE, players);
}
@Override
public void hide(@NotNull Collection<Player> players) {
ObjectivePacketAdapter pd = handler.localeLineHandler().sidebar().packetAdapter();
if (oldPlayer != null) {
handler.localeLineHandler().sidebar().packetAdapter().removeLine(players, oldPlayer);
pd.removeScore(players, oldPlayer);
}
handler.localeLineHandler().sidebar().packetAdapter().removeLine(players, player);
pd.removeScore(players, player);
info.packetAdapter().removeTeam(players);
}
}
@@ -1,26 +0,0 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale;
import java.util.function.BiFunction;
import net.megavex.scoreboardlibrary.implementation.sidebar.AbstractSidebar;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.SidebarLineHandler;
import org.bukkit.entity.Player;
public enum LineType {
MODERN(ModernLocaleLine::new),
LEGACY(LegacyLocaleLine::new);
private final BiFunction<GlobalLineInfo, SidebarLineHandler, LocaleLine<?>> constructor;
LineType(BiFunction<GlobalLineInfo, SidebarLineHandler, LocaleLine<?>> constructor) {
this.constructor = constructor;
}
public static LineType getType(AbstractSidebar sidebar, Player player) {
return sidebar.scoreboardLibrary().packetAdapter().isLegacy(player) ? LEGACY : MODERN;
}
public LocaleLine<?> create(GlobalLineInfo line, SidebarLineHandler localeLineHandler) {
return constructor.apply(line, localeLineHandler);
}
}
@@ -1,19 +1,22 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale;
import java.util.Collection;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public interface LocaleLine<C> extends ImmutableTeamProperties<C> {
import java.util.Collection;
public interface LocaleLine {
@NotNull GlobalLineInfo info();
void value(@NotNull Component renderedComponent);
void updateTeam();
default void resetOldPlayer() {
}
void sendScore(@NotNull Collection<Player> players);
void show(@NotNull Collection<Player> players);
@@ -1,30 +1,32 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale;
import java.util.Collection;
import java.util.Set;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.TeamsPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.SidebarLineHandler;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import static net.kyori.adventure.text.Component.empty;
// Implementation for versions above 1.12.2
class ModernLocaleLine implements LocaleLine<Component> {
// Implementation for versions above 1.13
public class ModernLocaleLine implements LocaleLine, ImmutableTeamProperties<Component> {
private final GlobalLineInfo info;
private final SidebarLineHandler handler;
private final Collection<String> entries;
private final TeamsPacketAdapter.TeamInfoPacketAdapter<Component> packetAdapter;
private final TeamDisplayPacketAdapter packetAdapter;
public ModernLocaleLine(GlobalLineInfo info, SidebarLineHandler handler) {
this.info = info;
this.handler = handler;
this.entries = Set.of(info.player());
this.packetAdapter = info.packetAdapter().createTeamInfoAdapter(this);
packetAdapter.updateTeamPackets(entries);
this.entries = ImmutableList.of(info.player());
this.packetAdapter = info.packetAdapter().createTeamDisplayAdapter(this);
packetAdapter.updateTeamPackets();
}
@Override
@@ -38,29 +40,32 @@ class ModernLocaleLine implements LocaleLine<Component> {
@Override
public void updateTeam() {
packetAdapter.updateTeamPackets(entries);
packetAdapter.updateTeam(handler.players());
packetAdapter.updateTeamPackets();
packetAdapter.sendProperties(PropertiesPacketType.UPDATE, handler.players());
}
@Override
public void sendScore(@NotNull Collection<Player> players) {
handler.localeLineHandler().sidebar().packetAdapter().score(players, info.objectiveScore(), info.player());
handler.localeLineHandler()
.sidebar()
.packetAdapter()
.sendScore(players, info.player(), info.objectiveScore(), null, info.scoreFormat());
}
@Override
public void show(@NotNull Collection<Player> players) {
sendScore(players);
packetAdapter.createTeam(players);
packetAdapter.sendProperties(PropertiesPacketType.CREATE, players);
}
@Override
public void hide(@NotNull Collection<Player> players) {
handler.localeLineHandler().sidebar().packetAdapter().removeLine(players, info.player());
handler.localeLineHandler().sidebar().packetAdapter().removeScore(players, info.player());
info.packetAdapter().removeTeam(players);
}
@Override
public @NotNull Collection<String> entries() {
public @NotNull Collection<String> syncedEntries() {
return entries;
}
@@ -71,7 +76,7 @@ class ModernLocaleLine implements LocaleLine<Component> {
@Override
public @NotNull Component prefix() {
var value = info.value();
Component value = info.value();
return value == null ? empty() : value;
}
@@ -0,0 +1,53 @@
package net.megavex.scoreboardlibrary.implementation.sidebar.line.locale;
import net.kyori.adventure.text.Component;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.GlobalLineInfo;
import net.megavex.scoreboardlibrary.implementation.sidebar.line.SidebarLineHandler;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
// Implementation for versions 1.20.3+
// This is not yet being used as it might break on servers with plugins such as ViaBackwards,
// however it will once Mojang decides to remove legacy chat format support
public class PostModernLocaleLine implements LocaleLine {
private final GlobalLineInfo info;
private final SidebarLineHandler handler;
public PostModernLocaleLine(GlobalLineInfo info, SidebarLineHandler handler) {
this.info = info;
this.handler = handler;
}
@Override
public @NotNull GlobalLineInfo info() {
return info;
}
@Override
public void value(@NotNull Component renderedComponent) {
}
@Override
public void updateTeam() {
}
@Override
public void sendScore(@NotNull Collection<Player> players) {
handler.localeLineHandler()
.sidebar()
.packetAdapter()
.sendScore(players, info.player(), info.objectiveScore(), info.value(), info.scoreFormat());
}
@Override
public void show(@NotNull Collection<Player> players) {
sendScore(players);
}
@Override
public void hide(@NotNull Collection<Player> players) {
handler.localeLineHandler().sidebar().packetAdapter().removeScore(players, info.player());
}
}
@@ -1,30 +1,30 @@
package net.megavex.scoreboardlibrary.implementation.team;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam;
import net.megavex.scoreboardlibrary.api.team.TeamInfo;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.TeamsPacketAdapter;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamsPacketAdapter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ScoreboardTeamImpl implements ScoreboardTeam {
private final TeamManagerImpl teamManager;
private final String name;
private final TeamsPacketAdapter<?, ?> packetAdapter;
private final TeamsPacketAdapter packetAdapter;
private final TeamInfoImpl globalInfo;
private final Map<Player, TeamInfoImpl> teamInfoMap = new ConcurrentHashMap<>();
private final TeamDisplayImpl defaultDisplay;
private final Map<Player, TeamDisplayImpl> displayMap = new ConcurrentHashMap<>();
public ScoreboardTeamImpl(@NotNull TeamManagerImpl teamManager, @NotNull String name) {
this.teamManager = teamManager;
this.name = name;
this.packetAdapter = teamManager.scoreboardLibrary().packetAdapter().createTeamPacketAdapter(name);
this.globalInfo = new TeamInfoImpl(this);
this.defaultDisplay = new TeamDisplayImpl(this);
}
@Override
@@ -38,97 +38,97 @@ public class ScoreboardTeamImpl implements ScoreboardTeam {
}
@Override
public @NotNull TeamInfoImpl globalInfo() {
return globalInfo;
public @NotNull TeamDisplayImpl defaultDisplay() {
return defaultDisplay;
}
@Override
public @NotNull TeamInfo teamInfo(@NotNull Player player) {
public @NotNull TeamDisplay display(@NotNull Player player) {
Preconditions.checkNotNull(player);
if (!teamManager.players().contains(player)) {
throw new IllegalArgumentException("player not in TeamManager");
}
return Objects.requireNonNull(teamInfoMap.get(player));
return Objects.requireNonNull(displayMap.get(player));
}
@Override
public void teamInfo(@NotNull Player player, @NotNull TeamInfo teamInfo) {
public void display(@NotNull Player player, @NotNull TeamDisplay teamDisplay) {
Preconditions.checkNotNull(player);
Preconditions.checkNotNull(teamInfo);
Preconditions.checkNotNull(teamDisplay);
if (!teamManager.players().contains(player)) {
throw new IllegalArgumentException("player not in TeamManager");
}
if (teamInfo.team() != this || !(teamInfo instanceof TeamInfoImpl)) {
throw new IllegalArgumentException("invalid TeamInfo");
if (teamDisplay.team() != this || !(teamDisplay instanceof TeamDisplayImpl)) {
throw new IllegalArgumentException("invalid TeamDisplay");
}
var oldTeamInfo = Objects.requireNonNull(teamInfoMap.put(player, (TeamInfoImpl) teamInfo));
if (oldTeamInfo == teamInfo) {
TeamDisplayImpl oldTeamDisplay = Objects.requireNonNull(displayMap.put(player, (TeamDisplayImpl) teamDisplay));
if (oldTeamDisplay == teamDisplay) {
return;
}
teamManager.taskQueue().add(new TeamManagerTask.ChangeTeamInfo(player, this, oldTeamInfo, (TeamInfoImpl) teamInfo));
teamManager.taskQueue().add(new TeamManagerTask.ChangeTeamDisplay(player, this, oldTeamDisplay, (TeamDisplayImpl) teamDisplay));
}
@Override
public @NotNull TeamInfo createTeamInfo() {
return new TeamInfoImpl(this);
public @NotNull TeamDisplay createDisplay() {
return new TeamDisplayImpl(this);
}
public @NotNull TeamsPacketAdapter<?, ?> packetAdapter() {
public @NotNull TeamsPacketAdapter packetAdapter() {
return packetAdapter;
}
public @NotNull Map<Player, TeamInfoImpl> teamInfoMap() {
return teamInfoMap;
public @NotNull Map<Player, TeamDisplayImpl> displayMap() {
return displayMap;
}
public void addPlayer(@NotNull Player player) {
var teamInfo = Objects.requireNonNull(teamInfoMap.get(player));
if (teamInfo.players().add(player)) {
teamInfo.packetAdapter().createTeam(Set.of(player));
TeamDisplayImpl teamDisplay = Objects.requireNonNull(displayMap.get(player));
if (teamDisplay.players().add(player)) {
teamDisplay.packetAdapter().sendProperties(PropertiesPacketType.CREATE, Collections.singleton(player));
}
}
public void removePlayer(@NotNull Player player) {
var teamInfo = Objects.requireNonNull(teamInfoMap.remove(player));
if (teamInfo.players().remove(player)) {
packetAdapter.removeTeam(Set.of(player));
TeamDisplayImpl teamDisplay = Objects.requireNonNull(displayMap.remove(player));
if (teamDisplay.players().remove(player)) {
packetAdapter.removeTeam(Collections.singleton(player));
}
}
public void changeTeamInfo(@NotNull Player player, @NotNull TeamInfoImpl oldTeamInfo, @NotNull TeamInfoImpl newTeamInfo) {
if (!oldTeamInfo.players().remove(player)) {
public void changeTeamDisplay(@NotNull Player player, @NotNull TeamDisplayImpl oldTeamDisplay, @NotNull TeamDisplayImpl newTeamDisplay) {
if (!oldTeamDisplay.players().remove(player)) {
return;
}
newTeamInfo.players().add(player);
newTeamDisplay.players().add(player);
var singleton = Set.of(player);
newTeamInfo.packetAdapter().updateTeam(singleton);
Collection<Player> singleton = Collections.singleton(player);
newTeamDisplay.packetAdapter().sendProperties(PropertiesPacketType.UPDATE, singleton);
var oldEntries = oldTeamInfo.entries();
var newEntries = newTeamInfo.entries();
Collection<String> oldEntries = oldTeamDisplay.syncedEntries();
Collection<String> newEntries = newTeamDisplay.syncedEntries();
if (oldEntries.isEmpty()) {
newTeamInfo.packetAdapter().addEntries(singleton, newEntries);
newTeamDisplay.packetAdapter().sendEntries(EntriesPacketType.ADD, singleton, newEntries);
return;
}
var entries = new ArrayList<>(oldEntries);
List<String> entries = new ArrayList<>(oldEntries);
entries.removeAll(newEntries);
if (!entries.isEmpty()) {
newTeamInfo.packetAdapter().removeEntries(singleton, entries);
newTeamDisplay.packetAdapter().sendEntries(EntriesPacketType.REMOVE, singleton, entries);
}
entries = new ArrayList<>(newEntries);
entries.removeAll(oldEntries);
if (!entries.isEmpty()) {
newTeamInfo.packetAdapter().addEntries(singleton, entries);
newTeamDisplay.packetAdapter().sendEntries(EntriesPacketType.ADD, singleton, entries);
}
}
}
@@ -1,33 +1,32 @@
package net.megavex.scoreboardlibrary.implementation.team;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
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.TeamInfo;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
import net.megavex.scoreboardlibrary.api.team.enums.CollisionRule;
import net.megavex.scoreboardlibrary.api.team.enums.NameTagVisibility;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.ImmutableTeamProperties;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.TeamsPacketAdapter;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.EntriesPacketType;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.team.TeamDisplayPacketAdapter;
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 class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component> {
public class TeamDisplayImpl implements TeamDisplay, ImmutableTeamProperties<Component> {
private final ScoreboardTeamImpl team;
private final TeamsPacketAdapter.TeamInfoPacketAdapter<?> packetAdapter;
private final TeamDisplayPacketAdapter packetAdapter;
private final Set<Player> players = CollectionProvider.set(4);
private final Set<String> entries = CollectionProvider.set(4);
private final List<String> entries = CollectionProvider.list(4);
private final List<String> syncedEntries = CollectionProvider.list(4);
private Component displayName = empty(),
prefix = empty(),
suffix = empty();
@@ -36,9 +35,10 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
private CollisionRule collisionRule = CollisionRule.ALWAYS;
private NamedTextColor playerColor = null;
public TeamInfoImpl(ScoreboardTeamImpl team) {
public TeamDisplayImpl(@NotNull ScoreboardTeamImpl team) {
this.team = team;
this.packetAdapter = team.packetAdapter().createTeamInfoAdapter(this);
this.packetAdapter = team.packetAdapter().createTeamDisplayAdapter(this);
packetAdapter.updateTeamPackets();
}
@Override
@@ -48,13 +48,19 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
@Override
public @NotNull Collection<String> entries() {
return Collections.unmodifiableSet(entries);
return Collections.unmodifiableCollection(entries);
}
@Override
public @NotNull Collection<String> syncedEntries() {
return syncedEntries;
}
@Override
public boolean addEntry(@NotNull String entry) {
if (entries.add(entry)) {
team.teamManager().taskQueue().add(new TeamManagerTask.AddEntries(this, Set.of(entry)));
if (!entries.contains(entry)) {
entries.add(entry);
team.teamManager().taskQueue().add(new TeamManagerTask.AddEntries(this, Collections.singleton(entry)));
return true;
}
@@ -64,7 +70,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
@Override
public boolean removeEntry(@NotNull String entry) {
if (entries.remove(entry)) {
team.teamManager().taskQueue().add(new TeamManagerTask.RemoveEntries(this, Set.of(entry)));
team.teamManager().taskQueue().add(new TeamManagerTask.RemoveEntries(this, Collections.singleton(entry)));
return true;
}
@@ -77,11 +83,12 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo displayName(@NotNull Component displayName) {
public @NotNull TeamDisplay displayName(@NotNull ComponentLike displayName) {
Preconditions.checkNotNull(displayName);
if (!Objects.equals(this.displayName, displayName)) {
this.displayName = displayName;
Component component = displayName.asComponent();
if (!Objects.equals(this.displayName, component)) {
this.displayName = component;
scheduleUpdate();
}
@@ -94,11 +101,12 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo prefix(@NotNull Component prefix) {
public @NotNull TeamDisplay prefix(@NotNull ComponentLike prefix) {
Preconditions.checkNotNull(prefix);
if (!Objects.equals(this.prefix, prefix)) {
this.prefix = prefix;
Component component = prefix.asComponent();
if (!Objects.equals(this.prefix, component)) {
this.prefix = component;
scheduleUpdate();
}
@@ -111,11 +119,12 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo suffix(@NotNull Component suffix) {
public @NotNull TeamDisplay suffix(@NotNull ComponentLike suffix) {
Preconditions.checkNotNull(suffix);
if (!Objects.equals(this.suffix, suffix)) {
this.suffix = suffix;
Component component = suffix.asComponent();
if (!Objects.equals(this.suffix, component)) {
this.suffix = component;
scheduleUpdate();
}
@@ -128,7 +137,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo friendlyFire(boolean friendlyFire) {
public @NotNull TeamDisplay friendlyFire(boolean friendlyFire) {
if (this.friendlyFire != friendlyFire) {
this.friendlyFire = friendlyFire;
scheduleUpdate();
@@ -143,7 +152,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
public @NotNull TeamDisplay canSeeFriendlyInvisibles(boolean canSeeFriendlyInvisibles) {
if (this.canSeeFriendlyInvisibles != canSeeFriendlyInvisibles) {
this.canSeeFriendlyInvisibles = canSeeFriendlyInvisibles;
scheduleUpdate();
@@ -158,7 +167,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility) {
public @NotNull TeamDisplay nameTagVisibility(@NotNull NameTagVisibility nameTagVisibility) {
Preconditions.checkNotNull(nameTagVisibility);
if (!Objects.equals(this.nameTagVisibility, nameTagVisibility)) {
@@ -175,7 +184,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo collisionRule(@NotNull CollisionRule collisionRule) {
public @NotNull TeamDisplay collisionRule(@NotNull CollisionRule collisionRule) {
Preconditions.checkNotNull(collisionRule);
if (!Objects.equals(this.collisionRule, collisionRule)) {
@@ -192,7 +201,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
}
@Override
public @NotNull TeamInfo playerColor(@Nullable NamedTextColor playerColor) {
public @NotNull TeamDisplay playerColor(@Nullable NamedTextColor playerColor) {
if (!Objects.equals(this.playerColor, playerColor)) {
this.playerColor = playerColor;
scheduleUpdate();
@@ -201,7 +210,7 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
return this;
}
public @NotNull TeamsPacketAdapter.TeamInfoPacketAdapter<?> packetAdapter() {
public @NotNull TeamDisplayPacketAdapter packetAdapter() {
return packetAdapter;
}
@@ -209,16 +218,30 @@ public class TeamInfoImpl implements TeamInfo, ImmutableTeamProperties<Component
return players;
}
public void updateTeamPackets() {
packetAdapter.updateTeamPackets(entries);
public void handleUpdateDisplay() {
packetAdapter.updateTeamPackets();
packetAdapter().sendProperties(PropertiesPacketType.UPDATE, players());
}
public void handleAddEntries(@NotNull Collection<String> newEntries) {
packetAdapter.sendEntries(EntriesPacketType.ADD, players(), newEntries);
syncedEntries.addAll(newEntries);
packetAdapter.updateTeamPackets();
}
public void handleRemoveEntries(@NotNull Collection<String> oldEntries) {
packetAdapter.sendEntries(EntriesPacketType.REMOVE, players(), oldEntries);
syncedEntries.removeAll(oldEntries);
packetAdapter.updateTeamPackets();
}
private void scheduleUpdate() {
var taskQueue = team.teamManager().taskQueue();
if (taskQueue.peek() instanceof TeamManagerTask.UpdateTeamInfo updateTeamTask && updateTeamTask.teamInfo() == this) {
Queue<TeamManagerTask> taskQueue = team.teamManager().taskQueue();
TeamManagerTask lastTask = taskQueue.peek();
if (lastTask instanceof TeamManagerTask.UpdateTeamDisplay && ((TeamManagerTask.UpdateTeamDisplay) lastTask).teamDisplay() == this) {
return;
}
taskQueue.add(new TeamManagerTask.UpdateTeamInfo(this));
taskQueue.add(new TeamManagerTask.UpdateTeamDisplay(this));
}
}
@@ -1,35 +1,33 @@
package net.megavex.scoreboardlibrary.implementation.team;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiFunction;
import java.util.function.Function;
import net.megavex.scoreboardlibrary.api.team.ScoreboardTeam;
import net.megavex.scoreboardlibrary.api.team.TeamInfo;
import net.megavex.scoreboardlibrary.api.team.TeamDisplay;
import net.megavex.scoreboardlibrary.api.team.TeamManager;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import net.megavex.scoreboardlibrary.implementation.commons.CollectionProvider;
import net.megavex.scoreboardlibrary.implementation.packetAdapter.PropertiesPacketType;
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 org.jetbrains.annotations.Nullable;
public class TeamManagerImpl implements TeamManager {
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiFunction;
import java.util.function.Function;
public class TeamManagerImpl implements TeamManager, PlayerDisplayable {
private final ScoreboardLibraryImpl scoreboardLibrary;
private final Set<Player> players = CollectionProvider.set(8);
private final List<Player> internalPlayers = CollectionProvider.list(8);
private final Map<String, ScoreboardTeamImpl> teams = new ConcurrentHashMap<>();
private boolean closed;
private final Queue<TeamManagerTask> taskQueue = new ConcurrentLinkedQueue<>();
private boolean closed;
public TeamManagerImpl(@NotNull ScoreboardLibraryImpl scoreboardLibrary) {
this.scoreboardLibrary = scoreboardLibrary;
@@ -69,12 +67,12 @@ public class TeamManagerImpl implements TeamManager {
}
@Override
public @NotNull ScoreboardTeam createIfAbsent(@NotNull String name, @Nullable BiFunction<Player, ScoreboardTeam, TeamInfo> teamInfoFunction) {
public @NotNull ScoreboardTeam createIfAbsent(@NotNull String name, @Nullable BiFunction<Player, ScoreboardTeam, TeamDisplay> teamDisplayFunction) {
Preconditions.checkNotNull(name);
checkClosed();
name = name.toLowerCase();
var team = teams.get(name);
ScoreboardTeamImpl team = teams.get(name);
if (team != null) {
return team;
}
@@ -82,13 +80,13 @@ public class TeamManagerImpl implements TeamManager {
team = new ScoreboardTeamImpl(this, name);
teams.put(name, team);
for (var player : players) {
var teamInfo = teamInfoFunction == null ? team.globalInfo() : teamInfoFunction.apply(player, team);
validateTeamInfo(team, teamInfo);
team.teamInfoMap().put(player, (TeamInfoImpl) teamInfo);
for (Player player : players) {
TeamDisplay teamDisplay = teamDisplayFunction == null ? team.defaultDisplay() : teamDisplayFunction.apply(player, team);
validateTeamDisplay(team, teamDisplay);
team.displayMap().put(player, (TeamDisplayImpl) teamDisplay);
}
var task = new TeamManagerTask.AddTeam(team);
TeamManagerTask.AddTeam task = new TeamManagerTask.AddTeam(team);
taskQueue.add(task);
return team;
}
@@ -98,7 +96,7 @@ public class TeamManagerImpl implements TeamManager {
Preconditions.checkNotNull(name);
checkClosed();
var team = teams.remove(name.toLowerCase());
ScoreboardTeamImpl team = teams.remove(name.toLowerCase());
if (team != null) {
taskQueue.add(new TeamManagerTask.RemoveTeam(team));
return true;
@@ -119,7 +117,7 @@ public class TeamManagerImpl implements TeamManager {
}
@Override
public boolean addPlayer(@NotNull Player player, @Nullable Function<ScoreboardTeam, TeamInfo> teamInfoFunction) {
public boolean addPlayer(@NotNull Player player, @Nullable Function<ScoreboardTeam, TeamDisplay> teamDisplayFunction) {
Preconditions.checkNotNull(player);
checkClosed();
@@ -127,10 +125,10 @@ public class TeamManagerImpl implements TeamManager {
return false;
}
for (var team : teams.values()) {
var teamInfo = teamInfoFunction == null ? team.globalInfo() : teamInfoFunction.apply(team);
validateTeamInfo(team, teamInfo);
team.teamInfoMap().put(player, (TeamInfoImpl) teamInfo);
for (ScoreboardTeamImpl team : teams.values()) {
TeamDisplay teamDisplay = teamDisplayFunction == null ? team.defaultDisplay() : teamDisplayFunction.apply(team);
validateTeamDisplay(team, teamDisplay);
team.displayMap().put(player, (TeamDisplayImpl) teamDisplay);
}
taskQueue.add(new TeamManagerTask.AddPlayer(player));
@@ -146,7 +144,7 @@ public class TeamManagerImpl implements TeamManager {
return false;
}
var task = new TeamManagerTask.RemovePlayer(player);
TeamManagerTask.RemovePlayer task = new TeamManagerTask.RemovePlayer(player);
taskQueue.add(task);
return true;
}
@@ -159,82 +157,93 @@ public class TeamManagerImpl implements TeamManager {
return taskQueue;
}
public void show(@NotNull Player player) {
for (var team : teams.values()) {
@Override
public void display(@NotNull Player player) {
for (ScoreboardTeamImpl team : teams.values()) {
team.addPlayer(player);
}
}
public void tick() {
public boolean tick() {
while (true) {
var task = taskQueue.poll();
TeamManagerTask task = taskQueue.poll();
if (task == null) {
break;
}
if (task instanceof TeamManagerTask.Close) {
scoreboardLibrary.teamManagers().remove(this);
for (var team : teams.values()) {
Set<Player> removePlayers = CollectionProvider.set(players.size());
for (TeamInfoImpl value : team.teamInfoMap().values()) {
for (ScoreboardTeamImpl team : teams.values()) {
Set<Player> removePlayers = CollectionProvider.set(internalPlayers.size());
for (TeamDisplayImpl value : team.displayMap().values()) {
removePlayers.addAll(value.players());
}
team.packetAdapter().removeTeam(removePlayers);
}
for (var player : players) {
Objects.requireNonNull(scoreboardLibrary.getPlayer(player)).removeTeamManager(this);
for (Player player : internalPlayers) {
Objects.requireNonNull(scoreboardLibrary.getPlayer(player)).teamManagerQueue().remove(this);
}
return;
} else if (task instanceof TeamManagerTask.AddPlayer addPlayerTask) {
var slPlayer = scoreboardLibrary.getOrCreatePlayer(addPlayerTask.player());
slPlayer.addTeamManager(this);
} else if (task instanceof TeamManagerTask.RemovePlayer removePlayerTask) {
for (var team : teams.values()) {
return false;
} else if (task instanceof TeamManagerTask.AddPlayer) {
TeamManagerTask.AddPlayer addPlayerTask = (TeamManagerTask.AddPlayer) task;
@NotNull ScoreboardLibraryPlayer slPlayer = scoreboardLibrary.getOrCreatePlayer(addPlayerTask.player());
slPlayer.teamManagerQueue().add(this);
internalPlayers.add(addPlayerTask.player());
} else if (task instanceof TeamManagerTask.RemovePlayer) {
TeamManagerTask.RemovePlayer removePlayerTask = (TeamManagerTask.RemovePlayer) task;
for (ScoreboardTeamImpl team : teams.values()) {
team.removePlayer(removePlayerTask.player());
}
Objects.requireNonNull(scoreboardLibrary.getPlayer(removePlayerTask.player())).removeTeamManager(this);
} else if (task instanceof TeamManagerTask.ReloadPlayer reloadPlayerTask) {
for (var team : teams.values()) {
var teamInfo = team.teamInfoMap().get(reloadPlayerTask.player());
if (teamInfo != null) {
teamInfo.packetAdapter().updateTeam(Set.of(reloadPlayerTask.player()));
internalPlayers.remove(removePlayerTask.player());
Objects.requireNonNull(scoreboardLibrary.getPlayer(removePlayerTask.player())).teamManagerQueue().remove(this);
} else if (task instanceof TeamManagerTask.ReloadPlayer) {
TeamManagerTask.ReloadPlayer reloadPlayerTask = (TeamManagerTask.ReloadPlayer) task;
for (ScoreboardTeamImpl team : teams.values()) {
TeamDisplayImpl teamDisplay = team.displayMap().get(reloadPlayerTask.player());
if (teamDisplay != null) {
teamDisplay.packetAdapter().sendProperties(PropertiesPacketType.UPDATE, Collections.singleton(reloadPlayerTask.player()));
}
}
} else if (task instanceof TeamManagerTask.AddTeam addTeamTask) {
var team = addTeamTask.team();
for (var player : team.teamInfoMap().keySet()) {
if (Objects.requireNonNull(scoreboardLibrary.getPlayer(player)).teamManager() == this) {
} else if (task instanceof TeamManagerTask.AddTeam) {
TeamManagerTask.AddTeam addTeamTask = (TeamManagerTask.AddTeam) task;
ScoreboardTeamImpl team = addTeamTask.team();
for (Player player : team.displayMap().keySet()) {
ScoreboardLibraryPlayer slPlayer = scoreboardLibrary.getPlayer(player);
if (slPlayer != null && slPlayer.teamManagerQueue().current() == this) {
team.addPlayer(player);
}
}
} else if (task instanceof TeamManagerTask.RemoveTeam removeTeamTask) {
List<Player> playersInTeam = CollectionProvider.list(removeTeamTask.team().teamInfoMap().size());
for (var entry : removeTeamTask.team().teamInfoMap().entrySet()) {
var player = entry.getKey();
var teamInfo = entry.getValue();
if (teamInfo.players().contains(player)) {
} else if (task instanceof TeamManagerTask.RemoveTeam) {
TeamManagerTask.RemoveTeam removeTeamTask = (TeamManagerTask.RemoveTeam) task;
List<Player> playersInTeam = CollectionProvider.list(removeTeamTask.team().displayMap().size());
for (Map.Entry<Player, TeamDisplayImpl> entry : removeTeamTask.team().displayMap().entrySet()) {
Player player = entry.getKey();
TeamDisplayImpl teamDisplay = entry.getValue();
if (teamDisplay.players().contains(player)) {
playersInTeam.add(player);
}
}
removeTeamTask.team().packetAdapter().removeTeam(playersInTeam);
} else if (task instanceof TeamManagerTask.UpdateTeamInfo updateTeamInfoTask) {
var teamInfo = updateTeamInfoTask.teamInfo();
teamInfo.updateTeamPackets();
teamInfo.packetAdapter().updateTeam(teamInfo.players());
} else if (task instanceof TeamManagerTask.AddEntries addEntriesTask) {
var teamInfo = addEntriesTask.teamInfo();
teamInfo.packetAdapter().addEntries(teamInfo.players(), addEntriesTask.entries());
} else if (task instanceof TeamManagerTask.RemoveEntries removeEntriesTask) {
var teamInfo = removeEntriesTask.teamInfo();
teamInfo.packetAdapter().removeEntries(teamInfo.players(), removeEntriesTask.entries());
} else if (task instanceof TeamManagerTask.ChangeTeamInfo changeTeamInfoTask) {
changeTeamInfoTask.team().changeTeamInfo(changeTeamInfoTask.player(), changeTeamInfoTask.oldTeamInfo(), changeTeamInfoTask.newTeamInfo());
} else if (task instanceof TeamManagerTask.UpdateTeamDisplay) {
TeamManagerTask.UpdateTeamDisplay updateTeamDisplayTask = (TeamManagerTask.UpdateTeamDisplay) task;
@NotNull TeamDisplayImpl teamDisplay = updateTeamDisplayTask.teamDisplay();
teamDisplay.handleUpdateDisplay();
} else if (task instanceof TeamManagerTask.AddEntries) {
TeamManagerTask.AddEntries addEntriesTask = (TeamManagerTask.AddEntries) task;
@NotNull TeamDisplayImpl teamDisplay = addEntriesTask.teamDisplay();
teamDisplay.handleAddEntries(addEntriesTask.entries());
} else if (task instanceof TeamManagerTask.RemoveEntries) {
TeamManagerTask.RemoveEntries removeEntriesTask = (TeamManagerTask.RemoveEntries) task;
@NotNull TeamDisplayImpl teamDisplay = removeEntriesTask.teamDisplay();
teamDisplay.handleRemoveEntries(removeEntriesTask.entries());
} else if (task instanceof TeamManagerTask.ChangeTeamDisplay) {
TeamManagerTask.ChangeTeamDisplay changeTeamDisplayTask = (TeamManagerTask.ChangeTeamDisplay) task;
changeTeamDisplayTask.team().changeTeamDisplay(changeTeamDisplayTask.player(), changeTeamDisplayTask.oldTeamDisplay(), changeTeamDisplayTask.newTeamDisplay());
}
}
return true;
}
private void checkClosed() {
@@ -243,13 +252,13 @@ public class TeamManagerImpl implements TeamManager {
}
}
private void validateTeamInfo(@NotNull ScoreboardTeam team, @Nullable TeamInfo teamInfo) {
if (teamInfo == null || teamInfo.team() != team) {
throw new IllegalArgumentException("invalid TeamInfo");
private void validateTeamDisplay(@NotNull ScoreboardTeam team, @Nullable TeamDisplay teamDisplay) {
if (teamDisplay == null || teamDisplay.team() != team) {
throw new IllegalArgumentException("invalid TeamDisplay");
}
if (!(teamInfo instanceof TeamInfoImpl)) {
throw new IllegalArgumentException("must be TeamInfoImpl");
if (!(teamDisplay instanceof TeamDisplayImpl)) {
throw new IllegalArgumentException("must be TeamDisplayImpl");
}
}
}
@@ -1,10 +1,14 @@
package net.megavex.scoreboardlibrary.implementation.team;
import java.util.Collection;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public sealed class TeamManagerTask {
import java.util.Collection;
public class TeamManagerTask {
private TeamManagerTask() {
}
public static final class Close extends TeamManagerTask {
public static final Close INSTANCE = new Close();
@@ -48,7 +52,6 @@ public sealed class TeamManagerTask {
}
}
public static final class AddTeam extends TeamManagerTask {
private final ScoreboardTeamImpl team;
@@ -73,29 +76,29 @@ public sealed class TeamManagerTask {
}
}
public static final class UpdateTeamInfo extends TeamManagerTask {
private final TeamInfoImpl teamInfo;
public static final class UpdateTeamDisplay extends TeamManagerTask {
private final TeamDisplayImpl teamDisplay;
public UpdateTeamInfo(@NotNull TeamInfoImpl teamInfo) {
this.teamInfo = teamInfo;
public UpdateTeamDisplay(@NotNull TeamDisplayImpl teamDisplay) {
this.teamDisplay = teamDisplay;
}
public @NotNull TeamInfoImpl teamInfo() {
return teamInfo;
public @NotNull TeamDisplayImpl teamDisplay() {
return teamDisplay;
}
}
public static final class AddEntries extends TeamManagerTask {
private final TeamInfoImpl teamInfo;
private final TeamDisplayImpl teamDisplay;
private final Collection<String> entry;
public AddEntries(@NotNull TeamInfoImpl teamInfo, @NotNull Collection<String> entries) {
this.teamInfo = teamInfo;
public AddEntries(@NotNull TeamDisplayImpl teamDisplay, @NotNull Collection<String> entries) {
this.teamDisplay = teamDisplay;
this.entry = entries;
}
public @NotNull TeamInfoImpl teamInfo() {
return teamInfo;
public @NotNull TeamDisplayImpl teamDisplay() {
return teamDisplay;
}
public @NotNull Collection<String> entries() {
@@ -104,16 +107,16 @@ public sealed class TeamManagerTask {
}
public static final class RemoveEntries extends TeamManagerTask {
private final TeamInfoImpl teamInfo;
private final TeamDisplayImpl teamDisplay;
private final Collection<String> entry;
public RemoveEntries(@NotNull TeamInfoImpl teamInfo, @NotNull Collection<String> entries) {
this.teamInfo = teamInfo;
public RemoveEntries(@NotNull TeamDisplayImpl teamDisplay, @NotNull Collection<String> entries) {
this.teamDisplay = teamDisplay;
this.entry = entries;
}
public @NotNull TeamInfoImpl teamInfo() {
return teamInfo;
public @NotNull TeamDisplayImpl teamDisplay() {
return teamDisplay;
}
public @NotNull Collection<String> entries() {
@@ -121,17 +124,17 @@ public sealed class TeamManagerTask {
}
}
public static final class ChangeTeamInfo extends TeamManagerTask {
public static final class ChangeTeamDisplay extends TeamManagerTask {
private final Player player;
private final ScoreboardTeamImpl team;
private final TeamInfoImpl oldTeamInfo;
private final TeamInfoImpl newTeamInfo;
private final TeamDisplayImpl oldTeamDisplay;
private final TeamDisplayImpl newTeamDisplay;
public ChangeTeamInfo(@NotNull Player player, @NotNull ScoreboardTeamImpl team, @NotNull TeamInfoImpl oldTeamInfo, @NotNull TeamInfoImpl newTeamInfo) {
public ChangeTeamDisplay(@NotNull Player player, @NotNull ScoreboardTeamImpl team, @NotNull TeamDisplayImpl oldTeamDisplay, @NotNull TeamDisplayImpl newTeamDisplay) {
this.player = player;
this.team = team;
this.oldTeamInfo = oldTeamInfo;
this.newTeamInfo = newTeamInfo;
this.oldTeamDisplay = oldTeamDisplay;
this.newTeamDisplay = newTeamDisplay;
}
public @NotNull Player player() {
@@ -142,12 +145,12 @@ public sealed class TeamManagerTask {
return team;
}
public @NotNull TeamInfoImpl oldTeamInfo() {
return oldTeamInfo;
public @NotNull TeamDisplayImpl oldTeamDisplay() {
return oldTeamDisplay;
}
public @NotNull TeamInfoImpl newTeamInfo() {
return newTeamInfo;
public @NotNull TeamDisplayImpl newTeamDisplay() {
return newTeamDisplay;
}
}
}
@@ -1,17 +1,24 @@
package net.megavex.scoreboardlibrary.implementation.team;
import java.util.logging.Level;
import net.megavex.scoreboardlibrary.implementation.ScoreboardLibraryImpl;
import org.bukkit.scheduler.BukkitRunnable;
import net.megavex.scoreboardlibrary.implementation.scheduler.RunningTask;
import org.jetbrains.annotations.NotNull;
public class TeamUpdaterTask extends BukkitRunnable {
import java.util.Iterator;
import java.util.logging.Level;
public class TeamUpdaterTask implements Runnable {
private final ScoreboardLibraryImpl scoreboardLibrary;
private final RunningTask task;
private final Object lock = new Object();
public TeamUpdaterTask(@NotNull ScoreboardLibraryImpl scoreboardLibrary) {
this.scoreboardLibrary = scoreboardLibrary;
runTaskTimerAsynchronously(scoreboardLibrary.plugin(), 1, 1);
this.task = scoreboardLibrary.taskScheduler().runEveryTick(this);
}
public @NotNull RunningTask task() {
return task;
}
public @NotNull Object lock() {
@@ -21,11 +28,19 @@ public class TeamUpdaterTask extends BukkitRunnable {
@Override
public void run() {
synchronized (lock) {
for (var teamManager : scoreboardLibrary.teamManagers()) {
Iterator<TeamManagerImpl> iterator = scoreboardLibrary.teamManagers().iterator();
while (iterator.hasNext()) {
TeamManagerImpl teamManager = iterator.next();
boolean result;
try {
teamManager.tick();
result = teamManager.tick();
} catch (Exception e) {
scoreboardLibrary.plugin().getLogger().log(Level.SEVERE, "an error occurred while updating a TeamManager instance", e);
scoreboardLibrary.plugin().getLogger().log(Level.WARNING, "an error occurred while updating a TeamManager instance", e);
continue;
}
if (!result) {
iterator.remove();
}
}
}
-5
View File
@@ -1,5 +0,0 @@
jdk:
- openjdk17
install:
- "bash gradlew build publishToMavenLocal"
Loaded 100 of 174 files, more files were not shown because too many files have changed in this diff. Show more