From b92e7b1f3d6f60b7634a9048ed962d4ef208e85d Mon Sep 17 00:00:00 2001 From: CyberedCake Date: Wed, 6 Jul 2022 03:49:17 -0400 Subject: [PATCH] Add MOTD system for bungee, fixed up some spigot MOTD features --- build.gradle | 2 +- bungee/build.gradle | 12 +- .../cybercake/cyberapi/bungee/CyberAPI.java | 39 ++- .../cybercake/cyberapi/bungee/Validators.java | 10 + .../bungee/chat/centered/TextType.java | 4 +- .../server/serverlist/ServerListInfo.java | 59 ++++ .../serverlist/ServerListInfoListener.java | 108 +++++++ .../serverlist/ServerListPingEvent.java | 67 +++++ .../serverlist/managers/MOTDManager.java | 108 +++++++ .../managers/PlayerListManager.java | 263 ++++++++++++++++++ .../serverlist/managers/ProtocolManager.java | 99 +++++++ .../bungee/server/serverlist/motd/MOTD.java | 222 +++++++++++++++ .../server/serverlist/motd/MOTDIconType.java | 30 ++ .../players/NewPlayerCountType.java | 57 ++++ .../common/builders/settings/Settings.java | 27 +- spigot/build.gradle | 7 +- .../spigot/chat/centered/TextType.java | 2 +- .../managers/PlayerListManager.java | 14 +- .../spigot/server/serverlist/motd/MOTD.java | 34 ++- 19 files changed, 1144 insertions(+), 20 deletions(-) create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfo.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfoListener.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListPingEvent.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/MOTDManager.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/PlayerListManager.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/ProtocolManager.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTD.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTDIconType.java create mode 100644 bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/players/NewPlayerCountType.java diff --git a/build.gradle b/build.gradle index 0f2b334..382658f 100644 --- a/build.gradle +++ b/build.gradle @@ -15,7 +15,7 @@ static int getTargetJavaVersion() { return 17; } // version info def major = '3' -def minor = '4' +def minor = '5' diff --git a/bungee/build.gradle b/bungee/build.gradle index 4ebd1ab..1022140 100644 --- a/bungee/build.gradle +++ b/bungee/build.gradle @@ -4,6 +4,10 @@ repositories { name = 'sonatype' url = 'https://oss.sonatype.org/content/groups/public/' } + maven { + name = 'exceptionflug' + url = 'https://mvn.exceptionflug.de/repository/exceptionflug-public/' + } } dependencies { @@ -13,13 +17,19 @@ dependencies { // bungeecord and plugin hooks compileOnly 'net.md-5:bungeecord-api:1.19-R0.1-SNAPSHOT' compileOnly 'net.luckperms:api:5.4' + compileOnly 'dev.simplix:protocolize-api:2.2.1' // utils implementation 'org.reflections:reflections:0.10.2' + implementation 'org.apache.commons:commons-lang3:3.12.0' } shadowJar { relocate 'javassist', 'net.cybercake.cyberapi.dependencies.javassist' - relocate 'javax.annotation', 'net.cybercake.cyberapi.dependencies.annotations' relocate 'org.reflections', 'net.cybercake.cyberapi.dependencies.reflections' + relocate 'com.google.gson', 'net.cybercake.cyberapi.dependencies.google.gson' + relocate 'org.apache.commons.lang3', 'net.cybercake.cyberapi.dependencies.apache.commons' + relocate 'org.intellij.lang.annotations', 'net.cybercake.cyberapi.dependencies.annotations.intellij' + relocate 'org.jetbrains.annotations', 'net.cybercake.cyberapi.dependencies.annotations.jetbrains' + relocate 'javax.annotation', 'net.cybercake.cyberapi.dependencies.annotations.javax' } \ No newline at end of file diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/CyberAPI.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/CyberAPI.java index 14547ef..27e8a0d 100644 --- a/bungee/src/main/java/net/cybercake/cyberapi/bungee/CyberAPI.java +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/CyberAPI.java @@ -9,6 +9,8 @@ import net.cybercake.cyberapi.bungee.player.BungeeTitle; import net.cybercake.cyberapi.bungee.player.CyberPlayer; import net.cybercake.cyberapi.bungee.server.commands.CommandManager; +import net.cybercake.cyberapi.bungee.server.serverlist.ServerListInfo; +import net.cybercake.cyberapi.bungee.server.serverlist.ServerListInfoListener; import net.cybercake.cyberapi.common.CommonManager; import net.cybercake.cyberapi.common.basic.Time; import net.cybercake.cyberapi.common.builders.player.UserHeadSettings; @@ -94,6 +96,9 @@ protected CyberAPI startCyberAPI(@Nullable Settings settings) { // load all support variables, so they're not 'null' // automatically sets the variable in these methods, so returned values are not used getLuckPermsSupport(); + getProtocolizeSupport(); + + registerListener(new ServerListInfoListener()); reflectionsConsoleFilter(); // deprecated because I don't want anyone else using it CommandManager.commandManager().init(settings.getCommandsPath()); @@ -123,6 +128,7 @@ protected CyberAPI startCyberAPI(@Nullable Settings settings) { private FeatureSupport miniMessageSupport = null; private FeatureSupport luckPermsSupport = null; private FeatureSupport protocolLibSupport = null; + private FeatureSupport protocolizeSupport = null; /** * Gets the settings CyberAPI is using to determine the developer's preferences @@ -332,6 +338,28 @@ public FeatureSupport getProtocolLibSupport() { return this.protocolLibSupport; } + /** + * Gets the Protocolize support. This method assumes the best of the developer as if they have marked Protocolize support as {@link FeatureSupport#SUPPORTED}, it will allow use of it. + * @return the {@link FeatureSupport} enum of the value + * @since 3.5 + */ + public FeatureSupport getProtocolizeSupport() { + if(protocolizeSupport == null) { + protocolizeSupport = settings.supportsProtocolize(); + + if(protocolizeSupport.equals(FeatureSupport.AUTO)) { + try { + Class.forName("dev.simplix.protocolize"); + this.protocolizeSupport = FeatureSupport.SUPPORTED; + } catch (Exception exception) { + this.protocolizeSupport = FeatureSupport.UNSUPPORTED; + } + log.verbose("Protocolize support was set to auto, detected: " + protocolizeSupport.name()); + } + } + return this.protocolizeSupport; + } + /** * Sends a title to a player with specified settings * @param player the player to send the title to @@ -391,6 +419,15 @@ public void performCommand(CommandSender sender, String command) { ProxyServer.getInstance().getPluginManager().dispatchCommand(sender, command.substring(1)); } + /** + * Returns an instance of {@link ServerListInfo}, which allows you to change things like the MOTD, player count, icon, etc. + * @return the {@link ServerListInfo} instance + * @since 3.5 + */ + public ServerListInfo getServerListInfo() { + return ServerListInfo.serverListInfo(); + } + /** * Returns a list of online players in forms of {@link ProxiedPlayer} objects * @return the online players in {@link ProxiedPlayer} objects @@ -465,7 +502,7 @@ protected APILog() {} public void verbose(String message) { verbose(StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass().getCanonicalName(), message); } public void verbose(String canonical, String message) { if(getSettings().isVerbose()) - log(Level.INFO, ChatColor.DARK_GRAY + " [" + ChatColor.GRAY + "VERBOSE/" + canonical + ChatColor.DARK_GRAY + " ] " + ChatColor.RESET + message); + log(Level.INFO, ChatColor.DARK_GRAY + " [" + ChatColor.GRAY + "VERBOSE/" + canonical + ChatColor.DARK_GRAY + "] " + ChatColor.RESET + message); } public void verboseException(Throwable throwable) { diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/Validators.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/Validators.java index f430745..e27a42f 100644 --- a/bungee/src/main/java/net/cybercake/cyberapi/bungee/Validators.java +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/Validators.java @@ -31,4 +31,14 @@ public static void validateLuckPermsHook() { if(!CyberAPI.getInstance().getLuckPermsSupport().equals(FeatureSupport.SUPPORTED)) throw serverHook("LuckPerms"); } + /** + * --{@literal >} MAINLY FOR USE INSIDE CYBERAPI ONLY {@literal <}-- + *

+ * Validates that Protocolize is supported and working + */ + public static void validateProtocolizeHook() { + validateIsNotAuto(CyberAPI.getInstance().getProtocolizeSupport()); + if(!CyberAPI.getInstance().getProtocolizeSupport().equals(FeatureSupport.SUPPORTED)) throw serverHook("Protocolize"); + } + } \ No newline at end of file diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/chat/centered/TextType.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/chat/centered/TextType.java index b08c2a7..cbb1198 100644 --- a/bungee/src/main/java/net/cybercake/cyberapi/bungee/chat/centered/TextType.java +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/chat/centered/TextType.java @@ -20,9 +20,9 @@ public enum TextType { *

* Used for the MOTD of the server on the server list *

- * 45 characters {@literal <}- default + * 60 characters {@literal <}- default */ - MOTD(45); + MOTD(60); private final int length; diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfo.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfo.java new file mode 100644 index 0000000..2873acc --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfo.java @@ -0,0 +1,59 @@ +package net.cybercake.cyberapi.bungee.server.serverlist; + +import net.cybercake.cyberapi.bungee.server.serverlist.managers.MOTDManager; +import net.cybercake.cyberapi.bungee.server.serverlist.managers.PlayerListManager; +import net.cybercake.cyberapi.bungee.server.serverlist.managers.ProtocolManager; + +public class ServerListInfo { + + /** + * @deprecated Please use {@link ServerListInfo#serverListInfo()} or + */ + @SuppressWarnings({"all"}) + @Deprecated + public ServerListInfo() { } + + private static ServerListInfo serverListInfo = null; + + /** + * Gets an instance of {@link ServerListInfo} + * @return the {@link ServerListInfo} instance + * @since 3.5 + */ + public static ServerListInfo serverListInfo() { + if(serverListInfo == null) serverListInfo = new ServerListInfo(); + return new ServerListInfo(); + } + + /** + * Gets an instance of MOTD manager, please use this method instead of instantiating {@link MOTDManager} + *
+ * This class if for managing the MOTD, allowing you to modify it to what you please + * @return the {@link MOTDManager} instance + * @since 3.5 + */ + public MOTDManager getMOTDManager() { + return MOTDManager.motdManager(); + } + + /** + * Gets an instance of Player List manager, please use this method instead of instantiating {@link PlayerListManager} + *
+ * This class is for managing the player list, including player count, max player count, and players online hover + * @return the {@link PlayerListManager} instance + * @since 3.5 + */ + public PlayerListManager getPlayerListManager() { + return PlayerListManager.playerListManager(); + } + + /** + * Gets an instance of Protocol manager, please use this method instead of instantiating {@link ProtocolManager} + *
+ * This class is for managing the protocol number and version name, for example, if a server is outdated, it'll show "Paper 1.18" or something, and you can change that by using this class + * @return the {@link ProtocolManager} instance + * @since 3.5 + */ + public ProtocolManager getProtocolManager() { return ProtocolManager.protocolManager(); } + +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfoListener.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfoListener.java new file mode 100644 index 0000000..0fd74ed --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListInfoListener.java @@ -0,0 +1,108 @@ +package net.cybercake.cyberapi.bungee.server.serverlist; + +import net.cybercake.cyberapi.bungee.CyberAPI; +import net.cybercake.cyberapi.bungee.chat.UChat; +import net.cybercake.cyberapi.bungee.server.serverlist.motd.MOTD; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.Favicon; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.ServerPing; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.event.ProxyPingEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.event.EventHandler; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.UUID; + +public class ServerListInfoListener implements Listener { + + private static final HashMap faviconsFromFile = new HashMap<>(); + private static final HashMap faviconsFromURL = new HashMap<>(); + + @EventHandler + public void onPing(ProxyPingEvent event) { + ServerPing ping = handlePing(event.getConnection(), event.getResponse()); + if(ping == null) return; + event.setResponse(ping); + } + + private ServerPing handlePing(PendingConnection address, ServerPing ping) { + try { + ServerListInfo info = CyberAPI.getInstance().getServerListInfo(); + ServerListPingEvent serverListPingEvent = + new ServerListPingEvent( + address, + info.getProtocolManager().getVersionName(), + info.getProtocolManager().getProtocolNumber(), + info.getPlayerListManager().shouldShowPlayers(), + info.getProtocolManager().shouldAlwaysShowVersion(), + info.getMOTDManager().getRandomMOTD(), + info.getPlayerListManager().getMaxPlayers(), + info.getPlayerListManager().getOnlinePlayers(), + info.getPlayerListManager().getCustomOnlinePlayers() + ); + if(serverListPingEvent.isCancelled()) return null; + ProxyServer.getInstance().getPluginManager().callEvent(serverListPingEvent); + + // MOTD + MOTD motd = (serverListPingEvent.getMOTD() == null ? MOTD.builder("default_temp").build() : serverListPingEvent.getMOTD()); + ping.setDescriptionComponent(UChat.bComponent(motd.getFormattedMOTD())); + try { + Favicon image = null; + switch(motd.getMOTDIconType()) { + case FILE -> { + // image = Favicon.create(Base64.getEncoder().encodeToString(Files.readAllBytes(motd.getFileIcon().toPath()))); + if(motd.getFileIcon() != null && faviconsFromFile.get(motd.getFileIcon()) == null) { + CyberAPI.getInstance().getAPILogger().verbose("New icon found (type=FILE)! Caching into temporary storage..."); + CyberAPI.getInstance().getAPILogger().verbose("This may take a second, and Bungee will likely provide a 'Listener took ms...', just ignore it!"); + image = Favicon.create(ImageIO.read(motd.getFileIcon())); + faviconsFromFile.put(motd.getFileIcon(), image); + } else if(faviconsFromFile.get(motd.getFileIcon()) != null) { + image = faviconsFromFile.get(motd.getFileIcon()); + } + } + case URL -> { + if(motd.getURLIcon() != null && faviconsFromURL.get(motd.getURLIcon()) == null) { + CyberAPI.getInstance().getAPILogger().verbose("New icon found (type=URL)! Caching into temporary storage..."); + CyberAPI.getInstance().getAPILogger().verbose("This may take a second, and Bungee will likely provide a 'Listener took ms...', just ignore it!"); + HttpURLConnection connection = (HttpURLConnection) motd.getURLIcon().openConnection(); + connection.connect(); + BufferedImage bufferedImage = ImageIO.read(connection.getInputStream()); + connection.disconnect(); + image = Favicon.create(bufferedImage); + faviconsFromURL.put(motd.getURLIcon(), image); + } else if(faviconsFromURL.get(motd.getURLIcon()) != null) { + image = faviconsFromURL.get(motd.getURLIcon()); + } + } + } + if(image != null) ping.setFavicon(image); + } catch (Exception exception) { + CyberAPI.getInstance().getAPILogger().error("An exception occurred whilst creating the Favicon for the server: " + ChatColor.DARK_GRAY + exception); + CyberAPI.getInstance().getAPILogger().verboseException(exception); + } + + // player count + ArrayList profiles = new ArrayList<>(); + for(String name : serverListPingEvent.getOnlinePlayers()) { + profiles.add(new ServerPing.PlayerInfo(name, UUID.randomUUID())); + } + ping.setPlayers(new ServerPing.Players(serverListPingEvent.getMaxPlayers(), (serverListPingEvent.isPlayerListVisible() ? serverListPingEvent.getOnlinePlayerCount() : Integer.MIN_VALUE), (serverListPingEvent.isPlayerListVisible() ? profiles.toArray(new ServerPing.PlayerInfo[]{}) : null))); + + ping.setVersion(new ServerPing.Protocol(UChat.chat(serverListPingEvent.getVersionName()), (serverListPingEvent.isVersionNameAlwaysVisible() ? 0 : (serverListPingEvent.getProtocolVersion() == Integer.MIN_VALUE ? ProxyServer.getInstance().getProtocolVersion() : serverListPingEvent.getProtocolVersion())))); + + return ping; + } catch (Exception exception){ + CyberAPI.getInstance().getAPILogger().error("An exception occurred whilst sending server ping packet: " + ChatColor.DARK_GRAY + exception); + CyberAPI.getInstance().getAPILogger().verboseException(exception); + } + return null; + } +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListPingEvent.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListPingEvent.java new file mode 100644 index 0000000..24b616d --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/ServerListPingEvent.java @@ -0,0 +1,67 @@ +package net.cybercake.cyberapi.bungee.server.serverlist; + +import net.cybercake.cyberapi.bungee.CyberAPI; +import net.cybercake.cyberapi.bungee.server.serverlist.motd.MOTD; +import net.md_5.bungee.api.connection.PendingConnection; +import net.md_5.bungee.api.plugin.Cancellable; +import net.md_5.bungee.api.plugin.Event; + +import java.util.ArrayList; +import java.util.List; + +/** + * The server list ping event for CyberAPI, an alternative way to change the server list. It is, however, still recommended that you use {@link CyberAPI#getServerListInfo()} instead! + */ +@SuppressWarnings({"unused"}) +public class ServerListPingEvent extends Event implements Cancellable { + private boolean cancelled; + + private final PendingConnection connection; + + private String versionName; + private int protocolVersion; + private boolean versionNameAlwaysVisible; + private boolean playerListVisible; + private MOTD motd; + private int maxPlayers; + private int onlinePlayerCount; + private List onlinePlayers; + + public ServerListPingEvent(PendingConnection connection, String versionName, int protocolVersion, boolean playerListVisible, boolean versionNameAlwaysVisible, MOTD motd, int maxPlayers, int onlinePlayerCount, List onlinePlayers) { + this.connection = connection; + + this.versionName = versionName; + this.protocolVersion = protocolVersion; + this.playerListVisible = playerListVisible; + this.versionNameAlwaysVisible = versionNameAlwaysVisible; + this.motd = motd; + this.maxPlayers = maxPlayers; + this.onlinePlayerCount = onlinePlayerCount; + this.onlinePlayers = onlinePlayers; + this.cancelled = false; + } + + public void setVersionName(String versionName) { this.versionName = versionName; } + public void setProtocolVersion(int protocolVersion) { this.protocolVersion = protocolVersion; } + public void setPlayerListVisible(boolean playerListVisible) { this.playerListVisible = playerListVisible; } + public void setVersionNameAlwaysVisible(boolean versionNameAlwaysVisible) { this.versionNameAlwaysVisible = versionNameAlwaysVisible; } + public void setMOTD(MOTD motd) { this.motd = motd; } + public void setMaxPlayers(int maxPlayers) { this.maxPlayers = maxPlayers; } + public void setOnlinePlayerCount(int onlinePlayerCount) { this.onlinePlayerCount = onlinePlayerCount; } + public void setOnlinePlayers(List onlinePlayers) { this.onlinePlayers = onlinePlayers; } + public void setOnlinePlayers(String... onlinePlayers) { this.onlinePlayers = new ArrayList<>(List.of(onlinePlayers)); } + + public PendingConnection getPendingConnection() { return connection; } + + public String getVersionName() { return this.versionName; } + public int getProtocolVersion() { return this.protocolVersion; } + public boolean isPlayerListVisible() { return this.playerListVisible; } + public boolean isVersionNameAlwaysVisible() { return this.versionNameAlwaysVisible; } + public MOTD getMOTD() { return this.motd; } + public int getMaxPlayers() { return this.maxPlayers; } + public int getOnlinePlayerCount() { return this.onlinePlayerCount; } + public List getOnlinePlayers() { return onlinePlayers; } + + @Override public boolean isCancelled() { return cancelled; } + @Override public void setCancelled(boolean cancelled) { this.cancelled = cancelled; } +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/MOTDManager.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/MOTDManager.java new file mode 100644 index 0000000..b983931 --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/MOTDManager.java @@ -0,0 +1,108 @@ +package net.cybercake.cyberapi.bungee.server.serverlist.managers; + +import net.cybercake.cyberapi.bungee.CyberAPI; +import net.cybercake.cyberapi.bungee.server.serverlist.ServerListInfo; +import net.cybercake.cyberapi.bungee.server.serverlist.motd.MOTD; +import net.cybercake.cyberapi.common.basic.NumberUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MOTDManager { + + /** + * Creates an instance of MOTD manager + * @deprecated Please use {@link ServerListInfo#getMOTDManager()} instead! + */ + @Deprecated + @SuppressWarnings({"all"}) + public MOTDManager() { } + + private static MOTDManager motdManager = null; + public static MOTDManager motdManager() { + if(MOTDManager.motdManager == null) MOTDManager.motdManager = new MOTDManager(); + return MOTDManager.motdManager; + } + + private final List motds = new ArrayList<>(); + + /** + * Shows the list of MOTDs. + *

+ * Add an MOTD by using the {@link MOTDManager#addMOTD(MOTD)} + * @return a list of active MOTDs created by the plugin + * @since 3.5 + */ + public List getMOTDs() { return motds; } + + /** + * Every MOTD has an id, because when you build the MOTD, using {@code new MOTDBuilder(String)}, it attaches the id to the MOTD. + * @param id the id to attempt to get from + * @return the MOTD obtained + * @since 3.5 + */ + public MOTD getMOTDFromID(String id) { + for(MOTD motd : motds) { + if(motd.getID().equals(id)) return motd; + } return null; + } + + /** + * Finds a random MOTD using the method {@link NumberUtils#randomInt(int, int)} + * @return a random {@link MOTD} out of all the ones you have added + * @since 3.5 + */ + public MOTD getRandomMOTD() { + MOTD returned; + try { + if(motds.size() == 1) returned = motds.get(0); + else { + int number = NumberUtils.randomInt(0, motds.size()-1); + returned = motds.get(number); + } + } catch (IllegalArgumentException illegalArgumentException) { + returned = null; + } + return returned; + } + + /** + * Add an {@link MOTD} to the + * @param motd the {@link MOTD} to add, create by creating a new instance of {@link MOTD.Builder} + * @since 3.5 + */ + public void addMOTD(MOTD motd) { motds.add(motd); } + + /** + * Adds multiple {@link MOTD}s to the {@link MOTD} cache, the best place to put this is in your {@link CyberAPI#onEnable()} + * @param motds the {@link MOTD} to add, create by creating a new instance of {@link MOTD.Builder} + * @since 3.5 + */ + public void addMOTDs(MOTD... motds) { this.motds.addAll(Arrays.asList(motds)); } + + /** + * Clear all the cached {@link MOTD}s that have been added + * @since 3.5 + */ + public void clearMOTDs() { motds.clear(); } + + /** + * Removes an {@link MOTD} from the cached {@link MOTD}s using its distinctive {@link String} ID + * @param id the {@link String} ID given to an {@link MOTD} when building it with {@link MOTD.Builder} + * @since 3.5 + */ + public void removeMOTD(String id) { motds.remove(getMOTDFromID(id)); } + + /** + * Returns a formatted MOTD from a MOTD object + * @param motd the MOTD to format + * @return the formatted {@link String} MOTD + * @since 3.5 + * @see MOTD#getFormattedMOTD() + */ + public String getFormattedMOTD(MOTD motd) { + return motd.getFormattedMOTD(); + } + +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/PlayerListManager.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/PlayerListManager.java new file mode 100644 index 0000000..90b5407 --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/PlayerListManager.java @@ -0,0 +1,263 @@ +package net.cybercake.cyberapi.bungee.server.serverlist.managers; + +import net.cybercake.cyberapi.bungee.CyberAPI; +import net.cybercake.cyberapi.bungee.chat.UChat; +import net.cybercake.cyberapi.bungee.server.serverlist.players.NewPlayerCountType; +import net.cybercake.cyberapi.common.basic.NumberUtils; +import net.md_5.bungee.api.ProxyServer; + +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.List; + +public class PlayerListManager { + + /** + * Creates an instance of Player List manager + * @deprecated Please use {@link ServerListInfo#getMOTDManager()} instead! + */ + @Deprecated + @SuppressWarnings({"all"}) + public PlayerListManager() { + resetMaxPlayers(); + resetOnlinePlayerCount(); + resetShowPlayers(); + } + + private static PlayerListManager playerListManager = null; + public static PlayerListManager playerListManager() { + if(PlayerListManager.playerListManager == null) PlayerListManager.playerListManager = new PlayerListManager(); + return PlayerListManager.playerListManager; + } + + private final List players = new ArrayList<>(); + private Integer maxPlayers; + private Object newCurrentPlayers; + private NewPlayerCountType newPlayerCountType; + private boolean showPlayers; + + /** + * Sets the players online. Note: This has no effect on the current player count/max player count!. + *
+ * This method resets the hover event when hovering over the player count and sets the players to the parameter. + * @param players the players to show when hovering over the player count + * @since 3.5 + */ + public void setOnlinePlayers(String... players) { + clearOnlinePlayers(); + List.of(players).forEach(this::addOnlinePlayer); + } + + /** + * Adds a player to the players online without resetting it first. Note: This has no effect on the current player count/max player count! + * @param player the player to add to the hover event + * @since 3.5 + */ + public void addOnlinePlayer(String player) { + this.players.add(UChat.chat(player)); + } + + /** + * Removes a player from the players online using the exact {@link String} match. Note: This has no effect on the current player count/max player count! + * @param player the player to remove from the hover event, must be an exact match to a currently online player to remove (alternatively, you could use {@link PlayerListManager#removeOnlinePlayer(int)}) + * @since 3.5 + */ + public void removeOnlinePlayer(String player) { + this.players.remove(player); + } + + /** + * Removes a player from the players online using an index number. Note: This has no effect on the current player count/max player count! + * @param index the index of the player to remove, 0 being the first item on the list (alternatively, you could use {@link PlayerListManager#removeOnlinePlayer(String)}) + * @since 3.5 + * @throws IndexOutOfBoundsException if the index is out of bounds ({@code index < 0 || index >= size()}) + */ + public void removeOnlinePlayer(int index) { + this.players.remove(index); + } + + /** + * Get a list of the fake online players provided by the user in {@link PlayerListManager#addOnlinePlayer(String)}} or {@link PlayerListManager#setOnlinePlayers(String...)}. + * @return the fake online players (that show up in the hover event when hovering over the player count in the multiplayer menu) + * @since 3.5 + */ + public List getCustomOnlinePlayers() { + return this.players; + } + + /** + * Clears the fake online players that show when hovering over the player count in the multiplayer menu. + * @since 3.5 + */ + public void clearOnlinePlayers() { + this.players.clear(); + } + + /** + * The fake players online, that show up when you hover over the player count in the multiplayer menu, current size. + * @return the amount of fake online players + * @since 3.5 + */ + public int getCustomOnlinePlayersSize() { + return this.players.size(); + } + + /** + * Sets the maximum player count displayed on the server list. Note: This has no effect on the actual max player count! Players can still join after it hits the maximum, it's just for show. + * @param maxPlayers the fake max player count + * @since 3.5 + */ + public void setMaxPlayers(int maxPlayers) { + this.maxPlayers = maxPlayers; + } + + /** + * Gets the current maximum player count displayed on the server list. + * @return the amount of fake max players + * @since 3.5 + */ + public int getMaxPlayers() { + return this.maxPlayers; + } + + /** + * Resets the fake maximum player count to it's value from the server.properties + * @since 3.5 + */ + public void resetMaxPlayers() { + this.maxPlayers = ProxyServer.getInstance().getConfig().getListeners().stream().toList().get(0).getMaxPlayers(); + } + + /** + * Sets the current player count for the players when viewing the server in the multiplayer menu + * @param newPlayerCountType what effect {@code newCurrentPlayers} should have on the server + * @param newCurrentPlayers the new current players that show up on the multiplayer menu + * @since 3.5 + */ + public void setOnlinePlayerCount(NewPlayerCountType newPlayerCountType, Object newCurrentPlayers) { + validateSetCurrentPlayerCountArgs(newPlayerCountType, newCurrentPlayers); + this.newPlayerCountType = newPlayerCountType; + this.newCurrentPlayers = switch(newPlayerCountType) { + case CONSTANT, KEEP_SAME, STAY_AT -> Integer.parseInt(String.valueOf(newCurrentPlayers)); + case PERCENT -> Double.parseDouble(String.valueOf(newCurrentPlayers)); + case RANDOM_BETWEEN -> String.valueOf(newCurrentPlayers); + }; + } + + /** + * Gets the raw version that was entered into {@link PlayerListManager#setOnlinePlayerCount(NewPlayerCountType, Object)} + * @return the raw new current players value + * @since 3.5 + */ + public Object getOnlinePlayersRaw() { + return newCurrentPlayers; + } + + /** + * Gets the new player count type that is used in the calculation for {@link PlayerListManager#getOnlinePlayers()} + * @return the {@link NewPlayerCountType} + * @since 3.5 + */ + public NewPlayerCountType getOnlinePlayersType() { + return newPlayerCountType; + } + + /** + * Gets the online players list according to the settings set using {@link PlayerListManager#setOnlinePlayerCount(NewPlayerCountType, Object)} + * @return the custom player count + * @since 3.5 + */ + public int getOnlinePlayers() { + int onlinePlayers = CyberAPI.getInstance().getOnlinePlayers().size(); + String newCurrentPlayersString = String.valueOf(newCurrentPlayers); + if(getOnlinePlayersType() == null) return onlinePlayers; + return switch(getOnlinePlayersType()) { + case STAY_AT -> Integer.parseInt(newCurrentPlayersString); + case CONSTANT -> onlinePlayers+Integer.parseInt(newCurrentPlayersString); + case PERCENT -> Integer.parseInt(NumberUtils.formatDecimal(onlinePlayers+(onlinePlayers*Double.parseDouble(newCurrentPlayersString)), 0, RoundingMode.HALF_EVEN)); + case RANDOM_BETWEEN -> onlinePlayers+(NumberUtils.randomInt(Integer.parseInt(newCurrentPlayersString.split(":")[0]), Integer.parseInt(newCurrentPlayersString.split(":")[1]))); + default -> onlinePlayers; + }; + } + + /** + * Resets the current player count for the players viewing the server in the multiplayer menu + * @since 3.5 + */ + public void resetOnlinePlayerCount() { + this.newPlayerCountType = null; + this.newCurrentPlayers = 0.0D; + } + + /** + * Should the server show the players online or the player count. This will make your custom player hover clear and set the player count to '???' + * @param showPlayers should the plugin show the online players + * @since 3.5 + */ + public void setShowPlayers(boolean showPlayers) { + this.showPlayers = showPlayers; + } + + /** + * Resets the showPlayers boolean to the server.properties value + * @since 3.5 + */ + public void resetShowPlayers() { + this.showPlayers = true; + } + + /** + * Should the plugin show the player count and player list. If set to false, it will show '???' as the player count. + *
+ * Note: Will override your custom player count and the hoverable player list! + * @return should the server send a player count and player hover packet to the client + * @since 3.5 + */ + public boolean shouldShowPlayers() { + return this.showPlayers; + } + + + + + /** + * Private method used in {@link PlayerListManager#setOnlinePlayerCount(NewPlayerCountType, Object)} + * @since 3.5 + */ + private void validateSetCurrentPlayerCountArgs(NewPlayerCountType newPlayerCountType, Object newCurrentPlayers) { + if(newPlayerCountType.getType() != null && !(newCurrentPlayers.getClass() == newPlayerCountType.getType())) + throw new IllegalArgumentException("newCurrentPlayers must be a " + newPlayerCountType.getType().getCanonicalName() + " (currently " + newCurrentPlayers.getClass().getCanonicalName() + ") because newPlayerCountType is set to " + newPlayerCountType.name() + "!"); + + switch(newPlayerCountType) { +// case CONSTANT -> { +// if(!NumberUtils.isInteger(String.valueOf(newCurrentPlayers))) throw validateSetCurrentPlayerCountArgsIllegalArgument("The constant number must be an integer!", null); +// } + case PERCENT -> { +// if(!NumberUtils.isDouble(String.valueOf(newCurrentPlayers))) throw validateSetCurrentPlayerCountArgsIllegalArgument("The percent number must be a double!", null); + if(NumberUtils.isBetweenEquals(Double.parseDouble(String.valueOf(newCurrentPlayers)), 0.0, 1.0)) throw validateSetCurrentPlayerCountArgsIllegalArgument("The percent number must be between 0.0 and 1.0!", null); + } + case RANDOM_BETWEEN -> { + String newCurrentPlayersString = String.valueOf(newCurrentPlayers); + if(newCurrentPlayersString.split(":").length != 1) throw validateSetCurrentPlayerCountArgsIllegalArgument("Must contain a ':' to separate values!", null); + String firstValue = newCurrentPlayersString.split(":")[0]; + String secondValue = newCurrentPlayersString.split(":")[1]; + if(!(NumberUtils.isInteger(firstValue)) || !(NumberUtils.isInteger(secondValue))) throw validateSetCurrentPlayerCountArgsIllegalArgument("Both values on either side of the colon ':' must be integers!", null); + int firstInt = Integer.parseInt(firstValue); + int secondInt = Integer.parseInt(secondValue); + if(firstInt > secondInt) throw validateSetCurrentPlayerCountArgsIllegalArgument("The first integer must be smaller than the second integer!", null); + // this means that what CyberAPI is going to use in setCurrentPlayerCount() can be safely assumed to be correct + } + } + } + + /** + * Private method used in {@link PlayerListManager#validateSetCurrentPlayerCountArgs(NewPlayerCountType, Object)} + * @since 3.5 + */ + private IllegalArgumentException validateSetCurrentPlayerCountArgsIllegalArgument(String why, Throwable cause) { + String reason = "Failed to parse newCurrentPlayers: " + why; + if(cause != null) return new IllegalArgumentException(why, cause); + return new IllegalArgumentException(reason); + } + +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/ProtocolManager.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/ProtocolManager.java new file mode 100644 index 0000000..dfd25a8 --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/managers/ProtocolManager.java @@ -0,0 +1,99 @@ +package net.cybercake.cyberapi.bungee.server.serverlist.managers; + +import net.cybercake.cyberapi.bungee.server.serverlist.ServerListInfo; +import net.md_5.bungee.api.ProxyServer; + +public class ProtocolManager { + + /** + * Creates an instance of Protocol manager + * @deprecated Please use {@link ServerListInfo#getProtocolManager()} instead! + */ + @Deprecated + @SuppressWarnings({"all"}) + public ProtocolManager() { + resetVersionName(); + resetProtocolNumber(); + this.alwaysShowVersion = false; + } + + private static ProtocolManager protocolManager = null; + public static ProtocolManager protocolManager() { + if(ProtocolManager.protocolManager == null) ProtocolManager.protocolManager = new ProtocolManager(); + return ProtocolManager.protocolManager; + } + + private boolean alwaysShowVersion; + private String versionName; + private int protocol; + + /** + * Sets the name of the version to show when the client is outdated or {@link ProtocolManager#setAlwaysShowVersion(boolean)} is true + * @param versionName the name to set the version to + * @since 3.5 + */ + public void setVersionName(String versionName) { + this.versionName = versionName; + } + + /** + * Gets the name of the version that is shown when the client is outdated or {@link ProtocolManager#setAlwaysShowVersion(boolean)} is true + * @return the {@link String} version name + * @since 3.5 + */ + public String getVersionName() { + return this.versionName; + } + + /** + * Resets the version name back to what it should be ({@link ProxyServer#getName()} and {@link ProxyServer#getGameVersion()} - 'Waterfall 1.19') + * @since 3.5 + */ + @SuppressWarnings({"deprecation"}) + public void resetVersionName() { + this.versionName = ProxyServer.getInstance().getName() + " " + ProxyServer.getInstance().getGameVersion(); + } + + /** + * Sets the protocol to a specified number, you can see all version protocols here: https://wiki.vg/Protocol_version_numbers + * @param protocol what to set the fake protocol number to + * @since 3.5 + */ + public void setProtocolNumber(int protocol) { + this.protocol = protocol; + } + + /** + * Gets the protocol, either set by the server or a custom protocol number, you can see all version protocols here: https://wiki.vg/Protocol_version_numbers + * @return the custom or server protocol number + * @since 3.5 + */ + public int getProtocolNumber() { + return this.protocol; + } + + /** + * Resets the protocol number to its default value (aka what the server's protocol version number is) + * @since 3.5 + */ + public void resetProtocolNumber() { this.protocol = Integer.MIN_VALUE; } + + /** + * Sets whether the server should always show an outdated client, which would mean the version name is constantly shown + * @param alwaysShowVersion whether to always display outdated client + * @since 3.5 + */ + public void setAlwaysShowVersion(boolean alwaysShowVersion) { + this.alwaysShowVersion = alwaysShowVersion; + } + + /** + * Should the server always show the version from {@link ProtocolManager#setVersionName(String)} + * @return whether the server should always show the version + * @since 3.5 + */ + public boolean shouldAlwaysShowVersion() { + return this.alwaysShowVersion; + } + +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTD.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTD.java new file mode 100644 index 0000000..90cf10b --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTD.java @@ -0,0 +1,222 @@ +package net.cybercake.cyberapi.bungee.server.serverlist.motd; + +import net.cybercake.cyberapi.bungee.chat.UChat; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.ProxyServer; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nullable; +import java.io.File; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +/** + * @since 3.5 + */ +public class MOTD { + + /** + * Creates a new {@link Builder} instance, which then the method {@link Builder#build()} can build into a {@link MOTD} + * @param id the ID of the new {@link MOTD} + * @return the Builder instance + * @since 3.5 + */ + public static Builder builder(String id) { return new Builder(id); } + + /** + * @since 3.5 + */ + public static class Builder { + private final String id; + + private String motd; + private boolean centered; + private @Nullable File iconFile; + private @Nullable URL iconURL; + private MOTDIconType iconType; + + /** + * Creates a new {@link Builder} instance, which then the method {@link Builder#build()} can build into a {@link MOTD} + * @param id the ID of the new {@link MOTD} + * @since 3.5 + */ + public Builder(String id) { + if(!net.cybercake.cyberapi.common.basic.StringUtils.isAlphanumericSpace(id.replace("_", "").replace("-", ""))) + throw parseError("The 'id' must be alpha-numeric space (only contains A-Za-z0-9 _-)"); + if(id.isEmpty()) + throw parseError("The 'id' cannot be empty!"); + if(id.length() > 20) + throw parseError("The 'id' must be less than or equal to 20 characters!"); + if(id.length() < 3) + throw parseError("The 'id' must be more than or equal to 3 characters!"); + + this.id = id; + + this.motd = String.valueOf(ProxyServer.getInstance().getConfig().getListeners().stream().toList().get(0).getMotd()); + this.centered = false; + this.iconFile = null; + this.iconURL = null; + this.iconType = MOTDIconType.UNSET; + } + + /** + * Creates a parse exception, usually used internally in the {@link Builder} for {@link MOTD} + * @param message the message to include after 'Failed to set the MOTD ID:' + * @return the {@link IllegalArgumentException} instance + * @since 3.5 + */ + private IllegalArgumentException parseError(String message) { + return new IllegalArgumentException("Failed to set the MOTD ID: " + message); + } + + /** + * Sets the MOTD to a String. + * @param motd the motd string + */ + public Builder text(String motd) { + this.motd = motd; return this; + } + + /** + * Sets the MOTD to two strings as different lines + * @param line1 the first line of the MOTD + * @param line2 the second line of the MOTD + */ + public Builder text(String line1, String line2) { + this.motd = line1 + "\n" + line2; return this; + } + + /** + * Sets the MOTD to multiple strings + * @param motd the motd strings + */ + public Builder text(String... motd) { + this.motd = String.join("\n", motd); return this; + } + + /** + * Should the text be centered (only works with legacy color codes) + * @param centered should the MOTD be centered + */ + public Builder shouldCenter(boolean centered) { + this.centered = centered; return this; + } + + /** + * Sets the MOTD icon to this image, a {@link File} + * @param icon the {@link File} to cache + */ + public Builder icon(File icon) { + this.iconType = MOTDIconType.FILE; this.iconFile = icon; return this; + } + + /** + * Sets the MOTD icon to this image, a {@link URL} + * @param icon the {@link URL} to cache + */ + public Builder icon(URL icon) { + this.iconType = MOTDIconType.URL; this.iconURL = icon; return this; + } + + /** + * Builds the builder into an {@link MOTD} instance + * @return the {@link MOTD} instance + * @since 3.5 + */ + public MOTD build() { + return new MOTD(this); + } + } + + private final Builder builder; + + /** + * The {@link MOTD} instance, created by the {@link Builder} instance + * @param builder the builder that can then be transformed into a {@link MOTD} + * @since 3.5 + */ + public MOTD(Builder builder) { + this.builder = builder; + } + + /** + * Gets the {@link String} ID of the MOTD + * @return the MOTD ID + * @since 3.5 + */ + public String getID() { + return builder.id; + } + + /** + * Gets the {@link String} form of the {@link MOTD}, a.k.a. what is shown on the server list + * @return the MOTD + * @since 3.5 + */ + public String getStringMOTD() { + return builder.motd; + } + + /** + * Gets whether the {@link MOTD} should be centered or not. + * @return whether the {@link MOTD} should be centered or not + * @since 3.5 + */ + public boolean isCentered() { + return builder.centered; + } + + /** + * Gets the icon type of the MOTD + * @return the icon type of MOTD + * @since 3.5 + */ + public MOTDIconType getMOTDIconType() { + return builder.iconType; + } + + /** + * Gets the {@link File} of the icon + * @return the icon in {@link File} form, {@code null} if {@link MOTDIconType} is {@link MOTDIconType#UNSET} or not {@link MOTDIconType#FILE} + * @since 3.5 + */ + public File getFileIcon() { + if(builder.iconFile != null && getMOTDIconType().equals(MOTDIconType.FILE)) { + return builder.iconFile; + } + return null; + } + + /** + * Gets the {@link URL} of the icon + * @return the icon in {@link URL} form, {@code null} if {@link MOTDIconType} is {@link MOTDIconType#UNSET} or not {@link MOTDIconType#URL} + * @since 3.5 + */ + public URL getURLIcon() { + if(builder.iconURL != null && getMOTDIconType().equals(MOTDIconType.URL)) { + return builder.iconURL; + } + return null; + } + + /** + * Gets the formatted MOTD with MiniMessage and proper centering + * @return the formatted {@link String} MOTD + * @since 3.5 + */ + public String getFormattedMOTD() { + boolean centered = isCentered(); + String text = getStringMOTD(); + + if(centered) { + List newMOTD = new ArrayList<>(); + for(String str : text.split("\\n")) { + newMOTD.add(StringUtils.center(ChatColor.stripColor(str), 45)); + } + return UChat.chat(String.join("\n", newMOTD)); + } + return UChat.chat(text); + } + +} diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTDIconType.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTDIconType.java new file mode 100644 index 0000000..e9463bc --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/motd/MOTDIconType.java @@ -0,0 +1,30 @@ +package net.cybercake.cyberapi.bungee.server.serverlist.motd; + +import java.io.File; + +/** + * Represents the MOTD icon type + * @since 3.5 + */ +public enum MOTDIconType { + /** + * Represents the {@link File} MOTD icon type + */ + FILE(File.class), + + /** + * Represents the {@link java.net.URL} MOTD icon type + */ + URL(java.net.URL.class), + + /** + * Represents if the MOTD icon type is unknown + */ + UNSET(null); + + private final Class type; + MOTDIconType(Class type) { + this.type = type; + } + public Class getType() { return type; } +} \ No newline at end of file diff --git a/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/players/NewPlayerCountType.java b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/players/NewPlayerCountType.java new file mode 100644 index 0000000..b695e7f --- /dev/null +++ b/bungee/src/main/java/net/cybercake/cyberapi/bungee/server/serverlist/players/NewPlayerCountType.java @@ -0,0 +1,57 @@ +package net.cybercake.cyberapi.bungee.server.serverlist.players; + +import javax.annotation.Nullable; + +/** + * The new player count type used in {@link net.cybercake.cyberapi.bungee.server.serverlist.managers.PlayerListManager PlayerListManager} + */ +public enum NewPlayerCountType { + /** + * Represents the value the player count should stay at. + *

+ * Example Stay At Value: current player count is 5, but server shows stay-at value of '20' + */ + STAY_AT(Integer.class), + + /** + * Represents a constant addition or subtraction from the player count. + *

+ * Example Constant Value: 5 - (constant)4 = 1
+ * Example Constant Value: 5 + (constant)3 = 8 + *

+ * If a value goes below 0 (i.e. -2), the player count will stay at 0 + */ + CONSTANT(Integer.class), + + /** + * Represents a percentage addition or subtraction from the player count. + *

+ * Example Percentage Value: 5 - (percentage)0.10 (which would be 0.5, rounded to 1.0) = 4
+ * Example Percentage Value: 5 + (percentage)0.50 (which would be 2.5, rounded to 3.0) = 8 + *

+ * If a value goes below 0 (i.e. -2), the player count will stay at 0 + */ + PERCENT(Double.class), + + /** + * Represents a random value between two integers addition or subtraction from the player count. + *

+ * Example Random Between Value: 5 - (random)3:8 (chose '4') = 1
+ * Example Random Between Value: 5 + (random)3:8 (chose '8') = 13 + *

+ * If a value goes below 0 (i.e. -2), the player count will stay at 0 + */ + RANDOM_BETWEEN(String.class), + + /** + * Represents keeping the player count the same and not changing it what-so-ever. + */ + KEEP_SAME(null); + + private final Class type; + NewPlayerCountType(@Nullable Class type) { + this.type = type; + } + + public Class getType() { return type; } +} diff --git a/common/src/main/java/net/cybercake/cyberapi/common/builders/settings/Settings.java b/common/src/main/java/net/cybercake/cyberapi/common/builders/settings/Settings.java index c608eed..f8dd25c 100644 --- a/common/src/main/java/net/cybercake/cyberapi/common/builders/settings/Settings.java +++ b/common/src/main/java/net/cybercake/cyberapi/common/builders/settings/Settings.java @@ -20,7 +20,7 @@ public class Settings { */ public static class Builder { private boolean verbose, silenced, checkForUpdates, showPrefixInLogs, muteStartMessage; - private FeatureSupport adventureSupport, miniMessageSupport, luckPermsSupport, protocolLibSupport; + private FeatureSupport adventureSupport, miniMessageSupport, luckPermsSupport, protocolLibSupport, protocolizeSupport; private String name, prefix, commandsPath; /** @@ -39,6 +39,7 @@ public Builder() { this.miniMessageSupport = FeatureSupport.AUTO; this.luckPermsSupport = FeatureSupport.AUTO; this.protocolLibSupport = FeatureSupport.AUTO; + this.protocolizeSupport = FeatureSupport.AUTO; this.name = null; this.prefix = null; this.commandsPath = null; @@ -93,6 +94,7 @@ public Builder() { *

* Default Value: {@link FeatureSupport#AUTO} * @param adventureSupport set this to whether or not Adventure API will be supported + * @apiNote This is only available for **SPIGOT** */ public Builder adventureSupport(FeatureSupport adventureSupport) { this.adventureSupport.setFeature("ADVENTURE_API"); this.adventureSupport = adventureSupport; return this; } @@ -104,6 +106,7 @@ public Builder() { *

* Default Value: {@link FeatureSupport#AUTO} * @param miniMessageSupport set this to whether or not MiniMessage will be supported + * @apiNote This is only available for **SPIGOT** */ public Builder miniMessageSupport(FeatureSupport miniMessageSupport) { this.miniMessageSupport.setFeature("MINI_MESSAGE"); this.miniMessageSupport = miniMessageSupport; return this; } @@ -114,19 +117,32 @@ public Builder() { *

* Default Value: {@link FeatureSupport#AUTO} * @param luckPermsSupport set this to whether or not LuckPerms will be supported + * @apiNote This is only available for SPIGOT and BUNGEECORD */ public Builder luckPermsSupport(FeatureSupport luckPermsSupport) { this.luckPermsSupport.setFeature("LUCKPERMS"); this.luckPermsSupport = luckPermsSupport; return this; } /** - * Should CyberAPI attempt and allow the use of LuckPerms data? {@link net.luckperms.api.LuckPerms} + * Should CyberAPI attempt and allow the use of ProtocolLib data? *

* If you have ProtocolLib installed on your server (check via /plugins), this should be fine to set to {@link FeatureSupport#SUPPORTED} *

* Default Value: {@link FeatureSupport#AUTO} * @param protocolLibSupport set this to whether or not ProtocolLib will be supported + * @apiNote This is only available for **SPIGOT** */ public Builder protocolLibSupport(FeatureSupport protocolLibSupport) { this.protocolLibSupport.setFeature("PROTOCOLLIB"); this.protocolLibSupport = protocolLibSupport; return this; } + /** + * Should CyberAPI attempt and allow the use of Protocolize data? + *

+ * If you have Protocolize installed on your server (check via /gplugins), this should be fine to set to {@link FeatureSupport#SUPPORTED} + *

+ * Default Value: {@link FeatureSupport#AUTO} + * @param protocolizeSupport set this to whether or not Protocolize will be supported + * @apiNote This is only available for **BUNGEECORD** + */ + public Builder protocolizeSupport(FeatureSupport protocolizeSupport) { this.protocolizeSupport.setFeature("PROTOCOLIZE"); this.protocolizeSupport = protocolizeSupport; return this; } + /** * Sets the name of the plugin CyberAPI uses *

@@ -233,6 +249,13 @@ public Settings build() { */ public FeatureSupport supportsProtocolLib() { return builder.protocolLibSupport; } + /** + * Gets whether CyberAPI supports Protocolize or not + * @return supports Protocolize + * @since 3.5 + */ + public FeatureSupport supportsProtocolize() { return builder.protocolizeSupport; } + /** * Gets the name of the plugin * @return the plugin name diff --git a/spigot/build.gradle b/spigot/build.gradle index 47f4a8a..9a79085 100644 --- a/spigot/build.gradle +++ b/spigot/build.gradle @@ -31,10 +31,15 @@ dependencies { // utils implementation 'org.reflections:reflections:0.10.2' + implementation 'org.apache.commons:commons-lang3:3.12.0' } shadowJar { relocate 'javassist', 'net.cybercake.cyberapi.dependencies.javassist' - relocate 'javax.annotation', 'net.cybercake.cyberapi.dependencies.annotations' relocate 'org.reflections', 'net.cybercake.cyberapi.dependencies.reflections' + relocate 'org.apache.commons.lang3', 'net.cybercake.cyberapi.dependencies.apache.commons' + relocate 'com.google.gson', 'net.cybercake.cyberapi.dependencies.google.gson' + relocate 'org.intellij.lang.annotations', 'net.cybercake.cyberapi.dependencies.annotations.intellij' + relocate 'org.jetbrains.annotations', 'net.cybercake.cyberapi.dependencies.annotations.jetbrains' + relocate 'javax.annotation', 'net.cybercake.cyberapi.dependencies.annotations.javax' } \ No newline at end of file diff --git a/spigot/src/main/java/net/cybercake/cyberapi/spigot/chat/centered/TextType.java b/spigot/src/main/java/net/cybercake/cyberapi/spigot/chat/centered/TextType.java index b061af3..3544343 100644 --- a/spigot/src/main/java/net/cybercake/cyberapi/spigot/chat/centered/TextType.java +++ b/spigot/src/main/java/net/cybercake/cyberapi/spigot/chat/centered/TextType.java @@ -22,7 +22,7 @@ public enum TextType { *

* 45 characters {@literal <}- default */ - MOTD(45); + MOTD(60); private final int length; diff --git a/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/managers/PlayerListManager.java b/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/managers/PlayerListManager.java index c755242..c2a0679 100644 --- a/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/managers/PlayerListManager.java +++ b/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/managers/PlayerListManager.java @@ -46,7 +46,7 @@ public static PlayerListManager playerListManager() { */ public void setOnlinePlayers(String... players) { clearOnlinePlayers(); - List.of(players).forEach(this::addPlayerOnline); + List.of(players).forEach(this::addOnlinePlayer); } /** @@ -54,31 +54,31 @@ public void setOnlinePlayers(String... players) { * @param player the player to add to the hover event * @since 3.1.0 */ - public void addPlayerOnline(String player) { + public void addOnlinePlayer(String player) { this.players.add(UChat.chat(player)); } /** * Removes a player from the players online using the exact {@link String} match. Note: This has no effect on the current player count/max player count! - * @param player the player to remove from the hover event, must be an exact match to a currently online player to remove (alternatively, you could use {@link PlayerListManager#removePlayerOnline(int)}) + * @param player the player to remove from the hover event, must be an exact match to a currently online player to remove (alternatively, you could use {@link PlayerListManager#removeOnlinePlayer(int)}) * @since 3.1.0 */ - public void removePlayerOnline(String player) { + public void removeOnlinePlayer(String player) { this.players.remove(player); } /** * Removes a player from the players online using an index number. Note: This has no effect on the current player count/max player count! - * @param index the index of the player to remove, 0 being the first item on the list (alternatively, you could use {@link PlayerListManager#removePlayerOnline(String)}) + * @param index the index of the player to remove, 0 being the first item on the list (alternatively, you could use {@link PlayerListManager#removeOnlinePlayer(String)}) * @since 3.1.0 * @throws IndexOutOfBoundsException if the index is out of bounds ({@code index < 0 || index >= size()}) */ - public void removePlayerOnline(int index) { + public void removeOnlinePlayer(int index) { this.players.remove(index); } /** - * Get a list of the fake online players provided by the user in {@link PlayerListManager#addPlayerOnline(String)} or {@link PlayerListManager#setOnlinePlayers(String...)}. + * Get a list of the fake online players provided by the user in {@link PlayerListManager#addOnlinePlayer(String)} or {@link PlayerListManager#setOnlinePlayers(String...)}. * @return the fake online players (that show up in the hover event when hovering over the player count in the multiplayer menu) * @since 3.1.0 */ diff --git a/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/motd/MOTD.java b/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/motd/MOTD.java index 4227043..94f1cfd 100644 --- a/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/motd/MOTD.java +++ b/spigot/src/main/java/net/cybercake/cyberapi/spigot/server/serverlist/motd/MOTD.java @@ -1,15 +1,16 @@ package net.cybercake.cyberapi.spigot.server.serverlist.motd; -import net.cybercake.cyberapi.spigot.chat.centered.CenteredMessage; -import net.cybercake.cyberapi.spigot.server.ServerProperties; import net.cybercake.cyberapi.spigot.chat.UChat; -import net.cybercake.cyberapi.spigot.chat.centered.TextType; +import net.cybercake.cyberapi.spigot.server.ServerProperties; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.md_5.bungee.api.ChatColor; import javax.annotation.Nullable; import java.io.File; import java.net.URL; +import java.util.ArrayList; +import java.util.List; /** * @since 3.1.0 @@ -43,6 +44,15 @@ public static class Builder { * @since 3.1.1 */ public Builder(String id) { + if(!net.cybercake.cyberapi.common.basic.StringUtils.isAlphanumericSpace(id.replace("_", "").replace("-", ""))) + throw parseError("The 'id' must be alpha-numeric space (only contains A-Za-z0-9 _-)"); + if(id.isEmpty()) + throw parseError("The 'id' cannot be empty!"); + if(id.length() > 20) + throw parseError("The 'id' must be less than or equal to 20 characters!"); + if(id.length() < 3) + throw parseError("The 'id' must be more than or equal to 3 characters!"); + this.id = id; this.motd = String.valueOf(new ServerProperties().getProperty("motd")); @@ -53,6 +63,16 @@ public Builder(String id) { this.iconType = MOTDIconType.UNSET; } + /** + * Creates a parse exception, usually used internally in the {@link Builder} for {@link MOTD} + * @param message the message to include after 'Failed to set the MOTD ID:' + * @return the {@link IllegalArgumentException} instance + * @since 3.5 + */ + private IllegalArgumentException parseError(String message) { + return new IllegalArgumentException("Failed to set the MOTD ID: " + message); + } + /** * Sets the MOTD to a String. * @param motd the motd string @@ -211,7 +231,13 @@ public String getFormattedMOTD() { String text = getStringMOTD(); if(mini) return UChat.chat(LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text))); - else if(centered) return new CenteredMessage(text, TextType.MOTD).getString(CenteredMessage.Method.TWO); + else if(centered) { + List newMOTD = new ArrayList<>(); + for(String str : text.split("\\n")) { + newMOTD.add(org.apache.commons.lang3.StringUtils.center(ChatColor.stripColor(str), 45)); + } + return UChat.chat(String.join("\n", newMOTD)); + } return UChat.chat(text); }