Compare commits

...
66 Commits
Author SHA1 Message Date
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
jmp fdfe651c02 Bump version, update readme 2021-02-14 18:14:05 -08:00
jmp c133e0452b Bump velocity-api, remove some duplication of int constant 2021-02-14 14:09:24 -08:00
Jason 93effdf83a Update GitHub Actions 2021-02-12 16:24:18 -08:00
jmp 186f174572 update bStats/add Velocity bStats 2021-02-12 16:18:48 -08:00
jmp 2b20b4b786 fabric: minor clean up 2021-02-10 21:03:51 -08:00
jmp 966943c161 Update bStats 2021-02-08 15:50:25 -08:00
jmp 04346d582d fix relocations again 2021-02-04 23:44:42 -08:00
jmp d00825bed9 fabric: fix logging 2021-02-04 22:58:01 -08:00
jmp 2fbbc9c0e3 update Indra to 1.3.1 2021-02-04 18:25:39 -08:00
jmp a1f01f959b fix relocations 2021-02-04 18:20:22 -08:00
jmp c97d8ecea6 make the sponge-8 module work 2021-02-04 17:55:39 -08:00
jmp 43b01fe833 move minimotd-fabric into main project 2021-01-22 03:27:33 -08:00
jmp b5667f75e5 fix virtual host configs, and add example configs 2021-01-21 19:50:54 -08:00
jmp f695835748 allow setting configs per virtual host 2021-01-21 01:59:47 -08:00
jmp 807fac6e71 switch configs to using configurate 2021-01-20 17:23:46 -08:00
jmp 31179943f7 Bump version to 1.2.5-SNAPSHOT 2021-01-13 21:24:52 -08:00
jmp a847dc5dd8 Looks like we need to use \n for newlines, even on Windows... 2021-01-13 21:19:55 -08:00
jmp 77f3ccf734 Bump version to 1.2.4-SNAPSHOT 2020-12-26 16:46:56 -08:00
jmp 541b6bb623 Improve universal jar 2020-12-26 15:13:51 -08:00
jmp 454827731f Allow pairing specific icons and motds 2020-12-20 21:48:28 -08:00
jmp bbb65c2638 Update readme 2020-12-20 19:35:00 -08:00
jmp a7f4161551 Fix gitignore 2020-12-20 19:01:34 -08:00
jmp e08ffee2f9 Build with gradle 2020-12-20 19:00:01 -08:00
jmp 93ae27770e Implement player list hover option on bungee 2020-11-30 22:27:00 -08:00
jmp b6c39163b7 Add option to disable player list hover (closes #30) 2020-11-30 22:19:28 -08:00
Renovate Bot 8f9697c0a7 Update dependency net.kyori:adventure-api to v4.2.0 2020-11-30 21:44:58 -08:00
Renovate Bot 81adaf4e1a Update dependency com.velocitypowered:velocity-api to v1.1.2 2020-11-30 21:44:51 -08:00
jmp 9093ebfecd Use native schedulers instead of CompletableFuture for update checker 2020-10-28 12:25:08 -07:00
jmp 76552f52a7 Use System.lineSeparator() instead of \n for newlines 2020-10-28 12:14:41 -07:00
jmp d17a1f7948 Update velocity-api from 1.1.0-SNAPSHOT to 1.1.0 2020-10-28 11:44:51 -07:00
Renovate Bot 4c6ad898a1 Update dependency org.projectlombok:lombok to v1.18.16 2020-10-27 16:08:23 -07:00
Renovate Bot e5c3a723a2 Update dependency org.apache.maven.plugins:maven-source-plugin to v3.2.1 2020-10-27 16:07:13 -07:00
Renovate Bot 314d7b15b4 Update actions/upload-artifact action to v2 2020-10-24 11:14:23 -07:00
jmp a66faf7251 Update adventure-api 2020-10-18 17:19:04 -07:00
jmp 0b948367e2 Update adventure-api 2020-10-15 23:53:57 -07:00
jmp c2ee7e8f4b Update for adventure-api changes 2020-10-14 12:08:19 -07:00
jmp df665e5810 Update dependencies 2020-10-10 23:04:15 -07:00
jmp 382229cbbf revert listener priority change 2020-09-23 12:04:48 -07:00
jmp 5257fa9e3b adjust spigot listener priority 2020-09-23 11:37:42 -07:00
jmp 21b991b313 bump to 1.2.2 2020-09-14 11:39:38 -07:00
jmp 3720fec2f5 add issue templates 2020-09-13 09:32:52 -07:00
jmp 6fe11c729a respect update checker toggle 2020-09-12 22:40:46 -07:00
jmp c1e40216d4 bump MiniMessage 2020-09-12 10:44:59 -07:00
jmp b04de1b951 the update checker shouldn't be blocking :P 2020-09-12 01:54:54 -07:00
jmp 5673730693 don't strip colors from console; spigot (and soon paper) support rgb in console now 2020-09-12 01:40:20 -07:00
jmp 60b83031ab update GH actions 2020-09-12 00:19:06 -07:00
jmp 2a750c289b update VelocityCommand 2020-09-12 00:18:07 -07:00
jmp 0092a2b6b7 add Velocity command, add update checker, bump version 2020-09-12 00:15:18 -07:00
Jason 0847d15eef Merge pull request #9 from jmanpenilla/renovate/configure
Configure Renovate
2020-09-10 18:10:03 -07:00
jmp c9d2f3fdb7 Set up multi-module, add Velocity support 2020-09-10 17:51:15 -07:00
Renovate Bot db0ea2e656 Add renovate.json 2020-08-28 02:52:04 +00:00
jmp 7741523e56 use fixed MiniMessage build 2020-08-25 18:33:09 -07:00
jmp f858f29d67 added random server icon feature. place 64x64 pngs in the plugins/MiniMOTD/icons/ folder 2020-08-25 18:29:44 -07:00
jmp 725855124c - add tab completor on spigot.
- always downsample colors on old version servers which don't support sending rgb colors.
2020-08-15 14:39:49 -07:00
jmp 84f6f2b5d5 add comment to config.yml 2020-08-01 23:37:54 -07:00
jmp 2e2fe64923 1.1.6 2020-08-01 23:33:26 -07:00
74 changed files with 3799 additions and 745 deletions

No files matched your search

+245
View File
@@ -0,0 +1,245 @@
<?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"/>
<!-- 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_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 ####
#####################
-->
</module>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "http://checkstyle.org/dtds/suppressions_1_2.dtd">
<suppressions>
<!-- This file is modified from KyoriPowered/adventure's suppressions.xml -->
<!-- no javadoc on test classes -->
<suppress files="src[\\/]test[\\/]java[\\/].*" checks="FilteringWriteTag"/>
<suppress files="src[\\/]test[\\/]java[\\/].*" checks="MissingJavadoc.*"/>
</suppressions>
+8
View File
@@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = off
+6
View File
@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
+32
View File
@@ -0,0 +1,32 @@
---
name: Bug Report
about: Report a bug
title: "[Bug Report] This happens when another thing should have"
labels: bug, unconfirmed
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Dig straight down
2. Right click 5 times
3. Craft a Netherite Hoe
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Server Software and Version:**
- 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)]
**Additional context**
Add any other context about the problem here.
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+20
View File
@@ -0,0 +1,20 @@
---
name: Feature Request
about: Suggest a feature
title: "[Feature Request] My Feature"
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-6
View File
@@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "daily"
+4 -5
View File
@@ -13,15 +13,14 @@ jobs:
steps:
- uses: actions/checkout@v2
- run: sed "s/{BUILDID}/$GITHUB_RUN_NUMBER/g" pom.xml > _pom.xml; rm pom.xml; mv _pom.xml pom.xml
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Build with Maven
run: mvn clean package
- run: mkdir staging && cp target/MiniMOTD*.jar staging
- uses: actions/upload-artifact@v1
- 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
+7 -1
View File
@@ -18,7 +18,6 @@ out/
*.ctxt
# Package Files #
*.jar
*.war
*.nar
*.ear
@@ -78,6 +77,7 @@ ehthumbs_vista.db
# Dump file
*.stackdump
*.hprof
# Folder config file
[Dd]esktop.ini
@@ -111,3 +111,9 @@ buildNumber.properties
# Common working directory
run/
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
+24 -3
View File
@@ -1,7 +1,28 @@
![MiniMOTD logo](https://i.imgur.com/kzapjUA.png)
![MiniMOTD logo](https://i.imgur.com/CXWwjOJ.png)
> MiniMOTD is a basic MOTD Spigot plugin that is compatible with legacy Minecraft versions up through 1.16+ (Also supports BungeeCord). MiniMOTD gets it's name from [MiniMessage](https://github.com/KyoriPowered/adventure-text-minimessage), the library used to parse text in MiniMOTD.
### MiniMOTD is a basic server list MOTD plugin/mod for Minecraft servers and proxies
- MiniMOTD supports RGB colors and gradients through [MiniMessage](https://github.com/KyoriPowered/adventure-text-minimessage), which is also where MiniMOTD gets it's name.
- For more detailed info on formatting text, refer to the [MiniMessage docs](https://docs.adventure.kyori.net/minimessage.html).
- RGB colors are automatically downsampled for outdated clients.
- RGB colors are only able to be sent by proxies and 1.16+ servers, and can only be seen by 1.16+ clients.
![demo motd image](https://i.imgur.com/5yqJx8t.png)
#### Server Platforms
- [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/)
- [Fabric](https://fabricmc.net/) (requires [Fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api))
#### Proxy Platforms
- [Velocity](https://velocitypowered.com/)
- [Waterfall](https://papermc.io/downloads#Waterfall) / Bungeecord
#### Downloads
Downloads can be obtained from [GitHub releases](https://github.com/jpenilla/MiniMOTD/releases). There is a separate jar for each platform, as well as a universal jar for all platforms. Paper and Spigot share the same jar, as do Waterfall and Bungeecord.
#### Configuration
See the [wiki](https://github.com/jpenilla/MiniMOTD/wiki) for configuration details
#### Screenshots
![demo motd image](https://i.ibb.co/1LRCV8b/minimotd-demo.png)
-24
View File
@@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
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 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.
For more information, please refer to <https://unlicense.org>
+101
View File
@@ -0,0 +1,101 @@
import com.adarshr.gradle.testlogger.TestLoggerPlugin
import com.adarshr.gradle.testlogger.theme.ThemeType
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"
id("com.adarshr.test-logger") version "2.1.1"
id("net.kyori.blossom") version "1.1.0" apply false
}
allprojects {
group = "xyz.jpenilla"
version = "2.0.1+${lastCommitHash()}-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>()
apply<TestLoggerPlugin>()
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")
}
dependencies {
testImplementation("org.junit.jupiter", "junit-jupiter-engine", "5.7.0")
}
indra {
javaVersions {
testWith(8, 11, 15)
target.set(8)
}
github("jpenilla", "MiniMOTD") {
issues = true
}
mitLicense()
}
testlogger {
theme = ThemeType.MOCHA_PARALLEL
showPassed = false
}
tasks {
shadowJar {
minimize()
if (!project.name.contains("fabric")) {
archiveClassifier.set("")
doLast {
val output = outputs.files.singleFile
output.copyTo(rootProject.buildDir.resolve("libs").resolve(output.name), overwrite = true)
}
}
}
withType<JavaCompile> {
options.compilerArgs.add("-Xlint:-processing")
}
withType<Jar> {
onlyIf { archiveClassifier.get() != "javadoc" }
}
withType<Javadoc> {
onlyIf { false }
}
}
}
tasks.withType<Jar> {
onlyIf { false }
}
fun lastCommitHash(): String = ByteArrayOutputStream().apply {
exec {
commandLine = listOf("git", "rev-parse", "--short", "HEAD")
standardOutput = this@apply
}
}.toString(Charsets.UTF_8.name()).trim()
+31
View File
@@ -0,0 +1,31 @@
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"
}
@@ -0,0 +1,80 @@
/*
* 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.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
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.CommandHandlerFactory;
public class BungeeCommand extends Command {
private final MiniMOTDPlugin plugin;
private final CommandHandlerFactory handlerFactory;
public BungeeCommand(final @NonNull MiniMOTDPlugin plugin) {
super("minimotd");
this.plugin = plugin;
this.handlerFactory = new CommandHandlerFactory(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(Component.text("No permission.", NamedTextColor.RED));
return;
}
if (args.length == 0) {
this.onInvalidUse(audience);
return;
}
switch (args[0]) {
case "about":
this.handlerFactory.about().execute(audience);
return;
case "help":
this.handlerFactory.help().execute(audience);
return;
case "reload":
this.handlerFactory.reload().execute(audience);
return;
}
this.onInvalidUse(audience);
}
private void onInvalidUse(final @NonNull Audience audience) {
audience.sendMessage(
Component.text("Invalid command usage. Use '/minimotd help' for a list of command provided by MiniMOTD.", NamedTextColor.RED)
.hoverEvent(Component.text("Click to execute '/minimotd help'"))
.clickEvent(ClickEvent.runCommand("/minimotd help"))
);
}
}
@@ -0,0 +1,88 @@
/*
* 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.platform.bungeecord.BungeeAudiences;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
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;
public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon> {
private Logger logger;
private BungeeAudiences audiences;
private MiniMOTD<Favicon> miniMOTD;
public @NonNull MiniMOTD<Favicon> miniMOTD() {
return this.miniMOTD;
}
@Override
public void onEnable() {
this.logger = LoggerFactory.getLogger(this.getDescription().getName());
this.miniMOTD = new MiniMOTD<>(this);
this.miniMOTD.configManager().loadExtraConfigs();
this.audiences = BungeeAudiences.create(this);
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().checkVersion().forEach(getLogger()::info));
}
}
@Override
public @NonNull Path dataDirectory() {
return this.getDataFolder().toPath();
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) {
return Favicon.create(image);
}
public @NonNull BungeeAudiences audiences() {
return this.audiences;
}
@Override
public void onReload() {
this.miniMOTD.configManager().loadExtraConfigs();
}
}
@@ -0,0 +1,95 @@
/*
* 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.serializer.bungeecord.BungeeComponentSerializer;
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.ComponentColorDownsampler;
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 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) {
return;
}
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());
final int maxPlayers = cfg.adjustedMaxPlayers(onlinePlayers, players.getMax());
if (cfg.hidePlayerCount()) {
response.setPlayers(null);
} else {
players.setOnline(onlinePlayers);
players.setMax(maxPlayers);
if (cfg.disablePlayerListHover()) {
players.setSample(new ServerPing.PlayerInfo[]{});
}
}
final MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
Component motdComponent = pair.motd();
if (motdComponent != null) {
if (e.getConnection().getVersion() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
motdComponent = ComponentColorDownsampler.downsampler().downsample(motdComponent);
}
response.setDescriptionComponent(new TextComponent(BungeeComponentSerializer.get().serialize(motdComponent)));
}
final Favicon favicon = pair.icon();
if (favicon != null) {
response.setFavicon(favicon);
}
e.setResponse(response);
}
}
+25
View File
@@ -0,0 +1,25 @@
plugins {
id("net.kyori.blossom")
}
dependencies {
api("org.slf4j", "slf4j-api", "1.7.30")
api("org.spongepowered", "configurate-hocon", "4.1.0-SNAPSHOT")
platform(api("net.kyori", "adventure-bom", "4.7.0"))
api("net.kyori", "adventure-text-serializer-gson")
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")
}
blossom {
fun replaceTokens(file: String, vararg tokens: Pair<String, String>) = tokens.forEach { (k, v) ->
replaceToken("\${$k}", v, file)
}
replaceTokens(
"src/main/java/xyz/jpenilla/minimotd/common/Constants.java",
"PLUGIN_NAME" to rootProject.name,
"PLUGIN_VERSION" to project.version.toString(),
"PLUGIN_WEBSITE" to rootProject.ext["url"].toString()
)
}
@@ -0,0 +1,124 @@
/*
* 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.common;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.stream.Stream;
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.WHITE;
import static net.kyori.adventure.text.format.TextColor.color;
import static net.kyori.adventure.text.format.TextDecoration.STRIKETHROUGH;
public final class CommandHandlerFactory {
private final MiniMOTD<?> miniMOTD;
public CommandHandlerFactory(final @NonNull MiniMOTD<?> miniMOTD) {
this.miniMOTD = miniMOTD;
}
public @NonNull CommandHandler about() {
return 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 @NonNull CommandHandler reload() {
return audience -> {
this.miniMOTD.reload();
audience.sendMessage(linear(Constants.COMMAND_PREFIX, space(), text("Done reloading configuration.", NamedTextColor.GREEN)));
};
}
public @NonNull CommandHandler help() {
return 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 @NonNull Component commandInfo(final @NonNull String command, final @NonNull String description) {
return text()
.content(" - ")
.color(GRAY)
.append(
text(command, color(0x007FFF))
.replaceText(config -> {
config.matchLiteral("/");
config.replacement(builder -> builder.color(WHITE));
})
)
.append(text(':', GRAY))
.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 @NonNull Component miniMessage(final @NonNull String message) {
return MiniMessage.get().parse(message);
}
@FunctionalInterface
public interface CommandHandler {
void execute(@NonNull Audience audience);
}
}
@@ -0,0 +1,50 @@
/*
* 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.common;
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)
);
}
}
}
@@ -0,0 +1,45 @@
/*
* 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.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 int MINECRAFT_1_16_PROTOCOL_VERSION = 735;
public static final Component COMMAND_PREFIX = MiniMessage.get().parse("<white>[</white><gradient:#0047AB:#ADD8E6>" + PluginMetadata.NAME + "<white>]</white>");
}
@@ -0,0 +1,89 @@
/*
* 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.common;
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 MiniMOTD<I> miniMOTD;
private final File iconsDirectory;
public IconManager(final @NonNull MiniMOTD<I> miniMOTD) {
this.miniMOTD = miniMOTD;
this.iconsDirectory = miniMOTD.dataDirectory().resolve("icons").toFile();
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);
}
}
}
}
public @Nullable I icon(final @Nullable String iconString) {
if (this.icons.isEmpty()) {
return null;
}
if (iconString == null || "random".equals(iconString)) {
final int randomIndex = ThreadLocalRandom.current().nextInt(this.icons.size());
final Iterator<I> iterator = this.icons.values().iterator();
for (int i = 0; i < randomIndex; i++) {
iterator.next();
}
return iterator.next();
}
return this.icons.get(iconString);
}
}
@@ -0,0 +1,47 @@
/*
* 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.common;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class MOTDIconPair<I> {
private final I icon;
private final Component motd;
public MOTDIconPair(final @Nullable I icon, final @Nullable Component motd) {
this.icon = icon;
this.motd = motd;
}
public @Nullable I icon() {
return this.icon;
}
public @Nullable Component motd() {
return this.motd;
}
}
@@ -0,0 +1,129 @@
/*
* 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.common;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.minimessage.MiniMessage;
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;
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) {
this.platform = platform;
this.iconManager = new IconManager<>(this);
this.configManager = new ConfigManager(this);
this.configManager.loadConfigs();
}
public @NonNull MiniMOTDPlatform<I> platform() {
return this.platform;
}
public final @NonNull Path dataDirectory() {
return this.platform.dataDirectory();
}
public final @NonNull IconManager<I> iconManager() {
return this.iconManager;
}
public final @NonNull Logger logger() {
return this.platform.logger();
}
public final @NonNull ConfigManager configManager() {
return this.configManager;
}
// todo: move 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;
Component motd = null;
String iconString = null;
if (config.motdEnabled()) {
if (config.motds().size() == 0) {
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 motdConfig = config.motds().get(index);
motd = TextComponent.ofChildren(
MiniMessage.get().parse(replacePlayerCount(motdConfig.line1(), onlinePlayers, maxPlayers)),
Component.newline(),
MiniMessage.get().parse(replacePlayerCount(motdConfig.line2(), onlinePlayers, maxPlayers))
);
iconString = motdConfig.icon();
}
if (config.iconEnabled()) {
icon = this.iconManager().icon(iconString);
}
return new MOTDIconPair<>(icon, motd);
}
private static @NonNull String replacePlayerCount(final @NonNull String input, final int onlinePlayers, final int maxPlayers) {
return input.replace("{onlinePlayers}", String.valueOf(onlinePlayers))
.replace("{maxPlayers}", String.valueOf(maxPlayers));
}
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]);
return realOnlinePlayers + ThreadLocalRandom.current().nextInt(start, end);
} else if (fakePlayersConfigString.contains("%")) {
final double factor = 1 + (Double.parseDouble(fakePlayersConfigString.replace("%", "")) / 100);
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 void reload() {
this.iconManager.loadIcons();
this.configManager.loadConfigs();
this.platform.onReload();
}
}
@@ -0,0 +1,41 @@
/*
* 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.common;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
public interface MiniMOTDPlatform<I> {
@NonNull Path dataDirectory();
@NonNull Logger logger();
@NonNull I loadIcon(@NonNull BufferedImage image) throws Exception;
default void onReload() {
}
}
@@ -0,0 +1,64 @@
/*
* 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.common;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
public final class Pair<L, R> {
private final L left;
private final R right;
private Pair(final @NonNull L left, final @NonNull R right) {
this.left = left;
this.right = right;
}
public @NonNull L left() {
return this.left;
}
public @NonNull R right() {
return this.right;
}
@Override
public boolean equals(final @Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Pair<?, ?> pair = (Pair<?, ?>) o;
return this.left.equals(pair.left) && this.right.equals(pair.right);
}
@Override
public int hashCode() {
return Objects.hash(this.left, this.right);
}
public static <L, R> @NonNull Pair<L, R> of(final @NonNull L left, final @NonNull R right) {
return new Pair<>(left, right);
}
}
@@ -0,0 +1,72 @@
/*
* 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.common;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public final class UpdateChecker {
private final JsonParser parser = new JsonParser();
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) {
return Collections.singletonList("Cannot look for updates: " + exception.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" + Constants.PluginMetadata.VERSION;
if (versionList.get(0).equals(currentVersion)) {
return Collections.emptyList(); // Up to date, do nothing
}
if (currentVersion.contains("SNAPSHOT")) {
return ImmutableList.of(
"This server is running a development build of MiniMOTD! (" + currentVersion + ")",
"The latest official release is " + versionList.get(0)
);
}
final int versionsBehind = versionList.indexOf(currentVersion);
return ImmutableList.of(
"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,92 @@
/*
* 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.common.config;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.configurate.CommentedConfigurationNode;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.hocon.HoconConfigurationLoader;
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;
public final class ConfigLoader<C> {
private static final TypeSerializerCollection SERIALIZERS;
static {
SERIALIZERS = TypeSerializerCollection.defaults();
}
private final HoconConfigurationLoader loader;
private final ObjectMapper<C> mapper;
public ConfigLoader(
final @NonNull Class<C> configClass,
final @NonNull Path configPath,
final @NonNull ConfigurationOptions options
) {
this.loader = HoconConfigurationLoader.builder()
.path(configPath)
.defaultOptions(options.serializers(SERIALIZERS))
.build();
try {
this.mapper = ObjectMapper.factory().get(configClass);
} catch (final SerializationException ex) {
throw new IllegalStateException(
"Failed to initialize an object mapper for type: " + configClass.getSimpleName(),
ex
);
}
}
public ConfigLoader(
final @NonNull Class<C> configClass,
final @NonNull Path configPath,
final @NonNull UnaryOperator<ConfigurationOptions> options
) {
this(configClass, configPath, options.apply(ConfigurationOptions.defaults()));
}
public ConfigLoader(
final @NonNull Class<C> configClass,
final @NonNull Path configPath
) {
this(configClass, configPath, ConfigurationOptions.defaults());
}
public @NonNull C load() throws ConfigurateException {
final CommentedConfigurationNode node = this.loader.load();
return this.mapper.load(node);
}
public void save(final @NonNull C config) throws ConfigurateException {
final CommentedConfigurationNode node = this.loader.createNode();
this.mapper.save(config, node);
this.loader.save(node);
}
}
@@ -0,0 +1,144 @@
/*
* 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.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.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public final class ConfigManager {
private final MiniMOTD<?> miniMOTD;
private final ConfigLoader<MiniMOTDConfig> mainConfigLoader;
private MiniMOTDConfig mainConfig;
private final ConfigLoader<PluginSettings> pluginSettingsLoader;
private PluginSettings pluginSettings;
private final Map<String, MiniMOTDConfig> extraConfigs = new HashMap<>();
public ConfigManager(final @NonNull MiniMOTD<?> miniMOTD) {
this.miniMOTD = miniMOTD;
this.mainConfigLoader = new ConfigLoader<>(
MiniMOTDConfig.class,
this.miniMOTD.dataDirectory().resolve("main.conf"),
options -> options.header("MiniMOTD Main Configuration")
);
this.pluginSettingsLoader = new ConfigLoader<>(
PluginSettings.class,
this.miniMOTD.dataDirectory().resolve("plugin_settings.conf"),
options -> options.header("MiniMOTD Plugin Configuration")
);
}
public void loadConfigs() {
try {
this.mainConfig = this.mainConfigLoader.load();
this.mainConfigLoader.save(this.mainConfig);
this.pluginSettings = this.pluginSettingsLoader.load();
this.pluginSettingsLoader.save(this.pluginSettings);
} catch (final ConfigurateException e) {
throw new IllegalStateException("Failed to load config", e);
}
}
public void loadExtraConfigs() {
this.extraConfigs.clear();
final Path extraConfigsDir = this.miniMOTD.dataDirectory().resolve("extra-configs");
try {
if (!Files.exists(extraConfigsDir)) {
Files.createDirectories(extraConfigsDir);
this.createDefaultExtraConfigs(extraConfigsDir);
}
for (final Path path : Files.list(extraConfigsDir).collect(Collectors.toList())) {
if (path.toString().endsWith(".conf")) {
final String name = path.getFileName().toString().replace(".conf", "");
final ConfigLoader<MiniMOTDConfig> loader = new ConfigLoader<>(
MiniMOTDConfig.class,
path,
options -> options.header(String.format("Extra MiniMOTD config '%s'", name))
);
final MiniMOTDConfig config = loader.load();
loader.save(config);
this.extraConfigs.put(name, config);
}
}
} catch (final IOException e) {
throw new IllegalStateException("Failed to load virtual host configs", e);
}
}
private void createDefaultExtraConfigs(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,
pair.left()
);
loader.save(new MiniMOTDConfig(ImmutableList.of(pair.right())));
}
}
public @NonNull MiniMOTDConfig mainConfig() {
if (this.mainConfig == null) {
throw new IllegalStateException("Config has not yet been loaded");
}
return this.mainConfig;
}
public @NonNull PluginSettings pluginSettings() {
if (this.pluginSettings == null) {
throw new IllegalStateException("Config has not yet been loaded");
}
return this.pluginSettings;
}
public @NonNull MiniMOTDConfig resolveConfig(final @NonNull String name) {
if ("default".equals(name)) {
return this.mainConfig();
}
final MiniMOTDConfig 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));
return this.mainConfig();
}
}
@@ -0,0 +1,187 @@
/*
* 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.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;
@ConfigSerializable
public final class MiniMOTDConfig {
public MiniMOTDConfig() {
this(Arrays.asList(
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);
}
@Comment("The list of MOTDs to display\n"
+ "\n"
+ " - Supported placeholders: {onlinePlayers}, {maxPlayers}\n"
+ " - Putting more than one will cause one to be randomly chosen each refresh")
private final List<MOTD> motds = new ArrayList<>();
@Comment("Enable MOTD-related features")
private boolean motdEnabled = true;
@Comment("Enable server list icon related features")
private boolean iconEnabled = true;
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 {
public MOTD() {
}
public MOTD(final @NonNull String line1, final @NonNull String line2) {
this.line1 = line1;
this.line2 = line2;
}
private String line1 = "<rainbow>MiniMOTD Default";
private String line2 = "MiniMessage <gradient:blue:red>Gradients";
@Comment("Set the icon to use with this MOTD\n"
+ " Either use 'random' to randomly choose an icon, or use the name\n"
+ " of a file in the icons folder (excluding the '.png' extension)\n"
+ " ex: icon=\"myIconFile\"")
private String icon = "random";
public @NonNull String line1() {
return this.line1;
}
public @NonNull String line2() {
return this.line2;
}
public @NonNull String icon() {
return this.icon;
}
}
@ConfigSerializable
public static final class PlayerCountSettings {
@Comment("Enable modification of the max player count")
private boolean maxPlayersEnabled = true;
@Comment("Changes the Max Players value")
private int maxPlayers = 69;
@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();
@Comment("Changes the Max Players to be X more than the online players\n"
+ "ex: x=3 -> 16/19 players online.")
private JustXMore justXMoreSettings = new JustXMore();
@ConfigSerializable
public static final class JustXMore {
@Comment("Enable this feature")
private boolean justXMoreEnabled = false;
private int xValue = 3;
}
@ConfigSerializable
public static final class FakePlayers {
@Comment("Enable fake player count feature")
private boolean fakePlayersEnabled = false;
@Comment("Modes: static, random, percent\n"
+ "\n"
+ " - static: This many fake players will be added\n"
+ " ex: fakePlayers: \"3\"\n"
+ " - random: A random number of fake players in this range will be added\n"
+ " ex: fakePlayers: \"3:6\"\n"
+ " - percent: The player count will be inflated by this much, rounding up\n"
+ " ex: fakePlayers: \"25%\"")
private String fakePlayers = "25%";
}
}
public boolean iconEnabled() {
return this.iconEnabled;
}
public @NonNull List<MOTD> motds() {
return this.motds;
}
public boolean motdEnabled() {
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;
}
}
@@ -0,0 +1,69 @@
/*
* 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.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.HashMap;
import java.util.Map;
import java.util.Optional;
@ConfigSerializable
public final class PluginSettings {
@Comment("Do you want the plugin to check for updates on GitHub at launch?\n"
+ "https://github.com/jpenilla/MiniMOTD")
private boolean updateChecker = true;
@Comment("Settings only applicable when running the plugin on a proxy (Velocity or Waterfall/Bungeecord)")
private ProxySettings proxySettings = new ProxySettings();
@ConfigSerializable
public static final class ProxySettings {
public ProxySettings() {
this.virtualHostConfigs.put("minigames.example.com:25565", "default");
this.virtualHostConfigs.put("survival.example.com:25565", "survival");
this.virtualHostConfigs.put("skyblock.example.com:25565", "skyblock");
}
@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<>();
}
public boolean updateChecker() {
return this.updateChecker;
}
public @NonNull Optional<String> configStringForHost(final @NonNull String host) {
return Optional.ofNullable(this.proxySettings.virtualHostConfigs.get(host));
}
}
@@ -0,0 +1,65 @@
/*
* 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.common;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
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 org.junit.jupiter.api.Assertions.assertEquals;
class ComponentColorDownsamplerTest {
private static final TextColor GREEN = TextColor.color(0x04E762);
private static final TextColor ORANGE_YELLOW = TextColor.color(0xF5B700);
private static final TextColor PINK = TextColor.color(0xDC0073);
private static final TextColor BLUE = TextColor.color(0x008BF8);
private static final TextColor LIME_GREEN = TextColor.color(0x89FC00);
@Test
void testColorDownsampling() {
final Component fullColor = TextComponent.ofChildren(
text("Green ", GREEN),
text("Orange Yellow ", ORANGE_YELLOW),
text("Pink ", PINK),
text("Blue ", BLUE),
text("Lime Green", LIME_GREEN)
);
final Component expectedDownsample = TextComponent.ofChildren(
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
);
}
}
+54
View File
@@ -0,0 +1,54 @@
plugins {
id("fabric-loom") version "0.6-SNAPSHOT"
}
val shade: Configuration by configurations.creating
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.31.0+1.16")
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(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")
destinationDirectory.set(rootProject.buildDir.resolve("libs"))
}
processResources {
filesMatching("fabric.mod.json") {
mapOf(
"{project.name}" to project.name,
"{rootProject.name}" to rootProject.name,
"{project.version}" to project.version.toString(),
"{project.description}" to project.description.toString()
).forEach { (k, v) ->
filter { resource -> resource.replace(k, v) }
}
}
}
}
@@ -0,0 +1,146 @@
/*
* 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;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.Unpooled;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.kyori.adventure.platform.fabric.FabricServerAudiences;
import net.minecraft.commands.CommandSourceStack;
import org.apache.commons.lang3.Validate;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.jpenilla.minimotd.common.CommandHandlerFactory;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
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 final class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<String> {
private static MiniMOTDFabric instance = null;
private final Logger logger = LoggerFactory.getLogger(MiniMOTD.class);
private final Path dataDirectory = FabricLoader.getInstance().getConfigDir().resolve("MiniMOTD");
private final MiniMOTD<String> miniMOTD = new MiniMOTD<>(this);
private FabricServerAudiences audiences;
public MiniMOTDFabric() {
if (instance != null) {
throw new IllegalStateException("Cannot create a second instance of " + this.getClass().getName());
}
instance = this;
}
public @NonNull MiniMOTD<String> miniMOTD() {
return this.miniMOTD;
}
@Override
public void onInitialize() {
this.registerCommand();
ServerLifecycleEvents.SERVER_STARTED.register(minecraftServer -> {
this.audiences = FabricServerAudiences.of(minecraftServer);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
CompletableFuture.runAsync(() -> new UpdateChecker().checkVersion().forEach(this.logger()::info));
}
});
this.miniMOTD.logger().info("Done initializing MiniMOTD");
}
private void registerCommand() {
final class WrappingExecutor implements Command<CommandSourceStack> {
private final CommandHandlerFactory.CommandHandler handler;
WrappingExecutor(final CommandHandlerFactory.@NonNull CommandHandler handler) {
this.handler = handler;
}
@Override
public int run(final @NonNull CommandContext<CommandSourceStack> context) {
this.handler.execute(MiniMOTDFabric.this.audiences.audience(context.getSource()));
return Command.SINGLE_SUCCESS;
}
}
final CommandHandlerFactory handlerFactory = new CommandHandlerFactory(this.miniMOTD);
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> dispatcher.register(
literal("minimotd")
.requires(source -> source.hasPermission(4))
.then(literal("reload").executes(new WrappingExecutor(handlerFactory.reload())))
.then(literal("about").executes(new WrappingExecutor(handlerFactory.about())))
.then(literal("help").executes(new WrappingExecutor(handlerFactory.help())))
));
}
public @NonNull FabricServerAudiences audiences() {
return this.audiences;
}
public static @NonNull MiniMOTDFabric get() {
return instance;
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@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;
}
}
@@ -0,0 +1,30 @@
/*
* 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.access;
public interface ConnectionAccess {
void protocolVersion(int protocolVersion);
int protocolVersion();
}
@@ -0,0 +1,47 @@
/*
* 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 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)
@Implements({@Interface(iface = ConnectionAccess.class, prefix = "tabtps$")})
abstract class ConnectionMixin {
private int protocolVersion = -1;
public void tabtps$protocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public int tabtps$protocolVersion() {
return this.protocolVersion;
}
}
@@ -0,0 +1,47 @@
/*
* 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 net.minecraft.network.Connection;
import net.minecraft.network.protocol.handshake.ClientIntentionPacket;
import net.minecraft.server.network.ServerHandshakePacketListenerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import xyz.jpenilla.minimotd.fabric.access.ConnectionAccess;
@Unique
@Mixin(ServerHandshakePacketListenerImpl.class)
abstract class ServerHandshakePacketListenerImplMixin {
@Shadow @Final private Connection connection;
@Inject(method = "handleIntention", at = @At("HEAD"))
public void injectHandleIntention(final ClientIntentionPacket packet, final CallbackInfo ci) {
((ConnectionAccess) this.connection).protocolVersion(packet.getProtocolVersion());
}
}
@@ -0,0 +1,98 @@
/*
* 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.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.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
import xyz.jpenilla.minimotd.common.ComponentColorDownsampler;
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;
@Unique
@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 injectHandleStatusRequest(final MinecraftServer minecraftServer) {
final ServerStatus vanillaStatus = minecraftServer.getStatus();
final ServerStatus modifiedStatus = new ServerStatus();
modifiedStatus.setDescription(vanillaStatus.getDescription());
modifiedStatus.setFavicon(vanillaStatus.getFavicon());
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, vanillaStatus.getPlayers().getMaxPlayers());
final MOTDIconPair<String> pair = miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
final Component motdComponent = pair.motd();
if (motdComponent != null) {
if (((ConnectionAccess) this.connection).protocolVersion() >= Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
modifiedStatus.setDescription(miniMOTDFabric.audiences().toNative(motdComponent));
} else {
modifiedStatus.setDescription(miniMOTDFabric.audiences().toNative(
ComponentColorDownsampler.downsampler().downsample(motdComponent)
));
}
}
final String favicon = pair.icon();
if (favicon != null) {
modifiedStatus.setFavicon(favicon);
}
if (!config.hidePlayerCount()) {
final GameProfile[] oldSample = vanillaStatus.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;
}
}
+25
View File
@@ -0,0 +1,25 @@
{
"schemaVersion": 1,
"id": "{project.name}",
"version": "{project.version}",
"name": "{rootProject.name}",
"description": "{project.description}",
"authors": [
"jmp"
],
"license": "MIT",
"environment": "server",
"entrypoints": {
"main": [
"xyz.jpenilla.minimotd.fabric.MiniMOTDFabric"
]
},
"mixins": [
"minimotd-fabric.mixins.json"
],
"depends": {
"fabricloader": ">=0.11.1",
"fabric": ">=0.31.0",
"minecraft": "1.16.x"
}
}
@@ -0,0 +1,16 @@
{
"required": true,
"minVersion": "0.8",
"package": "xyz.jpenilla.minimotd.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"ConnectionMixin",
"ServerHandshakePacketListenerImplMixin",
"ServerStatusPacketListenerImplMixin"
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}
+2
View File
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx1G
#org.gradle.parallel=true
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# 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
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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
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"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
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.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
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" ;;
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, 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"
exec "$JAVACMD" "$@"
Vendored
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+21
View File
@@ -0,0 +1,21 @@
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.
+21
View File
@@ -0,0 +1,21 @@
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.
-171
View File
@@ -1,171 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.jpenilla</groupId>
<artifactId>MiniMOTD</artifactId>
<version>1.1.5</version>
<packaging>jar</packaging>
<name>MiniMOTD</name>
<description>Set the server list MOTD using MiniMessage!</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<url>https://spigotmc.org</url>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
<relocations>
<relocation>
<pattern>org.apache.commons.io</pattern>
<shadedPattern>xyz.jpenilla.minimotd.lib.apachecommonsio</shadedPattern>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>xyz.jpenilla.minimotd.lib.bstats</shadedPattern>
</relocation>
<relocation>
<pattern>xyz.jpenilla.jmplib</pattern>
<shadedPattern>xyz.jpenilla.minimotd.lib.jmplib</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori.examination</pattern>
<shadedPattern>xyz.jpenilla.minimotd.lib.kyori.examination</shadedPattern>
</relocation>
<relocation>
<pattern>net.kyori.adventure</pattern>
<shadedPattern>xyz.jpenilla.minimotd.lib.kyori.adventure</shadedPattern>
</relocation>
<relocation>
<pattern>org.checkerframework</pattern>
<shadedPattern>xyz.jpenilla.minimotd.lib.checkerframework</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
<repositories>
<repository>
<id>spigotmc-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/groups/public/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>CodeMC</id>
<url>https://repo.codemc.org/repository/maven-public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.12-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.16-R0.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bungeecord</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-serializer-bungeecord</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-platform-bukkit</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>net.kyori</groupId>
<artifactId>adventure-text-minimessage</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.DiamondDagger590</groupId>
<artifactId>Prisma</artifactId>
<version>a622d01b80</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>1.7</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bungeecord</artifactId>
<version>1.7</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
+5
View File
@@ -0,0 +1,5 @@
{
"extends": [
"config:base"
]
}
+36
View File
@@ -0,0 +1,36 @@
rootProject.name = "MiniMOTD"
pluginManagement {
repositories {
jcenter()
maven("https://maven.fabricmc.net/")
gradlePluginPortal()
}
}
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")
}
inline fun setupSubproject(name: String, block: ProjectDescriptor.() -> Unit) {
include(name)
project(":$name").apply(block)
}
+41
View File
@@ -0,0 +1,41 @@
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"
}
}
}
@@ -0,0 +1,117 @@
/*
* 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.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.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;
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 Logger logger;
@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]);
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);
} 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.audiences = BukkitAudiences.create(this);
final PluginCommand command = getCommand("minimotd");
if (command != null) {
command.setExecutor(new SpigotCommand(this));
command.setTabCompleter(new SpigotCommand(this));
}
final Metrics metrics = new Metrics(this, 8132);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Bukkit.getScheduler().runTaskAsynchronously(this, () ->
new UpdateChecker().checkVersion().forEach(getLogger()::info));
}
}
public @NonNull MiniMOTD<CachedServerIcon> miniMOTD() {
return this.miniMOTD;
}
@Override
public @NonNull Path dataDirectory() {
return this.getDataFolder().toPath();
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull CachedServerIcon loadIcon(final @NonNull BufferedImage image) throws Exception {
return Bukkit.loadServerIcon(image);
}
public int majorMinecraftVersion() {
return this.majorMinecraftVersion;
}
public @NonNull BukkitAudiences audiences() {
return this.audiences;
}
}
@@ -0,0 +1,80 @@
/*
* 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.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 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 Component motdComponent = pair.motd();
if (motdComponent != null) {
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();
}
if (cfg.hidePlayerCount()) {
e.setHidePlayers(true);
}
}
}
@@ -0,0 +1,73 @@
/*
* 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.Component;
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 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 Component motdComponent = pair.motd();
if (motdComponent != null) {
if (this.plugin.majorMinecraftVersion() > 15) {
e.setMotd(this.serializer.serialize(motdComponent));
} else {
e.setMotd(this.legacySerializer.serialize(motdComponent));
}
}
final CachedServerIcon favicon = pair.icon();
if (favicon != null) {
e.setServerIcon(favicon);
}
}
}
@@ -0,0 +1,96 @@
/*
* 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.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
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.CommandHandlerFactory;
import java.util.Collections;
import java.util.List;
public class SpigotCommand implements CommandExecutor, TabCompleter {
private final MiniMOTDPlugin plugin;
private final CommandHandlerFactory handlerFactory;
public SpigotCommand(final @NonNull MiniMOTDPlugin plugin) {
this.plugin = plugin;
this.handlerFactory = new CommandHandlerFactory(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(Component.text("No permission.", NamedTextColor.RED));
return true;
}
if (args.length == 0) {
this.onInvalidUse(audience);
return true;
}
switch (args[0]) {
case "about":
this.handlerFactory.about().execute(audience);
return true;
case "help":
this.handlerFactory.help().execute(audience);
return true;
case "reload":
this.handlerFactory.reload().execute(audience);
return true;
}
this.onInvalidUse(audience);
return true;
}
private void onInvalidUse(final @NonNull Audience audience) {
audience.sendMessage(
Component.text("Invalid command usage. Use '/minimotd help' for a list of command provided by MiniMOTD.", NamedTextColor.RED)
.hoverEvent(Component.text("Click to execute '/minimotd help'"))
.clickEvent(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();
}
}
+55
View File
@@ -0,0 +1,55 @@
import org.spongepowered.gradle.plugin.config.PluginLoaders.JAVA_PLAIN
import org.spongepowered.plugin.metadata.PluginDependency.LoadOrder.AFTER
plugins {
id("org.spongepowered.gradle.plugin") version "1.0.2"
}
dependencies {
implementation(project(":minimotd-common"))
}
sponge {
apiVersion("8.0.0")
plugin(project.name) {
loader(JAVA_PLAIN)
displayName(rootProject.name)
mainClass("xyz.jpenilla.minimotd.sponge8.MiniMOTDPlugin")
description(project.description)
links {
val url = rootProject.ext["url"].toString()
homepage(url)
source(url)
issues("${url}issues/")
}
contributor("jmp") {
description("Lead Developer")
}
dependency("spongeapi") {
loadOrder(AFTER)
optional(false)
}
}
}
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"))
}
}
runServer {
classpath(shadowJar)
}
build {
dependsOn(shadowJar)
}
}
@@ -0,0 +1,104 @@
/*
* 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 net.kyori.adventure.text.Component;
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.ComponentColorDownsampler;
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.lang.reflect.Method;
final class ClientPingServerEventListener implements EventListener<ClientPingServerEvent> {
private final MiniMOTD<Favicon> miniMOTD;
private Method SpongeMinecraftVersion_getProtocol;
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.toString()));
return;
}
}
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 Component motdComponent = pair.motd();
try {
if (motdComponent != null) {
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) {
response.setDescription(ComponentColorDownsampler.downsampler().downsample(motdComponent));
} else {
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();
}
if (config.hidePlayerCount()) {
response.setHidePlayers(true);
}
}
}
@@ -0,0 +1,145 @@
/*
* 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 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.Listener;
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.jvm.Plugin;
import org.spongepowered.plugin.metadata.PluginMetadata;
import xyz.jpenilla.minimotd.common.CommandHandlerFactory;
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 java.util.Objects;
@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;
@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);
Sponge.getEventManager().registerListener(
pluginContainer,
ClientPingServerEvent.class,
new ClientPingServerEventListener(this.miniMOTD)
);
}
@Listener
public void onGameLoaded(final @NonNull LoadedGameEvent event) {
Sponge.getAsyncScheduler().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 CommandHandlerFactory.CommandHandler handler;
WrappingExecutor(final CommandHandlerFactory.@NonNull CommandHandler handler) {
this.handler = handler;
}
@Override
public CommandResult execute(final @NonNull CommandContext context) {
this.handler.execute(context.getCause().getAudience());
return CommandResult.success();
}
}
final CommandHandlerFactory handlerFactory = new CommandHandlerFactory(this.miniMOTD);
final Command.Parameterized about = Command.builder()
.setExecutor(new WrappingExecutor(handlerFactory.about()))
.build();
final Command.Parameterized help = Command.builder()
.setExecutor(new WrappingExecutor(handlerFactory.help()))
.build();
final Command.Parameterized reload = Command.builder()
.setExecutor(new WrappingExecutor(handlerFactory.reload()))
.build();
event.register(
this.pluginContainer,
Command.builder()
.setPermission("minimotd.admin")
.child(about, "about")
.child(help, "help")
.child(reload, "reload")
.build(),
"minimotd"
);
}
@Listener
public void onRefresh(final @NonNull RefreshGameEvent event) {
this.miniMOTD.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) throws Exception {
return Objects.requireNonNull(Favicon.load(image), "failed to load favicon");
}
}
@@ -1,94 +0,0 @@
package xyz.jpenilla.minimotd.bungee;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.bungeecord.BungeeCordComponentSerializer;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import java.util.ArrayList;
import java.util.List;
public class BungeeCommand extends Command {
private final MiniMOTD miniMOTD;
public BungeeCommand(MiniMOTD miniMOTD) {
super("minimotdbungee");
this.miniMOTD = miniMOTD;
}
@Override
public void execute(CommandSender sender, String[] args) {
if (!sender.hasPermission("minimotd.admin")) {
onNoPermission(sender, args);
return;
}
if (args.length == 0) {
onInvalidUse(sender, args);
return;
}
switch (args[0]) {
case "about":
onAbout(sender, args);
return;
case "help":
onHelp(sender, args);
return;
case "reload":
onReload(sender, args);
return;
}
onInvalidUse(sender, args);
}
private void onHelp(CommandSender sender, String[] args) {
final ArrayList<String> messages = new ArrayList<String>();
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");
send(sender, messages);
}
private void onReload(CommandSender sender, String[] args) {
miniMOTD.getCfg().reload();
send(sender, "<green>Done reloading.");
}
private void onAbout(CommandSender sender, 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://www.spigotmc.org/resources/minimotd.81254/>" + miniMOTD.getDescription().getName() + " <gradient:red:yellow>" + miniMOTD.getDescription().getVersion());
messages.add("<yellow>By</yellow><gray>:</gray> <gradient:blue:green>jmp");
messages.add(header);
send(sender, messages);
}
private void onInvalidUse(CommandSender sender, String[] args) {
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(CommandSender sender, String[] args) {
send(sender, "<gradient:red:yellow>No permission.");
}
private void send(CommandSender sender, String message) {
final String finalMessage;
if (sender instanceof ProxiedPlayer) {
finalMessage = message;
} else {
finalMessage = MiniMessage.get().stripTokens(message);
}
sender.sendMessage(BungeeCordComponentSerializer.get().serialize(MiniMessage.get().parse(finalMessage)));
}
private void send(CommandSender sender, List<String> messages) {
for (String message : messages) {
send(sender, message);
}
}
}
@@ -1,54 +0,0 @@
package xyz.jpenilla.minimotd.bungee;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import xyz.jpenilla.minimotd.common.MiniMOTDConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class BungeeConfig extends MiniMOTDConfig {
private final MiniMOTD miniMOTD;
public BungeeConfig(MiniMOTD miniMOTD) {
this.miniMOTD = miniMOTD;
reload();
}
public void reload() {
final Configuration config = loadFromDisk();
getMotds().clear();
getMotds().addAll(config.getStringList(MOTDS));
setMotdEnabled(config.getBoolean(MOTD_ENABLED));
setMaxPlayersEnabled(config.getBoolean(MAX_PLAYERS_ENABLED));
setJustXMoreEnabled(config.getBoolean(JUST_X_MORE_ENABLED));
setMaxPlayers(config.getInt(MAX_PLAYERS));
setXValue(config.getInt(X_VALUE));
setFakePlayersEnabled(config.getBoolean(FAKE_PLAYERS_ENABLED));
setFakePlayers(config.getString(FAKE_PLAYERS));
}
private Configuration loadFromDisk() {
if (!miniMOTD.getDataFolder().exists()) {
miniMOTD.getDataFolder().mkdir();
}
File file = new File(miniMOTD.getDataFolder(), "config.yml");
if (!file.exists()) {
try (InputStream in = miniMOTD.getResourceAsStream("config.yml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
}
try {
return ConfigurationProvider.getProvider(YamlConfiguration.class).load(new File(miniMOTD.getDataFolder(), "config.yml"));
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
@@ -1,17 +0,0 @@
package xyz.jpenilla.minimotd.bungee;
import lombok.Getter;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
public class MiniMOTD extends Plugin {
@Getter private BungeeConfig cfg;
@Override
public void onEnable() {
this.cfg = new BungeeConfig(this);
getProxy().getPluginManager().registerListener(this, new PingListener(this));
getProxy().getPluginManager().registerCommand(this, new BungeeCommand(this));
Metrics metrics = new Metrics(this, 8137);
}
}
@@ -1,70 +0,0 @@
package xyz.jpenilla.minimotd.bungee;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import java.util.concurrent.ThreadLocalRandom;
public class PingListener implements Listener {
private final BungeeConfig cfg;
private final MiniMessage miniMessage = MiniMessage.get();
private final LegacyComponentSerializer legacySerializer = LegacyComponentSerializer.builder().build();
private final GsonComponentSerializer gsonSerializer = GsonComponentSerializer.builder().build();
public PingListener(MiniMOTD miniMOTD) {
this.cfg = miniMOTD.getCfg();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPing(ProxyPingEvent e) {
final ServerPing response = e.getResponse();
if (response != null) {
final ServerPing.Players players = response.getPlayers();
int onlinePlayers = players.getOnline();
if (cfg.isFakePlayersEnabled()) {
try {
if (cfg.getFakePlayers().contains(":")) {
final String[] fakePlayers = cfg.getFakePlayers().split(":");
final int start = Integer.parseInt(fakePlayers[0]);
final int end = Integer.parseInt(fakePlayers[1]);
onlinePlayers = onlinePlayers + ThreadLocalRandom.current().nextInt(start, end);
} else if (cfg.getFakePlayers().contains("%")) {
final double factor = 1 + (Double.parseDouble(cfg.getFakePlayers().replace("%", "")) / 100);
onlinePlayers = (int) Math.ceil(factor * onlinePlayers);
} else {
final int addedPlayers = Integer.parseInt(cfg.getFakePlayers());
onlinePlayers = onlinePlayers + addedPlayers;
}
} catch (NumberFormatException ex) {
System.out.println("[MiniMOTD] fakePlayers config incorrect");
}
}
players.setOnline(onlinePlayers);
int maxPlayers = cfg.getAdjustedMaxPlayers(onlinePlayers, players.getMax());
players.setMax(maxPlayers);
if (cfg.isMotdEnabled()) {
Component motd = miniMessage.parse(cfg.getMOTD(onlinePlayers, maxPlayers));
if (e.getConnection().getVersion() < 735) {
motd = legacySerializer.deserialize(legacySerializer.serialize(motd));
}
response.setDescriptionComponent(ComponentSerializer.parse(gsonSerializer.serialize(motd))[0]);
//response.setDescriptionComponent(BungeeCordComponentSerializer.get().serialize(motd)[0]);
}
response.setPlayers(players);
e.setResponse(response);
}
}
}
@@ -1,51 +0,0 @@
package xyz.jpenilla.minimotd.common;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldNameConstants;
import java.util.ArrayList;
import java.util.Random;
@FieldNameConstants
public abstract class MiniMOTDConfig {
public final String MOTDS = "motd." + Fields.motds;
public final String MOTD_ENABLED = "motd." + Fields.motdEnabled;
public final String MAX_PLAYERS_ENABLED = Fields.maxPlayers + "." + Fields.maxPlayersEnabled;
public final String JUST_X_MORE_ENABLED = Fields.maxPlayers + "." + Fields.justXMoreEnabled;
public final String MAX_PLAYERS = Fields.maxPlayers + "." + Fields.maxPlayers;
public final String X_VALUE = Fields.maxPlayers + "." + Fields.xValue;
public final String FAKE_PLAYERS_ENABLED = "bungeeOnly." + Fields.fakePlayersEnabled;
public final String FAKE_PLAYERS = "bungeeOnly." + Fields.fakePlayers;
@Getter private final ArrayList<String> motds = new ArrayList<>();
@Getter @Setter private boolean motdEnabled;
@Getter @Setter private boolean maxPlayersEnabled;
@Getter @Setter private boolean justXMoreEnabled;
@Getter @Setter private boolean fakePlayersEnabled;
@Getter @Setter private int xValue;
@Getter @Setter private int maxPlayers;
@Getter @Setter private String fakePlayers;
public abstract void reload();
public String getMOTD(int onlinePlayers, int maxPlayers) {
String motd;
if (motds.size() == 1) {
motd = motds.get(0);
} else {
motd = motds.get(new Random().nextInt(motds.size()));
}
return motd
.replace("{onlinePlayers}", String.valueOf(onlinePlayers))
.replace("{maxPlayers}", String.valueOf(maxPlayers))
.replace("{br}", "\n");
}
public int getAdjustedMaxPlayers(int onlinePlayers, int actualMaxPlayers) {
if (maxPlayersEnabled) {
return justXMoreEnabled ? onlinePlayers + xValue : maxPlayers;
} else {
return actualMaxPlayers;
}
}
}
@@ -1,24 +0,0 @@
package xyz.jpenilla.minimotd.spigot;
import lombok.Getter;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public final class MiniMOTD extends JavaPlugin {
@Getter private static MiniMOTD instance;
@Getter private SpigotConfig cfg;
@Getter private PrismaHook prisma;
@Override
public void onEnable() {
instance = this;
if (Bukkit.getPluginManager().isPluginEnabled("Prisma")) {
prisma = new PrismaHook();
}
this.cfg = new SpigotConfig(this);
getServer().getPluginManager().registerEvents(new PingListener(this), this);
getCommand("minimotd").setExecutor(new SpigotCommand(this));
Metrics metrics = new Metrics(this, 8132);
}
}
@@ -1,26 +0,0 @@
package xyz.jpenilla.minimotd.spigot;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.ServerListPingEvent;
public class PingListener implements Listener {
private final SpigotConfig cfg;
public PingListener(MiniMOTD miniMOTD) {
this.cfg = miniMOTD.getCfg();
}
@EventHandler
public void onPing(ServerListPingEvent e) {
final int onlinePlayers = e.getNumPlayers();
final int actualMaxPlayers = e.getMaxPlayers();
final int maxPlayers = cfg.getAdjustedMaxPlayers(onlinePlayers, actualMaxPlayers);
e.setMaxPlayers(maxPlayers);
if (cfg.isMotdEnabled()) {
e.setMotd(cfg.getMOTD(onlinePlayers, maxPlayers));
}
}
}
@@ -1,10 +0,0 @@
package xyz.jpenilla.minimotd.spigot;
import lombok.NonNull;
import us.eunoians.prisma.ColorProvider;
public class PrismaHook {
public String translate(@NonNull String message) {
return ColorProvider.translatePrismaToHex(message);
}
}
@@ -1,98 +0,0 @@
package xyz.jpenilla.minimotd.spigot;
import lombok.NonNull;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
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.entity.Player;
import java.util.ArrayList;
import java.util.List;
public class SpigotCommand implements CommandExecutor {
private final MiniMOTD miniMOTD;
private final BukkitAudiences audience;
private final MiniMessage miniMessage;
public SpigotCommand(MiniMOTD miniMOTD) {
this.miniMOTD = miniMOTD;
audience = BukkitAudiences.create(miniMOTD);
miniMessage = MiniMessage.get();
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("minimotd.admin")) {
onNoPermission(sender, args);
return true;
}
if (args.length == 0) {
onInvalidUse(sender, args);
return true;
}
switch (args[0]) {
case "about":
onAbout(sender, args);
return true;
case "help":
onHelp(sender, args);
return true;
case "reload":
onReload(sender, args);
return true;
}
onInvalidUse(sender, args);
return true;
}
private void onHelp(CommandSender sender, String[] args) {
final ArrayList<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");
send(sender, messages);
}
private void onReload(CommandSender sender, String[] args) {
miniMOTD.getCfg().reload();
send(sender, "<green>Done reloading.");
}
private void onAbout(CommandSender sender, 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:" + miniMOTD.getDescription().getWebsite() + ">" + miniMOTD.getName() + " <gradient:red:yellow>" + miniMOTD.getDescription().getVersion());
messages.add("<yellow>By</yellow><gray>:</gray> <gradient:blue:green>jmp");
messages.add(header);
send(sender, messages);
}
private void onInvalidUse(CommandSender sender, String[] args) {
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(CommandSender sender, String[] args) {
send(sender, "<gradient:red:yellow>No permission.");
}
private void send(@NonNull CommandSender sender, @NonNull String message) {
if (sender instanceof Player) {
audience.player((Player) sender).sendMessage(miniMessage.parse(message));
} else {
audience.console().sendMessage(miniMessage.parse(miniMessage.stripTokens(message)));
}
}
private void send(@NonNull CommandSender sender, @NonNull List<String> messages) {
for (String message : messages) {
send(sender, message);
}
}
}
@@ -1,39 +0,0 @@
package xyz.jpenilla.minimotd.spigot;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.configuration.file.FileConfiguration;
import xyz.jpenilla.minimotd.common.MiniMOTDConfig;
public class SpigotConfig extends MiniMOTDConfig {
private final MiniMOTD miniMOTD;
private final LegacyComponentSerializer serializer = LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build();
private final MiniMessage miniMessage = MiniMessage.get();
public SpigotConfig(MiniMOTD miniMOTD) {
this.miniMOTD = miniMOTD;
miniMOTD.saveDefaultConfig();
reload();
}
public void reload() {
miniMOTD.reloadConfig();
final FileConfiguration config = miniMOTD.getConfig();
getMotds().clear();
for (String motd : config.getStringList(MOTDS)) {
final String temp;
if (miniMOTD.getPrisma() != null) {
temp = miniMOTD.getPrisma().translate(motd);
} else {
temp = motd;
}
getMotds().add(serializer.serialize(miniMessage.parse(temp)));
}
setMotdEnabled(config.getBoolean(MOTD_ENABLED));
setMaxPlayersEnabled(config.getBoolean(MAX_PLAYERS_ENABLED));
setJustXMoreEnabled(config.getBoolean(JUST_X_MORE_ENABLED));
setMaxPlayers(config.getInt(MAX_PLAYERS));
setXValue(config.getInt(X_VALUE));
}
}
-5
View File
@@ -1,5 +0,0 @@
name: MiniMOTD
version: ${project.version}
main: xyz.jpenilla.minimotd.bungee.MiniMOTD
author: jmp
description: Set the server list MOTD using MiniMessage!
-35
View File
@@ -1,35 +0,0 @@
motd:
# Do you want to enable this feature?
motdEnabled: true
# Placeholders: {onlinePlayers} {maxPlayers}
# Use {br} to separate lines.
# Putting more than one will cause one to be randomly chosen each refresh
motds:
- "<bold><gradient:black:white>----====[</gradient> <gradient:green:blue>UltraCraft <gradient:red:yellow></bold><italic>Official</gradient></italic><bold> <gradient:white:black>]=====----</gradient></bold>{br}<bold> <gradient:#EEFF4A:#F7FFAE>Custom <italic>Survival</italic></gradient> <color:#0092FF>Discord</bold><gray>:</gray> <gradient:#5FDAFF:#B8A6FF><italic>Ab3d5f"
- "<rainbow>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||</rainbow>{br}<white>Hello <blue>{onlinePlayers} <gray>/</gray> {maxPlayers}</blue> Players Online"
maxPlayers:
# Do you want to enable this feature?
maxPlayersEnabled: true
# Changes the Max Players value.
maxPlayers: 69
# Changes the Max Players to X more than the online players
# Example: 16/19 players online.
justXMoreEnabled: false
xValue: 3
bungeeOnly:
# Do you want to enable this feature?
fakePlayersEnabled: false
# Modes: static, random, percent
# static: This many fake players will be added
# ex: fakePlayers: "3"
# random: A random number of fake players in this range will be added
# ex: fakePlayers: "3:6"
# percent: The player count will be inflated by this much, rounding up
# ex: fakePlayers: "25%"
fakePlayers: "25%"
-12
View File
@@ -1,12 +0,0 @@
name: MiniMOTD
version: ${project.version}
main: xyz.jpenilla.minimotd.spigot.MiniMOTD
softdepend: [Prisma]
authors: [jmp]
description: Set the server list MOTD using MiniMessage!
website: https://www.spigotmc.org/resources/minimotd.81254/
commands:
minimotd:
description: MiniMOTD command
permission-message:
usage: /minimotd help
+19
View File
@@ -0,0 +1,19 @@
tasks {
val universal = register<Jar>("universal") {
artifacts.add("archives", this)
archiveClassifier.set("")
destinationDirectory.set(rootProject.buildDir.resolve("libs"))
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)
}
}
+42
View File
@@ -0,0 +1,42 @@
plugins {
id("net.kyori.blossom")
}
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 {
fun replaceTokens(file: String, vararg tokens: Pair<String, String>) = tokens.forEach { (k, v) ->
replaceToken("\${$k}", v, file)
}
replaceTokens(
"src/main/java/xyz/jpenilla/minimotd/velocity/MiniMOTDPlugin.java",
"project.name" to project.name,
"description" to description!!,
"url" to rootProject.ext["url"].toString()
)
}
@@ -0,0 +1,207 @@
/*
* 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.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.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 org.bstats.velocity.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import xyz.jpenilla.minimotd.common.CommandHandlerFactory;
import xyz.jpenilla.minimotd.common.ComponentColorDownsampler;
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 = Constants.PluginMetadata.NAME,
version = Constants.PluginMetadata.VERSION,
description = "${description}",
url = "${url}",
authors = {"jmp"}
)
public final 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.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 CommandHandlerFactory.CommandHandler handler;
WrappingExecutor(final CommandHandlerFactory.@NonNull CommandHandler handler) {
this.handler = handler;
}
@Override
public int run(final @NonNull CommandContext<CommandSource> context) {
this.handler.execute(context.getSource());
return Command.SINGLE_SUCCESS;
}
}
final CommandHandlerFactory handlerFactory = new CommandHandlerFactory(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(handlerFactory.help())))
.then(LiteralArgumentBuilder.<CommandSource>literal("about").executes(new WrappingExecutor(handlerFactory.about())))
.then(LiteralArgumentBuilder.<CommandSource>literal("reload").executes(new WrappingExecutor(handlerFactory.reload())))
));
}
@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 Component motdComponent = pair.motd();
if (motdComponent != null) {
if (pong.getVersion().getProtocol() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
pong.description(ComponentColorDownsampler.downsampler().downsample(motdComponent));
} else {
pong.description(motdComponent);
}
}
if (config.disablePlayerListHover()) {
pong.clearSamplePlayers();
}
if (config.hidePlayerCount()) {
pong.nullPlayers();
}
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();
}
@Override
public void onReload() {
this.miniMOTD.configManager().loadExtraConfigs();
}
}