Compare commits

...
219 Commits
Author SHA1 Message Date
Jason Penilla 922079cc7d release: Version 2.1.1 2024-06-02 00:17:52 -07:00
renovate[bot] 798946390b chore(deps): Update gradle and github actions 2024-06-01 20:55:47 -07:00
renovate[bot] 743a149f3b chore(deps): Update dependency org.checkerframework:checker-qual to v3.43.0 2024-06-01 20:54:59 -07:00
renovate[bot] 316945b30f chore(deps): Update dependency net.fabricmc.fabric-api:fabric-api to v0.99.4+1.20.6 2024-06-01 20:54:50 -07:00
renovate[bot] c8bf42463c chore(deps): Update adventurePlatform to v4.3.3 2024-06-01 20:54:41 -07:00
Jason Penilla a9bf505028 Update dependencies 2024-05-10 17:41:09 -07:00
Jason Penilla 60bfb21444 Opt-out of remapping on Paper 1.20.5+ 2024-05-02 14:35:06 -07:00
Jason Penilla fb13022642 feat(fabric): Initial 1.20.6 update 2024-04-29 21:45:58 -07:00
Jason Penilla 1bbf3abd9a build updates 2024-04-29 21:32:37 -07:00
renovate[bot] 1967d9802f chore(deps): Update dependency net.fabricmc:fabric-loader to v0.15.10 2024-04-29 21:25:12 -07:00
renovate[bot] 6b3d7aeab6 chore(deps): Update dependency me.modmuss50:mod-publish-plugin to v0.5.1 2024-02-26 22:12:54 -07:00
renovate[bot] 267c0426fe chore(deps): Update dependency net.kyori:adventure-bom to v4.16.0 2024-02-26 22:12:43 -07:00
renovate[bot] c3d5afa680 chore(deps): Update dependency net.kyori:adventure-platform-fabric to v5.12.0 2024-02-26 22:12:13 -07:00
renovate[bot] 2009684fb5 chore(deps): Update dependency org.junit.jupiter:junit-jupiter-engine to v5.10.2 2024-02-05 18:23:42 -07:00
renovate[bot] 56202f3f26 chore(deps): Update dependency io.papermc:hangar-publish-plugin to v0.1.2 2024-02-02 16:45:09 -07:00
renovate[bot] f2be2062e5 chore(deps): Update dependency net.kyori:adventure-platform-fabric to v5.11.0 2024-01-21 10:19:46 -07:00
Jason Penilla 4cbe7d4bf9 Use adventure textOfChildren method 2024-01-13 21:03:33 -07:00
renovate[bot] fd20b5e583 chore(deps): Update dependency io.github.waterfallmc:waterfall-api to v1.16-R0.5-SNAPSHOT 2024-01-13 20:29:52 -07:00
Jason Penilla e495e76a2d 2.1.1-SNAPSHOT 2024-01-13 20:17:46 -07:00
Jason Penilla a37e7d8b7c release: Version 2.1.0 2024-01-13 20:15:30 -07:00
Jason Penillaandens-gijs b97ebcbbd6 Filter player lists with counts
closes #161

Co-authored-by: ens-gijs <ens.gijs@gmail.com>
2024-01-13 20:09:17 -07:00
Jason Penilla 2abf0c1eba Rename MiniMOTDConfig to avoid confusion 2024-01-13 20:05:04 -07:00
renovate[bot] 80df7d0500 chore(deps): Update dependency net.fabricmc.fabric-api:fabric-api to v0.93.1+1.20.4 2024-01-13 20:01:37 -07:00
Jason Penilla b7318a1ac8 Support basic star matching and TCPShield hostnames for virtual host configs (#167)
* Allow using single star for parts of domains in virtual host config

* pre-split host strings

* Extract real hostname from TCPShield hostnames

* fix style

* Update config comment

* Add test for virtual host matching
2024-01-13 14:14:15 -07:00
Jason PenillaandddPn08 b5e5848bd9 Add server filtering for player counts (Velocity) (#171)
* Add feature to show number of players per server

* fixup

* remove star syntax; it could be used for other purposes in the future

* remove unused import

---------

Co-authored-by: ddPn08 <pyn.g.1125@gmail.com>
2024-01-12 21:08:40 -07:00
renovate[bot] 22064b8576 chore(deps): Update plugin org.gradle.toolchains.foojay-resolver-convention to v0.8.0 2024-01-12 12:59:31 -07:00
Jason Penilla aac6cab5f6 Update license 2024-01-06 16:47:31 -07:00
Jason Penilla 7197cf9100 Update loom, apply foojay resolver conventions, move build-logic dir 2024-01-06 16:20:37 -07:00
Jason Penilla b3c448136d Update dependencies for 1.20.4 2024-01-06 16:07:55 -07:00
renovate[bot] 25ad706ebf chore(deps): Update dependency net.kyori:adventure-bom to v4.15.0 2023-12-21 12:55:40 -08:00
renovate[bot] 3c04370488 chore(deps): Update adventurePlatform to v4.3.2 2023-12-21 12:52:44 -08:00
renovate[bot] 5a3c52481a chore(deps): Update gradle and github actions 2023-11-29 09:09:53 -08:00
renovate[bot] 2eb0ba1e98 chore(deps): Update dependency net.kyori:adventure-platform-fabric to v5.10.1 2023-11-17 11:56:29 -07:00
Jason Penilla b38e08daa0 build: switch from minotaur to mod-publish-plugin for modrinth publishing 2023-11-14 21:48:13 -07:00
renovate[bot] 3c61a2d24b chore(deps): Update dependency net.fabricmc.fabric-api:fabric-api to v0.90.7+1.20.2 2023-11-12 21:07:21 -07:00
renovate[bot] 611772f4bb chore(deps): Update dependency org.checkerframework:checker-qual to v3.40.0 2023-11-12 21:04:44 -07:00
renovate[bot] 118cef163a chore(deps): Update dependency net.fabricmc:fabric-loader to v0.14.24 2023-11-12 21:04:34 -07:00
renovate[bot] d17e4af825 chore(deps): Update dependency org.junit.jupiter:junit-jupiter-engine to v5.10.1 2023-11-12 21:04:26 -07:00
renovate[bot] 19a21d988f chore(deps): Update plugin blossom to v2.1.0 2023-11-12 21:04:17 -07:00
Jason Penilla 0cd04574f9 Remove plugin-yml-bungee 2023-11-12 20:58:04 -07:00
renovate[bot] 5acc5d285d chore(deps): Update dependency io.papermc:paperlib to v1.0.8 2023-11-12 20:49:22 -07:00
Jason Penilla 3d9e90d7bf Update gradle wrapper 2023-11-12 20:46:59 -07:00
Jason Penilla c39cad4017 Update loom to 1.4 2023-11-12 20:45:35 -07:00
Jason Penilla 9c9c67776b Remove guava dependency 2023-11-12 20:41:55 -07:00
Jason Penilla 2858de84ae Convert version catalog to toml and configure renovate 2023-11-12 20:39:43 -07:00
Jason Penilla 64b023fad7 2.0.15-SNAPSHOT 2023-09-30 12:20:22 -07:00
Jason Penilla f59de86c5e release: Version 2.0.14 2023-09-30 12:15:26 -07:00
Jason Penilla 0f1c69f8b2 update run-paper and adventure-platform 2023-09-30 12:12:31 -07:00
Jason Penilla f8deb9b7fb build updates 2023-09-23 21:17:36 -07:00
Jason Penilla 8428e2b124 1.20.2 2023-09-23 21:10:19 -07:00
Jason Penilla 8f91a1b445 bump SpongeGradle 2023-09-09 16:47:30 -07:00
Jason Penilla 0ca1f8f19d build updates 2023-09-09 15:34:03 -07:00
Jason Penilla 2fd3d41cbd build: Remove quilt repo
Quiltflower is succeeded by Vineflower which is on Maven Central.
2023-07-13 11:01:31 -07:00
Jason Penilla ce201ba89e build updates 2023-06-27 22:02:55 -07:00
Jason Penilla e33135d3c6 2.0.14-SNAPSHOT 2023-06-27 22:01:51 -07:00
Jason Penilla 5f6e2789e2 release: Version 2.0.13 2023-06-27 21:40:49 -07:00
Jason Penilla add32b31e7 fabric: 1.20.1 2023-06-17 10:08:41 -07:00
Jason Penilla b2b2801907 2.0.13-SNAPSHOT 2023-05-07 14:52:36 -07:00
Jason Penilla c919c40a59 release: Version 2.0.12 2023-05-07 14:35:19 -07:00
Jason Penilla b3cc4bce5c Fix style and add remap=false to favicon codec mixin 2023-05-07 13:54:36 -07:00
Jason Penilla 6fdf75dd3d Update license headers 2023-05-07 13:50:04 -07:00
Jason Penilla a8c2b0f23e Improve Folia detection 2023-05-07 13:49:21 -07:00
Jason Penilla 0ee99d90f2 fabric: Bring back caching of encoded server icons 2023-05-07 13:47:58 -07:00
renovate[bot] 6623a5928e chore(deps): update dependency gradle to v8.1.1 2023-05-07 13:18:43 -07:00
Jason Penilla 6532c03706 Add error handler to future 2023-03-27 21:21:15 -07:00
Jason Penilla 0bf3bfba7f Add Folia support 2023-03-27 21:17:18 -07:00
Jason Penilla f7094e0a77 2.0.12-SNAPSHOT 2023-03-18 13:21:57 -07:00
Jason Penilla 563da7f255 release: Version 2.0.11 2023-03-18 13:03:56 -07:00
Jason Penilla 51e599838d build: Setup automatic Hangar and Modrinth publishing 2023-03-18 12:57:17 -07:00
Jason Penilla e4970016e4 fabric: Update fabric.mod.json 2023-03-18 10:26:25 -07:00
Jason Penilla 2a04d0b2c8 fabric: Adjust lifecycle listeners 2023-03-18 10:22:44 -07:00
Jason Penilla f8c6c2db6f fabric: Update for Minecraft 1.19.4 2023-03-18 10:11:33 -07:00
Jason Penilla 9e25b68d00 Build and dependency updates 2023-01-31 14:22:26 -07:00
Jason Penilla 96b7d12743 Update loom 2023-01-31 14:17:39 -07:00
Jason Penilla 328ad6cd39 Preload some stuff on bungee
Avoids misleading and annoying log spam
2023-01-31 14:07:27 -07:00
Jason Penilla c0ee968e5b 2.0.11-SNAPSHOT 2022-12-09 10:18:17 -07:00
Jason Penilla bc4d1e3cdd release: Version 2.0.10 2022-12-09 10:07:42 -07:00
Jason Penilla 2626ed4551 Update testing Paper 2022-12-09 10:06:57 -07:00
Jason Penilla a1f51db093 Update for Minecraft 1.19.3 2022-12-08 22:03:20 -07:00
Jason Penilla 59ab61825b Update actions 2022-12-08 22:02:47 -07:00
Jason Penilla 5d7f79ce8a Update gradle wrapper files 2022-11-25 10:31:32 -07:00
renovate[bot] a5145384f6 chore(deps): update dependency gradle to v7.6 2022-11-25 09:58:39 -07:00
Jason Penilla fba4066522 Rename uploaded artifact 2022-11-22 20:18:02 -07:00
Jason Penilla 3c9261a92d Update actions 2022-11-22 20:09:06 -07:00
Jason Penilla ab409537ac Update jupiter engine 2022-11-15 12:26:46 -07:00
Jason Penilla 98a5bba407 Update adventure-platform-fabric 2022-11-15 12:26:38 -07:00
Jason Penilla 87bec5315b build: Update run-paper, apply run-velocity and run-waterfall to respective platforms 2022-11-15 12:26:15 -07:00
Jason Penilla bf9d92150c build: Update loom 2022-09-01 18:50:49 -07:00
Jason Penilla 4cad4d461b Remove universal jar and add bukkit/bungee jar for spigot resources, also reorganize project directory structure 2022-08-28 13:41:48 -07:00
Jason Penilla fc7b78f7a8 2.0.10-SNAPSHOT 2022-08-26 15:17:38 -07:00
Jason Penilla 15cffc2d71 release: Version 2.0.9 2022-08-26 14:39:08 -07:00
Jason Penilla 28cb91a40c Bump fabric loader 2022-08-26 14:38:19 -07:00
Jason Penilla 4ab6567fd2 Make virtual hosts case-insensitive (fixes #115) 2022-08-26 14:35:34 -07:00
Jason Penilla bdac395c2d Bump dependencies 2022-08-26 14:16:37 -07:00
Jason Penilla ffb475f529 Update gradle wrapper 2022-08-26 14:14:58 -07:00
renovate[bot] d690104a9e chore(deps): update dependency gradle to v7.5.1 2022-08-05 18:52:06 -07:00
Jason Penilla efa0c69840 Handle Bukkit/Spigot throwing UnsupportedOperationException in setServerIcon for legacy pings
this change does not affect Paper users
2022-07-21 20:19:12 -07:00
renovate[bot] 369e387a39 chore(deps): update dependency gradle to v7.5 2022-07-14 16:15:21 -07:00
Jason Penilla 2f98e67a93 build: Don't apply indra publishing, we don't have anything to publish 2022-06-19 00:11:26 -07:00
Jason Penilla e2894e211c 2.0.9-SNAPSHOT 2022-06-19 00:09:44 -07:00
Jason Penilla 58ff03aa95 release: Version 2.0.8 2022-06-18 23:51:55 -07:00
Jason Penilla 4155249878 Dependency updates 2022-06-18 23:51:25 -07:00
Jason Penilla de0936c5d2 Update for Minecraft 1.19 2022-06-13 19:28:31 -07:00
Jason Penilla a8e5e910ee fabric: update loom to 0.12 2022-05-29 19:49:41 -07:00
Jason Penilla 0d48b46621 Update SpongeGradle 2022-05-07 13:08:42 -07:00
Jason Penilla 2a18bc156d 2.0.8-SNAPSHOT 2022-05-03 18:15:53 -07:00
Jason Penilla 0dd650dacb release: Version 2.0.7 2022-05-03 18:06:49 -07:00
Jason Penilla 624a97346d Dependency updates 2022-05-03 18:04:56 -07:00
Jason Penilla 3d4fdc1f53 Minor clean up 2022-05-03 18:04:37 -07:00
Jason Penilla 286f72f746 Dependency updates 2022-04-20 22:18:29 -07:00
Renovate Bot 0cc8d9a98d chore(deps): update dependency gradle to v7.4.2 2022-03-31 10:51:27 -07:00
Jason Penilla 80ae413c9d 2.0.7-SNAPSHOT 2022-03-12 12:03:08 -07:00
Jason Penilla 1a2ba16194 release: Version 2.0.6 2022-03-12 11:59:28 -07:00
Jason Penilla 8c0a9b6811 Move project metadata into gradle.properties 2022-03-12 11:59:06 -07:00
Jason Penilla 310e8e7e7a Update Adventure and remove no longer needed repo 2022-03-12 11:54:02 -07:00
Renovate Bot 45604e6bb3 chore(deps): update dependency gradle to v7.4.1 2022-03-09 10:38:19 -08:00
Jason Penilla ec8d81144b update license 2022-03-04 11:17:51 -07:00
Jason Penilla 48c1c09d79 bukkit: Use paperlib for version detection 2022-03-04 11:16:56 -07:00
Renovate Bot df89471fdd chore(deps): update actions/upload-artifact action to v3 2022-03-03 14:13:35 -07:00
Jason Penilla d2e0bba539 Update Adventure & Initial update for Minecraft 1.18.2 2022-03-02 22:35:46 -07:00
Renovate Bot e715f3bb57 chore(deps): update actions/checkout action to v3 2022-03-01 13:45:10 -07:00
Renovate Bot bd774480b3 chore(deps): update actions/setup-java action to v3 2022-02-27 23:10:53 -07:00
Jason Penilla d91ea43aa8 Build updates 2022-02-27 23:10:22 -07:00
Jason Penilla 3bf3796ce2 Build updates 2022-02-08 11:53:31 -07:00
Jason Penilla 34e819abf4 Update checker framework 2022-01-27 14:30:59 -07:00
Jason Penilla 799dd82b4f Update loom 2022-01-27 14:30:47 -07:00
Jason Penilla 67d4a12546 update actions 2022-01-11 00:25:40 -07:00
Jason Penilla 06543a2c45 dependency updates 2022-01-11 00:22:38 -07:00
Renovate Bot acf9056f5b chore(deps): update dependency gradle to v7.3.3 2021-12-22 15:17:13 -08:00
Jason Penilla ae3fefb54d 2.0.6-SNAPSHOT 2021-12-02 01:50:32 -08:00
Jason Penilla bcfe67fc6f release: Version 2.0.5 2021-12-02 01:39:21 -08:00
Jason Penilla 10775836cb Update gradle wrapper 2021-12-01 21:33:45 -08:00
Jason Penilla 3a07128bd0 Update adventure-platform 2021-12-01 21:10:22 -08:00
Jason Penilla 336d613274 velocity: Use new event api 2021-12-01 02:16:34 -08:00
Jason Penilla a742fd9b62 fabric: Use release adventure-platform 2021-12-01 01:38:51 -08:00
Jason Penilla 577a77951a fabric: Bump java target 2021-12-01 01:34:06 -08:00
Jason Penilla f8456bec5c fabric: Build against 1.18 release 2021-12-01 01:30:50 -08:00
Jason Penilla b086550eae bukkit: Test on 1.18 2021-12-01 01:30:19 -08:00
Jason Penilla f512ca6174 Update loom 2021-11-14 00:27:08 -08:00
Jason Penilla 82b3a35a9e Update gradle wrapper and dependencies 2021-11-09 15:46:08 -08:00
Jason Penilla 5f4e8efa45 Enable build cache 2021-10-13 23:51:52 -07:00
Jason Penilla 22a47511a2 Use minimotd instead of minimotd-sponge7 and minimotd-sponge8 for Sponge platform plugin ids. 2021-10-13 23:51:24 -07:00
Jason Penilla 972ac8fcc5 Update shadow, adventure, junit 2021-10-09 15:01:34 -07:00
Jason Penilla c7f2d5f33b Improve Sponge 8 legacy check 2021-09-26 12:45:23 -07:00
Jason Penilla 2bc4a4caac Update for MiniMessage changes 2021-09-26 12:10:48 -07:00
Jason Penilla 3577d67820 fabric: Require >=1.17 2021-09-26 11:26:45 -07:00
Jason Penilla 9392601232 Simplify Components#ofChildren 2021-09-26 11:04:56 -07:00
Jason Penilla e4071d390c Update dependencies 2021-09-17 21:55:27 -07:00
Jason Penilla c4ca735e8b build: Update Gradle wrapper to 7.2 2021-08-27 09:33:36 -07:00
Jason Penilla 42ccd182b0 build: Update plugin-yml and run-paper 2021-08-27 09:33:13 -07:00
Jason Penilla 99e7954612 Improve handling of malformed config files 2021-08-12 03:38:42 -07:00
Jason Penilla bcf2589eb0 2.0.5-SNAPSHOT 2021-08-10 15:46:53 -07:00
Jason Penilla fe41b21d9b release: Version 2.0.4 2021-08-10 15:40:03 -07:00
Jason Penilla cbbe201710 Minor clean up 2021-08-10 15:14:12 -07:00
Jason Penilla 30d58fc0f9 2.0.4-SNAPSHOT 2021-07-30 14:45:43 -07:00
Jason Penilla 067ec4ea65 Build and test against 1.17.1 2021-07-23 23:51:06 -07:00
Jason Penilla 22f805f4ab build: Update Gradle & Indra 2021-07-23 23:50:36 -07:00
Jason Penilla ed21f45573 fabric: Use quiet-fabric-loom 2021-07-23 23:50:36 -07:00
Jason Penilla 41b1fdffe3 sponge 8: Update for Sponge API changes 2021-07-23 23:50:36 -07:00
Jason Penilla 142f7a34f7 Remove final from virtualHostTestMode field
This was not meant to be final in the first place, however this also was causing the config to silently fail to deserialize.
2021-07-13 22:53:59 -07:00
Jason Penilla ed7fe3591b Update Velocity API dependency to 3.0.0 2021-07-06 02:34:09 -07:00
Jason Penilla fa67cd3538 release: Version 2.0.3 2021-06-24 16:09:19 -07:00
Jason Penilla 615fce3cd6 Update MiniMessage to 4.2.0-SNAPSHOT and Adventure to 4.8.1 2021-06-24 05:01:41 -07:00
Jason Penilla faccf19535 build: Update Run Paper to 1.0.3 2021-06-24 05:00:04 -07:00
Jason Penilla e50953c9fb Update Adventure to 4.8.0 2021-06-11 07:23:27 -07:00
Jason Penilla 5f2df1debd fabric: Update to 1.17 2021-06-08 13:30:56 -07:00
Jason Penilla 0c90ef0886 Fix typo in ConnectionMixin 2021-06-08 02:46:34 -07:00
Jason Penilla 9bfd73e862 Update Velocity API to 1.1.7 2021-06-08 02:46:21 -07:00
Jason Penilla ec74878a1b Appease checkstyle 2021-06-07 15:23:46 -07:00
Jason Penilla 502a775a41 velocity: Register listeners on ProxyInitializeEvent and make PingListener public 2021-06-07 15:22:21 -07:00
Jason Penilla 245596426e build: Update run-paper to 1.0.2 2021-06-06 17:09:54 -07:00
Jason Penilla 996fd0f7d2 bukkit: Specify command permission in plugin.yml 2021-06-04 21:26:56 -07:00
Jason Penilla 4713809a27 build: Tag all subproject versions with git hash when snapshot 2021-06-04 21:26:53 -07:00
Jason Penilla 259a1e784e Minor codestyle fixes 2021-06-04 21:18:16 -07:00
Jason Penilla 793d3d7736 Update README 2021-06-04 21:16:29 -07:00
Jason Penilla 49f86cc4ef build: Improve FileCopyTask up to date checking 2021-06-04 21:14:13 -07:00
Jason Penilla 6d70e82fbe build: Update blossom to 1.3.0 2021-06-04 21:13:11 -07:00
Jason Penilla ffe1f389e2 build: centralize dependency declarations and remove unnecessary explicit task dependencies 2021-06-02 17:28:35 -07:00
Jason Penilla 2c709d3dc7 Rename Bukkit module from 'spigot' to 'bukkit'
Update build scripts to use convention plugins

Clean up command handling

Refactor ping handlers to use a response object
2021-05-29 22:27:27 -07:00
Jason Penilla 5fe30bf8f9 Update checkstyle config 2021-05-29 21:26:59 -07:00
Jason Penilla 196e6d8f00 Fix indent on checkstyle.xml 2021-05-29 21:26:58 -07:00
jmp 1465423927 Fix #62 2021-05-18 23:39:45 -07:00
jmp 4b61e91220 build: Update Gradle to 7.0.2, Indra to 2.0.4, polyglot-version-catalogs to 5.0.0 2021-05-14 18:52:38 -07:00
jmp 09c590c873 build: Update Indra to 2.0.3 and Gradle to 7.0.1 2021-05-11 01:30:24 -07:00
jmp 57f3de39a2 Update Indra and switch from buildSrc to build-logic included build 2021-05-09 19:37:14 -07:00
jmp f472736b32 Update GH Actions workflow 2021-05-09 02:45:26 -07:00
jmp 27fbefdd9c Add virtual host testing mode
Will print virtual host debug info to console on each ping when enabled. Intended to aid in configuration of the virtual host configs feature.

Also bump a couple of dependencies
2021-05-09 02:39:25 -07:00
jmp 454439a10c sponge8: Update for plugin-spi changes 2021-05-06 22:17:04 -07:00
jmp eebe13ae06 Fix MiniMessage shading 2021-04-20 18:59:19 -07:00
jmp 8b36863244 Build script improvements
- Use version catalog for plugin versions (except shadow in buildSrc)
- Don't shade annotations that we don't need at runtime
- Organize relocations
2021-04-20 18:56:19 -07:00
jmp 0b89b1fbf3 Use VERSION_CATALOGS and TYPESAFE_PROJECT_ACCESSORS Gradle feature previews and polyglot-version-catalogs 2021-04-14 19:27:50 -07:00
jmp 34ece94cbb Update GitHub Actions setup-java to v2 2021-04-12 14:38:48 -07:00
Renovate Bot 880757cd09 chore(deps): update dependency org.spongepowered.gradle.plugin:org.spongepowered.gradle.plugin.gradle.plugin to v1.0.3 2021-04-10 21:28:24 -07:00
jmp dbcbb9e48e Update Gradle to 7.0 2021-04-09 17:41:28 -07:00
jmp 9f31ba4406 Bump version to 2.0.3-SNAPSHOT 2021-04-08 23:31:27 -07:00
jmp 9d6d7feae0 Add config option for whether the displayed online player count is allowed to exceed the displayed maximum player count 2021-04-08 01:10:53 -07:00
jmp ed6595ee0d Refactor player count modification 2021-04-07 03:54:25 -07:00
jmp a462f0b96e Minor formatting changes 2021-04-07 01:26:14 -07:00
Mark Joseph KuebelandMark Kuebel af4f0c78f1 Added a minimum player fake player count, and a constant count. (#50)
* Added a minimum player fake player count, and a constant count.

* updates grammars and stuffs

* removes periods in config example

Co-authored-by: Mark Kuebel <mjkuebel@cs.utexas.edu>
2021-04-06 16:43:06 -07:00
jmp 7ef0ada06a Build script changes
Switch from Spigradle to Minecrell's plugin-yml plugin
2021-04-04 15:55:59 -07:00
jmp fedae0ab23 Clean up IconManager 2021-04-04 15:48:35 -07:00
jmp 157bc7b767 velocity: Remove manual component color downsampling as Velocity handles it automatically 2021-03-28 22:29:53 -07:00
jmp 71f653122a bungee: Stupid hacks to workaround Travertine issues with <=1.8 clients 2021-03-27 23:33:14 -07:00
Renovate Bot b44070bdb0 chore(deps): update dependency net.kyori.blossom:net.kyori.blossom.gradle.plugin to v1.2.0 2021-03-23 15:08:26 -07:00
jmp ae5ab6a95b sponge8: Fix logging 2021-03-22 23:21:28 -07:00
Jason 73ee4f393b Update README.md 2021-03-22 05:52:31 -07:00
jmp ff4a1da49c Implement Sponge API 7 platform 2021-03-22 04:40:33 -07:00
jmp 725e5c8d2b sponge: Update for API changes 2021-03-21 14:47:33 -07:00
jmp 5d644c326a Bump version to 2.0.2-SNAPSHOT 2021-03-19 23:15:23 -07:00
jmp 09683f06ce Implement 'hide-player-count' option
Setting this to true will disable the player list hover (same as 'disable-player-list-hover'), but will also cause the player count to appear as '???'.
2021-03-16 15:31:22 -07:00
jmp c92c718ced Add ComponentColorDownsampler 2021-03-13 18:06:24 -08:00
jmp e468600d83 Parse MOTD MiniMessage in a single place
as opposed to in each platform
2021-03-12 15:35:40 -08:00
Renovate Bot 101915290e chore(deps): update dependency org.spongepowered.gradle.plugin:org.spongepowered.gradle.plugin.gradle.plugin to v1.0.2 2021-03-11 15:40:42 -08:00
Jason b0c8c7a11d Update issue templates 2021-03-11 14:57:42 -08:00
Jason 5bac39d149 Create config.yml 2021-03-11 14:56:15 -08:00
Jason 37dd141af4 Update bug-report.md 2021-03-11 14:47:59 -08:00
jmp d348ee6b1d Update loom and Adventure 2021-03-03 01:28:25 -08:00
jmp b1336df817 Work around issue with shadowJar and the sponge runServer task 2021-02-28 13:01:32 -08:00
jmp 8e9c8aac5f Refactor commands, implement commands on sponge-8 2021-02-28 03:38:27 -08:00
98 changed files with 4064 additions and 2206 deletions

No files matched your search

+272 -230
View File
@@ -1,245 +1,287 @@
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<!-- This file is modified from KyoriPowered/adventure's checkstyle.xml -->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="fileExtensions" value="java, properties, xml"/>
<property name="severity" value="error"/>
<property name="charset" value="UTF-8"/>
<property name="fileExtensions" value="java, properties, xml"/>
<property name="severity" value="error"/>
<!-- https://checkstyle.org/config_filefilters.html#BeforeExecutionExclusionFileFilter -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
<!-- https://checkstyle.org/config_filefilters.html#BeforeExecutionExclusionFileFilter -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#FileTabCharacter -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<!-- https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${configDirectory}/suppressions.xml"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressWarningsFilter -->
<module name="SuppressWarningsFilter"/>
<!-- https://checkstyle.org/config_filters.html#SuppressWithPlainTextCommentFilter -->
<module name="SuppressWithPlainTextCommentFilter"/>
<module name="TreeWalker">
<!-- https://checkstyle.org/config_misc.html#ArrayTypeStyle -->
<module name="ArrayTypeStyle"/>
<!-- https://checkstyle.org/config_javadoc.html#AtclauseOrder -->
<module name="AtclauseOrder">
<property name="violateExecutionOnNonTightHtml" value="true"/>
<property name="tagOrder" value="@author, @deprecated, @exception, @param, @return, @serial, @serialData, @serialField, @throws, @see, @since, @sinceMinecraft, @version"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#FileTabCharacter -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
<!-- https://checkstyle.org/config_imports.html#AvoidStarImport -->
<module name="AvoidStarImport"/>
<!-- https://checkstyle.org/config_misc.html#AvoidEscapedUnicodeCharacters -->
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowByTailComment" value="true"/>
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<!-- https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile"/>
<!-- https://checkstyle.org/config_misc.html#CommentsIndentation -->
<module name="CommentsIndentation"/>
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${configDirectory}/suppressions.xml"/>
<!-- https://checkstyle.org/config_imports.html#CustomImportOrder -->
<module name="CustomImportOrder">
<property name="customImportOrderRules" value="THIRD_PARTY_PACKAGE###STATIC"/>
<property name="standardPackageRegExp" value="^$"/>
<property name="sortImportsInGroupAlphabetically" value="true"/>
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressWarningsFilter -->
<module name="SuppressWarningsFilter"/>
<!-- https://checkstyle.org/config_whitespace.html#EmptyForInitializerPad -->
<module name="EmptyForInitializerPad"/>
<!-- https://checkstyle.org/config_filters.html#SuppressWithPlainTextCommentFilter -->
<module name="SuppressWithPlainTextCommentFilter"/>
<module name="TreeWalker">
<!-- https://checkstyle.org/config_misc.html#ArrayTypeStyle -->
<module name="ArrayTypeStyle"/>
<!-- https://checkstyle.org/config_javadoc.html#AtclauseOrder -->
<module name="AtclauseOrder">
<property name="violateExecutionOnNonTightHtml" value="true"/>
<property name="tagOrder" value="@author, @deprecated, @exception, @param, @return, @serial, @serialData, @serialField, @throws, @see, @since, @sinceMinecraft, @version"/>
</module>
<!-- https://checkstyle.org/config_imports.html#AvoidStarImport -->
<module name="AvoidStarImport"/>
<!-- https://checkstyle.org/config_misc.html#AvoidEscapedUnicodeCharacters -->
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowByTailComment" value="true"/>
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<!-- https://checkstyle.org/config_misc.html#CommentsIndentation -->
<module name="CommentsIndentation"/>
<!-- https://checkstyle.org/config_whitespace.html#EmptyForInitializerPad -->
<module name="EmptyForInitializerPad"/>
<!-- https://checkstyle.org/config_whitespace.html#EmptyForIteratorPad -->
<module name="EmptyForIteratorPad"/>
<!-- https://checkstyle.org/config_whitespace.html#EmptyLineSeparator -->
<module name="EmptyLineSeparator">
<property name="allowMultipleEmptyLines" value="false"/>
<property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/>
<property name="allowNoEmptyLineBetweenFields" value="true"/>
<property name="tokens" value="CLASS_DEF, CTOR_DEF, ENUM_DEF, IMPORT, INSTANCE_INIT, INTERFACE_DEF, METHOD_DEF, STATIC_IMPORT, STATIC_INIT, VARIABLE_DEF"/> <!-- remove PACKAGE_DEF, temporarily remove COMPACT_CTOR_DEF, RECORD_DEF -->
</module>
<!-- https://checkstyle.org/config_coding.html#FallThrough -->
<module name="FallThrough">
<property name="checkLastCaseGroup" value="true"/>
</module>
<!-- https://checkstyle.org/config_design.html#FinalClass -->
<module name="FinalClass"/>
<!-- https://checkstyle.org/config_coding.html#FinalLocalVariable -->
<module name="FinalLocalVariable">
<property name="tokens" value="PARAMETER_DEF, VARIABLE_DEF"/> <!-- add PARAMETER_DEF -->
<property name="validateEnhancedForLoopVariable" value="true"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#GenericWhitespace -->
<module name="GenericWhitespace"/>
<!-- https://checkstyle.org/config_design.html#HideUtilityClassConstructor -->
<module name="HideUtilityClassConstructor"/>
<!-- https://checkstyle.org/config_imports.html#IllegalImport -->
<module name="IllegalImport">
<property name="illegalPkgs" value="sun.misc, jdk"/>
</module>
<!-- https://checkstyle.org/config_coding.html#IllegalTokenText -->
<module name="IllegalTokenText">
<property name="format" value="($|[^\\])\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
<property name="tokens" value="CHAR_LITERAL, STRING_LITERAL"/>
</module>
<!-- https://checkstyle.org/config_misc.html#Indentation -->
<module name="Indentation">
<property name="arrayInitIndent" value="2"/>
<property name="basicOffset" value="2"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="2"/>
<property name="lineWrappingIndentation" value="0"/>
<property name="throwsIndent" value="2"/>
</module>
<!-- https://checkstyle.org/config_javadoc.html#InvalidJavadocPosition -->
<module name="InvalidJavadocPosition"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocContentLocation -->
<module name="JavadocContentLocation"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocMissingWhitespaceAfterAsterisk -->
<module name="JavadocMissingWhitespaceAfterAsterisk"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocParagraph -->
<module name="JavadocParagraph"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocMissingWhitespaceAfterAsterisk -->
<module name="JavadocTagContinuationIndentation"/>
<!-- https://checkstyle.org/config_blocks.html#LeftCurly -->
<module name="LeftCurly"/>
<!-- https://checkstyle.org/config_naming.html#MethodName -->
<module name="MethodName">
<property name="format" value="^(?:(?:.{1,3})|(?:[gs]et[^A-Z].*)|(?:(?:[^gsA-Z]..|.[^e].|..[^t]).+))$"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#MethodParamPad -->
<module name="MethodParamPad"/>
<!-- https://checkstyle.org/config_coding.html#MultipleVariableDeclarations -->
<module name="MultipleVariableDeclarations"/>
<!-- https://checkstyle.org/config_coding.html#NoFinalizer -->
<module name="NoFinalizer"/>
<!-- https://checkstyle.org/config_whitespace.html#NoLineWrap -->
<module name="NoLineWrap"/>
<!-- https://checkstyle.org/config_javadoc.html#NonEmptyAtclauseDescription -->
<module name="NonEmptyAtclauseDescription"/>
<!-- https://checkstyle.org/config_whitespace.html#NoWhitespaceAfter -->
<module name="NoWhitespaceAfter">
<property name="allowLineBreaks" value="false"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#NoWhitespaceBefore -->
<module name="NoWhitespaceBefore">
<property name="allowLineBreaks" value="true"/>
<property name="tokens" value="COMMA, DOT, LABELED_STAT, METHOD_REF, POST_DEC, POST_INC, SEMI"/> <!-- remove ELLIPSIS -->
</module>
<!-- https://checkstyle.org/config_coding.html#OneStatementPerLine -->
<module name="OneStatementPerLine"/>
<!-- https://checkstyle.org/config_misc.html#OuterTypeFilename -->
<module name="OuterTypeFilename"/>
<!-- https://checkstyle.org/config_imports.html#RedundantImport -->
<module name="RedundantImport"/>
<!-- https://checkstyle.org/config_modifier.html#RedundantModifier -->
<module name="RedundantModifier">
<property name="tokens" value="ANNOTATION_FIELD_DEF, CLASS_DEF, CTOR_DEF, ENUM_DEF, INTERFACE_DEF, VARIABLE_DEF"/> <!-- remove METHOD_DEF and RESOURCE -->
</module>
<!-- https://checkstyle.org/config_javadoc.html#RequireEmptyLineBeforeBlockTagGroup -->
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
<!-- https://checkstyle.org/config_coding.html#RequireThis -->
<module name="RequireThis">
<property name="validateOnlyOverlapping" value="false"/>
</module>
<!-- https://checkstyle.org/config_blocks.html#RightCurly -->
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens" value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_DEF, INSTANCE_INIT, LITERAL_FOR, LITERAL_WHILE, METHOD_DEF, STATIC_INIT"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="option" value="same"/>
<property name="tokens" value="LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_IF, LITERAL_TRY"/> <!-- add LITERAL_DO -->
</module>
<!-- https://checkstyle.org/config_whitespace.html#SeparatorWrap -->
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapEol"/>
<property name="option" value="eol"/>
<property name="tokens" value="COMMA, SEMI, ELLIPSIS, RBRACK, ARRAY_DECLARATOR, METHOD_REF"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapNl"/>
<property name="option" value="nl"/>
<property name="tokens" value="DOT, AT"/>
</module>
<!-- https://checkstyle.org/config_coding.html#SimplifyBooleanExpression -->
<module name="SimplifyBooleanExpression"/>
<!-- https://checkstyle.org/config_coding.html#SimplifyBooleanReturn -->
<module name="SimplifyBooleanReturn"/>
<!-- https://checkstyle.org/config_whitespace.html#SingleSpaceSeparator -->
<module name="SingleSpaceSeparator">
<property name="validateComments" value="true"/>
</module>
<!-- https://checkstyle.org/config_javadoc.html#SummaryJavadoc -->
<module name="SummaryJavadoc"/>
<!-- https://checkstyle.org/config_annotation.html#SuppressWarningsHolder -->
<module name="SuppressWarningsHolder"/>
<!-- https://checkstyle.org/config_whitespace.html#TypecastParenPad -->
<module name="TypecastParenPad"/>
<!-- https://checkstyle.org/config_coding.html#UnnecessaryParentheses -->
<module name="UnnecessaryParentheses"/>
<!-- https://checkstyle.org/config_imports.html#UnusedImports -->
<module name="UnusedImports"/>
<!-- https://checkstyle.org/config_whitespace.html#WhitespaceAfter -->
<module name="WhitespaceAfter">
<property name="tokens" value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
</module>
<!--
#####################
#### third-party ####
#####################
-->
<!-- https://checkstyle.org/config_whitespace.html#EmptyForIteratorPad -->
<module name="EmptyForIteratorPad"/>
<!-- https://checkstyle.org/config_whitespace.html#EmptyLineSeparator -->
<module name="EmptyLineSeparator">
<property name="allowMultipleEmptyLines" value="false"/>
<property name="allowMultipleEmptyLinesInsideClassMembers" value="false"/>
<property name="allowNoEmptyLineBetweenFields" value="true"/>
<property name="tokens" value="CLASS_DEF, CTOR_DEF, ENUM_DEF, IMPORT, INSTANCE_INIT, INTERFACE_DEF, METHOD_DEF, STATIC_IMPORT, STATIC_INIT, VARIABLE_DEF"/> <!-- remove PACKAGE_DEF, temporarily remove COMPACT_CTOR_DEF, RECORD_DEF -->
</module>
<!-- https://checkstyle.org/config_coding.html#FallThrough -->
<module name="FallThrough">
<property name="checkLastCaseGroup" value="true"/>
</module>
<!-- https://checkstyle.org/config_design.html#FinalClass -->
<module name="FinalClass"/>
<!-- https://checkstyle.org/config_coding.html#FinalLocalVariable -->
<module name="FinalLocalVariable">
<property name="tokens" value="PARAMETER_DEF, VARIABLE_DEF"/> <!-- add PARAMETER_DEF -->
<property name="validateEnhancedForLoopVariable" value="true"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#GenericWhitespace -->
<module name="GenericWhitespace"/>
<!-- https://checkstyle.org/config_design.html#HideUtilityClassConstructor -->
<module name="HideUtilityClassConstructor"/>
<!-- https://checkstyle.org/config_imports.html#IllegalImport -->
<module name="IllegalImport">
<property name="illegalPkgs" value="sun, jdk, com.sun"/>
</module>
<!-- https://checkstyle.org/config_coding.html#IllegalTokenText -->
<module name="IllegalTokenText">
<property name="format" value="($|[^\\])\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message" value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
<property name="tokens" value="CHAR_LITERAL, STRING_LITERAL"/>
</module>
<!-- https://checkstyle.org/config_misc.html#Indentation -->
<module name="Indentation">
<property name="arrayInitIndent" value="2"/>
<property name="basicOffset" value="2"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="2"/>
<property name="lineWrappingIndentation" value="0"/>
<property name="throwsIndent" value="2"/>
</module>
<!-- https://checkstyle.org/config_javadoc.html#InvalidJavadocPosition -->
<module name="InvalidJavadocPosition"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocContentLocation -->
<module name="JavadocContentLocation"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocMissingWhitespaceAfterAsterisk -->
<module name="JavadocMissingWhitespaceAfterAsterisk"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocParagraph -->
<module name="JavadocParagraph"/>
<!-- https://checkstyle.org/config_javadoc.html#JavadocMissingWhitespaceAfterAsterisk -->
<module name="JavadocTagContinuationIndentation"/>
<!-- https://checkstyle.org/config_blocks.html#LeftCurly -->
<module name="LeftCurly"/>
<!-- https://checkstyle.org/config_naming.html#MethodName -->
<module name="MethodName">
<property name="format" value="^(?:(?:.{1,3})|(?:[gs]et[^A-Z].*)|(?:(?:[^gsA-Z]..|.[^e].|..[^t]).+))$"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#MethodParamPad -->
<module name="MethodParamPad"/>
<!-- https://checkstyle.org/config_javadoc.html#MissingJavadocMethod -->
<!-- <module name="MissingJavadocMethod"/> -->
<!-- https://checkstyle.org/config_javadoc.html#MissingJavadocPackage -->
<!-- <module name="MissingJavadocPackage"/> -->
<!-- https://checkstyle.org/config_javadoc.html#MissingJavadocType -->
<!-- <module name="MissingJavadocType"/> -->
<!-- https://checkstyle.org/config_coding.html#MultipleVariableDeclarations -->
<module name="MultipleVariableDeclarations"/>
<!-- https://checkstyle.org/config_coding.html#NoFinalizer -->
<module name="NoFinalizer"/>
<!-- https://checkstyle.org/config_whitespace.html#NoLineWrap -->
<module name="NoLineWrap"/>
<!-- https://checkstyle.org/config_javadoc.html#NonEmptyAtclauseDescription -->
<module name="NonEmptyAtclauseDescription"/>
<!-- https://checkstyle.org/config_whitespace.html#NoWhitespaceAfter -->
<module name="NoWhitespaceAfter">
<property name="allowLineBreaks" value="false"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#NoWhitespaceBefore -->
<module name="NoWhitespaceBefore">
<property name="allowLineBreaks" value="true"/>
<property name="tokens" value="COMMA, DOT, LABELED_STAT, METHOD_REF, POST_DEC, POST_INC, SEMI"/> <!-- remove ELLIPSIS -->
</module>
<!-- https://checkstyle.org/config_coding.html#OneStatementPerLine -->
<module name="OneStatementPerLine"/>
<!-- https://checkstyle.org/config_misc.html#OuterTypeFilename -->
<module name="OuterTypeFilename"/>
<!-- https://checkstyle.org/config_imports.html#RedundantImport -->
<module name="RedundantImport"/>
<!-- https://checkstyle.org/config_modifier.html#RedundantModifier -->
<module name="RedundantModifier">
<property name="tokens" value="ANNOTATION_FIELD_DEF, CLASS_DEF, CTOR_DEF, ENUM_DEF, INTERFACE_DEF, VARIABLE_DEF"/> <!-- remove METHOD_DEF and RESOURCE -->
</module>
<!-- https://checkstyle.org/config_javadoc.html#RequireEmptyLineBeforeBlockTagGroup -->
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
<!-- https://checkstyle.org/config_coding.html#RequireThis -->
<module name="RequireThis">
<property name="validateOnlyOverlapping" value="false"/>
</module>
<!-- https://checkstyle.org/config_blocks.html#RightCurly -->
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens" value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_DEF, INSTANCE_INIT, LITERAL_FOR, LITERAL_WHILE, METHOD_DEF, STATIC_INIT"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="option" value="same"/>
<property name="tokens" value="LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_IF, LITERAL_TRY"/> <!-- add LITERAL_DO -->
</module>
<!-- https://checkstyle.org/config_whitespace.html#SeparatorWrap -->
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapEol"/>
<property name="option" value="eol"/>
<property name="tokens" value="COMMA, SEMI, ELLIPSIS, RBRACK, ARRAY_DECLARATOR, METHOD_REF"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapNl"/>
<property name="option" value="nl"/>
<property name="tokens" value="DOT, AT"/>
</module>
<!-- https://checkstyle.org/config_coding.html#SimplifyBooleanExpression -->
<module name="SimplifyBooleanExpression"/>
<!-- https://checkstyle.org/config_coding.html#SimplifyBooleanReturn -->
<module name="SimplifyBooleanReturn"/>
<!-- https://checkstyle.org/config_whitespace.html#SingleSpaceSeparator -->
<module name="SingleSpaceSeparator">
<property name="validateComments" value="true"/>
</module>
<!-- https://checkstyle.org/config_javadoc.html#SummaryJavadoc -->
<module name="SummaryJavadoc"/>
<!-- https://checkstyle.org/config_annotation.html#SuppressWarningsHolder -->
<module name="SuppressWarningsHolder"/>
<!-- https://checkstyle.org/config_whitespace.html#TypecastParenPad -->
<module name="TypecastParenPad"/>
<!-- https://checkstyle.org/config_coding.html#UnnecessaryParentheses -->
<module name="UnnecessaryParentheses"/>
<!-- https://checkstyle.org/config_imports.html#UnusedImports -->
<module name="UnusedImports"/>
<!-- https://checkstyle.org/config_whitespace.html#WhitespaceAfter -->
<module name="WhitespaceAfter">
<property name="tokens" value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, DO_WHILE"/>
</module>
<!-- https://checkstyle.org/config_whitespace.html#WhitespaceAround -->
<module name="WhitespaceAround">
<property name="ignoreEnhancedForColon" value="false"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLambdas" value="true"/>
<property name="tokens" value="ASSIGN, COLON, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LCURLY, LE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
</module>
<!--
#####################
#### third-party ####
#####################
-->
<!-- https://checkstyle.org/config_javadoc.html#WriteTag -->
<!-- https://gitlab.com/stellardrift/stylecheck/-/blob/dev/src/main/java/ca/stellardrift/stylecheck/FilteringWriteTag.java -->
<!--
<module name="FilteringWriteTag">
<property name="tag" value="@since\s"/>
<property name="tagFormat" value="\d\.\d+\.\d+"/>
<property name="tagSeverity" value="ignore"/>
<property name="minimumScope" value="public"/>
<property name="tokens" value="INTERFACE_DEF, CLASS_DEF, ENUM_DEF, ANNOTATION_DEF, RECORD_DEF, METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, RECORD_DEF, COMPACT_CTOR_DEF"/>
</module>
-->
<!-- https://gitlab.com/stellardrift/stylecheck/-/blob/dev/src/main/java/ca/stellardrift/stylecheck/StatementNoWhitespaceAfter.java -->
<!--
<module name="StatementNoWhitespaceAfter">
<property name="tokens" value="LITERAL_CATCH, LITERAL_FOR, LITERAL_IF, LITERAL_TRY, LITERAL_WHILE"/>
</module>
-->
</module>
</module>
+2 -2
View File
@@ -2,7 +2,7 @@
name: Bug Report
about: Report a bug
title: "[Bug Report] This happens when another thing should have"
labels: bug
labels: bug, unconfirmed
assignees: ''
---
@@ -24,7 +24,7 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Server Software and Version:**
- Vendor: [e.g. Paper, Spigot, Tuinity, Velocity, BungeeCord, Waterfall]
- Vendor: [e.g. Paper, Tuinity, Velocity, BungeeCord, Waterfall, Fabric, Sponge]
- Minecraft Version: [e.g. 1.15.2, 1.16.3]
- Output of /version command: [e.g. This server is running Paper version git-Paper-53 (MC: 1.16.1) (Implementing API version 1.16.1-R0.1-SNAPSHOT)]
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+20
View File
@@ -0,0 +1,20 @@
name: "build"
on:
push:
branches: [ "**" ]
tags-ignore: [ "**" ]
pull_request:
release:
types: [ released ]
jobs:
call-build:
uses: "jpenilla/actions/.github/workflows/shared-ci.yml@master"
secrets: inherit
with:
artifacts-path: 'build/libs/*.jar'
modrinth-publish: true
hangar-publish: true
loom: true
jdk-version: 21
-26
View File
@@ -1,26 +0,0 @@
name: MiniMOTD
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Gradle
run: ./gradlew clean build
- run: mkdir staging && cp build/libs/*.jar staging
- uses: actions/upload-artifact@v2
with:
name: Package
path: staging
+1 -1
View File
@@ -116,4 +116,4 @@ run/
.gradle
# Ignore Gradle build output directory
build
build/
+2
View File
@@ -1,5 +1,6 @@
![MiniMOTD logo](https://i.imgur.com/CXWwjOJ.png)
[![build](https://img.shields.io/github/checks-status/jpenilla/MiniMOTD/master?label=build)](https://github.com/jpenilla/MiniMOTD/actions) [![latest release](https://img.shields.io/github/v/release/jpenilla/MiniMOTD)](https://github.com/jpenilla/MiniMOTD/releases)
### MiniMOTD is a basic server list MOTD plugin/mod for Minecraft servers and proxies
@@ -12,6 +13,7 @@
- [Paper](https://papermc.io/) / Spigot
- MiniMOTD is compatible with Spigot, however many features will not work. It is recommended to use Paper for full compatibility.
- [Sponge API 8](https://www.spongepowered.org/)
- [Sponge API 7](https://www.spongepowered.org/)
- [Fabric](https://fabricmc.net/) (requires [Fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api))
#### Proxy Platforms
+1 -114
View File
@@ -1,116 +1,3 @@
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import net.kyori.indra.IndraCheckstylePlugin
import net.kyori.indra.IndraLicenseHeaderPlugin
import net.kyori.indra.IndraPlugin
import net.kyori.indra.sonatypeSnapshots
import java.io.ByteArrayOutputStream
plugins {
`java-library`
id("net.kyori.indra") version "1.3.1"
id("com.github.johnrengelman.shadow") version "6.1.0"
}
allprojects {
group = "xyz.jpenilla"
version = "2.0.1+${latestCommitHash()}-SNAPSHOT"
description = "Use MiniMessage text formatting in your servers MOTD."
}
ext["url"] = "https://github.com/jpenilla/MiniMOTD/"
subprojects {
apply<JavaLibraryPlugin>()
apply<ShadowPlugin>()
apply<IndraPlugin>()
apply<IndraCheckstylePlugin>()
apply<IndraLicenseHeaderPlugin>()
repositories {
//mavenLocal()
mavenCentral()
sonatypeSnapshots()
jcenter()
maven("https://papermc.io/repo/repository/maven-public/")
maven("https://nexus.velocitypowered.com/repository/maven-public/")
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://oss.sonatype.org/content/groups/public/")
maven("https://repo.spongepowered.org/repository/maven-public/")
maven("https://repo.jpenilla.xyz/snapshots/")
maven("https://jitpack.io")
maven("https://repo.codemc.org/repository/maven-public")
}
indra {
javaVersions.target.set(8)
github("jpenilla", "MiniMOTD") {
issues = true
}
mitLicense()
}
tasks {
shadowJar {
minimize()
if (!project.name.contains("fabric")) {
archiveClassifier.set("")
}
}
withType<JavaCompile> {
options.compilerArgs.add("-Xlint:-processing")
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
allprojects {
tasks.withType<Jar> {
onlyIf {
val classifier = archiveClassifier.get()
classifier != "javadoc"
&& project.name != rootProject.name
}
}
tasks.withType<Javadoc> {
onlyIf { false }
}
}
configurations.archives {
artifacts.removeAll { true }
}
tasks {
val aggregate = create("aggregateJars") {
val artifacts = arrayListOf<File>()
dependsOn(project(":minimotd-universal").tasks.getByName("build"))
arrayOf("spigot", "bungeecord", "velocity", "sponge8").forEach {
val subProject = project(":minimotd-$it")
val shadow = subProject.tasks.getByName("shadowJar")
artifacts.add(shadow.outputs.files.singleFile)
}
doLast {
artifacts.add(project(":minimotd-fabric").tasks.getByName("remapJar").outputs.files.singleFile)
artifacts.add(project(":minimotd-universal").project.tasks.getByName("universal").outputs.files.singleFile)
val libs = rootProject.buildDir.resolve("libs")
libs.listFiles()?.forEach { it.delete() }
artifacts.forEach { it.copyTo(libs.resolve(it.name)) }
}
}
build {
dependsOn(aggregate)
}
}
fun latestCommitHash(): String {
val byteOut = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "--short", "HEAD")
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8.name()).trim()
id("minimotd.build-logic")
}
-31
View File
@@ -1,31 +0,0 @@
plugins {
id("kr.entree.spigradle.bungee") version "2.2.3"
}
dependencies {
implementation(project(":minimotd-common"))
implementation("org.slf4j", "slf4j-jdk14","1.7.30")
implementation("net.kyori", "adventure-platform-bungeecord", "4.0.0-SNAPSHOT")
implementation("org.bstats", "bstats-bungeecord", "2.2.0")
compileOnly("io.github.waterfallmc", "waterfall-api", "1.16-R0.4-SNAPSHOT")
}
tasks {
shadowJar {
relocate("org.slf4j", "xyz.jpenilla.minimotd.lib.slf4j")
relocate("io.leangen.geantyref", "xyz.jpenilla.minimotd.lib.io.leangen.geantyref")
relocate("org.spongepowered.configurate", "xyz.jpenilla.minimotd.lib.spongepowered.configurate")
relocate("com.typesafe.config", "xyz.jpenilla.minimotd.lib.typesafe.config")
relocate("net.kyori", "xyz.jpenilla.minimotd.lib.kyori")
relocate("org.checkerframework", "xyz.jpenilla.minimotd.lib.checkerframework")
relocate("org.bstats", "xyz.jpenilla.minimotd.lib.bstats")
}
build {
dependsOn(shadowJar)
}
}
bungee {
name = rootProject.name
author = "jmp"
}
@@ -1,115 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bungee;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.ArrayList;
import java.util.List;
public class BungeeCommand extends Command {
private final MiniMOTDPlugin plugin;
private final MiniMessage miniMessage;
public BungeeCommand(final @NonNull MiniMOTDPlugin plugin) {
super("minimotd");
this.plugin = plugin;
this.miniMessage = MiniMessage.get();
}
@Override
public void execute(final @NonNull CommandSender sender, final @NonNull String @NonNull [] args) {
if (!sender.hasPermission("minimotd.admin")) {
this.onNoPermission(sender, args);
return;
}
if (args.length == 0) {
this.onInvalidUse(sender, args);
return;
}
switch (args[0]) {
case "about":
this.onAbout(sender, args);
return;
case "help":
this.onHelp(sender, args);
return;
case "reload":
this.onReload(sender, args);
return;
}
this.onInvalidUse(sender, args);
}
private void onHelp(final @NonNull CommandSender sender, final @NonNull String[] args) {
final List<String> messages = new ArrayList<>();
messages.add("<gradient:blue:green>MiniMOTD Commands:");
messages.add(" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd about'><click:run_command:/minimotd about><yellow>/minimotd about");
messages.add(" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd reload'><click:run_command:/minimotd reload><yellow>/minimotd reload");
messages.add(" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd help'><click:run_command:/minimotd help><yellow>/minimotd help");
this.send(sender, messages);
}
private void onReload(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.plugin.miniMOTD().configManager().loadConfigs();
this.plugin.miniMOTD().configManager().loadExtraConfigs();
this.plugin.miniMOTD().iconManager().loadIcons();
this.send(sender, "<green>Done reloading.");
}
private void onAbout(final @NonNull CommandSender sender, final @NonNull String[] args) {
final ArrayList<String> messages = new ArrayList<>();
final String header = "<gradient:white:black>=============</gradient><gradient:black:white>=============";
messages.add(header);
messages.add("<hover:show_text:'<rainbow>click me!'><click:open_url:https://github.com/jmanpenilla/MiniMOTD/>" + this.plugin.getDescription().getName() + " <gradient:red:yellow>" + this.plugin.getDescription().getVersion());
messages.add("<yellow>By</yellow><gray>:</gray> <gradient:blue:green>jmp");
messages.add(header);
this.send(sender, messages);
}
private void onInvalidUse(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.send(sender, "<hover:show_text:'<green>Click for <yellow>/minimotd help'><click:run_command:/minimotd help><italic><gradient:red:gold>Invalid usage.</gradient> <blue>Try <yellow>/minimotd help</yellow> or click here");
}
private void onNoPermission(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.send(sender, "<gradient:red:yellow>No permission.");
}
private void send(final @NonNull CommandSender sender, final @NonNull String message) {
this.plugin.audiences().sender(sender).sendMessage(Identity.nil(), this.miniMessage.parse(message));
}
private void send(final @NonNull CommandSender sender, final @NonNull List<String> messages) {
for (final String message : messages) {
this.send(sender, message);
}
}
}
@@ -1,95 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bungee;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
import java.util.Optional;
public class PingListener implements Listener {
private final MiniMessage miniMessage = MiniMessage.get();
private final MiniMOTD<Favicon> miniMOTD;
public PingListener(final @NonNull MiniMOTD<Favicon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPing(final @NonNull ProxyPingEvent e) {
final ServerPing response = e.getResponse();
if (response != null) {
final String configString = this.miniMOTD.configManager().pluginSettings().configStringForHost(
Optional.ofNullable(e.getConnection().getVirtualHost())
.map(inetSocketAddress -> String.format("%s:%s", inetSocketAddress.getHostName(), inetSocketAddress.getPort()))
.orElse("default")
).orElse("default");
final MiniMOTDConfig cfg = this.miniMOTD.configManager().resolveConfig(configString);
final ServerPing.Players players = response.getPlayers();
final int onlinePlayers = this.miniMOTD.calculateOnlinePlayers(cfg, players.getOnline());
players.setOnline(onlinePlayers);
final int maxPlayers = cfg.adjustedMaxPlayers(onlinePlayers, players.getMax());
players.setMax(maxPlayers);
if (cfg.disablePlayerListHover()) {
players.setSample(new ServerPing.PlayerInfo[]{});
}
final MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
final String motdString = pair.motd();
if (motdString != null) {
Component motdComponent = this.miniMessage.parse(motdString);
if (e.getConnection().getVersion() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
motdComponent = GsonComponentSerializer.colorDownsamplingGson().deserialize(GsonComponentSerializer.colorDownsamplingGson().serialize(motdComponent));
}
response.setDescriptionComponent(new TextComponent(BungeeComponentSerializer.get().serialize(motdComponent)));
}
final Favicon favicon = pair.icon();
if (favicon != null) {
response.setFavicon(favicon);
}
response.setPlayers(players);
e.setResponse(response);
}
}
}
+36 -7
View File
@@ -1,8 +1,37 @@
dependencies {
api("org.slf4j", "slf4j-api", "1.7.30")
api("org.spongepowered", "configurate-hocon", "4.1.0-SNAPSHOT")
api("net.kyori", "adventure-api", "4.3.0")
api("net.kyori", "adventure-text-minimessage", "4.1.0-SNAPSHOT")
compileOnlyApi("com.google.code.gson", "gson", "2.8.0")
compileOnlyApi("com.google.guava", "guava", "21.0")
plugins {
alias(libs.plugins.blossom)
id("minimotd.base-conventions")
}
tasks.jar {
from(rootProject.file("license.txt")) {
rename { "license_${rootProject.name.lowercase()}.txt" }
}
}
dependencies {
api(libs.configurateHocon)
api(platform(libs.adventureBom))
api(libs.adventureApi)
api(libs.adventureTextSerializerPlain)
api(libs.adventureTextSerializerGson) {
exclude("com.google.code.gson", "gson")
}
api(libs.minimessage)
compileOnlyApi(libs.slf4jApi)
compileOnlyApi(libs.checkerQual)
compileOnlyApi(libs.gson)
testImplementation(libs.gson)
}
sourceSets.main {
blossom {
javaSources {
property("PLUGIN_NAME", rootProject.name)
property("PLUGIN_VERSION", project.version.toString())
property("PLUGIN_WEBSITE", Constants.GITHUB_URL)
property("GITHUB_USER", Constants.GITHUB_USER)
property("GITHUB_REPO", Constants.GITHUB_REPO)
}
}
}
@@ -0,0 +1,48 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
public final class Constants {
private Constants() {
}
public static final class PluginMetadata {
private PluginMetadata() {
}
public static final String NAME = "{{ PLUGIN_NAME }}";
public static final String VERSION = "{{ PLUGIN_VERSION }}";
public static final String WEBSITE = "{{ PLUGIN_WEBSITE }}";
public static final String GITHUB_USER = "{{ GITHUB_USER }}";
public static final String GITHUB_REPO = "{{ GITHUB_REPO }}";
public static final String GITHUB_REPO_URL = "https://github.com/" + GITHUB_USER + "/" + GITHUB_REPO;
}
public static final int MINECRAFT_1_16_PROTOCOL_VERSION = 735;
public static final Component COMMAND_PREFIX = MiniMessage.miniMessage().deserialize("<white>[</white><gradient:#0047AB:#ADD8E6>" + PluginMetadata.NAME + "</gradient><white>]</white>");
}
@@ -0,0 +1,130 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common;
import java.util.stream.Stream;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import static net.kyori.adventure.text.Component.space;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.LinearComponents.linear;
import static net.kyori.adventure.text.event.ClickEvent.openUrl;
import static net.kyori.adventure.text.event.ClickEvent.runCommand;
import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
import static net.kyori.adventure.text.format.NamedTextColor.WHITE;
import static net.kyori.adventure.text.format.TextColor.color;
import static net.kyori.adventure.text.format.TextDecoration.STRIKETHROUGH;
@DefaultQualifier(NonNull.class)
public final class CommandHandler {
private final MiniMOTD<?> miniMOTD;
public CommandHandler(final MiniMOTD<?> miniMOTD) {
this.miniMOTD = miniMOTD;
}
public void about(final Audience audience) {
final Component header = miniMessage("<gradient:white:#007FFF:white> ").decorate(STRIKETHROUGH);
Stream.of(
header,
text()
.hoverEvent(miniMessage("<rainbow>click me!"))
.clickEvent(openUrl(Constants.PluginMetadata.WEBSITE))
.content(Constants.PluginMetadata.NAME)
.color(WHITE)
.append(space())
.append(miniMessage("<gradient:#0047AB:#007FFF>" + Constants.PluginMetadata.VERSION))
.build(),
text()
.content("By ")
.color(GRAY)
.append(text("jmp", WHITE))
.build(),
header
).forEach(audience::sendMessage);
}
public void reload(final Audience audience) {
try {
this.miniMOTD.reload();
} catch (final Exception ex) {
this.miniMOTD.logger().warn("Failed to reload MiniMOTD. Ensure there are no errors in your config files.", ex);
audience.sendMessage(Component.textOfChildren(
Constants.COMMAND_PREFIX,
space(),
text("Failed to reload MiniMOTD. Ensure there are no errors in your config files. See console for more details.", RED)
));
return;
}
audience.sendMessage(Component.textOfChildren(
Constants.COMMAND_PREFIX,
space(),
text("Done reloading configuration.", GREEN)
));
}
public void help(final Audience audience) {
Stream.of(
linear(Constants.COMMAND_PREFIX, space(), text(Constants.PluginMetadata.NAME + " command help", WHITE)),
commandInfo("minimotd about", "Show information about MiniMOTD"),
commandInfo("minimotd reload", "Reload MiniMOTD configuration files"),
commandInfo("minimotd help", "Show this help menu")
).forEach(audience::sendMessage);
}
private static Component commandInfo(final String command, final String description) {
return text()
.content(" - ")
.color(GRAY)
.append(text('/', WHITE))
.append(text(command, color(0x007FFF)))
.append(text(':'))
.append(space())
.append(text(description, WHITE))
.hoverEvent(text()
.content("Click to execute '")
.color(GRAY)
.append(text("/" + command, WHITE))
.append(text("'"))
.build())
.clickEvent(runCommand("/" + command))
.build();
}
private static Component miniMessage(final String message) {
return MiniMessage.miniMessage().deserialize(message);
}
@FunctionalInterface
public interface Executor {
void execute(Audience audience);
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,49 +23,58 @@
*/
package xyz.jpenilla.minimotd.common;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Stream;
import javax.imageio.ImageIO;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
public final class IconManager<I> {
private final Map<String, I> icons = new HashMap<>();
private final Map<String, I> icons = new ConcurrentHashMap<>();
private final MiniMOTD<I> miniMOTD;
private final File iconsDirectory;
private final Path iconsDirectory;
public IconManager(final @NonNull MiniMOTD<I> miniMOTD) {
this.miniMOTD = miniMOTD;
this.iconsDirectory = miniMOTD.dataDirectory().resolve("icons").toFile();
this.iconsDirectory = miniMOTD.dataDirectory().resolve("icons");
this.loadIcons();
}
public void loadIcons() {
if (!this.iconsDirectory.exists()) {
this.iconsDirectory.mkdir();
}
this.icons.clear();
final File[] icons = this.iconsDirectory.listFiles(i -> i.getName().endsWith(".png"));
if (icons != null) {
for (final File icon : icons) {
try {
final BufferedImage bufferedImage = ImageIO.read(icon);
if (bufferedImage.getHeight() == 64 && bufferedImage.getWidth() == 64) {
final I newIcon = this.miniMOTD.platform().loadIcon(bufferedImage);
this.icons.put(icon.getName().split("\\.")[0], newIcon);
} else {
this.miniMOTD.logger().warn("Could not load " + icon.getName() + ": image must be 64x64px");
}
} catch (final Exception e) {
this.miniMOTD.logger().warn("Could not load " + icon.getName() + ": invalid image file", e);
}
try {
if (!Files.exists(this.iconsDirectory)) {
Files.createDirectories(this.iconsDirectory);
}
try (final Stream<Path> stream = Files.list(this.iconsDirectory)) {
stream.filter(Files::isRegularFile)
.filter(file -> file.getFileName().toString().endsWith(".png"))
.forEach(this::loadIcon);
}
} catch (final IOException ex) {
throw new RuntimeException("Exception loading server icons", ex);
}
}
private void loadIcon(final @NonNull Path iconFile) {
try (final InputStream inputStream = Files.newInputStream(iconFile)) {
final BufferedImage bufferedImage = ImageIO.read(inputStream);
if (bufferedImage.getHeight() == 64 && bufferedImage.getWidth() == 64) {
final I newIcon = this.miniMOTD.platform().loadIcon(bufferedImage);
this.icons.put(iconFile.getFileName().toString().split("\\.")[0], newIcon);
} else {
this.miniMOTD.logger().warn("Could not load {}: image must be 64x64px", iconFile.getFileName());
}
} catch (final Exception ex) {
this.miniMOTD.logger().warn("Could not load {}: invalid image file", iconFile.getFileName(), ex);
}
}
@@ -85,5 +94,4 @@ public final class IconManager<I> {
return this.icons.get(iconString);
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,89 +23,105 @@
*/
package xyz.jpenilla.minimotd.common;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import xyz.jpenilla.minimotd.common.config.ConfigManager;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
import java.nio.file.Path;
import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.slf4j.Logger;
import xyz.jpenilla.minimotd.common.config.ConfigManager;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
import static net.kyori.adventure.text.Component.newline;
@DefaultQualifier(NonNull.class)
public final class MiniMOTD<I> {
private final ConfigManager configManager;
private final IconManager<I> iconManager;
private final MiniMOTDPlatform<I> platform;
public MiniMOTD(final @NonNull MiniMOTDPlatform<I> platform) {
public MiniMOTD(final MiniMOTDPlatform<I> platform) {
this.platform = platform;
this.iconManager = new IconManager<>(this);
this.configManager = new ConfigManager(this);
this.configManager.loadConfigs();
try {
this.iconManager = new IconManager<>(this);
this.configManager = new ConfigManager(this);
this.configManager.loadConfigs();
} catch (final Exception ex) {
throw new IllegalStateException("Failed to load MiniMOTD. Ensure your config files are without errors.", ex);
}
}
public @NonNull MiniMOTDPlatform<I> platform() {
public MiniMOTDPlatform<I> platform() {
return this.platform;
}
public final @NonNull Path dataDirectory() {
public Path dataDirectory() {
return this.platform.dataDirectory();
}
public final @NonNull IconManager<I> iconManager() {
public IconManager<I> iconManager() {
return this.iconManager;
}
public final @NonNull Logger logger() {
public Logger logger() {
return this.platform.logger();
}
public final @NonNull ConfigManager configManager() {
public ConfigManager configManager() {
return this.configManager;
}
// todo: move deserialization from MM -> Component, color down-sampling here, take protocolVersion int as param
public final @NonNull MOTDIconPair<I> createMOTD(final @NonNull MiniMOTDConfig config, final int onlinePlayers, final int maxPlayers) {
I icon = null;
String motd = null;
String iconString = null;
public PingResponse<I> createMOTD(final MOTDConfig config, final int onlinePlayers, final int maxPlayers) {
final PingResponse.PlayerCount count = config.modifyPlayerCount(onlinePlayers, maxPlayers);
final PingResponse.Builder<I> response = PingResponse.<I>builder()
.playerCount(count)
.disablePlayerListHover(config.disablePlayerListHover())
.hidePlayerCount(config.hidePlayerCount());
@Nullable String iconString = null;
if (config.motdEnabled()) {
if (config.motds().isEmpty()) {
throw new IllegalStateException("MOTD is enabled, but there are no MOTDs in the config file?");
}
final int index = config.motds().size() == 1 ? 0 : ThreadLocalRandom.current().nextInt(config.motds().size());
final MiniMOTDConfig.MOTD m = config.motds().get(index);
motd = String.format("%s<reset>\n%s", m.line1(), m.line2())
.replace("{onlinePlayers}", String.valueOf(onlinePlayers))
.replace("{maxPlayers}", String.valueOf(maxPlayers))
.replace("{br}", "\n");
iconString = m.icon();
final MOTDConfig.MOTD motdConfig = config.motds().get(index);
final Component motd = Component.textOfChildren(
parse(motdConfig.line1(), count),
newline(),
parse(motdConfig.line2(), count)
);
response.motd(motd);
iconString = motdConfig.icon();
}
if (config.iconEnabled()) {
icon = this.iconManager().icon(iconString);
response.icon(this.iconManager().icon(iconString));
}
return new MOTDIconPair<>(icon, motd);
return response.build();
}
public final int calculateOnlinePlayers(final @NonNull MiniMOTDConfig config, final int realOnlinePlayers) {
if (config.fakePlayersEnabled()) {
try {
final String fakePlayersConfigString = config.fakePlayers();
if (fakePlayersConfigString.contains(":")) {
final String[] fakePlayers = fakePlayersConfigString.split(":");
final int start = Integer.parseInt(fakePlayers[0]);
final int end = Integer.parseInt(fakePlayers[1]);
private static Component parse(final String input, final PingResponse.PlayerCount count) {
final String online = Integer.toString(count.onlinePlayers());
final String max = Integer.toString(count.maxPlayers());
return MiniMessage.miniMessage().deserialize(
replacePlayerCount(input, online, max),
TagResolver.resolver(Placeholder.unparsed("online_players", online), Placeholder.unparsed("max_players", max))
);
}
return realOnlinePlayers + ThreadLocalRandom.current().nextInt(start, end);
} else if (fakePlayersConfigString.contains("%")) {
final double factor = 1 + (Double.parseDouble(fakePlayersConfigString.replace("%", "")) / 100);
private static String replacePlayerCount(final String input, final String online, final String max) {
return input.replace("{onlinePlayers}", online).replace("{maxPlayers}", max);
}
return (int) Math.ceil(factor * realOnlinePlayers);
} else {
final int addedPlayers = Integer.parseInt(fakePlayersConfigString);
return realOnlinePlayers + addedPlayers;
}
} catch (final NumberFormatException ex) {
this.logger().warn("fakePlayers config incorrect");
}
}
return realOnlinePlayers;
public synchronized void reload() {
this.iconManager.loadIcons();
this.configManager.loadConfigs();
this.platform.onReload();
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,11 +23,10 @@
*/
package xyz.jpenilla.minimotd.common;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
public interface MiniMOTDPlatform<I> {
@NonNull Path dataDirectory();
@@ -35,4 +34,7 @@ public interface MiniMOTDPlatform<I> {
@NonNull Logger logger();
@NonNull I loadIcon(@NonNull BufferedImage image) throws Exception;
default void onReload() {
}
}
@@ -0,0 +1,222 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import static java.util.Objects.requireNonNull;
@DefaultQualifier(NonNull.class)
public final class PingResponse<I> {
private final @Nullable I icon;
private final @Nullable Component motd;
private final PlayerCount playerCount;
private final boolean hidePlayerCount;
private final boolean disablePlayerListHover;
private PingResponse(
final @Nullable I icon,
final @Nullable Component motd,
final PlayerCount playerCount,
final boolean hidePlayerCount,
final boolean disablePlayerListHover
) {
this.icon = icon;
this.motd = motd;
this.playerCount = playerCount;
this.hidePlayerCount = hidePlayerCount;
this.disablePlayerListHover = disablePlayerListHover;
}
public @Nullable I icon() {
return this.icon;
}
/**
* If {@link #icon()} is non-null, the provided {@link Consumer} will be called using
* its return value. If {@link #icon()} is {@code null}, do nothing.
*
* @param iconConsumer icon consumer
*/
public void icon(final Consumer<I> iconConsumer) {
if (this.icon != null) {
iconConsumer.accept(this.icon);
}
}
public @Nullable Component motd() {
return this.motd;
}
/**
* If {@link #motd()} is non-null, the provided {@link Consumer} will be called using
* its return value. If {@link #motd()} is {@code null}, do nothing.
*
* @param motdConsumer motd consumer
*/
public void motd(final Consumer<Component> motdConsumer) {
if (this.motd != null) {
motdConsumer.accept(this.motd);
}
}
public PlayerCount playerCount() {
return this.playerCount;
}
public boolean hidePlayerCount() {
return this.hidePlayerCount;
}
public boolean disablePlayerListHover() {
return this.disablePlayerListHover;
}
public Builder<I> toBuilder() {
return new BuilderImpl<>(
this.motd,
this.icon,
this.playerCount,
this.hidePlayerCount,
this.disablePlayerListHover
);
}
public static <I> Builder<I> builder() {
return new BuilderImpl<>();
}
public static final class PlayerCount {
private final int onlinePlayers;
private final int maxPlayers;
private PlayerCount(final int onlinePlayers, final int maxPlayers) {
this.onlinePlayers = onlinePlayers;
this.maxPlayers = maxPlayers;
}
public int onlinePlayers() {
return this.onlinePlayers;
}
public int maxPlayers() {
return this.maxPlayers;
}
public void applyCount(final IntConsumer onlinePlayersSetter, final IntConsumer maxPlayersSetter) {
onlinePlayersSetter.accept(this.onlinePlayers);
maxPlayersSetter.accept(this.maxPlayers);
}
public static PlayerCount playerCount(final int onlinePlayers, final int maxPlayers) {
return new PlayerCount(onlinePlayers, maxPlayers);
}
}
public interface Builder<I> {
Builder<I> motd(@Nullable Component motd);
Builder<I> icon(@Nullable I icon);
Builder<I> playerCount(PlayerCount playerCount);
Builder<I> hidePlayerCount(boolean hidePlayerCount);
Builder<I> disablePlayerListHover(boolean disablePlayerListHover);
PingResponse<I> build();
}
private static final class BuilderImpl<I> implements Builder<I> {
private @Nullable Component motd;
private @Nullable I icon;
private @Nullable PlayerCount playerCount;
private boolean hidePlayerCount = false;
private boolean disablePlayerListHover = false;
private BuilderImpl() {
}
private BuilderImpl(
final @Nullable Component motd,
final @Nullable I icon,
final @Nullable PlayerCount playerCount,
final boolean hidePlayerCount,
final boolean disablePlayerListHover
) {
this.motd = motd;
this.icon = icon;
this.playerCount = playerCount;
this.hidePlayerCount = hidePlayerCount;
this.disablePlayerListHover = disablePlayerListHover;
}
@Override
public Builder<I> motd(final @Nullable Component motd) {
this.motd = motd;
return this;
}
@Override
public Builder<I> icon(final @Nullable I icon) {
this.icon = icon;
return this;
}
@Override
public Builder<I> playerCount(final PlayerCount playerCount) {
this.playerCount = playerCount;
return this;
}
@Override
public Builder<I> hidePlayerCount(final boolean hidePlayerCount) {
this.hidePlayerCount = hidePlayerCount;
return this;
}
@Override
public Builder<I> disablePlayerListHover(final boolean disablePlayerListHover) {
this.disablePlayerListHover = disablePlayerListHover;
return this;
}
@Override
public PingResponse<I> build() {
requireNonNull(this.playerCount, "playerCount");
return new PingResponse<>(
this.icon,
this.motd,
this.playerCount,
this.hidePlayerCount,
this.disablePlayerListHover
);
}
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,14 @@
*/
package xyz.jpenilla.minimotd.common.config;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationOptions;
@@ -32,27 +39,31 @@ import org.spongepowered.configurate.objectmapping.ObjectMapper;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializerCollection;
import java.nio.file.Path;
import java.util.function.UnaryOperator;
@DefaultQualifier(NonNull.class)
public final class ConfigLoader<C> {
private static final TypeSerializerCollection SERIALIZERS;
static {
SERIALIZERS = TypeSerializerCollection.defaults();
SERIALIZERS = TypeSerializerCollection.builder()
.register(PlayerCountModifier.serializer())
.build();
}
private final HoconConfigurationLoader loader;
private final ObjectMapper<C> mapper;
private final List<Consumer<C>> postProcess;
public ConfigLoader(
final @NonNull Class<C> configClass,
final @NonNull Path configPath,
final @NonNull ConfigurationOptions options
final Class<C> configClass,
final Path configPath,
final UnaryOperator<ConfigurationOptions> optionsModifier
) {
this.postProcess = this.findPostProcessors(configClass);
this.loader = HoconConfigurationLoader.builder()
.path(configPath)
.defaultOptions(options.serializers(SERIALIZERS))
.defaultOptions(options ->
optionsModifier.apply(options)
.serializers(builder -> builder.registerAll(SERIALIZERS)))
.build();
try {
this.mapper = ObjectMapper.factory().get(configClass);
@@ -64,24 +75,38 @@ public final class ConfigLoader<C> {
}
}
public ConfigLoader(
final @NonNull Class<C> configClass,
final @NonNull Path configPath,
final @NonNull UnaryOperator<ConfigurationOptions> options
) {
this(configClass, configPath, options.apply(ConfigurationOptions.defaults()));
private List<Consumer<C>> findPostProcessors(final Class<C> configClass) {
final List<Consumer<C>> ret = new ArrayList<>();
for (final Method method : configClass.getDeclaredMethods()) {
if (method.getAnnotation(PostProcessor.class) == null) {
continue;
}
ret.add(config -> {
try {
method.setAccessible(true);
method.invoke(config);
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException("Failed to invoke post processor method", ex);
}
});
}
return ret;
}
public ConfigLoader(
final @NonNull Class<C> configClass,
final @NonNull Path configPath
) {
this(configClass, configPath, ConfigurationOptions.defaults());
this(configClass, configPath, options -> options);
}
public @NonNull C load() throws ConfigurateException {
final CommentedConfigurationNode node = this.loader.load();
return this.mapper.load(node);
final C config = this.mapper.load(node);
for (final Consumer<C> processor : this.postProcess) {
processor.accept(config);
}
return config;
}
public void save(final @NonNull C config) throws ConfigurateException {
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,36 +23,41 @@
*/
package xyz.jpenilla.minimotd.common.config;
import com.google.common.collect.ImmutableList;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.configurate.ConfigurateException;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.Pair;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurateException;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.util.Pair;
import static xyz.jpenilla.minimotd.common.util.Pair.pair;
public final class ConfigManager {
private final MiniMOTD<?> miniMOTD;
private final ConfigLoader<MiniMOTDConfig> mainConfigLoader;
private MiniMOTDConfig mainConfig;
private final ConfigLoader<MOTDConfig> mainConfigLoader;
private MOTDConfig mainConfig;
private final ConfigLoader<PluginSettings> pluginSettingsLoader;
private PluginSettings pluginSettings;
private final Map<String, MiniMOTDConfig> extraConfigs = new HashMap<>();
private final Map<String, MOTDConfig> extraConfigs = new HashMap<>();
public ConfigManager(final @NonNull MiniMOTD<?> miniMOTD) {
this.miniMOTD = miniMOTD;
this.mainConfigLoader = new ConfigLoader<>(
MiniMOTDConfig.class,
MOTDConfig.class,
this.miniMOTD.dataDirectory().resolve("main.conf"),
options -> options.header("MiniMOTD Main Configuration")
);
@@ -81,17 +86,20 @@ public final class ConfigManager {
try {
if (!Files.exists(extraConfigsDir)) {
Files.createDirectories(extraConfigsDir);
this.createDefaultProxyConfigs(extraConfigsDir);
this.createDefaultExtraConfigs(extraConfigsDir);
}
for (final Path path : Files.list(extraConfigsDir).collect(Collectors.toList())) {
if (path.toString().endsWith(".conf")) {
try (final Stream<Path> stream = Files.list(extraConfigsDir)) {
for (final Path path : stream.collect(Collectors.toList())) {
if (!path.toString().endsWith(".conf")) {
continue;
}
final String name = path.getFileName().toString().replace(".conf", "");
final ConfigLoader<MiniMOTDConfig> loader = new ConfigLoader<>(
MiniMOTDConfig.class,
final ConfigLoader<MOTDConfig> loader = new ConfigLoader<>(
MOTDConfig.class,
path,
options -> options.header(String.format("Extra MiniMOTD config '%s'", name))
);
final MiniMOTDConfig config = loader.load();
final MOTDConfig config = loader.load();
loader.save(config);
this.extraConfigs.put(name, config);
}
@@ -101,21 +109,21 @@ public final class ConfigManager {
}
}
private void createDefaultProxyConfigs(final @NonNull Path extraConfigsDir) throws ConfigurateException {
final List<Pair<Path, MiniMOTDConfig.MOTD>> defaults = ImmutableList.of(
Pair.of(extraConfigsDir.resolve("skyblock.conf"), new MiniMOTDConfig.MOTD("<green><italic>Skyblock</green>", "<bold><rainbow>MiniMOTD Skyblock Server")),
Pair.of(extraConfigsDir.resolve("survival.conf"), new MiniMOTDConfig.MOTD("<gradient:blue:red>Survival Mode Hardcore", "<green><bold>MiniMOTD Survival Server"))
);
for (final Pair<Path, MiniMOTDConfig.MOTD> pair : defaults) {
final ConfigLoader<MiniMOTDConfig> loader = new ConfigLoader<>(
MiniMOTDConfig.class,
private void createDefaultExtraConfigs(final @NonNull Path extraConfigsDir) throws ConfigurateException {
final List<Pair<Path, MOTDConfig.MOTD>> defaults = Collections.unmodifiableList(Arrays.asList(
pair(extraConfigsDir.resolve("skyblock.conf"), new MOTDConfig.MOTD("<green><italic>Skyblock</green>", "<bold><rainbow>MiniMOTD Skyblock Server")),
pair(extraConfigsDir.resolve("survival.conf"), new MOTDConfig.MOTD("<gradient:blue:red>Survival Mode Hardcore", "<green><bold>MiniMOTD Survival Server"))
));
for (final Pair<Path, MOTDConfig.MOTD> pair : defaults) {
final ConfigLoader<MOTDConfig> loader = new ConfigLoader<>(
MOTDConfig.class,
pair.left()
);
loader.save(new MiniMOTDConfig(ImmutableList.of(pair.right())));
loader.save(new MOTDConfig(pair.right()));
}
}
public @NonNull MiniMOTDConfig mainConfig() {
public @NonNull MOTDConfig mainConfig() {
if (this.mainConfig == null) {
throw new IllegalStateException("Config has not yet been loaded");
}
@@ -129,15 +137,31 @@ public final class ConfigManager {
return this.pluginSettings;
}
public @NonNull MiniMOTDConfig resolveConfig(final @NonNull String name) {
public @NonNull MOTDConfig resolveConfig(final @Nullable InetSocketAddress address) {
if (address == null) {
return this.mainConfig();
}
final String configString = this.pluginSettings().proxySettings().findConfigStringForHost(address.getHostString(), address.getPort());
if (this.pluginSettings().proxySettings().virtualHostTestMode()) {
this.miniMOTD.platform().logger().info("[virtual-host-debug] Virtual Host: '{}:{}', Selected Config: '{}'", address.getHostString(), address.getPort(), configString == null ? "default" : configString);
}
if (configString == null) {
return this.mainConfig();
}
return this.resolveConfig(configString);
}
public @NonNull MOTDConfig resolveConfig(final @NonNull String name) {
if ("default".equals(name)) {
return this.mainConfig();
}
final MiniMOTDConfig cfg = this.extraConfigs.get(name);
final MOTDConfig cfg = this.extraConfigs.get(name);
if (cfg != null) {
return cfg;
}
this.miniMOTD.logger().warn(String.format("Invalid extra-config name: '%s', falling back to main.conf", name));
this.miniMOTD.logger().warn("Invalid extra-config name: '{}', falling back to main.conf", name);
return this.mainConfig();
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,31 +23,33 @@
*/
package xyz.jpenilla.minimotd.common.config;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import xyz.jpenilla.minimotd.common.PingResponse;
import static xyz.jpenilla.minimotd.common.PingResponse.PlayerCount.playerCount;
@ConfigSerializable
public final class MiniMOTDConfig {
public final class MOTDConfig {
public MiniMOTDConfig() {
this(Arrays.asList(
public MOTDConfig() {
this(
new MOTD(),
new MOTD("<blue>Another <bold><red>MOTD", "<italic><underlined><gradient:red:green>much wow")
));
);
}
public MiniMOTDConfig(final @NonNull List<MOTD> defaultMOTDs) {
this.motds.addAll(defaultMOTDs);
public MOTDConfig(final @NonNull MOTD @NonNull ... defaults) {
this.motds.addAll(Arrays.asList(defaults));
}
@Comment("The list of MOTDs to display\n"
+ "\n"
+ " - Supported placeholders: {onlinePlayers}, {maxPlayers}\n"
+ " - Supported placeholders: <online_players>, <max_players>\n"
+ " - Putting more than one will cause one to be randomly chosen each refresh")
private final List<MOTD> motds = new ArrayList<>();
@@ -59,14 +61,6 @@ public final class MiniMOTDConfig {
private PlayerCountSettings playerCountSettings = new PlayerCountSettings();
public int adjustedMaxPlayers(final int onlinePlayers, final int actualMaxPlayers) {
if (this.playerCountSettings.maxPlayersEnabled) {
return this.playerCountSettings.justXMoreSettings.justXMoreEnabled ? onlinePlayers + this.playerCountSettings.justXMoreSettings.xValue : this.playerCountSettings.maxPlayers;
} else {
return actualMaxPlayers;
}
}
@ConfigSerializable
public static final class MOTD {
@@ -114,6 +108,10 @@ public final class MiniMOTDConfig {
@Comment("Setting this to true will disable the hover text showing online player usernames")
private boolean disablePlayerListHover = false;
@Comment("Setting this to true will disable the player list hover (same as 'disable-player-list-hover'),\n"
+ "but will also cause the player count to appear as '???'")
private boolean hidePlayerCount = false;
@Comment("Settings for the fake player count feature")
private FakePlayers fakePlayers = new FakePlayers();
@@ -121,6 +119,15 @@ public final class MiniMOTDConfig {
+ "ex: x=3 -> 16/19 players online.")
private JustXMore justXMoreSettings = new JustXMore();
@Comment("Should the displayed online player count be allowed to exceed the displayed maximum player count?\n"
+ "If false, the online player count will be capped at the maximum player count")
private boolean allowExceedingMaximum = false;
@Comment("The list of server names that affect player counts/listing.\n"
+ "Only applicable when running the plugin on a proxy (Velocity or Waterfall/Bungeecord).\n"
+ "When set to an empty list, the default count & list as determined by the proxy will be used.")
private final List<String> servers = new ArrayList<>();
@ConfigSerializable
public static final class JustXMore {
@@ -137,20 +144,28 @@ public final class MiniMOTDConfig {
@Comment("Enable fake player count feature")
private boolean fakePlayersEnabled = false;
@Comment("Modes: static, random, percent\n"
@Comment("Modes: add, constant, minimum, random, percent\n"
+ "\n"
+ " - static: This many fake players will be added\n"
+ " ex: fakePlayers: \"3\"\n"
+ " - add: This many fake players will be added\n"
+ " ex: fake-players=\"3\"\n"
+ " - constant: A constant value for the player count\n"
+ " ex: fake-players=\"=42\"\n"
+ " - minimum: The minimum bound of the player count\n"
+ " ex: fake-players=\"7+\"\n"
+ " - random: A random number of fake players in this range will be added\n"
+ " ex: fakePlayers: \"3:6\"\n"
+ " ex: fake-players=\"3:6\"\n"
+ " - percent: The player count will be inflated by this much, rounding up\n"
+ " ex: fakePlayers: \"25%\"")
private String fakePlayers = "25%";
+ " ex: fake-players=\"25%\"")
private PlayerCountModifier fakePlayers = PlayerCountModifier.parse("25%");
}
}
public List<String> targetServers() {
return this.playerCountSettings.servers;
}
public boolean iconEnabled() {
return this.iconEnabled;
}
@@ -163,15 +178,41 @@ public final class MiniMOTDConfig {
return this.motdEnabled;
}
public boolean fakePlayersEnabled() {
return this.playerCountSettings.fakePlayers.fakePlayersEnabled;
}
public @NonNull String fakePlayers() {
return this.playerCountSettings.fakePlayers.fakePlayers;
}
public boolean disablePlayerListHover() {
return this.playerCountSettings.disablePlayerListHover;
}
public boolean hidePlayerCount() {
return this.playerCountSettings.hidePlayerCount;
}
private @NonNull PlayerCountModifier playerCountModifier() {
return this.playerCountSettings.fakePlayers.fakePlayers;
}
private int calculateOnlinePlayers(final int onlinePlayers) {
if (this.playerCountSettings.fakePlayers.fakePlayersEnabled) {
return this.playerCountModifier().apply(onlinePlayers);
}
return onlinePlayers;
}
private int calculateMaxPlayers(final int onlinePlayers, final int maxPlayers) {
if (this.playerCountSettings.maxPlayersEnabled) {
if (this.playerCountSettings.justXMoreSettings.justXMoreEnabled) {
return onlinePlayers + this.playerCountSettings.justXMoreSettings.xValue;
}
return this.playerCountSettings.maxPlayers;
}
return maxPlayers;
}
public PingResponse.@NonNull PlayerCount modifyPlayerCount(final int onlinePlayers, final int maxPlayers) {
final int online = this.calculateOnlinePlayers(onlinePlayers);
final int max = this.calculateMaxPlayers(online, maxPlayers);
if (!this.playerCountSettings.allowExceedingMaximum) {
return playerCount(Math.min(online, max), max);
}
return playerCount(online, max);
}
}
@@ -0,0 +1,130 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common.config;
import java.lang.reflect.Type;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import xyz.jpenilla.minimotd.common.util.Int2IntFunction;
public final class PlayerCountModifier implements Int2IntFunction {
private final String input;
private final Int2IntFunction function;
private PlayerCountModifier(final @NonNull String input, final @NonNull Int2IntFunction function) {
this.input = input;
this.function = function;
}
@Override
public int apply(final int actualPlayers) {
return this.function.apply(actualPlayers);
}
public @NonNull String input() {
return this.input;
}
public static @NonNull PlayerCountModifier parse(final @NonNull String input) {
try {
if (input.contains(":")) {
return parseRandomModifier(input);
} else if (input.contains("%")) {
return parsePercentModifier(input);
} else if (input.contains("=")) {
return parseConstantModifier(input);
} else if (input.contains("+")) {
return parseMinimumModifier(input);
} else {
return parseAddModifier(input);
}
} catch (final NumberFormatException ex) {
throw cannotParse(input, ex);
}
}
private static @NonNull PlayerCountModifier parseRandomModifier(final @NonNull String input) {
final String[] fakePlayers = input.split(":");
if (fakePlayers.length != 2) {
throw cannotParse(input, null);
}
final int start = Integer.parseInt(fakePlayers[0]);
final int end = Integer.parseInt(fakePlayers[1]);
return new PlayerCountModifier(input, actual -> actual + ThreadLocalRandom.current().nextInt(start, end));
}
private static @NonNull PlayerCountModifier parsePercentModifier(final @NonNull String input) {
final double factor = 1.00D + Double.parseDouble(input.replace("%", "")) / 100.00D;
return new PlayerCountModifier(input, actual -> (int) Math.ceil(factor * actual));
}
private static @NonNull PlayerCountModifier parseConstantModifier(final @NonNull String input) {
final int value = Integer.parseInt(input.replace("=", ""));
return new PlayerCountModifier(input, actual -> value);
}
private static @NonNull PlayerCountModifier parseMinimumModifier(final @NonNull String input) {
final int minPlayers = Integer.parseInt(input.replace("+", ""));
return new PlayerCountModifier(input, actual -> Math.max(minPlayers, actual));
}
private static @NonNull PlayerCountModifier parseAddModifier(final @NonNull String input) {
final int addedPlayers = Integer.parseInt(input);
return new PlayerCountModifier(input, actual -> actual + addedPlayers);
}
private static @NonNull IllegalArgumentException cannotParse(final @NonNull String input, final @Nullable Throwable cause) {
return new IllegalArgumentException(String.format("Unable to parse a player count modifier from input string '%s'.", input), cause);
}
public static @NonNull Serializer serializer() {
return Serializer.INSTANCE;
}
public static final class Serializer extends ScalarSerializer<PlayerCountModifier> {
private static final Serializer INSTANCE = new Serializer();
private Serializer() {
super(PlayerCountModifier.class);
}
@Override
public PlayerCountModifier deserialize(final @NonNull Type type, final @NonNull Object obj) throws SerializationException {
try {
return PlayerCountModifier.parse(obj.toString());
} catch (final IllegalArgumentException ex) {
throw new SerializationException(ex);
}
}
@Override
protected Object serialize(final @NonNull PlayerCountModifier item, final @NonNull Predicate<Class<?>> typeSupported) {
return item.input();
}
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,14 +23,15 @@
*/
package xyz.jpenilla.minimotd.common.config;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import org.spongepowered.configurate.objectmapping.meta.Comment;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@ConfigSerializable
public final class PluginSettings {
@@ -50,20 +51,96 @@ public final class PluginSettings {
this.virtualHostConfigs.put("skyblock.example.com:25565", "skyblock");
}
public ProxySettings(final Map<String, String> virtualHostConfigs) {
this.virtualHostConfigs.putAll(virtualHostConfigs);
}
@Comment("Here you can assign configs in the 'extra-configs' folder to specific virtual hosts\n"
+ "Either use the name of the config in 'extra-configs', or use \"default\" to use the configuration in main.conf\n"
+ "\n"
+ "Format is \"hostname:port\"=\"configName|default\"")
private final Map<String, String> virtualHostConfigs = new HashMap<>();
+ "Format is \"hostname:port\"=\"configName|default\"\n"
+ "Parts of domains can be substituted for wildcards, i.e. \"*.mydomain.com:25565\". Wildcard-containing configs are\n"
+ "checked in the order they are declared if there are no exact matches.")
private final Map<String, String> virtualHostConfigs = new LinkedHashMap<>();
private transient @MonotonicNonNull Map<String[], String> splitVirtualHostConfigs;
@Comment("Set whether to enable virtual host testing mode.\n"
+ "When enabled, MiniMOTD will print virtual host debug info to the console on each server ping.")
private boolean virtualHostTestMode = false;
public boolean virtualHostTestMode() {
return this.virtualHostTestMode;
}
public @Nullable String findConfigStringForHost(@NonNull String host, final int port) {
host = processTcpShieldHostname(host).toLowerCase(Locale.ENGLISH) + ':' + port;
final @Nullable String exactMatch = this.virtualHostConfigs.get(host);
if (exactMatch != null) {
return exactMatch;
}
if (this.splitVirtualHostConfigs.isEmpty()) {
return null;
}
final String[] splitHost = host.split("\\.");
configs:
for (final Map.Entry<String[], String> e : this.splitVirtualHostConfigs.entrySet()) {
final String[] splitKey = e.getKey();
if (splitKey.length != splitHost.length) {
continue;
}
for (int i = 0; i < splitHost.length; i++) {
final String keyPart = splitKey[i];
if (!keyPart.equals(splitHost[i]) && !keyPart.equals("*")) {
continue configs;
}
}
return e.getValue();
}
return null;
}
private static String processTcpShieldHostname(final String hostname) {
if (hostname.contains("///")) {
final String[] split = hostname.split("///");
if (split.length == 4) {
// <actual hostname>///<user-ip>:<user-port>///<unix timestamp>///<signature>
return split[0];
}
}
return hostname;
}
public void processVirtualHosts() {
final Map<String, String> virtualHosts = new LinkedHashMap<>(this.virtualHostConfigs);
this.virtualHostConfigs.clear();
virtualHosts.forEach((host, config) -> this.virtualHostConfigs.put(host.toLowerCase(Locale.ENGLISH), config));
this.splitVirtualHostConfigs = new LinkedHashMap<>();
this.virtualHostConfigs.forEach((host, config) -> {
if (!host.contains("*")) {
return;
}
this.splitVirtualHostConfigs.put(host.split("\\."), config);
});
}
}
public @NonNull ProxySettings proxySettings() {
return this.proxySettings;
}
public boolean updateChecker() {
return this.updateChecker;
}
public @NonNull Optional<String> configStringForHost(final @NonNull String host) {
return Optional.ofNullable(this.proxySettings.virtualHostConfigs.get(host));
@PostProcessor
private void processVirtualHosts() {
this.proxySettings.processVirtualHosts();
}
}
@@ -0,0 +1,34 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PostProcessor {
}
@@ -0,0 +1,50 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
public interface ComponentColorDownsampler {
static @NonNull ComponentColorDownsampler downsampler() {
return ComponentColorDownsamplerImpl.INSTANCE;
}
@NonNull Component downsample(@NonNull Component component);
final class ComponentColorDownsamplerImpl implements ComponentColorDownsampler {
private static final ComponentColorDownsampler INSTANCE = new ComponentColorDownsamplerImpl();
private ComponentColorDownsamplerImpl() {
}
@Override
public @NonNull Component downsample(final @NonNull Component component) {
return GsonComponentSerializer.gson().deserializeFromTree(
GsonComponentSerializer.colorDownsamplingGson().serializeToTree(component)
);
}
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,11 +21,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.minimotd.common;
package xyz.jpenilla.minimotd.common.util;
public final class Constants {
private Constants() {
}
public static final int MINECRAFT_1_16_PROTOCOL_VERSION = 735;
@FunctionalInterface
public interface Int2IntFunction {
int apply(int value);
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,12 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.minimotd.common;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
package xyz.jpenilla.minimotd.common.util;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class Pair<L, R> {
private final L left;
@@ -47,10 +46,15 @@ public final class Pair<L, R> {
@Override
public boolean equals(final @Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Pair<?, ?> pair = (Pair<?, ?>) o;
return this.left.equals(pair.left) && this.right.equals(pair.right);
return this.left.equals(pair.left)
&& this.right.equals(pair.right);
}
@Override
@@ -58,7 +62,7 @@ public final class Pair<L, R> {
return Objects.hash(this.left, this.right);
}
public static <L, R> @NonNull Pair<L, R> of(final @NonNull L left, final @NonNull R right) {
public static <L, R> @NonNull Pair<L, R> pair(final @NonNull L left, final @NonNull R right) {
return new Pair<>(left, right);
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,57 +21,54 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.minimotd.common;
package xyz.jpenilla.minimotd.common.util;
import com.google.common.base.Charsets;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.Constants;
public final class UpdateChecker {
private final JsonParser parser = new JsonParser();
private final String currentVersion;
public UpdateChecker(final @NonNull String currentVersion) {
this.currentVersion = currentVersion;
}
public List<String> checkVersion() {
final List<String> messages = new ArrayList<>();
public @NonNull List<String> checkVersion() {
final JsonArray result;
try {
result = this.parser.parse(new InputStreamReader(new URL("https://api.github.com/repos/jpenilla/MiniMOTD/releases").openStream(), Charsets.UTF_8)).getAsJsonArray();
} catch (final IOException exception) {
messages.add("Cannot look for updates: " + exception.getMessage());
return messages;
final String url = String.format("https://api.github.com/repos/%s/%s/releases", Constants.PluginMetadata.GITHUB_USER, Constants.PluginMetadata.GITHUB_REPO);
try (final InputStream is = new URL(url).openStream(); InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) {
result = this.parser.parse(reader).getAsJsonArray();
} catch (final IOException ex) {
return Collections.singletonList("Cannot look for updates: " + ex.getMessage());
}
final Map<String, String> versionMap = new LinkedHashMap<>();
result.forEach(element -> versionMap.put(element.getAsJsonObject().get("tag_name").getAsString(), element.getAsJsonObject().get("html_url").getAsString()));
final List<String> versionList = new LinkedList<>(versionMap.keySet());
final String currentVersion = "v" + this.currentVersion;
final String currentVersion = "v" + Constants.PluginMetadata.VERSION;
if (versionList.get(0).equals(currentVersion)) {
messages.add("You are running the latest version of MiniMOTD! :)");
return messages;
return Collections.emptyList(); // Up to date, do nothing
}
if (currentVersion.contains("SNAPSHOT")) {
messages.add("You are running a development build of MiniMOTD! (" + currentVersion + ")");
messages.add("The latest official release is " + versionList.get(0));
return messages;
return Arrays.asList(
"This server is running a development build of " + Constants.PluginMetadata.NAME + "! (" + currentVersion + ")",
"The latest official release is " + versionList.get(0)
);
}
final int versionsBehind = versionList.indexOf(currentVersion);
messages.add("There is an update available for MiniMOTD!");
messages.add("You are running version " + currentVersion + ", which is " + (versionsBehind == -1 ? "many" : versionsBehind) + " versions outdated.");
messages.add("Download the latest version, " + versionList.get(0) + " from GitHub at the link below:");
messages.add(versionMap.get(versionList.get(0)));
return messages;
return Arrays.asList(
"There is an update available for " + Constants.PluginMetadata.NAME + "!",
"This server is running version " + currentVersion + ", which is " + (versionsBehind == -1 ? "UNKNOWN" : versionsBehind) + " versions outdated.",
"Download the latest version, " + versionList.get(0) + " from GitHub at the link below:",
versionMap.get(versionList.get(0))
);
}
}
@@ -0,0 +1,56 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import xyz.jpenilla.minimotd.common.config.PluginSettings;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class VirtualHostMatchingTest {
@Test
void testMatching() {
final Map<String, String> map = new LinkedHashMap<>();
map.put("abc.xyz:25565", "1");
map.put("subdomain.abc.xyz:25565", "2");
map.put("*.abc.xyz:25565", "3");
map.put("abc.*.*.domain.com:25565", "4");
map.put("*.*.*.domain.com:25565", "5");
final PluginSettings.ProxySettings proxySettings = new PluginSettings.ProxySettings(map);
proxySettings.processVirtualHosts();
assertEquals("1", proxySettings.findConfigStringForHost("abc.xyz", 25565));
assertEquals("2", proxySettings.findConfigStringForHost("subdomain.abc.xyz", 25565));
assertEquals("3", proxySettings.findConfigStringForHost("other_subdomain.abc.xyz", 25565));
assertEquals("4", proxySettings.findConfigStringForHost("abc.2.3.domain.com", 25565));
assertEquals("5", proxySettings.findConfigStringForHost("1.2.3.domain.com", 25565));
// TCPShield-mangled hostname
assertEquals("5", proxySettings.findConfigStringForHost("1.2.3.domain.com///127.0.0.1:25565///1681016669///MGQCMAW5X13a9cF5ysnWdrBLovH4wfVu60l7eBUwWsCI8vw0cuZ9aj+UM7+y1wC//qGHngIwRsCke7eCpO99HTHOVdvZKjx9G0E51ALqRFGWH7kxMhVKYNF2IfMaT1FfbS0/D0du", 25565));
assertNull(proxySettings.findConfigStringForHost("subdomain.subdomain.abc.xyz", 25565));
assertNull(proxySettings.findConfigStringForHost("google.com", 25565));
}
}
@@ -0,0 +1,62 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.common.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import org.junit.jupiter.api.Test;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.format.NamedTextColor.nearestTo;
import static net.kyori.adventure.text.format.TextColor.color;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ComponentColorDownsamplerTest {
private static final TextColor GREEN = color(0x04E762);
private static final TextColor ORANGE_YELLOW = color(0xF5B700);
private static final TextColor PINK = color(0xDC0073);
private static final TextColor BLUE = color(0x008BF8);
private static final TextColor LIME_GREEN = color(0x89FC00);
@Test
void testColorDownsampling() {
final Component fullColor = Component.textOfChildren(
text("Green ", GREEN),
text("Orange Yellow ", ORANGE_YELLOW),
text("Pink ", PINK),
text("Blue ", BLUE),
text("Lime Green", LIME_GREEN)
);
final Component expectedDownsample = Component.textOfChildren(
text("Green ", nearestTo(GREEN)),
text("Orange Yellow ", nearestTo(ORANGE_YELLOW)),
text("Pink ", nearestTo(PINK)),
text("Blue ", nearestTo(BLUE)),
text("Lime Green", nearestTo(LIME_GREEN))
);
final Component actualDownsample = ComponentColorDownsampler.downsampler().downsample(fullColor);
assertEquals(expectedDownsample, actualDownsample);
}
}
+27
View File
@@ -0,0 +1,27 @@
plugins {
id("minimotd.platform-conventions")
}
tasks.jar {
archiveClassifier.set("empty")
}
val platforms = setOf(
projects.minimotdBukkit,
projects.minimotdBungeecord,
).map { it.dependencyProject }
val dist = tasks.register<Jar>("bukkitAndBungeeJar") {
artifacts.add("archives", this)
archiveClassifier.set(null as String?)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
for (platform in platforms) {
val jarTask = platform.miniMOTDPlatform.jarTask
from(zipTree(jarTask.flatMap { it.archiveFile }))
}
}
miniMOTDPlatform {
jarTask.set(dist)
}
-59
View File
@@ -1,59 +0,0 @@
plugins {
id("fabric-loom") version "0.5-SNAPSHOT"
id("net.kyori.blossom") version "1.1.0"
}
configurations {
create("shade")
}
val minecraftVersion = "1.16.5"
dependencies {
minecraft("com.mojang", "minecraft", minecraftVersion)
mappings(minecraft.officialMojangMappings())
modImplementation("net.fabricmc", "fabric-loader", "0.11.1")
modImplementation("net.fabricmc.fabric-api", "fabric-api", "0.29.4+1.16")
add("shade", implementation(project(":minimotd-common")) {
exclude("net.kyori")
exclude("org.slf4j")
})
modImplementation(include("net.kyori", "adventure-platform-fabric", "4.0.0-SNAPSHOT"))
implementation(include("net.kyori", "adventure-text-minimessage", "4.1.0-SNAPSHOT"))
implementation(include("org.slf4j", "slf4j-api", "1.7.30"))
implementation(include("org.apache.logging.log4j", "log4j-slf4j-impl", "2.8.1"))
}
tasks {
shadowJar {
configurations = listOf(project.configurations.getByName("shade"))
relocate("io.leangen.geantyref", "xyz.jpenilla.minimotd.lib.io.leangen.geantyref")
relocate("org.spongepowered.configurate", "xyz.jpenilla.minimotd.lib.spongepowered.configurate")
relocate("com.typesafe.config", "xyz.jpenilla.minimotd.lib.typesafe.config")
relocate("org.checkerframework", "xyz.jpenilla.minimotd.lib.checkerframework")
relocate("xyz.jpenilla.minimotd.common", "xyz.jpenilla.minimotd.lib.fabric.minimotd.common")
}
remapJar {
dependsOn(shadowJar)
input.set(shadowJar.get().outputs.files.singleFile)
archiveFileName.set("${project.name}-mc$minecraftVersion-${project.version}.jar")
}
processResources {
filesMatching("fabric.mod.json") {
mapOf(
"{project.name}" to project.name,
"{rootProject.name}" to rootProject.name,
"{version}" to version.toString(),
"{description}" to project.description,
"{url}" to rootProject.ext["url"].toString()
).entries.forEach { (k, v) -> filter { it.replace(k, v as String) } }
}
}
}
blossom {
replaceToken("{version}", version.toString(), "src/main/java/xyz/jpenilla/minimotd/fabric/MiniMOTDFabric.java")
}
@@ -1,98 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.fabric.mixin;
import com.mojang.authlib.GameProfile;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
import xyz.jpenilla.minimotd.fabric.MiniMOTDFabric;
import xyz.jpenilla.minimotd.fabric.access.ConnectionAccess;
@Mixin(ServerStatusPacketListenerImpl.class)
abstract class ServerStatusPacketListenerImplMixin {
@Shadow @Final private Connection connection;
@Redirect(method = "handleStatusRequest", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;getStatus()Lnet/minecraft/network/protocol/status/ServerStatus;"))
public ServerStatus method(final MinecraftServer minecraftServer) {
final ServerStatus vanillaStatus = minecraftServer.getStatus();
final ServerStatus modifiedStatus = new ServerStatus();
modifiedStatus.setDescription(vanillaStatus.getDescription());
modifiedStatus.setFavicon(vanillaStatus.getFavicon());
modifiedStatus.setPlayers(vanillaStatus.getPlayers());
modifiedStatus.setVersion(vanillaStatus.getVersion());
final MiniMOTDFabric miniMOTDFabric = MiniMOTDFabric.get();
final MiniMOTD<String> miniMOTD = miniMOTDFabric.miniMOTD();
final MiniMOTDConfig config = miniMOTD.configManager().mainConfig();
final int onlinePlayers = miniMOTD.calculateOnlinePlayers(config, minecraftServer.getPlayerCount());
final int maxPlayers = config.adjustedMaxPlayers(onlinePlayers, modifiedStatus.getPlayers().getMaxPlayers());
final MOTDIconPair<String> pair = miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
final String motdString = pair.motd();
if (motdString != null) {
final Component motdComponent = miniMOTDFabric.miniMessage().parse(motdString);
if (((ConnectionAccess) this.connection).minimotd$protocolVersion() >= Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
modifiedStatus.setDescription(miniMOTDFabric.audiences().toNative(motdComponent));
} else {
modifiedStatus.setDescription(net.minecraft.network.chat.Component.Serializer.fromJson(
GsonComponentSerializer.colorDownsamplingGson().serialize(motdComponent))
);
}
}
final String favicon = pair.icon();
if (favicon != null) {
modifiedStatus.setFavicon(favicon);
}
final GameProfile[] oldSample = modifiedStatus.getPlayers().getSample();
final ServerStatus.Players newPlayers = new ServerStatus.Players(maxPlayers, onlinePlayers);
if (config.disablePlayerListHover()) {
newPlayers.setSample(new GameProfile[]{});
} else {
newPlayers.setSample(oldSample);
}
modifiedStatus.setPlayers(newPlayers);
return modifiedStatus;
}
}
+8 -2
View File
@@ -1,2 +1,8 @@
org.gradle.jvmargs=-Xmx1G
#org.gradle.parallel=true
group=xyz.jpenilla
version=2.1.1
description=Use MiniMessage text formatting in the server list MOTD.
org.gradle.caching=true
org.gradle.configureondemand=true
org.gradle.jvmargs=-Xmx2G
org.gradle.parallel=true
+18
View File
@@ -0,0 +1,18 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
}
dependencies {
implementation(libs.indraCommon)
implementation(libs.indraLicenser)
implementation(libs.shadow)
implementation(libs.mod.publish.plugin)
implementation(libs.hangarPublishPlugin)
// https://github.com/gradle/gradle/issues/15383#issuecomment-779893192
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
}
+9
View File
@@ -0,0 +1,9 @@
rootProject.name = "build-logic"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../libs.versions.toml"))
}
}
}
@@ -0,0 +1,6 @@
object Constants {
const val GITHUB_USER = "jpenilla"
const val GITHUB_REPO = "MiniMOTD"
const val GITHUB_URL = "https://github.com/$GITHUB_USER/$GITHUB_REPO"
const val RELOCATION_BASE_PACKAGE = "xyz.jpenilla.minimotd.lib"
}
@@ -0,0 +1,19 @@
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class FileCopyTask : DefaultTask() {
@get:InputFile
abstract val fileToCopy: RegularFileProperty
@get:OutputFile
abstract val destination: RegularFileProperty
@TaskAction
fun copyFile() {
destination.get().asFile.parentFile.mkdirs()
fileToCopy.get().asFile.copyTo(destination.get().asFile, overwrite = true)
}
}
@@ -0,0 +1,7 @@
import org.gradle.api.provider.Property
import org.gradle.api.tasks.bundling.AbstractArchiveTask
interface MiniMOTDPlatformExtension {
val jarTask: Property<AbstractArchiveTask>
val hangar: Property<Boolean>
}
@@ -0,0 +1,34 @@
import net.kyori.indra.git.IndraGitExtension
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.getByType
fun Project.lastCommitHash(): String =
extensions.getByType<IndraGitExtension>().commit()?.name?.substring(0, 7) ?: "unknown"
val Project.libs: LibrariesForLibs
get() = extensions.getByType()
val Project.releaseNotes: Provider<String>
get() = providers.environmentVariable("RELEASE_NOTES")
val Project.minecraftVersion: String
get() = libs.versions.minecraft.get()
val bukkitVersions = listOf(
"1.8.8",
"1.8.9",
"1.9.4",
"1.10.2",
"1.11.2",
"1.12.2",
"1.13.2",
"1.14.4",
"1.15.2",
"1.16.5",
"1.17.1",
"1.18.2",
"1.19.4",
"1.20.4"
)
@@ -0,0 +1,40 @@
plugins {
id("java-library")
id("net.kyori.indra")
id("net.kyori.indra.git")
id("net.kyori.indra.checkstyle")
id("net.kyori.indra.licenser.spotless")
}
version = (version as String)
.run { if (this.endsWith("-SNAPSHOT")) "$this+${lastCommitHash()}" else this }
indra {
javaVersions {
minimumToolchain(17)
target(8)
}
github(Constants.GITHUB_USER, Constants.GITHUB_REPO)
mitLicense()
}
dependencies {
testImplementation(libs.jupiterEngine)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
tasks {
withType<JavaCompile> {
options.compilerArgs.add("-Xlint:-processing")
}
listOf(javadocJar, javadoc).forEach {
it.configure {
onlyIf { false }
}
}
test {
testLogging {
events("passed")
}
}
}
@@ -0,0 +1,33 @@
plugins {
base
id("io.papermc.hangar-publish-plugin")
}
fun jar(platform: String) = project(":minimotd-$platform")
.extensions.getByType<MiniMOTDPlatformExtension>().jarTask.flatMap { it.archiveFile }
hangarPublish.publications.register("plugin") {
version.set(project.version as String)
id.set("MiniMOTD")
channel.set("Release")
changelog.set(releaseNotes)
apiKey.set(providers.environmentVariable("HANGAR_UPLOAD_KEY"))
platforms {
paper {
jar.set(jar("bukkit"))
val vers = bukkitVersions.toMutableList()
vers -= "1.8.8"
vers -= "1.8.9"
vers += "1.8"
platformVersions.addAll(vers)
}
velocity {
jar.set(jar("velocity"))
platformVersions.addAll("3.2")
}
waterfall {
jar.set(jar("bungeecord"))
platformVersions.addAll("1.20")
}
}
}
@@ -0,0 +1,34 @@
import me.modmuss50.mpp.ModPublishExtension
plugins {
id("minimotd.base-conventions")
id("net.kyori.indra.git")
}
val platformExtension = extensions.create<MiniMOTDPlatformExtension>("miniMOTDPlatform")
tasks {
val copyJar = register<FileCopyTask>("copyJar") {
fileToCopy.set(platformExtension.jarTask.flatMap { it.archiveFile })
destination.set(rootProject.layout.buildDirectory.dir("libs").flatMap {
it.file(fileToCopy.map { file -> file.asFile.name })
})
}
build {
dependsOn(copyJar)
}
}
if (name != "minimotd-bukkit-bungeecord") {
plugins.apply("me.modmuss50.mod-publish-plugin")
extensions.configure(ModPublishExtension::class) {
modrinth {
projectId.set("16vhQOQN")
type = STABLE
file.set(platformExtension.jarTask.flatMap { it.archiveFile })
changelog.set(releaseNotes)
accessToken.set(providers.environmentVariable("MODRINTH_TOKEN"))
}
}
}
@@ -0,0 +1,18 @@
plugins {
id("minimotd.platform-conventions")
id("io.github.goooler.shadow")
}
tasks {
jar {
archiveClassifier.set("unshaded")
}
shadowJar {
archiveClassifier.set(null as String?)
commonConfiguration()
}
}
extensions.configure<MiniMOTDPlatformExtension> {
jarTask.set(tasks.shadowJar)
}
@@ -0,0 +1,24 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
fun ShadowJar.commonRelocation(pkg: String) {
relocate(pkg, "${Constants.RELOCATION_BASE_PACKAGE}.$pkg")
}
fun ShadowJar.platformRelocation(platform: String, pkg: String) {
relocate(pkg, "${Constants.RELOCATION_BASE_PACKAGE}.platform_$platform.$pkg")
}
fun ShadowJar.commonConfiguration() {
commonRelocation("org.spongepowered.configurate")
commonRelocation("com.typesafe.config")
dependencies {
exclude(dependency("org.checkerframework:checker-qual"))
exclude(dependency("org.jetbrains:annotations")) // transitive dep from adventure
}
}
fun ShadowJar.configureForNativeAdventurePlatform() {
dependencies {
exclude { dependency -> dependency.moduleGroup == "net.kyori" }
}
}
+79
View File
@@ -0,0 +1,79 @@
[plugins]
blossom = { id = "net.kyori.blossom", version = "2.1.0" }
sponge-gradle-plugin = { id = "org.spongepowered.gradle.plugin", version = "2.2.0" }
run-paper = { id = "xyz.jpenilla.run-paper", version.ref = "run-task" }
run-velocity = { id = "xyz.jpenilla.run-velocity", version.ref = "run-task" }
run-waterfall = { id = "xyz.jpenilla.run-waterfall", version.ref = "run-task" }
[versions]
run-task = "2.3.0"
mod-publish-plugin = "0.5.1"
hangarPublishPlugin = "0.1.2"
indra = "3.1.3"
shadow = "8.1.7"
adventure = "4.17.0"
adventurePlatform = "4.3.3"
adventurePlatformFabric = "5.13.0"
configurate = "4.1.2"
checkerQual = "3.43.0"
slf4j = "1.7.32"
gson = "2.8.0"
bstats = "3.0.2"
paperApi = "1.16.5-R0.1-SNAPSHOT"
paperlib = "1.0.8"
waterfallApi = "1.16-R0.5-SNAPSHOT"
fabricApi = "0.99.4+1.20.6"
fabricLoader = "0.15.11"
minecraft = "1.20.6"
spongeApi7 = "7.2.0"
velocityApi = "3.2.0-SNAPSHOT"
jupiterEngine = "5.10.2"
[libraries]
adventureBom = { group = "net.kyori", name = "adventure-bom", version.ref = "adventure" }
adventureApi = { group = "net.kyori", name = "adventure-api" }
adventureTextSerializerPlain = { group = "net.kyori", name = "adventure-text-serializer-plain" }
adventureTextSerializerGson = { group = "net.kyori", name = "adventure-text-serializer-gson" }
adventurePlatformBungeecord = { group = "net.kyori", name = "adventure-platform-bungeecord", version.ref = "adventurePlatform" }
adventurePlatformBukkit = { group = "net.kyori", name = "adventure-platform-bukkit", version.ref = "adventurePlatform" }
adventurePlatformFabric = { group = "net.kyori", name = "adventure-platform-fabric", version.ref = "adventurePlatformFabric" }
adventurePlatformSpongeApi = { group = "net.kyori", name = "adventure-platform-spongeapi", version.ref = "adventurePlatform" }
minimessage = { group = "net.kyori", name = "adventure-text-minimessage" }
configurateHocon = { group = "org.spongepowered", name = "configurate-hocon", version.ref = "configurate" }
checkerQual = { group = "org.checkerframework", name = "checker-qual", version.ref = "checkerQual" }
slf4jApi = { group = "org.slf4j", name = "slf4j-api", version.ref = "slf4j" }
slf4jJdk14 = { group = "org.slf4j", name = "slf4j-jdk14", version.ref = "slf4j" }
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
bstatsBukkit = { group = "org.bstats", name = "bstats-bukkit", version.ref = "bstats" }
bstatsBungeecord = { group = "org.bstats", name = "bstats-bungeecord", version.ref = "bstats" }
bstatsVelocity = { group = "org.bstats", name = "bstats-velocity", version.ref = "bstats" }
bstatsSponge = { group = "org.bstats", name = "bstats-sponge", version.ref = "bstats" }
paperApi = { group = "com.destroystokyo.paper", name = "paper-api", version.ref = "paperApi" }
paperlib = { group = "io.papermc", name = "paperlib", version.ref = "paperlib" }
waterfallApi = { group = "io.github.waterfallmc", name = "waterfall-api", version.ref = "waterfallApi" }
fabricApi = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabricApi" }
fabricLoader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabricLoader" }
minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" }
spongeApi7 = { group = "org.spongepowered", name = "spongeapi", version.ref = "spongeApi7" }
velocityApi = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocityApi" }
jupiterEngine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "jupiterEngine" }
# build-logic
indraCommon = { group = "net.kyori", name = "indra-common", version.ref = "indra" }
indraLicenser = { group = "net.kyori", name = "indra-licenser-spotless", version.ref = "indra" }
shadow = { group = "io.github.goooler.shadow", name = "shadow-gradle-plugin", version.ref = "shadow" }
mod-publish-plugin = { module = "me.modmuss50:mod-publish-plugin", version.ref = "mod-publish-plugin" }
hangarPublishPlugin = { group = "io.papermc", name = "hangar-publish-plugin", version.ref = "hangarPublishPlugin" }
[bundles]
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-6.8.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+179 -115
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,99 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# 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/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$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"'
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
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.
JAVACMD=java
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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
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
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# 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" \
-classpath "$CLASSPATH" \
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.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+19 -16
View File
@@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 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.
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
@@ -56,11 +57,11 @@ 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.
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
@@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
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!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
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
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 Jason Penilla
Copyright (c) 2020-2024 Jason Penilla
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
This file is part of MiniMOTD, licensed under the MIT License.
Copyright (c) 2021 Jason Penilla
Copyright (c) 2020-2024 Jason Penilla
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+48
View File
@@ -0,0 +1,48 @@
plugins {
id("minimotd.shadow-platform")
alias(libs.plugins.run.paper)
}
dependencies {
implementation(projects.minimotdCommon)
implementation(libs.slf4jJdk14)
implementation(libs.adventurePlatformBukkit)
implementation(libs.bstatsBukkit)
implementation(libs.paperlib)
compileOnly(libs.paperApi)
}
tasks {
runServer {
minecraftVersion(minecraftVersion)
}
shadowJar {
platformRelocation("bukkit", "xyz.jpenilla.minimotd.common")
commonRelocation("org.slf4j")
commonRelocation("io.leangen.geantyref")
commonRelocation("net.kyori")
commonRelocation("org.bstats")
commonRelocation("io.papermc.lib")
manifest {
attributes("paperweight-mappings-namespace" to "mojang")
}
}
processResources {
val props = mapOf(
"version" to project.version,
"website" to Constants.GITHUB_URL,
"description" to project.description
)
inputs.properties(props)
filesMatching("plugin.yml") {
expand(props)
}
}
}
runPaper.folia.registerTask()
publishMods.modrinth {
modLoaders.addAll("paper", "folia")
minecraftVersions.addAll(bukkitVersions)
}
@@ -0,0 +1,94 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bukkit;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import net.kyori.adventure.audience.Audience;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.CommandHandler;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.event.ClickEvent.runCommand;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
final class BukkitCommand implements CommandExecutor, TabCompleter {
private final MiniMOTDPlugin plugin;
private final CommandHandler handler;
BukkitCommand(final @NonNull MiniMOTDPlugin plugin) {
this.plugin = plugin;
this.handler = new CommandHandler(plugin.miniMOTD());
}
@Override
public boolean onCommand(final @NonNull CommandSender sender, final @NonNull Command command, final @NonNull String label, final @NonNull String[] args) {
final Audience audience = this.plugin.audiences().sender(sender);
if (!sender.hasPermission("minimotd.admin")) {
audience.sendMessage(text("No permission.", RED));
return true;
}
if (args.length == 0) {
this.onInvalidUse(audience);
return true;
}
switch (args[0]) {
case "about":
this.handler.about(audience);
return true;
case "help":
this.handler.help(audience);
return true;
case "reload":
this.handler.reload(audience);
return true;
}
this.onInvalidUse(audience);
return true;
}
private void onInvalidUse(final @NonNull Audience audience) {
audience.sendMessage(text("Invalid command usage. Use '/minimotd help' for a list of command provided by MiniMOTD.", RED)
.hoverEvent(text("Click to execute '/minimotd help'"))
.clickEvent(runCommand("/minimotd help")));
}
private static final List<String> COMMANDS = ImmutableList.of("about", "reload", "help");
@Override
public List<String> onTabComplete(final @NonNull CommandSender sender, final @NonNull Command command, final @NonNull String alias, final @NonNull String[] args) {
if (args.length < 2 && sender.hasPermission("minimotd.admin")) {
return COMMANDS;
}
return Collections.emptyList();
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,70 +21,68 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.minimotd.spigot;
package xyz.jpenilla.minimotd.bukkit;
import io.papermc.lib.PaperLib;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import xyz.jpenilla.minimotd.common.util.UpdateChecker;
public final class MiniMOTDPlugin extends JavaPlugin implements MiniMOTDPlatform<CachedServerIcon> {
private MiniMOTD<CachedServerIcon> miniMOTD;
private boolean isPaperServer;
private String serverPackageName;
private String serverApiVersion;
private int majorMinecraftVersion;
private BukkitAudiences audiences;
private static final boolean PAPER_PING_EVENT_EXISTS = findClass("com.destroystokyo.paper.event.server.PaperServerListPingEvent") != null;
private Logger logger;
private MiniMOTD<CachedServerIcon> miniMOTD;
private BukkitAudiences audiences;
@Override
public void onEnable() {
this.logger = LoggerFactory.getLogger(this.getName());
this.miniMOTD = new MiniMOTD<>(this);
this.serverPackageName = this.getServer().getClass().getPackage().getName();
this.serverApiVersion = this.serverPackageName.substring(this.serverPackageName.lastIndexOf('.') + 1);
this.majorMinecraftVersion = Integer.parseInt(this.serverApiVersion.split("_")[1]);
this.audiences = BukkitAudiences.create(this);
try {
Class.forName("com.destroystokyo.paper.event.server.PaperServerListPingEvent");
this.isPaperServer = true;
} catch (final ClassNotFoundException e) {
this.isPaperServer = false;
}
if (this.isPaperServer) {
getServer().getPluginManager().registerEvents(new PaperPingListener(this, this.miniMOTD), this);
if (PAPER_PING_EVENT_EXISTS) {
this.getServer().getPluginManager().registerEvents(new PaperPingListener(this.miniMOTD), this);
} else {
getServer().getPluginManager().registerEvents(new PingListener(this, this.miniMOTD), this);
if (this.majorMinecraftVersion > 11) {
getLogger().info("#");
getLogger().info("# This server is not using Paper, and therefore some features may be limited or disabled.");
getLogger().info("# Get Paper from https://papermc.io/downloads");
getLogger().info("#");
this.getServer().getPluginManager().registerEvents(new PingListener(this.miniMOTD), this);
if (PaperLib.getMinecraftVersion() >= 12) { // PaperServerListPingEvent was added in 1.12
this.suggestPaper();
}
}
this.audiences = BukkitAudiences.create(this);
final PluginCommand command = getCommand("minimotd");
final PluginCommand command = this.getCommand("minimotd");
if (command != null) {
command.setExecutor(new SpigotCommand(this));
command.setTabCompleter(new SpigotCommand(this));
final BukkitCommand bukkitCommand = new BukkitCommand(this);
command.setExecutor(bukkitCommand);
command.setTabCompleter(bukkitCommand);
}
final Metrics metrics = new Metrics(this, 8132);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Bukkit.getScheduler().runTaskAsynchronously(this, () ->
new UpdateChecker(this.getDescription().getVersion()).checkVersion().forEach(getLogger()::info));
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
CompletableFuture.runAsync(() -> new UpdateChecker().checkVersion().forEach(this.logger::info)).whenComplete(($, thr) -> {
if (thr != null) {
this.logger.warn("Exception checking for updates", thr);
}
});
} catch (final ReflectiveOperationException ex) {
this.getServer().getScheduler().runTaskAsynchronously(this, () ->
new UpdateChecker().checkVersion().forEach(this.logger::info));
}
}
}
@@ -104,14 +102,32 @@ public final class MiniMOTDPlugin extends JavaPlugin implements MiniMOTDPlatform
@Override
public @NonNull CachedServerIcon loadIcon(final @NonNull BufferedImage image) throws Exception {
return Bukkit.loadServerIcon(image);
}
public int majorMinecraftVersion() {
return this.majorMinecraftVersion;
return this.getServer().loadServerIcon(image);
}
public @NonNull BukkitAudiences audiences() {
return this.audiences;
}
private void suggestPaper() {
this.logger.warn("======================================================");
this.logger.warn(" MiniMOTD works better if you use Paper as your server");
this.logger.warn(" software.");
this.logger.warn(" ");
this.logger.warn(" Spigot does not include the necessary APIs for all");
this.logger.warn(" of MiniMOTD's features to operate. MiniMOTD was");
this.logger.warn(" designed to work with Paper and it's expanded API");
this.logger.warn(" for full compatibility.");
this.logger.warn(" ");
this.logger.warn(" Get Paper from https://papermc.io/downloads");
this.logger.warn("======================================================");
}
private static @Nullable Class<?> findClass(final @NonNull String className) {
try {
return Class.forName(className);
} catch (final ClassNotFoundException ex) {
return null;
}
}
}
@@ -0,0 +1,69 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bukkit;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import io.papermc.lib.PaperLib;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
public final class PaperPingListener implements Listener {
private final LegacyComponentSerializer unusualHexSerializer = LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build();
private final MiniMOTD<CachedServerIcon> miniMOTD;
PaperPingListener(final @NonNull MiniMOTD<CachedServerIcon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@EventHandler
public void handlePing(final @NonNull PaperServerListPingEvent event) {
final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig();
final PingResponse<CachedServerIcon> response = this.miniMOTD.createMOTD(cfg, event.getNumPlayers(), event.getMaxPlayers());
response.playerCount().applyCount(event::setNumPlayers, event::setMaxPlayers);
response.motd(motd -> {
if (event.getClient().getProtocolVersion() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION || PaperLib.getMinecraftVersion() < 16) {
event.setMotd(LegacyComponentSerializer.legacySection().serialize(motd));
} else {
event.setMotd(this.unusualHexSerializer.serialize(motd));
}
});
response.icon(event::setServerIcon);
if (response.disablePlayerListHover()) {
event.getPlayerSample().clear();
}
if (response.hidePlayerCount()) {
event.setHidePlayers(true);
}
}
}
@@ -0,0 +1,66 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bukkit;
import io.papermc.lib.PaperLib;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
public final class PingListener implements Listener {
private final MiniMOTD<CachedServerIcon> miniMOTD;
private final LegacyComponentSerializer unusualHexSerializer = LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build();
PingListener(final @NonNull MiniMOTD<CachedServerIcon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@EventHandler
public void handlePing(final @NonNull ServerListPingEvent event) {
final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig();
final PingResponse<CachedServerIcon> response = this.miniMOTD.createMOTD(cfg, event.getNumPlayers(), event.getMaxPlayers());
event.setMaxPlayers(response.playerCount().maxPlayers());
response.motd(motd -> {
if (PaperLib.getMinecraftVersion() > 15) {
event.setMotd(this.unusualHexSerializer.serialize(motd));
} else {
event.setMotd(LegacyComponentSerializer.legacySection().serialize(motd));
}
});
response.icon(icon -> {
try {
event.setServerIcon(icon);
} catch (final UnsupportedOperationException ignore) {
}
});
}
}
@@ -0,0 +1,16 @@
name: MiniMOTD
version: $version
main: xyz.jpenilla.minimotd.bukkit.MiniMOTDPlugin
folia-supported: true
description: $description
authors:
- jmp
website: $website
commands:
minimotd:
description: MiniMOTD Command
permission: minimotd.admin
usage: /minimotd help
api-version: 1.13
softdepend:
- ViaVersion
+38
View File
@@ -0,0 +1,38 @@
plugins {
id("minimotd.shadow-platform")
alias(libs.plugins.run.waterfall)
}
dependencies {
implementation(projects.minimotdCommon)
implementation(libs.adventurePlatformBungeecord)
implementation(libs.bstatsBungeecord)
compileOnly(libs.waterfallApi)
}
tasks {
shadowJar {
platformRelocation("bungee", "xyz.jpenilla.minimotd.common")
commonRelocation("io.leangen.geantyref")
commonRelocation("net.kyori")
commonRelocation("org.bstats")
}
runWaterfall {
waterfallVersion("1.20")
}
processResources {
val props = mapOf(
"version" to project.version,
"description" to project.description
)
inputs.properties(props)
filesMatching("bungee.yml") {
expand(props)
}
}
}
publishMods.modrinth {
modLoaders.add("waterfall")
minecraftVersions.addAll(minecraftVersion)
}
@@ -0,0 +1,79 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bungee;
import net.kyori.adventure.audience.Audience;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.CommandHandler;
import static net.kyori.adventure.text.Component.text;
import static net.kyori.adventure.text.event.ClickEvent.runCommand;
import static net.kyori.adventure.text.format.NamedTextColor.RED;
final class BungeeCommand extends Command {
private final MiniMOTDPlugin plugin;
private final CommandHandler handler;
BungeeCommand(final @NonNull MiniMOTDPlugin plugin) {
super("minimotd");
this.plugin = plugin;
this.handler = new CommandHandler(plugin.miniMOTD());
}
@Override
public void execute(final @NonNull CommandSender sender, final @NonNull String @NonNull [] args) {
final Audience audience = this.plugin.audiences().sender(sender);
if (!sender.hasPermission("minimotd.admin")) {
audience.sendMessage(text("No permission.", RED));
return;
}
if (args.length == 0) {
this.onInvalidUse(audience);
return;
}
switch (args[0]) {
case "about":
this.handler.about(audience);
return;
case "help":
this.handler.help(audience);
return;
case "reload":
this.handler.reload(audience);
return;
}
this.onInvalidUse(audience);
}
private void onInvalidUse(final @NonNull Audience audience) {
audience.sendMessage(text("Invalid command usage. Use '/minimotd help' for a list of command provided by MiniMOTD.", RED)
.hoverEvent(text("Click to execute '/minimotd help'"))
.clickEvent(runCommand("/minimotd help")));
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,21 +23,27 @@
*/
package xyz.jpenilla.minimotd.bungee;
import com.google.gson.Gson;
import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import java.nio.file.Path;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
import xyz.jpenilla.minimotd.common.util.UpdateChecker;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon> {
public final class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon> {
private Logger logger;
private BungeeAudiences audiences;
private MiniMOTD<Favicon> miniMOTD;
@@ -52,14 +58,18 @@ public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon>
this.miniMOTD = new MiniMOTD<>(this);
this.miniMOTD.configManager().loadExtraConfigs();
this.audiences = BungeeAudiences.create(this);
this.injectTravertineGson();
this.getProxy().getPluginManager().registerListener(this, new PingListener(this.miniMOTD));
this.getProxy().getPluginManager().registerCommand(this, new BungeeCommand(this));
final Metrics metrics = new Metrics(this, 8137);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
this.getProxy().getScheduler().runAsync(this, () ->
new UpdateChecker(this.getDescription().getVersion()).checkVersion().forEach(getLogger()::info));
new UpdateChecker().checkVersion().forEach(this.logger::info));
}
// preload some stuff, so the first ping is as fast as the rest
this.preload();
}
@Override
@@ -80,4 +90,34 @@ public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon>
public @NonNull BungeeAudiences audiences() {
return this.audiences;
}
@Override
public void onReload() {
this.miniMOTD.configManager().loadExtraConfigs();
}
private void injectTravertineGson() {
final Field gsonLegacyField = findDeclaredField(ProxyServer.getInstance().getClass(), "gsonLegacy");
if (gsonLegacyField != null) {
try {
BungeeComponentSerializer.inject((Gson) gsonLegacyField.get(ProxyServer.getInstance()));
} catch (final IllegalAccessException ex) {
this.miniMOTD.logger().warn("Failed to inject into Travertine's gsonLegacy gson instance. There will likely be issues with 1.7.x clients.", ex);
}
}
}
private static @Nullable Field findDeclaredField(final @NonNull Class<?> holder, final @NonNull String name) {
try {
return holder.getDeclaredField(name);
} catch (final NoSuchFieldException ex) {
return null;
}
}
private void preload() {
final MOTDConfig cfg = this.miniMOTD.configManager().mainConfig();
final PingResponse<Favicon> mini = this.miniMOTD.createMOTD(cfg, 0, 0);
mini.motd(motd -> BungeeComponentSerializer.get().serialize(motd));
}
}
@@ -0,0 +1,84 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.bungee;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
public final class PingListener implements Listener {
private final MiniMOTD<Favicon> miniMOTD;
PingListener(final @NonNull MiniMOTD<Favicon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@EventHandler
public void onPing(final @NonNull ProxyPingEvent e) {
final ServerPing response = e.getResponse();
if (response == null) {
return;
}
final ServerPing.Players players = response.getPlayers();
final MOTDConfig cfg = this.miniMOTD.configManager().resolveConfig(e.getConnection().getVirtualHost());
final PingResponse<Favicon> mini = this.miniMOTD.createMOTD(cfg, players.getOnline(), players.getMax());
if (mini.hidePlayerCount()) {
response.setPlayers(null);
} else {
mini.playerCount().applyCount(players::setOnline, players::setMax);
if (mini.disablePlayerListHover()) {
players.setSample(new ServerPing.PlayerInfo[]{});
}
}
mini.motd(motd -> {
final BaseComponent[] bungee;
if (e.getConnection().getVersion() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
bungee = BungeeComponentSerializer.legacy().serialize(motd);
} else {
bungee = BungeeComponentSerializer.get().serialize(motd);
}
if (BungeeComponentSerializer.isNative()) {
response.setDescriptionComponent(bungee[0]);
} else {
response.setDescriptionComponent(new TextComponent(bungee));
}
});
mini.icon(response::setFavicon);
e.setResponse(response);
}
}
@@ -0,0 +1,5 @@
name: MiniMOTD
main: xyz.jpenilla.minimotd.bungee.MiniMOTDPlugin
version: $version
author: jmp
description: $description
+60
View File
@@ -0,0 +1,60 @@
plugins {
id("quiet-fabric-loom")
id("minimotd.platform-conventions")
id("io.github.goooler.shadow")
}
val shade: Configuration by configurations.creating
dependencies {
minecraft(libs.minecraft)
mappings(loom.officialMojangMappings())
modImplementation(libs.fabricLoader)
modImplementation(libs.fabricApi)
shade(implementation(projects.minimotdCommon) {
exclude("net.kyori")
})
modImplementation(libs.adventurePlatformFabric)
include(libs.adventurePlatformFabric)
}
miniMOTDPlatform {
jarTask.set(tasks.remapJar)
}
indra {
javaVersions {
target(21)
}
}
tasks {
shadowJar {
configurations = listOf(shade)
commonConfiguration()
commonRelocation("io.leangen.geantyref")
}
remapJar {
archiveFileName.set("${project.name}-mc$minecraftVersion-${project.version}.jar")
}
processResources {
val replacements = mapOf(
"modid" to project.name,
"name" to rootProject.name,
"version" to project.version.toString(),
"description" to project.description.toString(),
"github_url" to Constants.GITHUB_URL
)
inputs.properties(replacements)
filesMatching("fabric.mod.json") {
expand(replacements)
}
}
}
publishMods.modrinth {
modLoaders.add("fabric")
minecraftVersions.addAll(minecraftVersion)
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,41 +23,40 @@
*/
package xyz.jpenilla.minimotd.fabric;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.nio.file.Path;
import java.util.concurrent.CompletableFuture;
import javax.imageio.ImageIO;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.minecraft.commands.Commands;
import org.apache.commons.lang3.Validate;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.server.MinecraftServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.jpenilla.minimotd.common.CommandHandler;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
import xyz.jpenilla.minimotd.common.util.UpdateChecker;
import xyz.jpenilla.minimotd.fabric.access.ServerStatusFaviconAccess;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.concurrent.CompletableFuture;
import static net.minecraft.commands.Commands.literal;
public class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<String> {
public final class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<ServerStatus.Favicon> {
private static MiniMOTDFabric instance = null;
private final Logger logger = LoggerFactory.getLogger(MiniMOTD.class);
private final Path dataDirectory = FabricLoader.getInstance().getConfigDir().resolve("MiniMOTD");
private final MiniMessage miniMessage = MiniMessage.get();
private final MiniMOTD<String> miniMOTD = new MiniMOTD<>(this);
private final MiniMOTD<ServerStatus.Favicon> miniMOTD = new MiniMOTD<>(this);
private MinecraftServer server;
private FabricServerAudiences audiences;
public MiniMOTDFabric() {
@@ -67,58 +66,52 @@ public class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<String>
instance = this;
}
public @NonNull MiniMOTD<String> miniMOTD() {
public @NonNull MiniMOTD<ServerStatus.Favicon> miniMOTD() {
return this.miniMOTD;
}
public @NonNull MiniMessage miniMessage() {
return this.miniMessage;
}
@Override
public void onInitialize() {
this.registerCommand();
ServerLifecycleEvents.SERVER_STARTED.register(minecraftServer -> {
ServerLifecycleEvents.SERVER_STARTING.register(minecraftServer -> {
this.server = minecraftServer;
this.audiences = FabricServerAudiences.of(minecraftServer);
});
ServerLifecycleEvents.SERVER_STARTED.register(minecraftServer -> {
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
CompletableFuture.runAsync(() -> new UpdateChecker("{version}").checkVersion().forEach(this.logger()::info));
CompletableFuture.runAsync(() -> new UpdateChecker().checkVersion().forEach(this.logger()::info));
}
});
ServerLifecycleEvents.SERVER_STOPPED.register($ -> {
this.server = null;
this.audiences = null;
});
this.miniMOTD.logger().info("Done initializing MiniMOTD");
}
private void registerCommand() {
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> dispatcher.register(
Commands.literal("minimotd")
.requires(source -> source.hasPermission(4))
.then(Commands.literal("reload")
.executes(ctx -> {
final Audience audience = this.audiences.audience(ctx.getSource());
this.send(audience, "<white>[<gradient:blue:aqua>MiniMOTD</gradient>] <italic><gray>Reloading MiniMOTD...");
this.miniMOTD.configManager().loadConfigs();
this.miniMOTD.iconManager().loadIcons();
this.send(audience, "<white>[<gradient:blue:aqua>MiniMOTD</gradient>] <green>Done reloading MiniMOTD.");
return 1;
})
)
.then(Commands.literal("about")
.executes(ctx -> {
this.send(this.audiences.audience(ctx.getSource()),
"<strikethrough><gradient:black:white>------------------",
"<hover:show_text:'<gradient:blue:aqua>click me!'><click:open_url:https://github.com/jpenilla/MiniMOTD> MiniMOTD v{version}",
"<gray> By <gradient:gold:yellow>jmp",
"<strikethrough><gradient:black:white>------------------"
);
return 1;
})
)
));
}
final class WrappingExecutor implements Command<CommandSourceStack> {
private final CommandHandler.Executor handler;
private void send(final @NonNull Audience audience, final String... strings) {
for (final String string : strings) {
audience.sendMessage(this.miniMessage.parse(string));
WrappingExecutor(final CommandHandler.@NonNull Executor handler) {
this.handler = handler;
}
@Override
public int run(final @NonNull CommandContext<CommandSourceStack> context) {
this.handler.execute(context.getSource());
return Command.SINGLE_SUCCESS;
}
}
final CommandHandler handler = new CommandHandler(this.miniMOTD);
CommandRegistrationCallback.EVENT.register((dispatcher, commandBuildContext, commandSelection) -> dispatcher.register(
literal("minimotd")
.requires(source -> source.hasPermission(4))
.then(literal("reload").executes(new WrappingExecutor(handler::reload)))
.then(literal("about").executes(new WrappingExecutor(handler::about)))
.then(literal("help").executes(new WrappingExecutor(handler::help)))
));
}
public @NonNull FabricServerAudiences audiences() {
@@ -129,6 +122,13 @@ public class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<String>
return instance;
}
public @NonNull MinecraftServer requireServer() {
if (this.server == null) {
throw new IllegalStateException("Server requested before started");
}
return this.server;
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
@@ -140,18 +140,11 @@ public class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<String>
}
@Override
public @NonNull String loadIcon(final @NonNull BufferedImage bufferedImage) throws Exception {
Validate.validState(bufferedImage.getWidth() == 64, "Must be 64 pixels wide");
Validate.validState(bufferedImage.getHeight() == 64, "Must be 64 pixels high");
final ByteBuf byteBuf = Unpooled.buffer();
final String icon;
try {
ImageIO.write(bufferedImage, "PNG", new ByteBufOutputStream(byteBuf));
final ByteBuffer byteBuffer = Base64.getEncoder().encode(byteBuf.nioBuffer());
icon = "data:image/png;base64," + StandardCharsets.UTF_8.decode(byteBuffer);
} finally {
byteBuf.release();
}
return icon;
public ServerStatus.@NonNull Favicon loadIcon(final @NonNull BufferedImage bufferedImage) throws Exception {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "PNG", out);
final ServerStatus.Favicon favicon = new ServerStatus.Favicon(out.toByteArray());
((ServerStatusFaviconAccess) (Object) favicon).cacheEncodedIcon();
return favicon;
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,7 +24,7 @@
package xyz.jpenilla.minimotd.fabric.access;
public interface ConnectionAccess {
void minimotd$protocolVersion(int protocolVersion);
void protocolVersion(int protocolVersion);
int minimotd$protocolVersion();
int protocolVersion();
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -21,26 +21,12 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.minimotd.common;
package xyz.jpenilla.minimotd.fabric.access;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class MOTDIconPair<I> {
private final I icon;
private final String motd;
public MOTDIconPair(final @Nullable I icon, final @Nullable String motd) {
this.icon = icon;
this.motd = motd;
}
public @Nullable I icon() {
return this.icon;
}
public @Nullable String motd() {
return this.motd;
}
public interface ServerStatusFaviconAccess {
@Nullable String cachedEncodedIcon();
void cacheEncodedIcon();
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,21 +24,23 @@
package xyz.jpenilla.minimotd.fabric.mixin;
import net.minecraft.network.Connection;
import org.spongepowered.asm.mixin.Implements;
import org.spongepowered.asm.mixin.Interface;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import xyz.jpenilla.minimotd.fabric.access.ConnectionAccess;
@SuppressWarnings("checkstyle:FinalLocalVariable")
@Unique
@Mixin(Connection.class)
abstract class ConnectionMixin implements ConnectionAccess {
@Unique
private int protocolVersion = -1;
@Implements({@Interface(iface = ConnectionAccess.class, prefix = "miniMOTD$")})
abstract class ConnectionMixin {
private int miniMOTD$protocolVersion = -1;
public void minimotd$protocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
public void miniMOTD$protocolVersion(final int protocolVersion) {
this.miniMOTD$protocolVersion = protocolVersion;
}
public int minimotd$protocolVersion() {
return this.protocolVersion;
public int miniMOTD$protocolVersion() {
return this.miniMOTD$protocolVersion;
}
}
@@ -1,7 +1,7 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -39,7 +39,7 @@ abstract class ServerHandshakePacketListenerImplMixin {
@Shadow @Final private Connection connection;
@Inject(method = "handleIntention", at = @At("HEAD"))
public void onRequest(final ClientIntentionPacket packet, final CallbackInfo ci) {
((ConnectionAccess) this.connection).minimotd$protocolVersion(packet.getProtocolVersion());
public void injectHandleIntention(final ClientIntentionPacket packet, final CallbackInfo ci) {
((ConnectionAccess) this.connection).protocolVersion(packet.protocolVersion());
}
}
@@ -0,0 +1,78 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.fabric.mixin;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.codecs.PrimitiveCodec;
import java.util.function.Function;
import net.minecraft.network.protocol.status.ServerStatus;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import xyz.jpenilla.minimotd.fabric.access.ServerStatusFaviconAccess;
@Mixin(ServerStatus.Favicon.class)
abstract class ServerStatusFaviconMixin implements ServerStatusFaviconAccess {
@Unique
private static Function<ServerStatus.Favicon, String> VANILLA_ENCODE_TO_STRING;
@Redirect(
method = "<clinit>",
at = @At(
target = "Lcom/mojang/serialization/codecs/PrimitiveCodec;comapFlatMap(Ljava/util/function/Function;Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;",
value = "INVOKE"
),
remap = false
)
private static Codec<ServerStatus.Favicon> makeCodec(
final PrimitiveCodec<String> stringCodec,
final Function<String, DataResult<ServerStatus.Favicon>> decode,
final Function<ServerStatus.Favicon, String> encode
) {
VANILLA_ENCODE_TO_STRING = encode;
return stringCodec.comapFlatMap(decode, favicon -> {
final @Nullable String encodedIcon = ((ServerStatusFaviconAccess) (Object) favicon).cachedEncodedIcon();
if (encodedIcon != null) {
return encodedIcon;
}
return encode.apply(favicon);
});
}
@Unique
private String encodedIcon;
@Override
public @Nullable String cachedEncodedIcon() {
return this.encodedIcon;
}
@Override
public void cacheEncodedIcon() {
this.encodedIcon = VANILLA_ENCODE_TO_STRING.apply((ServerStatus.Favicon) (Object) this);
}
}
@@ -0,0 +1,94 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.fabric.mixin;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import net.minecraft.network.Connection;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
import xyz.jpenilla.minimotd.common.util.ComponentColorDownsampler;
import xyz.jpenilla.minimotd.fabric.MiniMOTDFabric;
import xyz.jpenilla.minimotd.fabric.access.ConnectionAccess;
import xyz.jpenilla.minimotd.fabric.util.MutableServerStatus;
@Mixin(ServerStatusPacketListenerImpl.class)
abstract class ServerStatusPacketListenerImplMixin {
@Shadow @Final private Connection connection;
@Redirect(
method = "handleStatusRequest",
at = @At(value = "FIELD", target = "Lnet/minecraft/server/network/ServerStatusPacketListenerImpl;status:Lnet/minecraft/network/protocol/status/ServerStatus;")
)
public ServerStatus injectHandleStatusRequest(final ServerStatusPacketListenerImpl instance) {
final MiniMOTDFabric miniMOTDFabric = MiniMOTDFabric.get();
final MinecraftServer minecraftServer = miniMOTDFabric.requireServer();
final ServerStatus vanillaStatus = Objects.requireNonNull(minecraftServer.getStatus(), "vanillaStatus");
final MiniMOTD<ServerStatus.Favicon> miniMOTD = miniMOTDFabric.miniMOTD();
final MOTDConfig config = miniMOTD.configManager().mainConfig();
final PingResponse<ServerStatus.Favicon> response = miniMOTD.createMOTD(
config,
minecraftServer.getPlayerCount(),
vanillaStatus.players().map(ServerStatus.Players::max).orElseGet(minecraftServer::getMaxPlayers)
);
final MutableServerStatus modifiedStatus = new MutableServerStatus(vanillaStatus);
response.motd(motd -> {
if (((ConnectionAccess) this.connection).protocolVersion() >= Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
modifiedStatus.description(miniMOTDFabric.audiences().toNative(motd));
} else {
modifiedStatus.description(miniMOTDFabric.audiences().toNative(ComponentColorDownsampler.downsampler().downsample(motd)));
}
});
response.icon(favicon -> modifiedStatus.favicon(Optional.of(favicon)));
if (response.hidePlayerCount()) {
modifiedStatus.players(Optional.empty());
} else {
final ServerStatus.Players newPlayers = new ServerStatus.Players(
response.playerCount().maxPlayers(),
response.playerCount().onlinePlayers(),
response.disablePlayerListHover()
? Collections.emptyList()
: vanillaStatus.players().map(ServerStatus.Players::sample).orElse(Collections.emptyList())
);
modifiedStatus.players(Optional.of(newPlayers));
}
return modifiedStatus.toServerStatus();
}
}
@@ -0,0 +1,97 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.fabric.util;
import java.util.Objects;
import java.util.Optional;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.status.ServerStatus;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
@SuppressWarnings({"OptionalUsedAsFieldOrParameterType", "unused"})
@DefaultQualifier(NonNull.class)
public final class MutableServerStatus {
private Component description;
private Optional<ServerStatus.Players> players;
private Optional<ServerStatus.Version> version;
private Optional<ServerStatus.Favicon> favicon;
private boolean enforcesSecureChat;
public MutableServerStatus(final ServerStatus status) {
this.description = status.description();
this.players = status.players();
this.version = status.version();
this.favicon = status.favicon();
this.enforcesSecureChat = status.enforcesSecureChat();
}
public ServerStatus toServerStatus() {
return new ServerStatus(this.description, this.players, this.version, this.favicon, this.enforcesSecureChat);
}
public Component description() {
return this.description;
}
public void description(final Component description) {
Objects.requireNonNull(description, "description");
this.description = description;
}
public Optional<ServerStatus.Players> players() {
return this.players;
}
public void players(final Optional<ServerStatus.Players> players) {
Objects.requireNonNull(players, "players");
this.players = players;
}
public Optional<ServerStatus.Version> version() {
return this.version;
}
public void version(final Optional<ServerStatus.Version> version) {
Objects.requireNonNull(version, "version");
this.version = version;
}
public Optional<ServerStatus.Favicon> favicon() {
return this.favicon;
}
public void favicon(final Optional<ServerStatus.Favicon> favicon) {
Objects.requireNonNull(favicon, "favicon");
this.favicon = favicon;
}
public boolean enforcesSecureChat() {
return this.enforcesSecureChat;
}
public void enforcesSecureChat(final boolean enforcesSecureChat) {
this.enforcesSecureChat = enforcesSecureChat;
}
}
@@ -1,12 +1,17 @@
{
"schemaVersion": 1,
"id": "{project.name}",
"version": "{version}",
"name": "{rootProject.name}",
"description": "{description}",
"id": "${modid}",
"version": "${version}",
"name": "${name}",
"description": "${description}",
"authors": [
"jmp"
],
"contact": {
"homepage": "${github_url}",
"sources": "${github_url}",
"issues": "${github_url}/issues"
},
"license": "MIT",
"environment": "server",
"entrypoints": {
@@ -18,8 +23,8 @@
"minimotd-fabric.mixins.json"
],
"depends": {
"fabricloader": ">=0.11.1",
"fabricloader": ">=0.15.10",
"fabric": "*",
"minecraft": "1.16.x"
"minecraft": "~1.20.5"
}
}
@@ -1,11 +1,12 @@
{
"required": true,
"minVersion": "0.8",
"minVersion": "0.8.4",
"package": "xyz.jpenilla.minimotd.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"compatibilityLevel": "JAVA_16",
"mixins": [
"ConnectionMixin",
"ServerHandshakePacketListenerImplMixin",
"ServerStatusFaviconMixin",
"ServerStatusPacketListenerImplMixin"
],
"client": [
+36
View File
@@ -0,0 +1,36 @@
plugins {
id("minimotd.shadow-platform")
}
dependencies {
implementation(projects.minimotdCommon)
implementation(libs.adventurePlatformSpongeApi)
compileOnly(libs.spongeApi7)
implementation(libs.bstatsSponge)
}
tasks {
processResources {
val replacements = mapOf(
"modid" to rootProject.name.lowercase(),
"name" to rootProject.name,
"version" to project.version.toString(),
"description" to project.description.toString(),
"url" to Constants.GITHUB_URL
)
inputs.properties(replacements)
filesMatching("mcmod.info") {
expand(replacements)
}
}
shadowJar {
commonRelocation("io.leangen.geantyref")
commonRelocation("net.kyori")
commonRelocation("org.bstats")
}
}
publishMods.modrinth {
modLoaders.add("sponge")
minecraftVersions.add("1.12.2")
}
@@ -0,0 +1,75 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.sponge7;
import com.google.inject.Inject;
import net.kyori.adventure.text.serializer.spongeapi.SpongeComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.api.event.EventListener;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
final class ClientPingServerEventListener implements EventListener<ClientPingServerEvent> {
private final MiniMOTD<Favicon> miniMOTD;
@Inject
private ClientPingServerEventListener(final @NonNull MiniMOTD<Favicon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@Override
public void handle(final @NonNull ClientPingServerEvent event) {
final ClientPingServerEvent.Response response = event.getResponse();
final ClientPingServerEvent.Response.Players players;
final ClientPingServerEvent.Response.Players players0 = response.getPlayers().orElse(null);
if (players0 != null) {
players = players0;
} else {
response.setHidePlayers(false);
players = response.getPlayers().orElse(null);
if (players == null) {
this.miniMOTD.logger().warn(String.format("Failed to handle ClientPingServerEvent: '%s', response.getPlayers() was null.", event));
return;
}
}
final MOTDConfig config = this.miniMOTD.configManager().mainConfig();
final PingResponse<Favicon> mini = this.miniMOTD.createMOTD(config, players.getOnline(), players.getMax());
mini.playerCount().applyCount(players::setOnline, players::setMax);
mini.motd(motd -> response.setDescription(SpongeComponentSerializer.get().serialize(motd)));
mini.icon(response::setFavicon);
if (mini.disablePlayerListHover()) {
players.getProfiles().clear();
}
if (mini.hidePlayerCount()) {
response.setHidePlayers(true);
}
}
}
@@ -0,0 +1,162 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.sponge7;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import net.kyori.adventure.platform.spongeapi.SpongeAudiences;
import org.bstats.sponge.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.spongepowered.api.Game;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.GameReloadEvent;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.Task;
import xyz.jpenilla.minimotd.common.CommandHandler;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.util.UpdateChecker;
@Plugin(id = "minimotd-sponge7")
public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
private final Logger logger;
private final Path dataDirectory;
private final MiniMOTD<Favicon> miniMOTD;
private final SpongeAudiences audiences;
private final Game game;
private final Injector injector;
@Inject
public MiniMOTDPlugin(
final @NonNull Logger logger,
@ConfigDir(sharedRoot = false) final @NonNull Path dataDirectory,
final @NonNull SpongeAudiences audiences,
final Metrics.@NonNull Factory metricsFactory,
final @NonNull Injector injector,
final @NonNull Game game
) {
this.logger = logger;
this.dataDirectory = dataDirectory;
this.audiences = audiences;
this.game = game;
this.miniMOTD = new MiniMOTD<>(this);
this.injector = injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
this.bind(new TypeLiteral<MiniMOTD<Favicon>>() {
}).toInstance(MiniMOTDPlugin.this.miniMOTD);
}
});
metricsFactory.make(10768);
}
@Listener
public void gameStarted(final @NonNull GameStartedServerEvent event) {
Sponge.getEventManager().registerListener(this, ClientPingServerEvent.class, this.injector.getInstance(ClientPingServerEventListener.class));
this.registerCommands();
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Task.builder()
.async()
.execute(() -> new UpdateChecker().checkVersion().forEach(this.logger::info))
.submit(this);
}
}
@Listener
public void reloaded(final @NonNull GameReloadEvent event) {
try {
this.miniMOTD.reload();
} catch (final Exception ex) {
this.miniMOTD.logger().warn("Failed to reload MiniMOTD.", ex);
}
}
private void registerCommands() {
final class WrappingExecutor implements CommandExecutor {
private final CommandHandler.Executor handler;
WrappingExecutor(final CommandHandler.@NonNull Executor handler) {
this.handler = handler;
}
@Override
public @NonNull CommandResult execute(final @NonNull CommandSource src, final @NonNull CommandContext args) {
this.handler.execute(MiniMOTDPlugin.this.audiences.receiver(src));
return CommandResult.success();
}
}
final CommandHandler handler = new CommandHandler(this.miniMOTD);
final CommandSpec help = CommandSpec.builder()
.executor(new WrappingExecutor(handler::help))
.build();
final CommandSpec about = CommandSpec.builder()
.executor(new WrappingExecutor(handler::about))
.build();
final CommandSpec reload = CommandSpec.builder()
.executor(new WrappingExecutor(handler::reload))
.build();
Sponge.getCommandManager().register(
this,
CommandSpec.builder()
.permission("minimotd.admin")
.child(help, "help")
.child(about, "about")
.child(reload, "reload")
.build(),
"minimotd"
);
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) throws IOException {
return this.game.getRegistry().loadFavicon(image);
}
}
@@ -0,0 +1,18 @@
[
{
"modid": "${modid}",
"name": "${name}",
"version": "${version}",
"description": "${description}",
"url": "${url}",
"authorList": [
"jmp"
],
"dependencies": [
"spongeapi@7.2.0"
],
"requiredMods": [
"spongeapi@7.2.0"
]
}
]
+58
View File
@@ -0,0 +1,58 @@
import org.spongepowered.gradle.plugin.config.PluginLoaders
import org.spongepowered.plugin.metadata.model.PluginDependency
plugins {
id("minimotd.shadow-platform")
alias(libs.plugins.sponge.gradle.plugin)
}
dependencies {
implementation(projects.minimotdCommon)
}
sponge {
injectRepositories(false)
apiVersion("8.1.0-SNAPSHOT")
plugin(rootProject.name.lowercase()) {
loader {
name(PluginLoaders.JAVA_PLAIN)
version("1.0")
}
license("MIT")
displayName(rootProject.name)
entrypoint("xyz.jpenilla.minimotd.sponge8.MiniMOTDPlugin")
description(project.description)
links {
homepage(Constants.GITHUB_URL)
source(Constants.GITHUB_URL)
issues("${Constants.GITHUB_URL}/issues")
}
contributor("jmp") {
description("Lead Developer")
}
dependency("spongeapi") {
loadOrder(PluginDependency.LoadOrder.AFTER)
optional(false)
}
}
}
tasks {
shadowJar {
configureForNativeAdventurePlatform()
dependencies {
exclude(dependency("io.leangen.geantyref:geantyref"))
}
}
}
publishMods.modrinth {
modLoaders.add("sponge")
minecraftVersions.addAll(
"1.16.5",
"1.17.1",
"1.18.2",
"1.19.4",
"1.20.4"
)
}
@@ -0,0 +1,104 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.sponge8;
import com.google.inject.Inject;
import java.lang.reflect.Method;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.api.MinecraftVersion;
import org.spongepowered.api.event.EventListener;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
import xyz.jpenilla.minimotd.common.util.ComponentColorDownsampler;
final class ClientPingServerEventListener implements EventListener<ClientPingServerEvent> {
private static final Method GET_PROTOCOL;
static {
try {
final Class<?> protocolMinecraftVersion = Class.forName("org.spongepowered.common.ProtocolMinecraftVersion");
GET_PROTOCOL = protocolMinecraftVersion.getMethod("getProtocol");
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
private final MiniMOTD<Favicon> miniMOTD;
@Inject
private ClientPingServerEventListener(final @NonNull MiniMOTD<Favicon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@Override
public void handle(final @NonNull ClientPingServerEvent event) {
final ClientPingServerEvent.Response response = event.response();
final ClientPingServerEvent.Response.Players players;
final ClientPingServerEvent.Response.Players players0 = response.players().orElse(null);
if (players0 != null) {
players = players0;
} else {
response.setHidePlayers(false);
players = response.players().orElse(null);
if (players == null) {
this.miniMOTD.logger().warn(String.format("Failed to handle ClientPingServerEvent: '%s', response.players() was null.", event));
return;
}
}
final MOTDConfig config = this.miniMOTD.configManager().mainConfig();
final PingResponse<Favicon> mini = this.miniMOTD.createMOTD(config, players.online(), players.max());
mini.playerCount().applyCount(players::setOnline, players::setMax);
mini.motd(motd -> {
if (this.legacy(event.client().version())) {
response.setDescription(ComponentColorDownsampler.downsampler().downsample(motd));
} else {
response.setDescription(motd);
}
});
mini.icon(response::setFavicon);
if (mini.disablePlayerListHover()) {
players.profiles().clear();
}
if (mini.hidePlayerCount()) {
response.setHidePlayers(true);
}
}
private boolean legacy(final @NonNull MinecraftVersion version) {
try {
return version.isLegacy()
|| (int) GET_PROTOCOL.invoke(version) < Constants.MINECRAFT_1_16_PROTOCOL_VERSION;
} catch (final ReflectiveOperationException e) {
throw new IllegalStateException("Failed to get protocol version", e);
}
}
}
@@ -0,0 +1,165 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.sponge8;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.Command;
import org.spongepowered.api.command.CommandExecutor;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.parameter.CommandContext;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.EventListenerRegistration;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.lifecycle.LoadedGameEvent;
import org.spongepowered.api.event.lifecycle.RefreshGameEvent;
import org.spongepowered.api.event.lifecycle.RegisterCommandEvent;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.plugin.PluginContainer;
import org.spongepowered.plugin.builtin.jvm.Plugin;
import org.spongepowered.plugin.metadata.PluginMetadata;
import xyz.jpenilla.minimotd.common.CommandHandler;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.util.UpdateChecker;
@Plugin("minimotd-sponge8")
public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
private final Path dataDirectory;
private final PluginMetadata pluginMetadata;
private final Logger logger;
private final PluginContainer pluginContainer;
private final MiniMOTD<Favicon> miniMOTD;
private final Injector injector;
@Inject
public MiniMOTDPlugin(
@ConfigDir(sharedRoot = false) final @NonNull Path dataDirectory,
final @NonNull PluginContainer pluginContainer,
final @NonNull Injector injector
) {
this.dataDirectory = dataDirectory;
this.pluginContainer = pluginContainer;
this.pluginMetadata = pluginContainer.metadata();
this.logger = LoggerFactory.getLogger(this.pluginMetadata.id());
this.miniMOTD = new MiniMOTD<>(this);
this.injector = injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
this.bind(new TypeLiteral<MiniMOTD<Favicon>>() {
}).toInstance(MiniMOTDPlugin.this.miniMOTD);
}
});
Sponge.eventManager().registerListener(
EventListenerRegistration.builder(ClientPingServerEvent.class)
.plugin(pluginContainer)
.listener(this.injector.getInstance(ClientPingServerEventListener.class))
.order(Order.DEFAULT)
.build()
);
}
@Listener
public void onGameLoaded(final @NonNull LoadedGameEvent event) {
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Sponge.asyncScheduler().submit(Task.builder()
.plugin(this.pluginContainer)
.execute(() -> new UpdateChecker().checkVersion().forEach(this.logger::info))
.build());
}
}
@Listener
public void registerCommands(final @NonNull RegisterCommandEvent<Command.Parameterized> event) {
final class WrappingExecutor implements CommandExecutor {
private final CommandHandler.Executor handler;
WrappingExecutor(final CommandHandler.@NonNull Executor handler) {
this.handler = handler;
}
@Override
public CommandResult execute(final @NonNull CommandContext context) {
this.handler.execute(context.cause().audience());
return CommandResult.success();
}
}
final CommandHandler handler = new CommandHandler(this.miniMOTD);
final Command.Parameterized about = Command.builder()
.executor(new WrappingExecutor(handler::about))
.build();
final Command.Parameterized help = Command.builder()
.executor(new WrappingExecutor(handler::help))
.build();
final Command.Parameterized reload = Command.builder()
.executor(new WrappingExecutor(handler::reload))
.build();
event.register(
this.pluginContainer,
Command.builder()
.permission("minimotd.admin")
.addChild(about, "about")
.addChild(help, "help")
.addChild(reload, "reload")
.build(),
"minimotd"
);
}
@Listener
public void onRefresh(final @NonNull RefreshGameEvent event) {
try {
this.miniMOTD.reload();
} catch (final Exception ex) {
this.miniMOTD.logger().warn("Failed to reload MiniMOTD.", ex);
}
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) throws Exception {
return Favicon.load(image);
}
}
+39
View File
@@ -0,0 +1,39 @@
plugins {
id("minimotd.shadow-platform")
alias(libs.plugins.run.velocity)
}
dependencies {
implementation(projects.minimotdCommon)
implementation(libs.bstatsVelocity)
compileOnly(libs.velocityApi)
}
java.disableAutoTargetJvm()
tasks {
shadowJar {
configureForNativeAdventurePlatform()
commonRelocation("io.leangen.geantyref")
commonRelocation("org.bstats")
}
runVelocity {
velocityVersion(libs.versions.velocityApi.get())
}
processResources {
val props = mapOf(
"description" to project.description as String,
"url" to Constants.GITHUB_URL,
"version" to project.version,
)
inputs.properties(props)
filesMatching("velocity-plugin.json") {
expand(props)
}
}
}
publishMods.modrinth {
modLoaders.add("velocity")
minecraftVersions.addAll(minecraftVersion)
}
@@ -0,0 +1,150 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.velocity;
import com.google.common.collect.ImmutableSet;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.util.Favicon;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
import java.util.Set;
import org.bstats.velocity.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import xyz.jpenilla.minimotd.common.CommandHandler;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.util.UpdateChecker;
public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
private static final Set<Class<?>> LISTENER_CLASSES = ImmutableSet.of(
PingListener.class
);
private final MiniMOTD<Favicon> miniMOTD;
private final ProxyServer server;
private final Logger logger;
private final CommandManager commandManager;
private final Path dataDirectory;
private final Metrics.Factory metricsFactory;
private final Injector injector;
@Inject
public MiniMOTDPlugin(
final @NonNull ProxyServer server,
final @NonNull Logger logger,
final @NonNull CommandManager commandManager,
@DataDirectory final @NonNull Path dataDirectory,
final Metrics.@NonNull Factory metricsFactory,
final @NonNull Injector injector
) {
this.server = server;
this.logger = logger;
this.commandManager = commandManager;
this.dataDirectory = dataDirectory;
this.metricsFactory = metricsFactory;
this.miniMOTD = new MiniMOTD<>(this);
this.miniMOTD.configManager().loadExtraConfigs();
this.injector = injector.createChildInjector(new AbstractModule() {
@Override
protected void configure() {
this.bind(new TypeLiteral<MiniMOTD<Favicon>>() {
}).toInstance(MiniMOTDPlugin.this.miniMOTD);
}
});
}
@Subscribe
public void onProxyInitialization(final @NonNull ProxyInitializeEvent event) {
for (final Class<?> clazz : LISTENER_CLASSES) {
this.server.getEventManager().register(this, this.injector.getInstance(clazz));
}
this.registerCommand();
this.metricsFactory.make(this, 10257);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
this.server.getScheduler().buildTask(
this,
() -> new UpdateChecker().checkVersion().forEach(this.logger::info)
).schedule();
}
}
private void registerCommand() {
final class WrappingExecutor implements Command<CommandSource> {
private final CommandHandler.Executor handler;
WrappingExecutor(final CommandHandler.@NonNull Executor handler) {
this.handler = handler;
}
@Override
public int run(final @NonNull CommandContext<CommandSource> context) {
this.handler.execute(context.getSource());
return Command.SINGLE_SUCCESS;
}
}
final CommandHandler handler = new CommandHandler(this.miniMOTD);
this.commandManager.register(this.commandManager.metaBuilder("minimotd").build(), new BrigadierCommand(
LiteralArgumentBuilder.<CommandSource>literal("minimotd")
.requires(source -> source.hasPermission("minimotd.admin"))
.then(LiteralArgumentBuilder.<CommandSource>literal("help").executes(new WrappingExecutor(handler::help)))
.then(LiteralArgumentBuilder.<CommandSource>literal("about").executes(new WrappingExecutor(handler::about)))
.then(LiteralArgumentBuilder.<CommandSource>literal("reload").executes(new WrappingExecutor(handler::reload)))
));
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) {
return Favicon.create(image);
}
@Override
public void onReload() {
this.miniMOTD.configManager().loadExtraConfigs();
}
}
@@ -0,0 +1,98 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2020-2024 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.event.EventTask;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.util.Favicon;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.PingResponse;
import xyz.jpenilla.minimotd.common.config.MOTDConfig;
@DefaultQualifier(NonNull.class)
public final class PingListener {
private final MiniMOTD<Favicon> miniMOTD;
private final ProxyServer proxy;
@Inject
private PingListener(final MiniMOTD<Favicon> miniMOTD, final ProxyServer proxy) {
this.miniMOTD = miniMOTD;
this.proxy = proxy;
}
@Subscribe
public EventTask onProxyPingEvent(final ProxyPingEvent event) {
return EventTask.async(() -> this.handle(event));
}
private void handle(final ProxyPingEvent event) {
final MOTDConfig config = this.miniMOTD.configManager().resolveConfig(event.getConnection().getVirtualHost().orElse(null));
final ServerPing.Builder pong = event.getPing().asBuilder();
final List<String> targetServers = config.targetServers();
int playersCount = 0;
if (targetServers.isEmpty()) {
playersCount = pong.getOnlinePlayers();
} else {
final Set<ServerPing.SamplePlayer> players = new HashSet<>();
for (final String serverName : targetServers) {
final @Nullable RegisteredServer server = this.proxy.getServer(serverName).orElse(null);
if (server != null) {
playersCount += server.getPlayersConnected().size();
players.addAll(server.getPlayersConnected().stream()
.map(p -> new ServerPing.SamplePlayer(p.getGameProfile().getName(), p.getUniqueId()))
.collect(Collectors.toList()));
}
}
pong.clearSamplePlayers();
pong.samplePlayers(players.toArray(new ServerPing.SamplePlayer[0]));
}
final PingResponse<Favicon> response = this.miniMOTD.createMOTD(config, playersCount, pong.getMaximumPlayers());
response.icon(pong::favicon);
response.motd(pong::description);
response.playerCount().applyCount(pong::onlinePlayers, pong::maximumPlayers);
if (response.disablePlayerListHover()) {
pong.clearSamplePlayers();
}
if (response.hidePlayerCount()) {
pong.nullPlayers();
}
event.setPing(pong.build());
}
}
@@ -0,0 +1,12 @@
{
"id": "minimotd-velocity",
"name": "MiniMOTD",
"version": "$version",
"description": "$description",
"url": "$url",
"authors": [
"jmp"
],
"dependencies": [],
"main": "xyz.jpenilla.minimotd.velocity.MiniMOTDPlugin"
}
+31 -1
View File
@@ -1,5 +1,35 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
],
"ignoreDeps": [
"quiet-fabric-loom",
"com.mojang:minecraft",
"com.google.code.gson:gson"
],
"labels": [
"dependencies"
],
"packageRules": [
{
"description": "Correct Fabric API version handling",
"matchPackageNames": ["net.fabricmc.fabric-api:fabric-api", "net.fabricmc.fabric-api:fabric-api-deprecated"],
"versioning": "regex:^(?<major>\\d+)(\\.(?<minor>\\d+))?(\\.(?<patch>\\d+))?(?:\\+(?<compatibility>.*))?$"
},
{
"matchManagers": ["github-actions", "gradle-wrapper"],
"groupName": "gradle and github actions"
},
{
"matchDepTypes": ["plugin"],
"groupName": "gradle and github actions"
},
{
"matchPaths": ["build-logic/*", "buildSrc/*"],
"groupName": "gradle and github actions"
}
],
"semanticCommitType": "build",
"commitMessagePrefix": "chore(deps): "
}
+47 -25
View File
@@ -1,36 +1,58 @@
rootProject.name = "MiniMOTD"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {
repositories {
mavenCentral {
mavenContent { releasesOnly() }
}
maven("https://oss.sonatype.org/content/repositories/snapshots/") {
mavenContent { snapshotsOnly() }
}
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") {
mavenContent { snapshotsOnly() }
}
maven("https://repo.papermc.io/repository/maven-public/")
maven("https://repo.spongepowered.org/repository/maven-public/")
maven("https://repo.jpenilla.xyz/snapshots/") {
mavenContent { snapshotsOnly() }
}
}
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
}
pluginManagement {
repositories {
jcenter()
maven("https://maven.fabricmc.net/")
gradlePluginPortal()
maven("https://maven.fabricmc.net/")
maven("https://repo.jpenilla.xyz/snapshots/")
}
includeBuild("gradle/build-logic")
}
setupSubproject("minimotd-common") {
projectDir = file("common")
}
setupSubproject("minimotd-spigot") {
projectDir = file("spigot")
}
setupSubproject("minimotd-sponge8") {
projectDir = file("sponge8")
}
setupSubproject("minimotd-bungeecord") {
projectDir = file("bungee")
}
setupSubproject("minimotd-velocity") {
projectDir = file("velocity")
}
setupSubproject("minimotd-fabric") {
projectDir = file("fabric")
}
setupSubproject("minimotd-universal") {
projectDir = file("universal")
plugins {
id("quiet-fabric-loom") version "1.6-SNAPSHOT"
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
}
inline fun setupSubproject(name: String, block: ProjectDescriptor.() -> Unit) {
rootProject.name = "MiniMOTD"
fun setup(name: String, dir: String) {
include(name)
project(":$name").apply(block)
project(":$name").projectDir = file(dir)
}
fun platform(name: String) = setup("minimotd-$name", "platform/$name")
fun dist(name: String) = setup("minimotd-$name", "dist/$name")
setup("minimotd-common", "common")
sequenceOf(
"bukkit",
"sponge8",
"sponge7",
"bungeecord",
"velocity",
"fabric",
).forEach(::platform)
dist("bukkit-bungeecord")
-41
View File
@@ -1,41 +0,0 @@
plugins {
id("kr.entree.spigradle") version "2.2.3"
}
dependencies {
implementation(project(":minimotd-common"))
implementation("org.slf4j", "slf4j-jdk14","1.7.30")
implementation("net.kyori", "adventure-platform-bukkit", "4.0.0-SNAPSHOT")
implementation("org.bstats", "bstats-bukkit", "2.2.0")
compileOnly("com.destroystokyo.paper", "paper-api", "1.16.4-R0.1-SNAPSHOT")
}
tasks {
shadowJar {
relocate("org.slf4j", "xyz.jpenilla.minimotd.lib.slf4j")
relocate("io.leangen.geantyref", "xyz.jpenilla.minimotd.lib.io.leangen.geantyref")
relocate("org.spongepowered.configurate", "xyz.jpenilla.minimotd.lib.spongepowered.configurate")
relocate("com.typesafe.config", "xyz.jpenilla.minimotd.lib.typesafe.config")
relocate("net.kyori", "xyz.jpenilla.minimotd.lib.kyori")
relocate("org.checkerframework", "xyz.jpenilla.minimotd.lib.checkerframework")
relocate("org.bstats", "xyz.jpenilla.minimotd.lib.bstats")
}
build {
dependsOn(shadowJar)
}
}
spigot {
name = rootProject.name
apiVersion = "1.13"
website = rootProject.ext["url"].toString()
authors("jmp")
softDepends("Prisma", "ViaVersion")
commands {
create("minimotd") {
description = "MiniMOTD Command"
permissionMessage = "No permission"
usage = "/minimotd help"
}
}
}
@@ -1,80 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.spigot;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
public class PaperPingListener implements Listener {
private final MiniMOTDPlugin plugin;
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build();
private final MiniMessage miniMessage = MiniMessage.get();
private final LegacyComponentSerializer legacySerializer = LegacyComponentSerializer.builder().build();
private final MiniMOTD<CachedServerIcon> miniMOTD;
public PaperPingListener(final @NonNull MiniMOTDPlugin plugin, final @NonNull MiniMOTD<CachedServerIcon> miniMOTD) {
this.miniMOTD = miniMOTD;
this.plugin = plugin;
}
@EventHandler
public void onPing(final @NonNull PaperServerListPingEvent e) {
final MiniMOTDConfig cfg = this.miniMOTD.configManager().mainConfig();
final int onlinePlayers = this.miniMOTD.calculateOnlinePlayers(cfg, e.getNumPlayers());
e.setNumPlayers(onlinePlayers);
final int maxPlayers = cfg.adjustedMaxPlayers(onlinePlayers, e.getMaxPlayers());
e.setMaxPlayers(maxPlayers);
final MOTDIconPair<CachedServerIcon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
final String motdString = pair.motd();
if (motdString != null) {
final Component motdComponent = this.miniMessage.parse(motdString);
if (e.getClient().getProtocolVersion() < 735 || this.plugin.majorMinecraftVersion() < 16) {
e.setMotd(this.legacySerializer.serialize(motdComponent));
} else {
e.setMotd(this.serializer.serialize(motdComponent));
}
}
final CachedServerIcon favicon = pair.icon();
if (favicon != null) {
e.setServerIcon(favicon);
}
if (cfg.disablePlayerListHover()) {
e.getPlayerSample().clear();
}
}
}
@@ -1,74 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.spigot;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
public class PingListener implements Listener {
private final MiniMOTD<CachedServerIcon> miniMOTD;
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build();
private final LegacyComponentSerializer legacySerializer = LegacyComponentSerializer.builder().build();
private final MiniMessage miniMessage = MiniMessage.get();
private final MiniMOTDPlugin plugin;
public PingListener(final @NonNull MiniMOTDPlugin plugin, final @NonNull MiniMOTD<CachedServerIcon> miniMOTD) {
this.plugin = plugin;
this.miniMOTD = miniMOTD;
}
@EventHandler
public void onPing(final @NonNull ServerListPingEvent e) {
final MiniMOTDConfig cfg = this.miniMOTD.configManager().mainConfig();
final int onlinePlayers = e.getNumPlayers();
final int actualMaxPlayers = e.getMaxPlayers();
final int maxPlayers = cfg.adjustedMaxPlayers(onlinePlayers, actualMaxPlayers);
e.setMaxPlayers(maxPlayers);
final MOTDIconPair<CachedServerIcon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
final String motdString = pair.motd();
if (motdString != null) {
if (this.plugin.majorMinecraftVersion() > 15) {
e.setMotd(this.serializer.serialize(this.miniMessage.parse(motdString)));
} else {
e.setMotd(this.legacySerializer.serialize(this.miniMessage.parse(motdString)));
}
}
final CachedServerIcon favicon = pair.icon();
if (favicon != null) {
e.setServerIcon(favicon);
}
}
}
@@ -1,138 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.spigot;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collections;
import java.util.List;
public class SpigotCommand implements CommandExecutor, TabCompleter {
private final MiniMOTDPlugin plugin;
private final MiniMessage miniMessage;
public SpigotCommand(final @NonNull MiniMOTDPlugin plugin) {
this.plugin = plugin;
this.miniMessage = MiniMessage.get();
}
@Override
public boolean onCommand(final @NonNull CommandSender sender,
final @NonNull Command command,
final @NonNull String label,
final @NonNull String[] args) {
if (!sender.hasPermission("minimotd.admin")) {
this.onNoPermission(sender, args);
return true;
}
if (args.length == 0) {
this.onInvalidUse(sender, args);
return true;
}
switch (args[0]) {
case "about":
this.onAbout(sender, args);
return true;
case "help":
this.onHelp(sender, args);
return true;
case "reload":
this.onReload(sender, args);
return true;
}
this.onInvalidUse(sender, args);
return true;
}
private void onHelp(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.send(sender, ImmutableList.of(
"<gradient:blue:green>MiniMOTD Commands:",
" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd about'><click:run_command:/minimotd about><yellow>/minimotd about",
" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd reload'><click:run_command:/minimotd reload><yellow>/minimotd reload",
" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd help'><click:run_command:/minimotd help><yellow>/minimotd help"
));
}
private void onReload(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.plugin.miniMOTD().iconManager().loadIcons();
this.plugin.miniMOTD().configManager().loadConfigs();
this.send(sender, "<green>Done reloading.");
}
private void onAbout(final @NonNull CommandSender sender, final @NonNull String[] args) {
final String header = "<gradient:white:black>=============</gradient><gradient:black:white>=============";
this.send(sender, ImmutableList.of(
header,
"<hover:show_text:'<rainbow>click me!'><click:open_url:" + this.plugin.getDescription().getWebsite() + ">" + this.plugin.getName() + " <gradient:red:yellow>" + this.plugin.getDescription().getVersion(),
"<yellow>By</yellow><gray>:</gray> <gradient:blue:green>jmp",
header
));
}
private void onInvalidUse(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.send(sender, "<hover:show_text:'<green>Click for <yellow>/minimotd help'><click:run_command:/minimotd help><italic><gradient:red:gold>Invalid usage.</gradient> <blue>Try <yellow>/minimotd help</yellow> or click here");
}
private void onNoPermission(final @NonNull CommandSender sender, final @NonNull String[] args) {
this.send(sender, "<gradient:red:yellow>No permission.");
}
private void send(final @NonNull CommandSender sender, final @NonNull String message) {
if (sender instanceof Player) {
this.plugin.audiences().player((Player) sender).sendMessage(Identity.nil(), this.miniMessage.parse(message));
} else {
this.plugin.audiences().console().sendMessage(Identity.nil(), this.miniMessage.parse(message));
}
}
private void send(final @NonNull CommandSender sender, final @NonNull List<String> messages) {
for (final String message : messages) {
this.send(sender, message);
}
}
private static final List<String> COMMANDS = ImmutableList.of("about", "reload", "help");
@Override
public List<String> onTabComplete(final @NonNull CommandSender sender,
final @NonNull Command command,
final @NonNull String alias,
final @NonNull String[] args) {
if (args.length < 2 && sender.hasPermission("minimotd.admin")) {
return COMMANDS;
}
return Collections.emptyList();
}
}
-36
View File
@@ -1,36 +0,0 @@
dependencies {
implementation(project(":minimotd-common"))
implementation("net.kyori", "adventure-text-serializer-legacy", "4.4.0")
compileOnly("org.spongepowered", "spongeapi", "8.0.0-SNAPSHOT")
annotationProcessor("org.spongepowered", "spongeapi", "8.0.0-SNAPSHOT")
}
tasks {
shadowJar {
relocate("org.spongepowered.configurate", "xyz.jpenilla.minimotd.lib.spongepowered.configurate")
relocate("io.leangen.geantyref", "xyz.jpenilla.minimotd.lib.io.leangen.geantyref")
relocate("com.typesafe.config", "xyz.jpenilla.minimotd.lib.typesafe.config")
relocate("net.kyori.adventure.text.minimessage", "xyz.jpenilla.minimotd.lib.kyori_native.minimessage")
relocate("net.kyori.adventure.text.serializer.legacy", "xyz.jpenilla.minimotd.lib.kyori_native.legacy.text.serializer")
relocate("org.checkerframework", "xyz.jpenilla.minimotd.lib.checkerframework")
relocate("xyz.jpenilla.minimotd.common", "xyz.jpenilla.minimotd.lib.sponge8.minimotd.common")
dependencies {
exclude { dep -> dep.moduleGroup == "net.kyori" && !dep.name.contains("minimessage") && !dep.name.contains("text-serializer-legacy") }
exclude(dependency("org.slf4j:slf4j-api"))
}
}
processResources {
filesMatching("**/plugins.json") {
mapOf(
"{project.name}" to project.name,
"{rootProject.name}" to rootProject.name,
"{version}" to version.toString(),
"{description}" to project.description,
"{url}" to rootProject.ext["url"].toString()
).entries.forEach { (k, v) -> filter { it.replace(k, v as String) } }
}
}
build {
dependsOn(shadowJar)
}
}
@@ -1,143 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.sponge8;
import com.google.inject.Inject;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.api.MinecraftVersion;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.lifecycle.LoadedGameEvent;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.plugin.PluginContainer;
import org.spongepowered.plugin.jvm.Plugin;
import org.spongepowered.plugin.metadata.PluginMetadata;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
import java.awt.image.BufferedImage;
import java.lang.reflect.Method;
import java.nio.file.Path;
import java.util.Objects;
@Plugin("minimotd-sponge8") // todo commands
public class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
private final Path dataDirectory;
private final PluginMetadata pluginMetadata;
private final Logger logger;
private final MiniMessage miniMessage = MiniMessage.get();
private final PluginContainer pluginContainer;
private final MiniMOTD<Favicon> miniMOTD;
@Inject
public MiniMOTDPlugin(
@ConfigDir(sharedRoot = false) final @NonNull Path dataDirectory,
final @NonNull PluginContainer pluginContainer
) {
this.dataDirectory = dataDirectory;
this.pluginContainer = pluginContainer;
this.pluginMetadata = pluginContainer.getMetadata();
this.logger = LoggerFactory.getLogger(this.pluginMetadata.getId());
this.miniMOTD = new MiniMOTD<>(this);
}
@Listener
public void onServerStart(final @NonNull LoadedGameEvent event) {
Sponge.getAsyncScheduler().submit(Task.builder().plugin(this.pluginContainer).execute(() ->
new UpdateChecker(this.pluginMetadata.getVersion()).checkVersion().forEach(this.logger::info)
).build());
}
private Method SpongeMinecraftVersion_getProtocol;
@Listener
public void onPing(final @NonNull ClientPingServerEvent event) {
final ClientPingServerEvent.Response response = event.getResponse();
response.getPlayers().ifPresent(players -> {
final MiniMOTDConfig config = this.miniMOTD.configManager().mainConfig();
final int onlinePlayers = this.miniMOTD.calculateOnlinePlayers(config, players.getOnline());
players.setOnline(onlinePlayers);
final int maxPlayers = config.adjustedMaxPlayers(onlinePlayers, players.getMax());
players.setMax(maxPlayers);
final @NonNull MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
final String motdString = pair.motd();
try {
if (motdString != null) {
Component motdComponent = this.miniMessage.parse(motdString);
final MinecraftVersion version = event.getClient().getVersion();
if (!version.isLegacy() && this.SpongeMinecraftVersion_getProtocol == null) {
this.SpongeMinecraftVersion_getProtocol = version.getClass().getMethod("getProtocol");
}
if (version.isLegacy() || (int) this.SpongeMinecraftVersion_getProtocol.invoke(version) < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
motdComponent = GsonComponentSerializer.colorDownsamplingGson().deserialize(GsonComponentSerializer.colorDownsamplingGson().serialize(motdComponent));
}
response.setDescription(motdComponent);
}
} catch (final ReflectiveOperationException e) {
throw new IllegalStateException("Failed to get protocol version", e);
}
final Favicon favicon = pair.icon();
if (favicon != null) {
response.setFavicon(favicon);
}
if (config.disablePlayerListHover()) {
players.getProfiles().clear();
}
});
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) throws Exception {
return Objects.requireNonNull(Favicon.load(image), "failed to load favicon");
}
}
@@ -1,29 +0,0 @@
{
"plugins": [
{
"loader": "java_plain",
"id": "{project.name}",
"name": "{rootProject.name}",
"version": "{version}",
"main-class": "xyz.jpenilla.minimotd.sponge8.MiniMOTDPlugin",
"description": "{description}",
"links": {
"homepage": "{url}",
"source": "{url}",
"issues": "{url}issues"
},
"contributors": [
{
"name": "jmp",
"description": "Lead Developer"
}
],
"dependencies": [
{
"id": "spongeapi",
"version": "8.0.0"
}
]
}
]
}
-18
View File
@@ -1,18 +0,0 @@
tasks {
val universal = register<Jar>("universal") {
artifacts.add("archives", this)
archiveClassifier.set("")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
arrayOf("spigot", "bungeecord", "velocity", "sponge8").forEach {
val shadowJar = rootProject.project(":minimotd-$it").tasks.getByName("shadowJar")
from(zipTree(shadowJar.outputs.files.singleFile))
dependsOn(shadowJar)
}
val fabricRemapJarTask = rootProject.project(":minimotd-fabric").tasks.getByName("remapJar")
dependsOn(fabricRemapJarTask)
from(zipTree(fabricRemapJarTask.outputs.files.singleFile))
}
build {
dependsOn(universal)
}
}
-50
View File
@@ -1,50 +0,0 @@
import net.kyori.blossom.BlossomExtension
plugins {
id("net.kyori.blossom") version "1.1.0"
}
dependencies {
implementation(project(":minimotd-common")) {
exclude("org.slf4j", "slf4j-api")
}
implementation("org.bstats", "bstats-velocity", "2.2.0")
compileOnly("com.velocitypowered", "velocity-api", "1.1.4")
annotationProcessor("com.velocitypowered", "velocity-api", "1.1.4")
}
tasks {
shadowJar {
relocate("org.spongepowered.configurate", "xyz.jpenilla.minimotd.lib.spongepowered.configurate")
relocate("io.leangen.geantyref", "xyz.jpenilla.minimotd.lib.io.leangen.geantyref")
relocate("com.typesafe.config", "xyz.jpenilla.minimotd.lib.typesafe.config")
relocate("net.kyori.adventure.text.minimessage", "xyz.jpenilla.minimotd.lib.kyori_native.minimessage")
relocate("xyz.jpenilla.minimotd.common", "xyz.jpenilla.minimotd.lib.velocity.minimotd.common")
relocate("org.checkerframework", "xyz.jpenilla.minimotd.lib.checkerframework")
relocate("org.bstats", "xyz.jpenilla.minimotd.lib.bstats")
dependencies {
exclude { dep -> dep.moduleGroup == "net.kyori" && !dep.name.contains("minimessage") }
}
}
build {
dependsOn(shadowJar)
}
}
blossom {
replaceTokens(
"src/main/java/xyz/jpenilla/minimotd/velocity/MiniMOTDPlugin.java",
"{project.name}" to project.name,
"{rootProject.name}" to rootProject.name,
"{version}" to version.toString(),
"{description}" to description!!,
"{url}" to rootProject.ext["url"].toString()
)
}
fun BlossomExtension.replaceTokens(
filePath: String,
vararg replacements: Pair<String, String>
) {
for ((token, replacement) in replacements) replaceToken(token, replacement, filePath)
}
@@ -1,168 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.ServerPing;
import com.velocitypowered.api.util.Favicon;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import org.bstats.velocity.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import xyz.jpenilla.minimotd.common.Constants;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
@Plugin(
id = "{project.name}",
name = "{rootProject.name}",
version = "{version}",
description = "{description}",
url = "{url}",
authors = {"jmp"}
)
public class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
private final MiniMOTD<Favicon> miniMOTD;
private final ProxyServer server;
private final Logger logger;
private final MiniMessage miniMessage = MiniMessage.get();
private final PluginContainer pluginContainer;
private final CommandManager commandManager;
private final Path dataDirectory;
private final Metrics.Factory metricsFactory;
@Inject
public MiniMOTDPlugin(
final @NonNull ProxyServer server,
final @NonNull Logger logger,
final @NonNull CommandManager commandManager,
final @NonNull PluginContainer pluginContainer,
@DataDirectory final @NonNull Path dataDirectory,
final Metrics.@NonNull Factory metricsFactory
) {
this.server = server;
this.logger = logger;
this.commandManager = commandManager;
this.pluginContainer = pluginContainer;
this.dataDirectory = dataDirectory;
this.metricsFactory = metricsFactory;
this.miniMOTD = new MiniMOTD<>(this);
this.miniMOTD.configManager().loadExtraConfigs();
}
@Subscribe
public void onProxyInitialization(final @NonNull ProxyInitializeEvent event) {
this.commandManager.register(this.commandManager.metaBuilder("minimotd").build(), new VelocityCommand(this));
this.metricsFactory.make(this, 10257);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
this.server.getScheduler().buildTask(this, () ->
new UpdateChecker(this.pluginDescription().getVersion().orElse("")).checkVersion().forEach(this.logger::info)
).schedule();
}
}
@Subscribe
public void onServerListPing(final @NonNull ProxyPingEvent ping) {
final String configString = this.miniMOTD.configManager().pluginSettings().configStringForHost(
ping.getConnection().getVirtualHost()
.map(inetSocketAddress -> String.format("%s:%s", inetSocketAddress.getHostName(), inetSocketAddress.getPort()))
.orElse("default")
).orElse("default");
final MiniMOTDConfig config = this.miniMOTD.configManager().resolveConfig(configString);
final ServerPing.Builder pong = ping.getPing().asBuilder();
final int onlinePlayers = this.miniMOTD.calculateOnlinePlayers(config, pong.getOnlinePlayers());
pong.onlinePlayers(onlinePlayers);
final int maxPlayers = config.adjustedMaxPlayers(onlinePlayers, pong.getMaximumPlayers());
pong.maximumPlayers(maxPlayers);
final MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
final Favicon favicon = pair.icon();
if (favicon != null) {
pong.favicon(favicon);
}
final String motdString = pair.motd();
if (motdString != null) {
Component motdComponent = this.miniMessage.parse(motdString);
if (pong.getVersion().getProtocol() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
motdComponent = GsonComponentSerializer.colorDownsamplingGson().deserialize(GsonComponentSerializer.colorDownsamplingGson().serialize(motdComponent));
}
pong.description(motdComponent);
}
if (config.disablePlayerListHover()) {
pong.clearSamplePlayers();
}
ping.setPing(pong.build());
}
public @NonNull MiniMOTD<Favicon> miniMOTD() {
return this.miniMOTD;
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) {
return Favicon.create(image);
}
public @NonNull MiniMessage miniMessage() {
return this.miniMessage;
}
public @NonNull PluginDescription pluginDescription() {
return this.pluginContainer.getDescription();
}
}
@@ -1,109 +0,0 @@
/*
* This file is part of MiniMOTD, licensed under the MIT License.
*
* Copyright (c) 2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* 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.
*/
package xyz.jpenilla.minimotd.velocity;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.SimpleCommand;
import net.kyori.adventure.identity.Identity;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
public class VelocityCommand implements SimpleCommand {
private final MiniMOTDPlugin plugin;
public VelocityCommand(final @NonNull MiniMOTDPlugin plugin) {
this.plugin = plugin;
}
@Override
public void execute(final @NonNull Invocation invocation) {
final String[] args = invocation.arguments();
if (args.length == 0) {
this.onInvalidUse(invocation);
return;
}
switch (args[0]) {
case "about":
this.onAbout(invocation);
return;
case "help":
this.onHelp(invocation);
return;
case "reload":
this.onReload(invocation);
return;
}
this.onInvalidUse(invocation);
}
private void onInvalidUse(final @NonNull Invocation invocation) {
this.sendMessages(invocation, ImmutableList.of("<hover:show_text:'<green>Click for <yellow>/minimotd help'><click:run_command:/minimotd help><italic><gradient:red:gold>Invalid usage.</gradient> <blue>Try <yellow>/minimotd help</yellow> or click here"));
}
private void onReload(final @NonNull Invocation invocation) {
this.plugin.miniMOTD().iconManager().loadIcons();
this.plugin.miniMOTD().configManager().loadExtraConfigs();
this.plugin.miniMOTD().configManager().loadConfigs();
this.sendMessages(invocation, ImmutableList.of("<green>Done reloading."));
}
private void onHelp(final @NonNull Invocation invocation) {
this.sendMessages(invocation, ImmutableList.of(
"<gradient:blue:green>MiniMOTD Commands:",
" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd about'><click:run_command:/minimotd about><yellow>/minimotd about",
" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd reload'><click:run_command:/minimotd reload><yellow>/minimotd reload",
" <gray>-</gray> <hover:show_text:'<green>Click for <yellow>/minimotd help'><click:run_command:/minimotd help><yellow>/minimotd help"
));
}
private void onAbout(final @NonNull Invocation invocation) {
final String header = "<gradient:white:black>=============</gradient><gradient:black:white>=============";
this.sendMessages(invocation, ImmutableList.of(
header,
"<hover:show_text:'<rainbow>click me!'><click:open_url:" + this.plugin.pluginDescription().getUrl().orElse("") + ">" + this.plugin.pluginDescription().getName().orElse("") + " <gradient:red:yellow>" + this.plugin.pluginDescription().getVersion().orElse(""),
"<yellow>By</yellow><gray>:</gray> <gradient:blue:green>jmp",
header
));
}
private void sendMessages(final @NonNull Invocation invocation, final @NonNull List<String> messages) {
messages.forEach(message -> invocation.source().sendMessage(Identity.nil(), this.plugin.miniMessage().parse(message)));
}
private static final List<String> COMMANDS = ImmutableList.of("about", "reload", "help");
@Override
public List<String> suggest(final @NonNull Invocation invocation) {
return COMMANDS;
}
@Override
public boolean hasPermission(final @NonNull Invocation invocation) {
return invocation.source().hasPermission("minimotd.admin");
}
}