Compare commits

...
14 Commits
Author SHA1 Message Date
jmp 9d6d7feae0 Add config option for whether the displayed online player count is allowed to exceed the displayed maximum player count 2021-04-08 01:10:53 -07:00
jmp ed6595ee0d Refactor player count modification 2021-04-07 03:54:25 -07:00
jmp a462f0b96e Minor formatting changes 2021-04-07 01:26:14 -07:00
Mark Joseph KuebelandMark Kuebel af4f0c78f1 Added a minimum player fake player count, and a constant count. (#50)
* Added a minimum player fake player count, and a constant count.

* updates grammars and stuffs

* removes periods in config example

Co-authored-by: Mark Kuebel <mjkuebel@cs.utexas.edu>
2021-04-06 16:43:06 -07:00
jmp 7ef0ada06a Build script changes
Switch from Spigradle to Minecrell's plugin-yml plugin
2021-04-04 15:55:59 -07:00
jmp fedae0ab23 Clean up IconManager 2021-04-04 15:48:35 -07:00
jmp 157bc7b767 velocity: Remove manual component color downsampling as Velocity handles it automatically 2021-03-28 22:29:53 -07:00
jmp 71f653122a bungee: Stupid hacks to workaround Travertine issues with <=1.8 clients 2021-03-27 23:33:14 -07:00
Renovate Bot b44070bdb0 chore(deps): update dependency net.kyori.blossom:net.kyori.blossom.gradle.plugin to v1.2.0 2021-03-23 15:08:26 -07:00
jmp ae5ab6a95b sponge8: Fix logging 2021-03-22 23:21:28 -07:00
Jason 73ee4f393b Update README.md 2021-03-22 05:52:31 -07:00
jmp ff4a1da49c Implement Sponge API 7 platform 2021-03-22 04:40:33 -07:00
jmp 725e5c8d2b sponge: Update for API changes 2021-03-21 14:47:33 -07:00
jmp 5d644c326a Bump version to 2.0.2-SNAPSHOT 2021-03-19 23:15:23 -07:00
33 changed files with 752 additions and 217 deletions

No files matched your search

+1
View File
@@ -12,6 +12,7 @@
- [Paper](https://papermc.io/) / Spigot
- MiniMOTD is compatible with Spigot, however many features will not work. It is recommended to use Paper for full compatibility.
- [Sponge API 8](https://www.spongepowered.org/)
- [Sponge API 7](https://www.spongepowered.org/)
- [Fabric](https://fabricmc.net/) (requires [Fabric API](https://www.curseforge.com/minecraft/mc-mods/fabric-api))
#### Proxy Platforms
+6 -5
View File
@@ -11,17 +11,17 @@ 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
id("com.adarshr.test-logger") version "3.0.0"
id("net.kyori.blossom") version "1.2.0" apply false
}
allprojects {
group = "xyz.jpenilla"
version = "2.0.1+${lastCommitHash()}-SNAPSHOT"
description = "Use MiniMessage text formatting in your servers MOTD."
version = "2.0.2+${lastCommitHash()}-SNAPSHOT"
description = "Use MiniMessage text formatting in the server list MOTD."
}
ext["url"] = "https://github.com/jpenilla/MiniMOTD/"
ext["url"] = "https://github.com/jpenilla/MiniMOTD"
subprojects {
apply<JavaLibraryPlugin>()
@@ -69,6 +69,7 @@ subprojects {
tasks {
shadowJar {
minimize()
exclude("META-INF/versions/**")
if (!project.name.contains("fabric")) {
archiveClassifier.set("")
doLast {
+3 -2
View File
@@ -1,12 +1,12 @@
plugins {
id("kr.entree.spigradle.bungee") version "2.2.3"
id("net.minecrell.plugin-yml.bungee") version "0.3.0"
}
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")
implementation("org.bstats", "bstats-bungeecord", "2.2.1")
compileOnly("io.github.waterfallmc", "waterfall-api", "1.16-R0.4-SNAPSHOT")
}
@@ -27,5 +27,6 @@ tasks {
bungee {
name = rootProject.name
main = "xyz.jpenilla.minimotd.bungee.MiniMOTDPlugin"
author = "jmp"
}
@@ -23,11 +23,15 @@
*/
package xyz.jpenilla.minimotd.bungee;
import com.google.gson.Gson;
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.jpenilla.minimotd.common.MiniMOTD;
@@ -35,6 +39,7 @@ import xyz.jpenilla.minimotd.common.MiniMOTDPlatform;
import xyz.jpenilla.minimotd.common.UpdateChecker;
import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import java.nio.file.Path;
public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon> {
@@ -52,13 +57,14 @@ public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon>
this.miniMOTD = new MiniMOTD<>(this);
this.miniMOTD.configManager().loadExtraConfigs();
this.audiences = BungeeAudiences.create(this);
this.injectTravertineGson();
this.getProxy().getPluginManager().registerListener(this, new PingListener(this.miniMOTD));
this.getProxy().getPluginManager().registerCommand(this, new BungeeCommand(this));
final Metrics metrics = new Metrics(this, 8137);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
this.getProxy().getScheduler().runAsync(this, () ->
new UpdateChecker().checkVersion().forEach(getLogger()::info));
new UpdateChecker().checkVersion().forEach(this.logger::info));
}
}
@@ -85,4 +91,23 @@ public class MiniMOTDPlugin extends Plugin implements MiniMOTDPlatform<Favicon>
public void onReload() {
this.miniMOTD.configManager().loadExtraConfigs();
}
private void injectTravertineGson() {
final Field gsonLegacyField = findDeclaredField(ProxyServer.getInstance().getClass(), "gsonLegacy");
if (gsonLegacyField != null) {
try {
BungeeComponentSerializer.inject((Gson) gsonLegacyField.get(ProxyServer.getInstance()));
} catch (final IllegalAccessException ex) {
this.miniMOTD.logger().warn("Failed to inject into Travertine's gsonLegacy gson instance. There will likely be issues with 1.7.x clients.", ex);
}
}
}
private static @Nullable Field findDeclaredField(final @NonNull Class<?> holder, final @NonNull String name) {
try {
return holder.getDeclaredField(name);
} catch (final NoSuchFieldException ex) {
return null;
}
}
}
@@ -27,17 +27,17 @@ 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.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import 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 xyz.jpenilla.minimotd.common.config.MiniMOTDConfig.PlayerCount;
import java.util.Optional;
@@ -48,7 +48,7 @@ public class PingListener implements Listener {
this.miniMOTD = miniMOTD;
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler
public void onPing(final @NonNull ProxyPingEvent e) {
final ServerPing response = e.getResponse();
if (response == null) {
@@ -62,8 +62,9 @@ public class PingListener implements Listener {
).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());
final PlayerCount count = cfg.modifyPlayerCount(players.getOnline(), players.getMax());
final int onlinePlayers = count.onlinePlayers();
final int maxPlayers = count.maxPlayers();
if (cfg.hidePlayerCount()) {
response.setPlayers(null);
@@ -77,12 +78,19 @@ public class PingListener implements Listener {
final MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
Component motdComponent = pair.motd();
final Component motdComponent = pair.motd();
if (motdComponent != null) {
final BaseComponent[] bungee;
if (e.getConnection().getVersion() < Constants.MINECRAFT_1_16_PROTOCOL_VERSION) {
motdComponent = ComponentColorDownsampler.downsampler().downsample(motdComponent);
bungee = BungeeComponentSerializer.legacy().serialize(motdComponent);
} else {
bungee = BungeeComponentSerializer.get().serialize(motdComponent);
}
if (BungeeComponentSerializer.isNative()) {
response.setDescriptionComponent(bungee[0]);
} else {
response.setDescriptionComponent(new TextComponent(bungee));
}
response.setDescriptionComponent(new TextComponent(BungeeComponentSerializer.get().serialize(motdComponent)));
}
final Favicon favicon = pair.icon();
+10 -1
View File
@@ -2,13 +2,22 @@ plugins {
id("net.kyori.blossom")
}
tasks.jar {
from(rootProject.file("license.txt")) {
rename { "license_${rootProject.name.toLowerCase()}.txt" }
}
}
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-serializer-gson") {
exclude("com.google.code.gson", "gson")
}
api("net.kyori", "adventure-text-minimessage", "4.1.0-SNAPSHOT")
compileOnlyApi("com.google.code.gson", "gson", "2.8.0")
testImplementation("com.google.code.gson", "gson", "2.8.0")
compileOnlyApi("com.google.guava", "guava", "21.0")
}
@@ -29,6 +29,9 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -38,34 +41,41 @@ public final class IconManager<I> {
private final Map<String, I> icons = new HashMap<>();
private final MiniMOTD<I> miniMOTD;
private final File iconsDirectory;
private final Path iconsDirectory;
public IconManager(final @NonNull MiniMOTD<I> miniMOTD) {
this.miniMOTD = miniMOTD;
this.iconsDirectory = miniMOTD.dataDirectory().resolve("icons").toFile();
this.iconsDirectory = miniMOTD.dataDirectory().resolve("icons");
this.loadIcons();
}
public void loadIcons() {
if (!this.iconsDirectory.exists()) {
this.iconsDirectory.mkdir();
}
this.icons.clear();
final File[] icons = this.iconsDirectory.listFiles(i -> i.getName().endsWith(".png"));
if (icons != null) {
for (final File icon : icons) {
try {
final BufferedImage bufferedImage = ImageIO.read(icon);
if (bufferedImage.getHeight() == 64 && bufferedImage.getWidth() == 64) {
final I newIcon = this.miniMOTD.platform().loadIcon(bufferedImage);
this.icons.put(icon.getName().split("\\.")[0], newIcon);
} else {
this.miniMOTD.logger().warn("Could not load " + icon.getName() + ": image must be 64x64px");
}
} catch (final Exception e) {
this.miniMOTD.logger().warn("Could not load " + icon.getName() + ": invalid image file", e);
}
try {
if (!Files.exists(this.iconsDirectory)) {
Files.createDirectories(this.iconsDirectory);
}
Files.list(this.iconsDirectory)
.map(Path::toFile)
.filter(File::isFile)
.filter(file -> file.getName().endsWith(".png"))
.forEach(this::loadIcon);
} catch (final IOException ex) {
throw new RuntimeException("Exception loading server icons", ex);
}
}
private void loadIcon(final @NonNull File iconFile) {
try {
final BufferedImage bufferedImage = ImageIO.read(iconFile);
if (bufferedImage.getHeight() == 64 && bufferedImage.getWidth() == 64) {
final I newIcon = this.miniMOTD.platform().loadIcon(bufferedImage);
this.icons.put(iconFile.getName().split("\\.")[0], newIcon);
} else {
this.miniMOTD.logger().warn("Could not load " + iconFile.getName() + ": image must be 64x64px");
}
} catch (final Exception ex) {
this.miniMOTD.logger().warn("Could not load " + iconFile.getName() + ": invalid image file", ex);
}
}
@@ -66,7 +66,6 @@ public final class MiniMOTD<I> {
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;
@@ -95,32 +94,6 @@ public final class MiniMOTD<I> {
.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();
@@ -39,7 +39,10 @@ public final class ConfigLoader<C> {
private static final TypeSerializerCollection SERIALIZERS;
static {
SERIALIZERS = TypeSerializerCollection.defaults();
SERIALIZERS = TypeSerializerCollection.defaults()
.childBuilder()
.register(PlayerCountModifier.serializer())
.build();
}
private final HoconConfigurationLoader loader;
@@ -27,7 +27,7 @@ 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 xyz.jpenilla.minimotd.common.util.Pair;
import java.io.IOException;
import java.nio.file.Files;
@@ -111,7 +111,7 @@ public final class ConfigManager {
MiniMOTDConfig.class,
pair.left()
);
loader.save(new MiniMOTDConfig(ImmutableList.of(pair.right())));
loader.save(new MiniMOTDConfig(pair.right()));
}
}
@@ -35,14 +35,14 @@ import java.util.List;
public final class MiniMOTDConfig {
public MiniMOTDConfig() {
this(Arrays.asList(
this(
new MOTD(),
new MOTD("<blue>Another <bold><red>MOTD", "<italic><underlined><gradient:red:green>much wow")
));
);
}
public MiniMOTDConfig(final @NonNull List<MOTD> defaultMOTDs) {
this.motds.addAll(defaultMOTDs);
public MiniMOTDConfig(final @NonNull MOTD @NonNull ... defaults) {
this.motds.addAll(Arrays.asList(defaults));
}
@Comment("The list of MOTDs to display\n"
@@ -59,16 +59,6 @@ public final class MiniMOTDConfig {
private PlayerCountSettings playerCountSettings = new PlayerCountSettings();
public int adjustedMaxPlayers(final int onlinePlayers, final int actualMaxPlayers) {
if (this.playerCountSettings.maxPlayersEnabled) {
return this.playerCountSettings.justXMoreSettings.justXMoreEnabled
? onlinePlayers + this.playerCountSettings.justXMoreSettings.xValue
: this.playerCountSettings.maxPlayers;
} else {
return actualMaxPlayers;
}
}
@ConfigSerializable
public static final class MOTD {
@@ -127,6 +117,10 @@ public final class MiniMOTDConfig {
+ "ex: x=3 -> 16/19 players online.")
private JustXMore justXMoreSettings = new JustXMore();
@Comment("Should the displayed online player count be allowed to exceed the displayed maximum player count?\n"
+ "If false, the online player count will be capped at the maximum player count")
private boolean allowExceedingMaximum = false;
@ConfigSerializable
public static final class JustXMore {
@@ -143,15 +137,19 @@ public final class MiniMOTDConfig {
@Comment("Enable fake player count feature")
private boolean fakePlayersEnabled = false;
@Comment("Modes: static, random, percent\n"
@Comment("Modes: add, constant, minimum, random, percent\n"
+ "\n"
+ " - static: This many fake players will be added\n"
+ " ex: fakePlayers: \"3\"\n"
+ " - add: This many fake players will be added\n"
+ " ex: fake-players=\"3\"\n"
+ " - constant: A constant value for the player count\n"
+ " ex: fake-players=\"=42\"\n"
+ " - minimum: The minimum bound of the player count\n"
+ " ex: fake-players=\"7+\"\n"
+ " - random: A random number of fake players in this range will be added\n"
+ " ex: fakePlayers: \"3:6\"\n"
+ " ex: fake-players=\"3:6\"\n"
+ " - percent: The player count will be inflated by this much, rounding up\n"
+ " ex: fakePlayers: \"25%\"")
private String fakePlayers = "25%";
+ " ex: fake-players=\"25%\"")
private PlayerCountModifier fakePlayers = PlayerCountModifier.parse("25%");
}
@@ -169,14 +167,6 @@ public final class MiniMOTDConfig {
return this.motdEnabled;
}
public boolean fakePlayersEnabled() {
return this.playerCountSettings.fakePlayers.fakePlayersEnabled;
}
public @NonNull String fakePlayers() {
return this.playerCountSettings.fakePlayers.fakePlayers;
}
public boolean disablePlayerListHover() {
return this.playerCountSettings.disablePlayerListHover;
}
@@ -184,4 +174,52 @@ public final class MiniMOTDConfig {
public boolean hidePlayerCount() {
return this.playerCountSettings.hidePlayerCount;
}
private @NonNull PlayerCountModifier playerCountModifier() {
return this.playerCountSettings.fakePlayers.fakePlayers;
}
private int calculateOnlinePlayers(final int onlinePlayers) {
if (this.playerCountSettings.fakePlayers.fakePlayersEnabled) {
return this.playerCountModifier().apply(onlinePlayers);
}
return onlinePlayers;
}
private int calculateMaxPlayers(final int onlinePlayers, final int maxPlayers) {
if (this.playerCountSettings.maxPlayersEnabled) {
if (this.playerCountSettings.justXMoreSettings.justXMoreEnabled) {
return onlinePlayers + this.playerCountSettings.justXMoreSettings.xValue;
}
return this.playerCountSettings.maxPlayers;
}
return maxPlayers;
}
public @NonNull PlayerCount modifyPlayerCount(final int onlinePlayers, final int maxPlayers) {
final int online = this.calculateOnlinePlayers(onlinePlayers);
final int max = this.calculateMaxPlayers(online, maxPlayers);
if (!this.playerCountSettings.allowExceedingMaximum) {
return new PlayerCount(Math.min(online, max), max);
}
return new PlayerCount(online, max);
}
public static final class PlayerCount {
private final int onlinePlayers;
private final int maxPlayers;
private PlayerCount(final int onlinePlayers, final int maxPlayers) {
this.onlinePlayers = onlinePlayers;
this.maxPlayers = maxPlayers;
}
public int onlinePlayers() {
return this.onlinePlayers;
}
public int maxPlayers() {
return this.maxPlayers;
}
}
}
@@ -0,0 +1,131 @@
/*
* 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.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import xyz.jpenilla.minimotd.common.util.Int2IntFunction;
import java.lang.reflect.Type;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;
public final class PlayerCountModifier implements Int2IntFunction {
private final String input;
private final Int2IntFunction function;
private PlayerCountModifier(final @NonNull String input, final @NonNull Int2IntFunction function) {
this.input = input;
this.function = function;
}
@Override
public int apply(final int actualPlayers) {
return this.function.apply(actualPlayers);
}
public final @NonNull String input() {
return this.input;
}
public static @NonNull PlayerCountModifier parse(final @NonNull String input) {
try {
if (input.contains(":")) {
return parseRandomModifier(input);
} else if (input.contains("%")) {
return parsePercentModifier(input);
} else if (input.contains("=")) {
return parseConstantModifier(input);
} else if (input.contains("+")) {
return parseMinimumModifier(input);
} else {
return parseAddModifier(input);
}
} catch (final NumberFormatException ex) {
throw cannotParse(input, ex);
}
}
private static @NonNull PlayerCountModifier parseRandomModifier(final @NonNull String input) {
final String[] fakePlayers = input.split(":");
if (fakePlayers.length != 2) {
throw cannotParse(input, null);
}
final int start = Integer.parseInt(fakePlayers[0]);
final int end = Integer.parseInt(fakePlayers[1]);
return new PlayerCountModifier(input, actual -> actual + ThreadLocalRandom.current().nextInt(start, end));
}
private static @NonNull PlayerCountModifier parsePercentModifier(final @NonNull String input) {
final double factor = 1.00D + Double.parseDouble(input.replace("%", "")) / 100.00D;
return new PlayerCountModifier(input, actual -> (int) Math.ceil(factor * actual));
}
private static @NonNull PlayerCountModifier parseConstantModifier(final @NonNull String input) {
final int value = Integer.parseInt(input.replace("=", ""));
return new PlayerCountModifier(input, actual -> value);
}
private static @NonNull PlayerCountModifier parseMinimumModifier(final @NonNull String input) {
final int minPlayers = Integer.parseInt(input.replace("+", ""));
return new PlayerCountModifier(input, actual -> Math.max(minPlayers, actual));
}
private static @NonNull PlayerCountModifier parseAddModifier(final @NonNull String input) {
final int addedPlayers = Integer.parseInt(input);
return new PlayerCountModifier(input, actual -> actual + addedPlayers);
}
private static @NonNull IllegalArgumentException cannotParse(final @NonNull String input, final @Nullable Throwable cause) {
return new IllegalArgumentException(String.format("Unable to parse a player count modifier from input string '%s'.", input), cause);
}
public static @NonNull Serializer serializer() {
return Serializer.INSTANCE;
}
public static final class Serializer extends ScalarSerializer<PlayerCountModifier> {
private static final Serializer INSTANCE = new Serializer();
private Serializer() {
super(PlayerCountModifier.class);
}
@Override
public PlayerCountModifier deserialize(final @NonNull Type type, final @NonNull Object obj) throws SerializationException {
try {
return PlayerCountModifier.parse(obj.toString());
} catch (final IllegalArgumentException ex) {
throw new SerializationException(ex);
}
}
@Override
protected Object serialize(final @NonNull PlayerCountModifier item, final @NonNull Predicate<Class<?>> typeSupported) {
return item.input();
}
}
}
@@ -0,0 +1,29 @@
/*
* 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.util;
@FunctionalInterface
public interface Int2IntFunction {
int apply(int value);
}
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.minimotd.common;
package xyz.jpenilla.minimotd.common.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -47,10 +47,15 @@ public final class Pair<L, R> {
@Override
public boolean equals(final @Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final Pair<?, ?> pair = (Pair<?, ?>) o;
return this.left.equals(pair.left) && this.right.equals(pair.right);
return this.left.equals(pair.left)
&& this.right.equals(pair.right);
}
@Override
+3 -2
View File
@@ -45,9 +45,10 @@ tasks {
"{project.name}" to project.name,
"{rootProject.name}" to rootProject.name,
"{project.version}" to project.version.toString(),
"{project.description}" to project.description.toString()
"{project.description}" to project.description.toString(),
"{project.github_url}" to rootProject.ext["url"].toString()
).forEach { (k, v) ->
filter { resource -> resource.replace(k, v) }
filter { it.replace(k, v) }
}
}
}
@@ -34,7 +34,6 @@ 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;
@@ -130,14 +129,12 @@ public final class MiniMOTDFabric implements ModInitializer, MiniMOTDPlatform<St
@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);
final ByteBuffer base64 = Base64.getEncoder().encode(byteBuf.nioBuffer());
icon = "data:image/png;base64," + StandardCharsets.UTF_8.decode(base64);
} finally {
byteBuf.release();
}
@@ -40,6 +40,7 @@ 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.common.config.MiniMOTDConfig.PlayerCount;
import xyz.jpenilla.minimotd.fabric.MiniMOTDFabric;
import xyz.jpenilla.minimotd.fabric.access.ConnectionAccess;
@@ -61,8 +62,9 @@ abstract class ServerStatusPacketListenerImplMixin {
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 PlayerCount count = config.modifyPlayerCount(minecraftServer.getPlayerCount(), vanillaStatus.getPlayers().getMaxPlayers());
final int onlinePlayers = count.onlinePlayers();
final int maxPlayers = count.maxPlayers();
final MOTDIconPair<String> pair = miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
@@ -7,6 +7,11 @@
"authors": [
"jmp"
],
"contact": {
"homepage": "{project.github_url}",
"sources": "{project.github_url}",
"issues": "{project.github_url}/issues"
},
"license": "MIT",
"environment": "server",
"entrypoints": {
+3
View File
@@ -17,6 +17,9 @@ setupSubproject("minimotd-spigot") {
setupSubproject("minimotd-sponge8") {
projectDir = file("sponge8")
}
setupSubproject("minimotd-sponge7") {
projectDir = file("sponge7")
}
setupSubproject("minimotd-bungeecord") {
projectDir = file("bungee")
}
+7 -7
View File
@@ -1,13 +1,13 @@
plugins {
id("kr.entree.spigradle") version "2.2.3"
id("net.minecrell.plugin-yml.bukkit") version "0.3.0"
}
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")
implementation("org.bstats", "bstats-bukkit", "2.2.1")
compileOnly("com.destroystokyo.paper", "paper-api", "1.16.5-R0.1-SNAPSHOT")
}
tasks {
@@ -25,16 +25,16 @@ tasks {
}
}
spigot {
bukkit {
name = rootProject.name
main = "xyz.jpenilla.minimotd.spigot.MiniMOTDPlugin"
apiVersion = "1.13"
website = rootProject.ext["url"].toString()
authors("jmp")
softDepends("Prisma", "ViaVersion")
authors = listOf("jmp")
softDepend = listOf("ViaVersion")
commands {
create("minimotd") {
description = "MiniMOTD Command"
permissionMessage = "No permission"
usage = "/minimotd help"
}
}
@@ -30,6 +30,7 @@ import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.util.CachedServerIcon;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.jpenilla.minimotd.common.MiniMOTD;
@@ -40,51 +41,45 @@ import java.awt.image.BufferedImage;
import java.nio.file.Path;
public final class MiniMOTDPlugin extends JavaPlugin implements MiniMOTDPlatform<CachedServerIcon> {
private static final boolean PAPER_PING_EVENT_EXISTS = findClass("com.destroystokyo.paper.event.server.PaperServerListPingEvent") != null;
private Logger logger;
private MiniMOTD<CachedServerIcon> miniMOTD;
private boolean isPaperServer;
private BukkitAudiences audiences;
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.audiences = BukkitAudiences.create(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);
if (PAPER_PING_EVENT_EXISTS) {
Bukkit.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("#");
Bukkit.getPluginManager().registerEvents(new PingListener(this, this.miniMOTD), this);
if (this.majorMinecraftVersion >= 12) { // PaperServerListPingEvent was added in 1.12
this.suggestPaper();
}
}
this.audiences = BukkitAudiences.create(this);
final PluginCommand command = getCommand("minimotd");
final PluginCommand command = this.getCommand("minimotd");
if (command != null) {
command.setExecutor(new SpigotCommand(this));
command.setTabCompleter(new SpigotCommand(this));
final SpigotCommand spigotCommand = new SpigotCommand(this);
command.setExecutor(spigotCommand);
command.setTabCompleter(spigotCommand);
}
final Metrics metrics = new Metrics(this, 8132);
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Bukkit.getScheduler().runTaskAsynchronously(this, () ->
new UpdateChecker().checkVersion().forEach(getLogger()::info));
new UpdateChecker().checkVersion().forEach(this.logger::info));
}
}
@@ -114,4 +109,26 @@ public final class MiniMOTDPlugin extends JavaPlugin implements MiniMOTDPlatform
public @NonNull BukkitAudiences audiences() {
return this.audiences;
}
private void suggestPaper() {
this.logger.warn("======================================================");
this.logger.warn(" MiniMOTD works better if you use Paper as your server");
this.logger.warn(" software.");
this.logger.warn(" ");
this.logger.warn(" Spigot does not include the necessary APIs for all");
this.logger.warn(" of MiniMOTD's features to operate. MiniMOTD was");
this.logger.warn(" designed to work with Paper and it's expanded API");
this.logger.warn(" for full compatibility.");
this.logger.warn(" ");
this.logger.warn(" Get Paper from https://papermc.io/downloads");
this.logger.warn("======================================================");
}
private static @Nullable Class<?> findClass(final @NonNull String className) {
try {
return Class.forName(className);
} catch (final ClassNotFoundException ex) {
return null;
}
}
}
@@ -33,6 +33,7 @@ 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;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig.PlayerCount;
public class PaperPingListener implements Listener {
private final MiniMOTDPlugin plugin;
@@ -48,10 +49,12 @@ public class PaperPingListener implements Listener {
@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());
final PlayerCount count = cfg.modifyPlayerCount(e.getNumPlayers(), e.getMaxPlayers());
final int onlinePlayers = count.onlinePlayers();
final int maxPlayers = count.maxPlayers();
e.setNumPlayers(onlinePlayers);
e.setMaxPlayers(maxPlayers);
final MOTDIconPair<CachedServerIcon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
@@ -33,6 +33,7 @@ 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;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig.PlayerCount;
public class PingListener implements Listener {
private final MiniMOTD<CachedServerIcon> miniMOTD;
@@ -49,9 +50,9 @@ public class PingListener implements Listener {
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);
final PlayerCount count = cfg.modifyPlayerCount(onlinePlayers, e.getMaxPlayers());
final int maxPlayers = count.maxPlayers();
e.setMaxPlayers(maxPlayers);
final MOTDIconPair<CachedServerIcon> pair = this.miniMOTD.createMOTD(cfg, onlinePlayers, maxPlayers);
+35
View File
@@ -0,0 +1,35 @@
dependencies {
implementation(project(":minimotd-common")) {
exclude("org.slf4j", "slf4j-api")
}
implementation("net.kyori", "adventure-platform-spongeapi", "4.0.0-SNAPSHOT")
compileOnly("org.spongepowered", "spongeapi", "7.2.0")
implementation("org.bstats", "bstats-sponge", "2.2.1")
}
tasks {
processResources {
filesMatching("mcmod.info") {
mapOf(
"{project.name}" to project.name,
"{rootProject.name}" to rootProject.name,
"{project.version}" to project.version.toString(),
"{project.description}" to project.description.toString(),
"{url}" to rootProject.ext["url"].toString()
).forEach { (k, v) ->
filter { resource -> resource.replace(k, v) }
}
}
}
shadowJar {
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)
}
}
@@ -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.sponge7;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.spongeapi.SpongeComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.api.event.EventListener;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import xyz.jpenilla.minimotd.common.MOTDIconPair;
import xyz.jpenilla.minimotd.common.MiniMOTD;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig;
import xyz.jpenilla.minimotd.common.config.MiniMOTDConfig.PlayerCount;
final class ClientPingServerEventListener implements EventListener<ClientPingServerEvent> {
private final MiniMOTD<Favicon> miniMOTD;
ClientPingServerEventListener(final @NonNull MiniMOTD<Favicon> miniMOTD) {
this.miniMOTD = miniMOTD;
}
@Override
public void handle(final @NonNull ClientPingServerEvent event) {
final ClientPingServerEvent.Response response = event.getResponse();
final ClientPingServerEvent.Response.Players players;
final ClientPingServerEvent.Response.Players players0 = response.getPlayers().orElse(null);
if (players0 != null) {
players = players0;
} else {
response.setHidePlayers(false);
players = response.getPlayers().orElse(null);
if (players == null) {
this.miniMOTD.logger().warn(String.format("Failed to handle ClientPingServerEvent: '%s', response.getPlayers() was null.", event));
return;
}
}
final MiniMOTDConfig config = this.miniMOTD.configManager().mainConfig();
final PlayerCount count = config.modifyPlayerCount(players.getOnline(), players.getMax());
final int onlinePlayers = count.onlinePlayers();
final int maxPlayers = count.maxPlayers();
players.setOnline(onlinePlayers);
players.setMax(maxPlayers);
final MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
final Component motd = pair.motd();
if (motd != null) {
response.setDescription(SpongeComponentSerializer.get().serialize(motd));
}
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,143 @@
/*
* 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.sponge7;
import com.google.inject.Inject;
import net.kyori.adventure.platform.spongeapi.SpongeAudiences;
import org.bstats.sponge.Metrics;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.slf4j.Logger;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.config.ConfigDir;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.GameReloadEvent;
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
import org.spongepowered.api.event.server.ClientPingServerEvent;
import org.spongepowered.api.network.status.Favicon;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.Task;
import xyz.jpenilla.minimotd.common.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.io.IOException;
import java.nio.file.Path;
@Plugin(id = "minimotd-sponge7")
public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
private final Logger logger;
private final Path dataDirectory;
private final MiniMOTD<Favicon> miniMOTD;
private final SpongeAudiences audiences;
@Inject
public MiniMOTDPlugin(
final @NonNull Logger logger,
@ConfigDir(sharedRoot = false) final @NonNull Path dataDirectory,
final @NonNull SpongeAudiences audiences,
final Metrics.@NonNull Factory metricsFactory
) {
this.logger = logger;
this.dataDirectory = dataDirectory;
this.audiences = audiences;
this.miniMOTD = new MiniMOTD<>(this);
metricsFactory.make(10768);
}
@Listener
public void gameStarted(final @NonNull GameStartedServerEvent event) {
Sponge.getEventManager().registerListener(this, ClientPingServerEvent.class, new ClientPingServerEventListener(this.miniMOTD));
this.registerCommands();
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Task.builder()
.async()
.execute(() -> new UpdateChecker().checkVersion().forEach(this.logger::info))
.submit(this);
}
}
@Listener
public void reloaded(final @NonNull GameReloadEvent event) {
this.miniMOTD.reload();
}
private void registerCommands() {
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 CommandSource src, final @NonNull CommandContext args) {
this.handler.execute(MiniMOTDPlugin.this.audiences.receiver(src));
return CommandResult.success();
}
}
final CommandHandlerFactory factory = new CommandHandlerFactory(this.miniMOTD);
final CommandSpec help = CommandSpec.builder()
.executor(new WrappingExecutor(factory.help()))
.build();
final CommandSpec about = CommandSpec.builder()
.executor(new WrappingExecutor(factory.about()))
.build();
final CommandSpec reload = CommandSpec.builder()
.executor(new WrappingExecutor(factory.reload()))
.build();
Sponge.getCommandManager().register(
this,
CommandSpec.builder()
.permission("minimotd.admin")
.child(help, "help")
.child(about, "about")
.child(reload, "reload")
.build(),
"minimotd"
);
}
@Override
public @NonNull Path dataDirectory() {
return this.dataDirectory;
}
@Override
public @NonNull Logger logger() {
return this.logger;
}
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) throws IOException {
return Sponge.getRegistry().loadFavicon(image);
}
}
+18
View File
@@ -0,0 +1,18 @@
[
{
"modid": "{project.name}",
"name": "{rootProject.name}",
"version": "{project.version}",
"description": "{project.description}",
"url": "{url}",
"authorList": [
"jmp"
],
"dependencies": [
"spongeapi@7.2.0"
],
"requiredMods": [
"spongeapi@7.2.0"
]
}
]
+7 -4
View File
@@ -7,6 +7,9 @@ plugins {
dependencies {
implementation(project(":minimotd-common"))
implementation("org.apache.logging.log4j", "log4j-slf4j-impl", "2.8.1") {
isTransitive = false
}
}
sponge {
@@ -20,7 +23,7 @@ sponge {
val url = rootProject.ext["url"].toString()
homepage(url)
source(url)
issues("${url}issues/")
issues("${url}/issues")
}
contributor("jmp") {
description("Lead Developer")
@@ -34,16 +37,16 @@ sponge {
tasks {
shadowJar {
relocate("org.apache.logging.slf4j", "xyz.jpenilla.minimotd.lib.apache.logging.slf4j")
relocate("org.slf4j", "xyz.jpenilla.minimotd.lib.slf4j_log4j")
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"))
exclude { dep -> dep.moduleGroup == "net.kyori" && !dep.name.contains("minimessage") }
}
}
runServer {
@@ -24,6 +24,7 @@
package xyz.jpenilla.minimotd.sponge8;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.spongepowered.api.MinecraftVersion;
import org.spongepowered.api.event.EventListener;
@@ -34,12 +35,13 @@ 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.common.config.MiniMOTDConfig.PlayerCount;
import java.lang.reflect.Method;
final class ClientPingServerEventListener implements EventListener<ClientPingServerEvent> {
private final MiniMOTD<Favicon> miniMOTD;
private Method SpongeMinecraftVersion_getProtocol;
private @MonotonicNonNull Method SpongeMinecraftVersion_getProtocol;
ClientPingServerEventListener(final @NonNull MiniMOTD<Favicon> miniMOTD) {
this.miniMOTD = miniMOTD;
@@ -47,46 +49,39 @@ final class ClientPingServerEventListener implements EventListener<ClientPingSer
@Override
public void handle(final @NonNull ClientPingServerEvent event) {
final ClientPingServerEvent.Response response = event.getResponse();
final ClientPingServerEvent.Response response = event.response();
final ClientPingServerEvent.Response.Players players;
final ClientPingServerEvent.Response.Players players0 = response.getPlayers().orElse(null);
final ClientPingServerEvent.Response.Players players0 = response.players().orElse(null);
if (players0 != null) {
players = players0;
} else {
response.setHidePlayers(false);
players = response.getPlayers().orElse(null);
players = response.players().orElse(null);
if (players == null) {
this.miniMOTD.logger().warn(String.format("Failed to handle ClientPingServerEvent: '%s', response.getPlayers() was null.", event.toString()));
this.miniMOTD.logger().warn(String.format("Failed to handle ClientPingServerEvent: '%s', response.players() was null.", event));
return;
}
}
final MiniMOTDConfig config = this.miniMOTD.configManager().mainConfig();
final int onlinePlayers = this.miniMOTD.calculateOnlinePlayers(config, players.getOnline());
final PlayerCount count = config.modifyPlayerCount(players.online(), players.max());
final int onlinePlayers = count.onlinePlayers();
final int maxPlayers = count.maxPlayers();
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 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);
}
if (motdComponent != null) {
final MinecraftVersion version = event.client().version();
if (this.legacy(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();
@@ -95,10 +90,22 @@ final class ClientPingServerEventListener implements EventListener<ClientPingSer
}
if (config.disablePlayerListHover()) {
players.getProfiles().clear();
players.profiles().clear();
}
if (config.hidePlayerCount()) {
response.setHidePlayers(true);
}
}
private boolean legacy(final @NonNull MinecraftVersion version) {
try {
if (!version.isLegacy() && this.SpongeMinecraftVersion_getProtocol == null) {
this.SpongeMinecraftVersion_getProtocol = version.getClass().getMethod("getProtocol");
}
return version.isLegacy()
|| (int) this.SpongeMinecraftVersion_getProtocol.invoke(version) < Constants.MINECRAFT_1_16_PROTOCOL_VERSION;
} catch (final ReflectiveOperationException e) {
throw new IllegalStateException("Failed to get protocol version", e);
}
}
}
@@ -50,7 +50,6 @@ 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> {
@@ -70,7 +69,7 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
this.pluginMetadata = pluginContainer.getMetadata();
this.logger = LoggerFactory.getLogger(this.pluginMetadata.getId());
this.miniMOTD = new MiniMOTD<>(this);
Sponge.getEventManager().registerListener(
Sponge.eventManager().registerListener(
pluginContainer,
ClientPingServerEvent.class,
new ClientPingServerEventListener(this.miniMOTD)
@@ -79,10 +78,12 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
@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());
if (this.miniMOTD.configManager().pluginSettings().updateChecker()) {
Sponge.asyncScheduler().submit(Task.builder()
.plugin(this.pluginContainer)
.execute(() -> new UpdateChecker().checkVersion().forEach(this.logger::info))
.build());
}
}
@Listener
@@ -96,28 +97,28 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
@Override
public CommandResult execute(final @NonNull CommandContext context) {
this.handler.execute(context.getCause().getAudience());
this.handler.execute(context.cause().audience());
return CommandResult.success();
}
}
final CommandHandlerFactory handlerFactory = new CommandHandlerFactory(this.miniMOTD);
final Command.Parameterized about = Command.builder()
.setExecutor(new WrappingExecutor(handlerFactory.about()))
.executor(new WrappingExecutor(handlerFactory.about()))
.build();
final Command.Parameterized help = Command.builder()
.setExecutor(new WrappingExecutor(handlerFactory.help()))
.executor(new WrappingExecutor(handlerFactory.help()))
.build();
final Command.Parameterized reload = Command.builder()
.setExecutor(new WrappingExecutor(handlerFactory.reload()))
.executor(new WrappingExecutor(handlerFactory.reload()))
.build();
event.register(
this.pluginContainer,
Command.builder()
.setPermission("minimotd.admin")
.child(about, "about")
.child(help, "help")
.child(reload, "reload")
.permission("minimotd.admin")
.addChild(about, "about")
.addChild(help, "help")
.addChild(reload, "reload")
.build(),
"minimotd"
);
@@ -140,6 +141,6 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
@Override
public @NonNull Favicon loadIcon(final @NonNull BufferedImage image) throws Exception {
return Objects.requireNonNull(Favicon.load(image), "failed to load favicon");
return Favicon.load(image);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ tasks {
archiveClassifier.set("")
destinationDirectory.set(rootProject.buildDir.resolve("libs"))
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
arrayOf("spigot", "bungeecord", "velocity", "sponge8").forEach {
arrayOf("spigot", "bungeecord", "velocity", "sponge7", "sponge8").forEach {
val shadowJar = rootProject.project(":minimotd-$it").tasks.getByName("shadowJar")
from(zipTree(shadowJar.outputs.files.singleFile))
dependsOn(shadowJar)
+1 -1
View File
@@ -6,7 +6,7 @@ dependencies {
implementation(project(":minimotd-common")) {
exclude("org.slf4j", "slf4j-api")
}
implementation("org.bstats", "bstats-velocity", "2.2.0")
implementation("org.bstats", "bstats-velocity", "2.2.1")
compileOnly("com.velocitypowered", "velocity-api", "1.1.4")
annotationProcessor("com.velocitypowered", "velocity-api", "1.1.4")
}
@@ -34,25 +34,22 @@ 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 xyz.jpenilla.minimotd.common.config.MiniMOTDConfig.PlayerCount;
import java.awt.image.BufferedImage;
import java.nio.file.Path;
@@ -69,8 +66,6 @@ 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;
@@ -80,14 +75,12 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
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);
@@ -132,20 +125,20 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
}
@Subscribe
public void onServerListPing(final @NonNull ProxyPingEvent ping) {
public void onServerListPing(final @NonNull ProxyPingEvent event) {
final String configString = this.miniMOTD.configManager().pluginSettings().configStringForHost(
ping.getConnection().getVirtualHost()
event.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 ServerPing.Builder pong = event.getPing().asBuilder();
final int onlinePlayers = this.miniMOTD.calculateOnlinePlayers(config, pong.getOnlinePlayers());
final PlayerCount count = config.modifyPlayerCount(pong.getOnlinePlayers(), pong.getMaximumPlayers());
final int onlinePlayers = count.onlinePlayers();
final int maxPlayers = count.maxPlayers();
pong.onlinePlayers(onlinePlayers);
final int maxPlayers = config.adjustedMaxPlayers(onlinePlayers, pong.getMaximumPlayers());
pong.maximumPlayers(maxPlayers);
final MOTDIconPair<Favicon> pair = this.miniMOTD.createMOTD(config, onlinePlayers, maxPlayers);
@@ -156,11 +149,7 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<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);
}
pong.description(motdComponent);
}
if (config.disablePlayerListHover()) {
@@ -170,11 +159,7 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
pong.nullPlayers();
}
ping.setPing(pong.build());
}
public @NonNull MiniMOTD<Favicon> miniMOTD() {
return this.miniMOTD;
event.setPing(pong.build());
}
@Override
@@ -192,14 +177,6 @@ public final class MiniMOTDPlugin implements MiniMOTDPlatform<Favicon> {
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();